SerializationMapper.php 9.87 KiB
<?php declare(strict_types=1);
/*
 * This file is part of "irstea/ng-model-generator-bundle".
 * "irstea/ng-model-generator-bundle" generates Typescript interfaces for Angular using api-platform metadata.
 * Copyright (C) 2018-2019 IRSTEA
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License and the GNU
 * Lesser General Public License along with this program. If not, see
 * <https://www.gnu.org/licenses/>.
namespace Irstea\NgModelGeneratorBundle;
use Assert\Assertion;
use Irstea\NgModelGeneratorBundle\Exceptions\DomainException;
use Irstea\NgModelGeneratorBundle\Metadata\PropertyMetadata;
use Irstea\NgModelGeneratorBundle\Metadata\RepresentationMetadata;
use Irstea\NgModelGeneratorBundle\Metadata\SerializationMetadata;
use Irstea\NgModelGeneratorBundle\Models\ClassInfo;
use Irstea\NgModelGeneratorBundle\Models\ClassName;
use Irstea\NgModelGeneratorBundle\Models\PHPClass;
use Irstea\NgModelGeneratorBundle\Models\Types\Alias;
use Irstea\NgModelGeneratorBundle\Models\Types\Factory\ContextInterface;
use Irstea\NgModelGeneratorBundle\Models\Types\Factory\TypeFactoryInterface;
use Irstea\NgModelGeneratorBundle\Models\Types\Factory\TypeHelper;
use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property;
use Irstea\NgModelGeneratorBundle\Models\Types\Reference;
use Irstea\NgModelGeneratorBundle\Models\Types\Resources\IRI;
use Irstea\NgModelGeneratorBundle\Models\Types\Resources\Representation;
use Irstea\NgModelGeneratorBundle\Models\Types\StringConst;
use Irstea\NgModelGeneratorBundle\Models\Types\Type;
use Irstea\NgModelGeneratorBundle\Models\Types\Union;
use Symfony\Component\PropertyInfo\Type as PropertyType;
/**
 * Class SerializationMapper.
final class SerializationMapper implements TypeFactoryInterface
    /** @var SerializationMetadata */
    private $serialization;
    /** @var ClassInfo[]|null */
    private $classInfo;
    /** @var bool */
    private $withAtFields;
    /**
     * SerializationMapper constructor.
     * @param SerializationMetadata $serialization
     * @param bool                  $withAtFields
    public function __construct(
        SerializationMetadata $serialization,
        bool $withAtFields
    ) {
        $this->serialization = $serialization;
        $this->withAtFields = $withAtFields;
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
} /** * {@inheritdoc} */ public function supportsType(PropertyType $type, ContextInterface $context): bool { $className = $type->getClassName(); return $className && $this->serialization->hasRepresentationOf(PHPClass::get($className)); } /** * {@inheritdoc} */ public function createType(PropertyType $type, ContextInterface $context): Type { Assertion::true($this->supportsType($type, $context)); $class = PHPClass::get($type->getClassName()); $repr = $this->serialization->getRepresentationOf($class); return $this->mapRepresentation($repr, $context); } /** * @param RepresentationMetadata $repr * @param ContextInterface $context * * @return Type */ private function mapRepresentation(RepresentationMetadata $repr, ContextInterface $context): Type { $classInfo = $this->getClassInfo($repr); if ($classInfo->isUndefined()) { throw new DomainException(sprintf('%s has not been rearranged', $repr)); } $desc = []; $desc[] = 'Resource: ' . $repr; $desc[] = 'Direction: ' . ($this->serialization->isNormalization() ? 'response' : 'request'); $desc[] = sprintf('Serialization groups: %s', implode(', ', $this->serialization->getGroups()) ?: '-'); $desc = trim(implode("\n", $desc)); if ($classInfo->isUnion()) { /** @var Type[] $types */ $types = []; foreach ($classInfo->iterateInterfaceDescendants() as $child) { $types[] = $context->createType(TypeHelper::fromClassName($child->getFullName())); } switch (\count($types)) { case 0: throw new DomainException(sprintf('Union with no children: %s', $repr)); case 1: return $types[0]; default: return new Alias($repr->getName(), Union::create($types), $desc); } } if ($classInfo->isIRI()) { return $this->createIRI([$classInfo], $context); } $parent = null; $parentInfo = $classInfo->getParent(); if ($parentInfo !== null && $parentInfo->isInterface()) { $parent = $context->createType(TypeHelper::fromClassName($parentInfo->getFullName()));
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
} $properties = $this->mapProperties($classInfo, $context); $children = []; /* @var ClassName $class */ foreach ($classInfo->getChildren() as $child) { if ($child->isInterface()) { $children[] = $context->createType(TypeHelper::fromClassName($child->getFullName())); } } return new Representation($repr, $repr->getName(), $parent, $properties, $desc, $children); } /** * @param array $resources * @param ContextInterface $context * * @return IRI */ private function createIRI(array $resources, ContextInterface $context): IRI { return new IRI( array_map( function (ClassName $class) use ($context) { return $context->createType(TypeHelper::fromClassName($class->getFullName())); }, $resources ) ); } /** * @param ClassInfo $classInfo * @param ContextInterface $context * * @return array */ private function mapProperties(ClassInfo $classInfo, ContextInterface $context): array { $properties = []; $identifierCount = 0; foreach ($classInfo->getConcreteProperties() as $propertyMeta) { /** @var PropertyMetadata $propertyMeta */ if ($propertyMeta->isIdentifier()) { ++$identifierCount; } $property = $this->mapProperty($propertyMeta, $context); $properties[$property->getName()] = $property; } if ($identifierCount > 1) { throw new DomainException( sprintf( 'Resource %s must have at most one identifier, found %d', $classInfo->getBaseName(), $identifierCount ) ); } if ($this->withAtFields && $classInfo->isResource()) { $properties['@id'] = new Property( '@id', '', $this->createIRI(\iterator_to_array($classInfo->iterateConcreteDescendants()), $context), true,
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
!$this->serialization->isNormalization(), true ); $properties['@type'] = new Property( '@type', '', $this->buildTypeEnumFor($classInfo), false, !$this->serialization->isNormalization(), true ); } return $properties; } /** * @param ClassInfo $classInfo * * @return Type */ private function buildTypeEnumFor(ClassInfo $classInfo): Type { $types = []; foreach ($classInfo->iterateConcreteDescendants() as $child) { $types[] = StringConst::get($child->getBaseName()); } return Union::create($types); } /** * @param PropertyMetadata $propertyMeta * @param ContextInterface $context * * @return Property */ private function mapProperty(PropertyMetadata $propertyMeta, ContextInterface $context): Property { return new Property( $propertyMeta->getName(), $propertyMeta->getDescription() ?: '', $context->createType($propertyMeta->getType()), $propertyMeta->isIdentifier(), $propertyMeta->isNullable(), !$propertyMeta->isWritable() ); } /** * @param ClassName $class * * @return ClassInfo */ private function getClassInfo(ClassName $class): ClassInfo { if ($this->classInfo === null) { $this->init(); } $name = $class->getFullName(); $info = $this->classInfo[$name] ?? null; Assertion::notNull($info, 'Unknown class $name'); return $info; } private function init(): void {
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
$reprs = $this->serialization->getRepresentations(); $this->classInfo = []; // Crée les instances de métadonnées sur les ressources foreach ($reprs as $className => $repr) { $this->classInfo[$className] = new ClassInfo( $repr, $repr->getProperties(), $repr->isAbstract(), $repr->isResource() ); } // Crée les liens de parenté foreach ($reprs as $className => $repr) { $classInfo = $this->classInfo[$className]; $parent = $repr->getParent(); if ($parent) { $classInfo->setParent($this->classInfo[$parent->getFullName()]); } } // Optimise les hierarchies foreach ($this->classInfo as $className => $classInfo) { $classInfo->rearrangeHiearchy(); } } }