skqr/hateoas

View on GitHub
Metadata/Resource/EntityMetadataMiner.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php
/**
 * @copyright 2014 Integ S.A.
 * @license http://opensource.org/licenses/MIT The MIT License (MIT)
 * @author Javier Lorenzana <javier.lorenzana@gointegro.com>
 */

namespace GoIntegro\Hateoas\Metadata\Resource;

// Metadata.
use GoIntegro\Hateoas\Metadata\Entity\MetadataCache;
// Utils.
use GoIntegro\Hateoas\Util\Inflector;
// ORM.
use Doctrine\ORM\Mapping\ClassMetadata;
// Reflection.
use GoIntegro\Hateoas\Util\Reflection;

class EntityMetadataMiner implements MetadataMinerInterface
{
    use MiningTools;

    const ERROR_MAPPING_TYPE_UNKNOWN = "El tipo de mapeo es desconocido.";

    /**
     * @var MetadataCache
     */
    private $metadataCache;
    /**
     * @var string
     */
    private $resourceClassPath;

    /**
     * @param MetadataCache $metadataCache
     * @param string $resourceClassPath
     */
    public function __construct(
        MetadataCache $metadataCache, $resourceClassPath
    )
    {
        $this->metadataCache = $metadataCache;
        $this->resourceClassPath = $resourceClassPath;
    }

    /**
     * @param \GoIntegro\Hateoas\JsonApi\ResourceEntityInterface|string $entityClassName
     * @param ResourceMetadata
     */
    public function mine($entityClassName)
    {
        $entityClassName = $this->metadataCache
            ->getMapping($entityClassName)
            ->getName();
        $type = $this->parseType($entityClassName);
        $subtype = $this->parseSubtype($entityClassName);
        $resourceClass = $this->getResourceClass($entityClassName);
        $relationships = $this->getRelationships($entityClassName, $type);
        $fields = $this->getFields($entityClassName, $relationships);
        $pageSize = $resourceClass->getProperty('pageSize')->getValue();

        return new ResourceMetadata(
            $type, $subtype, $resourceClass, $fields, $relationships, $pageSize
        );
    }

    /**
     * @param \GoIntegro\Hateoas\JsonApi\ResourceEntityInterface|string $entityClass
     * @return string
     */
    protected function parseType($entityClassName)
    {
        $entityClassName = $this->getEntityClass($entityClassName);

        return $this->parseSubtype($entityClassName);
    }

    /**
     * @param \GoIntegro\Hateoas\JsonApi\ResourceEntityInterface|string $entityClass
     * @return string
     * @todo Move to \GoIntegro\Hateoas\Config\ResourceEntityMapper.
     */
    protected function getEntityClass($entityClassName)
    {
        $mapping = $this->metadataCache->getMapping($entityClassName);

        if (!empty($mapping->parentClasses)) {
            $parentClassName = reset($mapping->parentClasses);
            $parentClass
                = $this->metadataCache->getReflection($parentClassName);

            if ($parentClass->implementsInterface(
                MetadataMiner::RESOURCE_ENTITY_INTERFACE
            )) {
                $entityClassName = $parentClassName;
            }
        }

        return $entityClassName;
    }

    /**
     * @param \GoIntegro\Hateoas\JsonApi\ResourceEntityInterface|string $entityClassName
     * @param string $primaryType
     * @return array
     * @todo Publicar o eliminar el parĂ¡metro $entityClassName.
     */
    protected function getRelationships($entityClassName, $primaryType)
    {
        $relationships = new ResourceRelationships;
        $metadata = $this->metadataCache->getMapping($entityClassName);
        $class = $this->metadataCache->getReflection($entityClassName);

        foreach ($metadata->getAssociationMappings() as $name => $mapping) {
            $relationshipName = Inflector::hyphenate($name);
            $mappingClass = $this->metadataCache->getReflection(
                $mapping['targetEntity']
            );

            if (self::isDbOnlyRelation($class, $mappingClass, $name)) {
                $relationships->dbOnly[] = $relationshipName;
                continue;
            }

            $className = $mappingClass->getName();
            $mappingField = $mapping['mappedBy'] ?: $mapping['inversedBy'];
            $relationshipKind = $this->getRelationshipKind($mapping['type']);
            $relationship = new ResourceRelationship(
                $className,
                $this->parseType($className),
                $this->parseSubtype($className),
                $relationshipKind,
                $relationshipName,
                $mappingField
            );

            if (self::isLinkOnlyRelation($class, $name)) {
                $relationshipKind = 'linkOnly';
            }

            $relationshipsPerKind = &$relationships->$relationshipKind;
            $relationshipsPerKind[$relationshipName] = $relationship;
        }

        return $relationships;
    }

    /**
     * Para relaciones que no van en el campo "links" del resource object.
     * @param \ReflectionClass $class
     * @param string $name
     * @return boolean
     * @see http://jsonapi.org/format/#document-structure-url-templates
     */
    private static function isLinkOnlyRelation(
        \ReflectionClass $class,
        $name
    )
    {
        $getterName = 'get' . Inflector::camelize($name);

        return !$class->hasMethod($getterName)
            || !Reflection::isMethodGetter($class->getMethod($getterName));
    }

    /**
     * @param \ReflectionClass $class
     * @param \ReflectionClass $mappingClass
     * @param string $name
     * @return boolean
     */
    private static function isDbOnlyRelation(
        \ReflectionClass $class,
        \ReflectionClass $mappingClass,
        $name
    )
    {
        return !self::isLinkOnlyRelation($class, $name)
            && !$mappingClass->implementsInterface(
                MetadataMiner::RESOURCE_ENTITY_INTERFACE
            );
    }

    /**
     * @param string $mappingType
     * @return string
     * @throws \Exception
     */
    private function getRelationshipKind($mappingType)
    {
        switch ($mappingType) {
            case ClassMetadata::ONE_TO_ONE:
            case ClassMetadata::MANY_TO_ONE:
                return 'toOne';

            case ClassMetadata::ONE_TO_MANY:
            case ClassMetadata::MANY_TO_MANY:
                return 'toMany';

            default:
                throw new \Exception(self::ERROR_MAPPING_TYPE_UNKNOWN);
        }
    }
}