An error occurred while loading the file. Please try again.
-
Guillaume Perréal authored2ec19af7
<?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();
}
}
}