diff --git a/src/Command/NgModelGenerateCommand.php b/src/Command/NgModelGenerateCommand.php index fe4d1048674d4a5bbdc839914bb2fc016fd7a7b8..5e7507d863e74c4ea9f99105f99b6b6162dfb8a4 100644 --- a/src/Command/NgModelGenerateCommand.php +++ b/src/Command/NgModelGenerateCommand.php @@ -63,7 +63,7 @@ final class NgModelGenerateCommand extends Command { $this ->setName('ng-model:generate') - ->setDescription('Dump Typescript models'); + ->setDescription('Dump Typescript representations'); } /** diff --git a/src/Context.php b/src/Context.php deleted file mode 100644 index ac9cb04c3d4d5571b639f1c65d977d6dc017bf25..0000000000000000000000000000000000000000 --- a/src/Context.php +++ /dev/null @@ -1,130 +0,0 @@ -<?php declare(strict_types=1); -/* - * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata. - * Copyright (C) 2018 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 Irstea\NgModelGeneratorBundle\Models\PHPClass; - -/** - * Class Context. - */ -class Context implements ContextInterface -{ - /** @var PHPClass */ - private $rootClass; - - /** @var bool */ - private $normalization; - - /** @var string[] */ - private $groups; - - /** @var string[][] */ - private $defaultGroups; - - /** - * Context constructor. - * - * @param string $rootClass - * @param bool $normalization - * @param string[] $groups - * @param \string[][] $defaultGroups - */ - public function __construct(PHPClass $rootClass, array $defaultGroups, bool $normalization = false, array $groups = []) - { - $this->rootClass = $rootClass; - $this->defaultGroups = $defaultGroups; - $this->normalization = $normalization; - $this->groups = $groups; - sort($this->groups); - } - - /** - * @param bool $normalization - * - * @return Context - */ - public function withNormalization(bool $normalization): ContextInterface - { - if ($normalization === $this->normalization) { - return $this; - } - - $clone = clone $this; - $clone->normalization = $normalization; - - return $clone; - } - - /** - * @param string[] $groups - * - * @return Context - */ - public function withGroups(array $groups): ContextInterface - { - sort($groups); - - /* @noinspection TypeUnsafeComparisonInspection */ - if ($groups == $this->groups) { - return $this; - } - - $clone = clone $this; - $clone->groups = $groups; - - return $clone; - } - - /** - * Get rootClass. - * - * @return PHPClass - */ - public function getRootClass(): PHPClass - { - return $this->rootClass; - } - - /** - * @return bool - */ - public function isNormalization(): bool - { - return $this->normalization; - } - - /** - * @return array - */ - public function getGroups(): array - { - return $this->groups; - } - - /** - * @param PHPClass $class - * - * @return string[] - */ - public function getDefaultGroups(PHPClass $class): array - { - return $this->defaultGroups[$this->normalization ? 'normalization' : 'denormalization'][$class->getFullName()] ?? []; - } -} diff --git a/src/ContextInterface.php b/src/ContextInterface.php deleted file mode 100644 index 0b02806664be49c2a382d3da70c971a96b29ee0f..0000000000000000000000000000000000000000 --- a/src/ContextInterface.php +++ /dev/null @@ -1,66 +0,0 @@ -<?php declare(strict_types=1); -/* - * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata. - * Copyright (C) 2018 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 Irstea\NgModelGeneratorBundle\Models\PHPClass; - -/** - * Interface ContextInterface. - */ -interface ContextInterface -{ - /** - * @param bool $normalization - * - * @return Context - */ - public function withNormalization(bool $normalization): self; - - /** - * @param string[] $groups - * - * @return Context - */ - public function withGroups(array $groups): self; - - /** - * Get rootClass. - * - * @return PHPClass - */ - public function getRootClass(): PHPClass; - - /** - * @return bool - */ - public function isNormalization(): bool; - - /** - * @return array - */ - public function getGroups(): array; - - /** - * @param PHPClass $class - * - * @return string[] - */ - public function getDefaultGroups(PHPClass $class): array; -} diff --git a/src/DefaultNamingStrategy.php b/src/DefaultNamingStrategy.php deleted file mode 100644 index 656f8b0f48b94f6e0f530f688d98a0a8268ce473..0000000000000000000000000000000000000000 --- a/src/DefaultNamingStrategy.php +++ /dev/null @@ -1,64 +0,0 @@ -<?php declare(strict_types=1); -/* - * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata. - * Copyright (C) 2018 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 Irstea\NgModelGeneratorBundle\Models\PHPClass; - -/** - * Class DefaultNamingStrategy. - */ -final class DefaultNamingStrategy implements NamingStrategy -{ - /** - * {@inheritdoc} - */ - public function getName(ContextInterface $ctx, PHPClass $resourceClass): string - { - $parts = array_diff( - $ctx->getGroups(), - $ctx->getDefaultGroups($resourceClass), - $ctx->getDefaultGroups($ctx->getRootClass()) - ); - - $baseName = $resourceClass->getBaseName(); - $shortPrefix = $ctx->isNormalization() ? '' : 'Put'; - $fullPrefix = $ctx->isNormalization() ? 'Get' : 'Put'; - - if ($resourceClass === $ctx->getRootClass()) { - if (\in_array($shortPrefix . $baseName, $parts, true)) { - array_unshift($parts, $baseName); - } else { - array_unshift($parts, $shortPrefix . $baseName); - } - } else { - $rootBaseName = $ctx->getRootClass()->getBaseName(); - $rootGroup = $fullPrefix . $rootBaseName; - if (\in_array($rootGroup, $parts, true)) { - array_unshift($parts, $rootBaseName); - } else { - array_unshift($parts, $rootGroup); - } - - $parts[] = $baseName; - } - - return implode('', $parts); - } -} diff --git a/src/Metadata/CachingMetadataFactory.php b/src/Metadata/CachingMetadataFactory.php index 018e6568ded4183d388649c09a2487284285f1eb..35f553f2eec8ba76c06abd80f66dc5a468ef404d 100644 --- a/src/Metadata/CachingMetadataFactory.php +++ b/src/Metadata/CachingMetadataFactory.php @@ -60,30 +60,7 @@ final class CachingMetadataFactory implements MetadataFactoryInterface public function getResourceMetadata(PHPClass $class): ResourceMetadata { return $this->memoize(__METHOD__, $class->getFullName(), function () use ($class) { - return $this->inner->getResourceMetadata($class)->setFactory($this); - }); - } - - /** - * {@inheritdoc} - */ - public function getOperations(PHPClass $class): array - { - return $this->memoize(__METHOD__, $class->getFullName(), function () use ($class) { - return $this->inner->getOperations($class); - }); - } - - /** - * {@inheritdoc} - */ - public function getProperties(PHPClass $class, array $groups = []): array - { - sort($groups); - $key = $class->getFullName() . '::' . implode('-', $groups); - - return $this->memoize(__METHOD__, $key, function () use ($class, $groups) { - return $this->inner->getProperties($class, $groups); + return $this->inner->getResourceMetadata($class); }); } diff --git a/src/Metadata/MetadataFactory.php b/src/Metadata/MetadataFactory.php index 95ebdd784c694a9cf9440eda50d20929d3cdbe6c..40f7ef7bfa3b8ccd0fa1ee46e4e12350359d5092 100644 --- a/src/Metadata/MetadataFactory.php +++ b/src/Metadata/MetadataFactory.php @@ -65,6 +65,9 @@ final class MetadataFactory implements MetadataFactoryInterface /** @var PaginationMetadata */ private $paginationMetadata; + /** @var SerializationMetadata[] */ + private $serializations = []; + /** * MetadataFactory constructor. * @@ -125,13 +128,17 @@ final class MetadataFactory implements MetadataFactoryInterface $metadata->getShortName(), $metadata->getDescription(), $classMeta->isAbstract(), - $this->buildPagination( - $metadata->getAttribute('pagination_enabled', true), - $metadata->getAttribute('pagination_client_items_per_page', true) + $this->getSerialization( + $class, + true, + $metadata->getAttribute('normalization_context', [])['groups'] ?? [] + ), + $this->getSerialization( + $class, + false, + $metadata->getAttribute('normalization_context', [])['groups'] ?? [] ), - $metadata->getAttribute('normalization_context', [])['groups'] ?? [], - $metadata->getAttribute('denormalization_context', [])['groups'] ?? [], - $this + $this->getOperations($class) ); } @@ -151,6 +158,137 @@ final class MetadataFactory implements MetadataFactoryInterface ); } + /** + * @param PHPClass $class + * @param bool $normalization + * @param string[] $groups + * + * @return SerializationMetadata + */ + private function getSerialization(PHPClass $class, bool $normalization, array $groups): SerializationMetadata + { + sort($groups); + $key = sprintf('%s:%d:%s', $class->getFullName(), $normalization, implode('+', $groups)); + if (!isset($this->serializations[$key])) { + $this->serializations[$key] = $this->doGetSerialization($class, $normalization, $groups); + } + + return $this->serializations[$key]; + } + + /** + * @param PHPClass $class + * @param bool $normalization + * @param string[] $groups + * + * @return SerializationMetadata + */ + private function doGetSerialization(PHPClass $class, bool $normalization, array $groups): SerializationMetadata + { + /** @var RepresentationMetadata[] $reprs */ + $representations = []; + + /** @var string[] $queue */ + $queue = [$class->getFullName()]; + + while ($queue) { + /** @var string $currentName */ + $currentName = array_shift($queue); + $current = PHPClass::get($currentName); + + if (isset($representations[$currentName]) || !$this->isResource($current)) { + continue; + } + + $parent = null; + $reflParent = $current->getReflection()->getParentClass(); + if ($reflParent) { + $parent = PHPClass::get($reflParent->getName()); + $queue[] = $reflParent->getName(); + } + + $properties = $this->getProperties($current, $normalization, $groups); + foreach ($properties as $property) { + if ($property->getType()->getClassName()) { + $queue[] = $property->getType()->getClassName(); + } + } + + $representations[$currentName] = new RepresentationMetadata($current, $parent, $properties); + } + + return new SerializationMetadata($class, $groups, $normalization, $representations); + } + + /** + * @param PHPClass $class + * @param bool $normalization + * @param array $groups + * + * @throws \ApiPlatform\Core\Exception\PropertyNotFoundException + * @throws \ApiPlatform\Core\Exception\ResourceClassNotFoundException + * + * @return PropertyMetadata[] + */ + private function getProperties(PHPClass $class, bool $normalization, array $groups): array + { + $className = $class->getFullName(); + $properties = []; + + $options = $groups ? ['serializer_groups' => $groups] : []; + + foreach ($this->propertyNameCollectionFactory->create($className, $options) as $propertyName) { + \assert(\is_string($propertyName)); + + $propertyMeta = $this->propertyMetadataFactory->create($className, $propertyName); + + if ($propertyMeta->isChildInherited()) { + continue; + } + + $typeMeta = $propertyMeta->getType(); + if (!$typeMeta) { + continue; + } + + $readable = $propertyMeta->isReadable() && $this->propertyInfoExtractor->isReadable($className, $propertyName); + $writable = ($propertyMeta->isWritable() && $this->propertyInfoExtractor->isWritable($className, $propertyName)); + $initializable = (bool) $propertyMeta->isInitializable(); + $identifier = (bool) $propertyMeta->isIdentifier(); + + if ($normalization ? !$readable : !($identifier || $writable || $initializable)) { + continue; + } + + $link = false; + $embedded = true; + + $leafType = $typeMeta; + while ($leafType && $leafType->isCollection()) { + $leafType = $leafType->getCollectionValueType(); + } + $propClass = $leafType ? $leafType->getClassName() : null; + if ($propClass && $this->isResource(PHPClass::get($propClass))) { + $link = true; + $embedded = \count($this->propertyNameCollectionFactory->create($propClass, $options)) > 0; + } + + $properties[$propertyName] = new PropertyMetadata( + $propertyName, + $this->propertyInfoExtractor->getShortDescription($className, $propertyName) ?: '', + $typeMeta, + $identifier, + $readable, + $writable, + $initializable, + $link, + $embedded + ); + } + + return $properties; + } + /** * Get paginationMetadata. * @@ -166,7 +304,7 @@ final class MetadataFactory implements MetadataFactoryInterface * * @return OperationMetadata[] */ - public function getOperations(PHPClass $class): array + private function getOperations(PHPClass $class): array { $resourceMetadata = $this->resourceMetadataFactory->create($class->getFullName()); @@ -216,20 +354,48 @@ final class MetadataFactory implements MetadataFactoryInterface return $resourceMetadata->getTypedOperationAttribute($type, $name, $attrName, $default, true); }; + if ($type === OperationType::COLLECTION && $method === 'GET') { + $filters = $this->getFilters($class, $getAttribute('filters', [])); + $pagination = $this->buildPagination( + $getAttribute('pagination_enabled', true), + $getAttribute('pagination_client_items_per_page', true) + ); + } else { + $filters = []; + $pagination = null; + } + + if (\in_array($method, ['GET', 'PUT', 'POST'], true)) { + $normalization = $this->getSerialization( + $class, + true, + $getAttribute('normalization_context', [])['groups'] ?? [] + ); + } else { + $normalization = null; + } + + if (\in_array($method, ['POST', 'PUT'], true)) { + $denormalization = $this->getSerialization( + $class, + false, + $getAttribute('denormalization_context', [])['groups'] ?? [] + ); + } else { + $denormalization = null; + } + return new OperationMetadata( $name, $operation['description'] ?? '', $type, $method, $path, - $getAttribute('normalization_context', [])['groups'] ?? [], - $getAttribute('denormalization_context', [])['groups'] ?? [], $getAttribute('requirements', []), - $this->getFilters($class, $getAttribute('filters', [])), - $this->buildPagination( - $getAttribute('pagination_enabled', true), - $getAttribute('pagination_client_items_per_page', true) - ) + $filters, + $pagination, + $normalization, + $denormalization ); } @@ -257,67 +423,4 @@ final class MetadataFactory implements MetadataFactoryInterface return $filters; } - - /** - * @param string $className - * @param array $groups - * - * @throws \ApiPlatform\Core\Exception\PropertyNotFoundException - * @throws \ApiPlatform\Core\Exception\ResourceClassNotFoundException - * - * @return PropertyMetadata[] - */ - public function getProperties(PHPClass $class, array $groups = []): array - { - $className = $class->getFullName(); - $properties = []; - - $options = $groups ? ['serializer_groups' => $groups] : []; - - foreach ($this->propertyNameCollectionFactory->create($className, $options) as $propertyName) { - \assert(\is_string($propertyName)); - - $propertyMeta = $this->propertyMetadataFactory->create($className, $propertyName); - - if ($propertyMeta->isChildInherited()) { - continue; - } - - $typeMeta = $propertyMeta->getType(); - if (!$typeMeta) { - continue; - } - - $identifier = (bool) $propertyMeta->isIdentifier(); - $readable = $propertyMeta->isReadable() && $this->propertyInfoExtractor->isReadable($className, $propertyName); - $writable = ($propertyMeta->isWritable() && $this->propertyInfoExtractor->isWritable($className, $propertyName)); - $initializable = (bool) $propertyMeta->isInitializable(); - $link = false; - $embedded = true; - - $leafType = $typeMeta; - while ($leafType && $leafType->isCollection()) { - $leafType = $leafType->getCollectionValueType(); - } - $propClass = $leafType ? $leafType->getClassName() : null; - if ($propClass && $this->isResource(PHPClass::get($propClass))) { - $link = true; - $embedded = \count($this->propertyNameCollectionFactory->create($propClass, $options)) > 0; - } - - $properties[$propertyName] = new PropertyMetadata( - $propertyName, - $this->propertyInfoExtractor->getShortDescription($className, $propertyName) ?: '', - $typeMeta, - $identifier, - $readable, - $writable, - $initializable, - $link, - $embedded - ); - } - - return $properties; - } } diff --git a/src/Metadata/MetadataFactoryInterface.php b/src/Metadata/MetadataFactoryInterface.php index 2d6d1964c55ba07bf22fcb8e7bbcbfb9944bb257..42dfa79f199d2b7122876dbd0f67ef977a28431f 100644 --- a/src/Metadata/MetadataFactoryInterface.php +++ b/src/Metadata/MetadataFactoryInterface.php @@ -40,21 +40,6 @@ interface MetadataFactoryInterface */ public function getResourceMetadata(PHPClass $class): ResourceMetadata; - /** - * @param PHPClass $class - * - * @return OperationMetadata[] - */ - public function getOperations(PHPClass $class): array; - - /** - * @param PHPClass $class - * @param array $groups - * - * @return PropertyMetadata[] - */ - public function getProperties(PHPClass $class, array $groups = []): array; - /** * @return PaginationMetadata */ diff --git a/src/Metadata/Normalizer.php b/src/Metadata/Normalizer.php deleted file mode 100644 index bdc59e4999cc637b2edd81c6fc5c8582934fb043..0000000000000000000000000000000000000000 --- a/src/Metadata/Normalizer.php +++ /dev/null @@ -1,356 +0,0 @@ -<?php declare(strict_types=1); -/* - * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata. - * Copyright (C) 2018 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\Metadata; - -use ApiPlatform\Core\Documentation\Documentation; -use Irstea\NgModelGeneratorBundle\Context; -use Irstea\NgModelGeneratorBundle\NamingStrategy; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; - -/** - * Class Normalizer. - */ -final class Normalizer implements NormalizerInterface -{ - /** @var string */ - public const FORMAT_MIME_TYPE = 'application/x-api-platform-metadata+json'; - - /** @var string */ - public const FORMAT_NAME = 'metadata.json'; - - /** - * @var MetadataFactoryInterface - */ - private $metadataFactory; - - /** - * @var NamingStrategy - */ - private $namingStrategy; - - /** - * Normalizer constructor. - * - * @param MetadataFactoryInterface $metadataFactory - * @param NamingStrategy $namingStrategy - */ - public function __construct( - MetadataFactoryInterface $metadataFactory, - NamingStrategy $namingStrategy - ) { - $this->metadataFactory = $metadataFactory; - $this->namingStrategy = $namingStrategy; - } - - /** - * {@inheritdoc} - */ - public function normalize($data, $format = null, array $context = []) - { - if ($format === self::FORMAT_NAME && $data instanceof Documentation) { - return $this->normalizeDocumentation($data, $context); - } - - return null; - } - - /** - * {@inheritdoc} - */ - public function supportsNormalization($data, $format = null) - { - return $format === self::FORMAT_NAME && $data instanceof Documentation; - } - - /** - * @param Documentation $doc - * @param array $context - * - * @return array - */ - private function normalizeDocumentation(Documentation $doc, array $context): array - { - $context['documentation'] = $doc; - $context['models'] = $models = new \ArrayObject(); - $context['references'] = new \ArrayObject(); - - $defaultGroups = []; - /** @var string $className */ - foreach ($doc->getResourceNameCollection() as $className) { - $meta = $this->metadataFactory->getResourceMetadata($className); - $defaultGroups['normalization'][$className] = $meta->getSerializationGroups(true); - $defaultGroups['denormalization'][$className] = $meta->getSerializationGroups(false); - } - - $context['defaultGroups'] = $defaultGroups; - - $resources = []; - /** @var string $className */ - foreach ($doc->getResourceNameCollection() as $className) { - $meta = $this->metadataFactory->getResourceMetadata($className); - $context['resourceClassName'] = $className; - - $resources[$meta->getShortName()] = $this->normalizeResource($meta, $context); - } - - return [ - 'title' => $doc->getTitle(), - 'version' => $doc->getVersion(), - 'description' => $doc->getDescription(), - 'mime_types' => $doc->getMimeTypes(), - 'resources' => $resources, - 'models' => $models, - ]; - } - - /** - * @param ResourceMetadata $resource - * @param array $context - * - * @return array - */ - private function normalizeResource(ResourceMetadata $resource, array $context): array - { - $context['resource'] = $resource; - - $operations = []; - - foreach ($resource->getOperations() as $operation) { - $context['operation'] = $operation; - $type = $operation->getType(); - - if (!isset($operations[$type])) { - $operations[$type] = []; - } - $operations[$type][$operation->getName()] = $this->normalizeOperation($operation, $context); - } - - return [ - 'className' => $resource->getClassName(), - 'shortName' => $resource->getShortName(), - 'parentClassName' => $resource->getParentClassName(), - 'description' => $resource->getDescription(), - 'pagination' => $this->normalizePagination($resource->getPagination(), $context), - 'normalization_groups' => $resource->getSerializationGroups(true), - 'denormalization_groups' => $resource->getSerializationGroups(false), - 'operations' => $operations, - ]; - } - - /** - * @param OperationMetadata $operation - * @param array $context - * - * @return array - */ - private function normalizeOperation(OperationMetadata $operation, array $context): array - { - $response = $requestBody = null; - $context['type'] = $operation->getType(); - - if (\in_array($operation->getMethod(), ['GET', 'PUT', 'POST'], true)) { - $context['normalization'] = true; - $context['groups'] = $operation->getSerializationGroups(true); - $response = $this->normalizeType($operation->getClassName(), $context); - } - if (\in_array($operation->getMethod(), ['PUT', 'POST'], true)) { - $context['normalization'] = false; - $context['groups'] = $operation->getSerializationGroups(false); - $requestBody = $this->normalizeType($operation->getClassName(), $context); - } - - return [ - 'name' => $operation->getName(), - 'type' => $operation->getType(), - 'description' => $operation->getDescription(), - 'method' => $operation->getMethod(), - 'path' => $operation->getPath(), - 'requirements' => $operation->getRequirements(), - 'pagination' => $operation->getPagination(), - 'filters' => $operation->getFilters(), - 'normalization_groups' => $operation->getSerializationGroups(true), - 'denormalization_groups' => $operation->getSerializationGroups(false), - 'requestBody' => $requestBody, - 'response' => $response, - ]; - } - - /** - * @param PropertyMetadata $property - * @param array $context - * - * @return array - */ - private function normalizeProperty(PropertyMetadata $property, array $context): array - { - return [ - 'name' => $property->getName(), - 'description' => $property->getDescription(), - 'type' => $this->normalizeTypeInfo($property->getType(), $context), - 'identifier' => $property->isIdentifier(), - 'nullable' => $property->isNullable(), - 'readable' => $property->isReadable(), - 'writable' => $property->isWritable(), - ]; - } - - /** - * @param string $type - * @param array $context - * - * @return mixed - */ - private function normalizeTypeInfo(?Type $info, array $context) - { - if (!$info) { - return 'any'; - } - if ($info->isCollection()) { - return [ - 'type' => 'Array', - 'itemType' => $this->normalizeTypeInfo($info->getCollectionValueType(), $context), - ]; - } - - return $this->normalizeType($info->getClassName() ?: $info->getBuiltinType(), $context); - } - - /** - * @param string $name - * @param array $context - * - * @return mixed - */ - private function normalizeType(string $name, array $context) - { - switch ($name) { - case Type::BUILTIN_TYPE_BOOL: - return 'boolean'; - case Type::BUILTIN_TYPE_NULL: - return 'null'; - case Type::BUILTIN_TYPE_FLOAT: - case Type::BUILTIN_TYPE_INT: - return 'number'; - case Type::BUILTIN_TYPE_STRING: - return 'string'; - case Type::BUILTIN_TYPE_ARRAY: - return 'Array'; - case \DateTime::class: - case \DateTimeImmutable::class: - case \DateTimeInterface::class: - return ['type' => 'string', 'subtype' => 'DateTime']; - case 'Ramsey\\Uuid\\UuidInterface': - return ['type' => 'string', 'subtype' => 'UUID']; - } - - if (\class_exists($name)) { - if ($this->metadataFactory->isResource($name)) { - return $this->normalizeResourceRepresentation($name, $context); - } - if (\method_exists($name, '__toString')) { - return ['type' => 'string', 'subtype' => $name]; - } - - return ['type' => 'object', 'subtype' => $name]; - } - - return $name; - } - - /** - * @param PaginationMetadata $data - * @param array $context - * - * @return array - */ - private function normalizePagination(PaginationMetadata $data, array $context): array - { - return [ - 'enabled' => $data->isEnabled(), - 'page_parameter_name' => $data->getPageParameterName(), - 'client_items_per_page_enabled' => $data->isClientItemsPerPage(), - 'client_items_per_page_parameter_name' => $data->getItemsPerPageParameterName(), - ]; - } - - /** - * @param string $name - * @param array $context - */ - private function normalizeResourceRepresentation(string $className, array $context) - { - $reprName = $this->namingStrategy->getName(Context::fromArray($context), $className); - - if (isset($context['references'][$reprName])) { - return $context['references'][$reprName]; - } - - $resource = $this->metadataFactory->getResourceMetadata($className); - $properties = $resource->getProperties($context['groups']); - - $hasNonIdentifier = false; - foreach ($properties as $property) { - if (!$property->isIdentifier()) { - $hasNonIdentifier = true; - break; - } - } - - if (!$hasNonIdentifier) { - return $context['references'][$reprName] = [ - 'type' => 'string', - 'subtype' => 'IRI', - 'resource' => $resource->getShortName(), - ]; - } - - $context['references'][$reprName] = '#/models/' . $reprName; - $context['models'][$reprName] = array_merge( - ['@type' => $resource->getShortName()], - $this->normalizeModel($properties, $context) - ); - - return $context['references'][$reprName]; - } - - /** - * @param array $meta - * @param array $context - * - * @return array - */ - private function normalizeModel(array $meta, array $context): array - { - $properties = []; - - /** @var PropertyMetadata $property */ - foreach ($meta as $property) { - $properties[$property->getName()] = $this->normalizeProperty($property, $context); - } - - ksort($properties); - - return [ - 'groups' => $context['groups'], - 'properties' => $properties, - ]; - } -} diff --git a/src/Metadata/OperationMetadata.php b/src/Metadata/OperationMetadata.php index 4c7404299e753459469c60b1114d8efa71d949cf..63a0ec8dacf0565e7a45df0dc45785c133783180 100644 --- a/src/Metadata/OperationMetadata.php +++ b/src/Metadata/OperationMetadata.php @@ -27,9 +27,6 @@ use ApiPlatform\Core\Api\OperationType; */ class OperationMetadata implements \JsonSerializable { - use PaginedTrait; - use SerializationGroupsTrait; - /** @var string */ private $name; @@ -54,19 +51,28 @@ class OperationMetadata implements \JsonSerializable /** @var string[] */ private $requirements; + /** @var PaginationMetadata|null */ + private $pagination; + + /** @var SerializationMetadata|null */ + private $normalization; + + /** @var SerializationMetadata|null */ + private $denormalization; + /** * OperationMetadata constructor. * - * @param string $name - * @param string $description - * @param string $type - * @param string $method - * @param string $path - * @param array $normalizationGroups - * @param array $denormalizationGroups - * @param array $requirements - * @param array $filters - * @param PaginationMetadata $pagination + * @param string $name + * @param string $description + * @param string $type + * @param string $method + * @param string $path + * @param array $requirements + * @param array $filters + * @param PaginationMetadata|null $pagination + * @param SerializationMetadata|null $normalization + * @param SerializationMetadata|null $denormalization */ public function __construct( string $name, @@ -74,11 +80,11 @@ class OperationMetadata implements \JsonSerializable string $type, string $method, string $path, - array $normalizationGroups, - array $denormalizationGroups, array $requirements, array $filters, - PaginationMetadata $pagination + ?PaginationMetadata $pagination, + ?SerializationMetadata $normalization, + ?SerializationMetadata $denormalization ) { $this->name = $name; $this->type = $type; @@ -87,9 +93,9 @@ class OperationMetadata implements \JsonSerializable $this->filters = $filters; $this->description = $description; $this->pagination = $pagination; - $this->normalizationGroups = $normalizationGroups; - $this->denormalizationGroups = $denormalizationGroups; $this->requirements = $requirements; + $this->normalization = $normalization; + $this->denormalization = $denormalization; } /** @@ -191,6 +197,16 @@ class OperationMetadata implements \JsonSerializable return $this->requirements; } + /** + * Get pagination. + * + * @return PaginationMetadata|null + */ + public function getPagination(): ?PaginationMetadata + { + return $this->pagination; + } + /** * Get filters. * @@ -201,6 +217,26 @@ class OperationMetadata implements \JsonSerializable return $this->filters; } + /** + * Get normalization. + * + * @return SerializationMetadata|null + */ + public function getNormalization(): ?SerializationMetadata + { + return $this->normalization; + } + + /** + * Get denormalization. + * + * @return SerializationMetadata|null + */ + public function getDenormalization(): ?SerializationMetadata + { + return $this->denormalization; + } + /** * Get resource. * @@ -216,23 +252,9 @@ class OperationMetadata implements \JsonSerializable */ public function jsonSerialize() { - return [ - 'name' => $this->name, - 'description' => $this->description, - 'type' => $this->type, - 'method' => $this->method, - 'path' => $this->path, - 'filters' => $this->filters, - 'requirements' => $this->requirements, - 'pagination' => $this->pagination, - 'normalization' => [ - 'groups' => $this->normalizationGroups, - 'properties' => $this->getResource()->getProperties($this->normalizationGroups), - ], - 'denormalization' => [ - 'groups' => $this->denormalizationGroups, - 'properties' => $this->getResource()->getProperties($this->denormalizationGroups), - ], - ]; + $vars = \get_object_vars($this); + unset($vars['resource']); + + return $vars; } } diff --git a/src/Metadata/PaginationMetadata.php b/src/Metadata/PaginationMetadata.php index 79394e23fc6f74d28d338c507284f36b0c551a02..08447aefd8e4614fb6990e4589c5e27257f0514f 100644 --- a/src/Metadata/PaginationMetadata.php +++ b/src/Metadata/PaginationMetadata.php @@ -99,11 +99,6 @@ final class PaginationMetadata implements \JsonSerializable */ public function jsonSerialize() { - return [ - 'enabled' => $this->enabled, - 'page_parameter_name' => $this->pageParameterName, - 'client_items_per_page_enabled' => $this->clientItemsPerPage, - 'items_per_page_parameter_name' => $this->itemsPerPageParameterName, - ]; + return \get_object_vars($this); } } diff --git a/src/Metadata/PropertyMetadata.php b/src/Metadata/PropertyMetadata.php index f0b8db1223f504b38cedbe5252848a1faefc4159..60d7c361acc6a75d287f795846f2a95bb54073ec 100644 --- a/src/Metadata/PropertyMetadata.php +++ b/src/Metadata/PropertyMetadata.php @@ -35,8 +35,7 @@ class PropertyMetadata implements \JsonSerializable /** @var Type */ private $type; - /** @var bool */ - private $identifier; + /** @var bool */private $identifier; /** @var bool */ private $readable; @@ -44,9 +43,6 @@ class PropertyMetadata implements \JsonSerializable /** @var bool */ private $writable; - /** @var ResourceMetadata */ - private $resource; - /** @var bool */ private $initializable; @@ -91,29 +87,6 @@ class PropertyMetadata implements \JsonSerializable $this->embedded = $embedded; } - /** - * @param ResourceMetadata $resource - * - * @return PropertyMetadata - */ - public function withResource(ResourceMetadata $resource): self - { - $new = clone $this; - $new->resource = $resource; - - return $new; - } - - /** - * Get className. - * - * @return string - */ - public function getClassName(): string - { - return $this->resource->getClassName(); - } - /** * Get name. * @@ -150,7 +123,7 @@ class PropertyMetadata implements \JsonSerializable public function getLeafType(): Type { $type = $this->type; - while ($type->isCollection()) { + while ($type && $type->isCollection() && $type->getCollectionValueType()) { $type = $type->getCollectionValueType(); } @@ -225,33 +198,16 @@ class PropertyMetadata implements \JsonSerializable return $this->embedded; } - /** - * Get resource. - * - * @return ResourceMetadata - */ - public function getResource(): ResourceMetadata - { - return $this->resource; - } - /** * {@inheritdoc} */ public function jsonSerialize() { - return [ - 'name' => $this->name, - 'description' => $this->description, - 'type' => $this->serializeType($this->type), - 'identifier' => $this->identifier, - 'readable' => $this->readable, - 'writable' => $this->writable, - 'nullable' => $this->isNullable(), - 'initializable' => $this->initializable, - 'link' => $this->link, - 'embedded' => $this->embedded, - ]; + $vars = \get_object_vars($this); + $vars['type'] = $this->serializeType($this->type); + unset($vars['resource']); + + return $vars; } /** diff --git a/src/Metadata/RepresentationMetadata.php b/src/Metadata/RepresentationMetadata.php new file mode 100644 index 0000000000000000000000000000000000000000..58f61bf3ef8fe4f65f8d099d1c60e51d9e7fbebd --- /dev/null +++ b/src/Metadata/RepresentationMetadata.php @@ -0,0 +1,99 @@ +<?php declare(strict_types=1); +/* + * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata. + * Copyright (C) 2018 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\Metadata; + +use Irstea\NgModelGeneratorBundle\Models\PHPClass; + +/** + * Class RepresentationMetadata. + */ +final class RepresentationMetadata implements \JsonSerializable +{ + /** + * @var PHPClass + */ + private $class; + + /** + * @var null|PHPClass + */ + private $parent; + + /** + * @var array + */ + private $properties = []; + + /** + * RepresentationMetadata constructor. + * + * @param PHPClass $class + * @param PHPClass|null $parent + * @param PropertyMetadata[] $properties + */ + public function __construct(PHPClass $class, ?PHPClass $parent, array $properties) + { + $this->class = $class; + $this->parent = $parent; + + foreach ($properties as $property) { + $this->properties[$property->getName()] = $property; + } + ksort($this->properties); + } + + /** + * Get class. + * + * @return PHPClass + */ + public function getClass(): PHPClass + { + return $this->class; + } + + /** + * Get parent. + * + * @return null|PHPClass + */ + public function getParent(): ?PHPClass + { + return $this->parent; + } + + /** + * Get properties. + * + * @return PropertyMetadata[] + */ + public function getProperties(): array + { + return $this->properties; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() + { + return \get_object_vars($this); + } +} diff --git a/src/Metadata/ResourceMetadata.php b/src/Metadata/ResourceMetadata.php index cda350562ba0aeca44a32f69ae48abc0ce73c95c..95eb4b67a366edd4c92a507652cda1f500a0ea7e 100644 --- a/src/Metadata/ResourceMetadata.php +++ b/src/Metadata/ResourceMetadata.php @@ -26,39 +26,38 @@ use Irstea\NgModelGeneratorBundle\Models\PHPClass; */ class ResourceMetadata implements \JsonSerializable { - use PaginedTrait; - use SerializationGroupsTrait; - /** @var PHPClass */ private $class; /** @var PHPClass|null */ private $parentClass; - /** @var string */ - private $shortName; - /** @var string */ private $description; /** @var bool */ private $abstract; - /** @var MetadataFactoryInterface */ - private $factory; + /** @var SerializationMetadata */ + private $defaultNormalization; + + /** @var SerializationMetadata */ + private $defaultDenormalization; + + /** @var OperationMetadata[] */ + private $operations = []; /** * ResourceMetadata constructor. * - * @param PHPClass $class - * @param PHPClass|null $parentClass - * @param string $shortName - * @param string $description - * @param bool $abstract - * @param PaginationMetadata $pagination - * @param string[] $normalizationGroups - * @param string[] $denormalizationGroups - * @param MetadataFactoryInterface $factory + * @param PHPClass $class + * @param PHPClass|null $parentClass + * @param string $shortName + * @param string $description + * @param bool $abstract + * @param SerializationMetadata $defaultNormalization + * @param SerializationMetadata $defaultDenormalization + * @param OperationMetadata[] $operations */ public function __construct( PHPClass $class, @@ -66,34 +65,21 @@ class ResourceMetadata implements \JsonSerializable string $shortName, string $description, bool $abstract, - PaginationMetadata $pagination, - array $normalizationGroups, - array $denormalizationGroups, - MetadataFactoryInterface $factory + SerializationMetadata $defaultNormalization, + SerializationMetadata $defaultDenormalization, + array $operations ) { $this->class = $class; $this->parentClass = $parentClass; - $this->shortName = $shortName; - $this->description = $description; - $this->factory = $factory; $this->abstract = $abstract; - $this->pagination = $pagination; - $this->normalizationGroups = $normalizationGroups; - $this->denormalizationGroups = $denormalizationGroups; - } - - /** - * Set factory. - * - * @param MetadataFactoryInterface $factory - * - * @return ResourceMetadata - */ - public function setFactory(MetadataFactoryInterface $factory): self - { - $this->factory = $factory; + $this->description = $description; + $this->defaultNormalization = $defaultNormalization; + $this->defaultDenormalization = $defaultDenormalization; - return $this; + foreach ($operations as $operation) { + $this->operations[$operation->getName() . $operation->getType()] = $operation->withResource($this); + } + ksort($this->operations); } /** @@ -163,29 +149,27 @@ class ResourceMetadata implements \JsonSerializable */ public function getOperations(): array { - return array_map( - function (OperationMetadata $op): OperationMetadata { - return $op->withResource($this); - }, - $this->factory->getOperations($this->class) - ); + return $this->operations; } /** - * Get properties. + * Get defaultNormalization. * - * @param array $groups + * @return SerializationMetadata + */ + public function getDefaultNormalization(): SerializationMetadata + { + return $this->defaultNormalization; + } + + /** + * Get defaultDenormalization. * - * @return PropertyMetadata[] + * @return SerializationMetadata */ - public function getProperties(array $groups = []): array + public function getDefaultDenormalization(): SerializationMetadata { - return array_map( - function (PropertyMetadata $p): PropertyMetadata { - return $p->withResource($this); - }, - $this->factory->getProperties($this->class, $groups) - ); + return $this->defaultDenormalization; } /** @@ -193,22 +177,6 @@ class ResourceMetadata implements \JsonSerializable */ public function jsonSerialize() { - return [ - 'class_name' => $this->class->getFullName(), - 'parent_class_name' => $this->getParentClassName(), - 'short_name' => $this->shortName, - 'description' => $this->description, - 'abstract' => $this->abstract, - 'pagination' => $this->pagination, - 'normalization' => [ - 'groups' => $this->normalizationGroups, - 'properties' => $this->getProperties($this->normalizationGroups), - ], - 'denormalization' => [ - 'groups' => $this->denormalizationGroups, - 'properties' => $this->getProperties($this->denormalizationGroups), - ], - 'operations' => $this->getOperations(), - ]; + return \get_object_vars($this); } } diff --git a/src/Metadata/SerializationGroupsTrait.php b/src/Metadata/SerializationGroupsTrait.php deleted file mode 100644 index 309cb11d46a6f47eacfeaf2a1c2019756a36510e..0000000000000000000000000000000000000000 --- a/src/Metadata/SerializationGroupsTrait.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php declare(strict_types=1); -/* - * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata. - * Copyright (C) 2018 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\Metadata; - -/** - * Trait SerializationGroupsTrait. - */ -trait SerializationGroupsTrait -{ - /** @var string[] */ - private $normalizationGroups; - - /** @var string[] */ - private $denormalizationGroups; - - /** - * @param bool $normalization - * - * @return string[] - */ - public function getSerializationGroups(bool $normalization): array - { - return $normalization ? $this->normalizationGroups : $this->denormalizationGroups; - } -} diff --git a/src/Metadata/SerializationMetadata.php b/src/Metadata/SerializationMetadata.php new file mode 100644 index 0000000000000000000000000000000000000000..999bd5d45ec33af2dd5eb8bf9c6c01aa299e0a92 --- /dev/null +++ b/src/Metadata/SerializationMetadata.php @@ -0,0 +1,148 @@ +<?php declare(strict_types=1); +/* + * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata. + * Copyright (C) 2018 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\Metadata; + +use Irstea\NgModelGeneratorBundle\Models\PHPClass; + +/** + * Class SerializationMetadata. + */ +final class SerializationMetadata implements \JsonSerializable +{ + /** + * @var PHPClass + */ + private $root; + + /** + * @var string[] + */ + private $groups; + + /** + * @var bool + */ + private $normalization; + + /** + * @var RepresentationMetadata[] + */ + private $representations = []; + + /** + * SerializationMetadata constructor. + * + * @param PHPClass $root + * @param string[] $groups + * @param bool $normalization + * @param RepresentationMetadata[] $representations + */ + public function __construct(PHPClass $root, array $groups, bool $normalization, array $representations) + { + $this->groups = $groups; + sort($this->groups); + + $this->root = $root; + $this->normalization = $normalization; + + foreach ($representations as $representation) { + $this->representations[$representation->getClass()->getFullName()] = $representation; + } + ksort($this->representations); + } + + /** + * Get groups. + * + * @return string[] + */ + public function getGroups(): array + { + return $this->groups; + } + + /** + * Get root. + * + * @return PHPClass + */ + public function getRoot(): PHPClass + { + return $this->root; + } + + /** + * @param PHPClass|string $class + * + * @return bool + */ + public function isRoot($class): bool + { + return $this->root === PHPClass::get($class); + } + + /** + * Get representations. + * + * @return RepresentationMetadata[] + */ + public function getRepresentations(): array + { + return $this->representations; + } + + /** + * @param PHPClass $class + * + * @return bool + */ + public function hasRepresentationOf(PHPClass $class): bool + { + return isset($this->representations[$class->getFullName()]); + } + + /** + * @param PHPClass $class + * + * @return RepresentationMetadata + */ + public function getRepresentationOf(PHPClass $class): RepresentationMetadata + { + return $this->representations[$class->getFullName()]; + } + + /** + * Get normalization. + * + * @return bool + */ + public function isNormalization(): bool + { + return $this->normalization; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() + { + return \get_object_vars($this); + } +} diff --git a/src/ModelGenerator.php b/src/ModelGenerator.php index 72a3aa831289882736925f700913f7d00fe46d29..00cce57644ecb907dae1032a940572aa103d41cc 100644 --- a/src/ModelGenerator.php +++ b/src/ModelGenerator.php @@ -28,6 +28,7 @@ use Irstea\NgModelGeneratorBundle\Metadata\ClassHierarchy; use Irstea\NgModelGeneratorBundle\Metadata\DefaultClassHierarchy; use Irstea\NgModelGeneratorBundle\Metadata\MetadataFactoryInterface; use Irstea\NgModelGeneratorBundle\Metadata\ResourceMetadata; +use Irstea\NgModelGeneratorBundle\Metadata\SerializationMetadata; use Irstea\NgModelGeneratorBundle\Models\Declaration; use Irstea\NgModelGeneratorBundle\Models\PHPClass; use Irstea\NgModelGeneratorBundle\Models\Types\Alias; @@ -58,8 +59,8 @@ final class ModelGenerator /** @var TypeFactory */ private $typeFactory; - /** @var string[][][] */ - private $defaultGroups; + /** @var SerializationMetadata[][] */ + private $defaultSerializations; /** @var ConcreteRepresentation[][] */ private $resources; @@ -67,24 +68,18 @@ final class ModelGenerator /** @var ClassHierarchy */ private $classHierarchy; - /** @var NamingStrategy */ - private $namingStrategy; - /** * Serializer constructor. * * @param MetadataFactoryInterface $metadataFactory * @param Environment $twigEnv - * @param NamingStrategy $namingStrategy */ public function __construct( MetadataFactoryInterface $metadataFactory, - Environment $twigEnv, - NamingStrategy $namingStrategy + Environment $twigEnv ) { $this->metadataFactory = $metadataFactory; $this->twigEnv = $twigEnv; - $this->namingStrategy = $namingStrategy; } /** @@ -125,9 +120,7 @@ final class ModelGenerator $this->typeFactory = $this->createTypeFactory(); - $this->defaultGroups = $this->extractDefaultGroups(); - - $this->resources = $this->extractBaseRepresentations(); + $this->defaultSerializations = $this->extractDefaultSerializations(); [$repositories, $iriPatterns] = $this->extractRepositories(); @@ -139,7 +132,6 @@ final class ModelGenerator 'title' => $this->documentation->getTitle(), 'version' => $this->documentation->getVersion(), 'description' => $this->documentation->getDescription() ?: '', - 'resources' => $this->resources, 'repositories' => $repositories, 'declarations' => $declarations, 'iriPatterns' => $iriPatterns, @@ -206,12 +198,7 @@ final class ModelGenerator $factory->addAlias($alias, $target); } - return new ResourceTypeFactory( - $this->metadataFactory, - $factory, - $this->namingStrategy, - $this->classHierarchy - ); + return $factory; } /** @@ -237,11 +224,11 @@ final class ModelGenerator /** * Extrait les groupes de sérialization par défaut des ressources. * - * @return string[][][] + * @return SerializationMetadata[][] */ - private function extractDefaultGroups(): array + private function extractDefaultSerializations(): array { - $defaultGroups = [ + $serializations = [ 'normalization' => [], 'denormalization' => [], ]; @@ -253,14 +240,13 @@ final class ModelGenerator foreach ($this->getResourceMetadata() as $class => $resourceMeta) { $className = $class->getFullName(); - foreach (['normalization', 'denormalization'] as $normalization) { - $groups = $resourceMeta->getSerializationGroups($normalization === 'normalization'); - sort($groups); - $defaultGroups[$normalization][$className] = $groups; - } + $serializations['normalization'][$className] = + $resourceMeta->getDefaultNormalization(); + $serializations['denormalization'][$className] = + $resourceMeta->getDefaultDenormalization(); } - return $defaultGroups; + return $serializations; } /** @@ -279,38 +265,6 @@ final class ModelGenerator } } - /** - * Extrait les représentations par défaut des ressources. - * - * @return ConcreteRepresentation[][] - */ - private function extractBaseRepresentations(): array - { - $representations = []; - - /** @var PHPClass $class */ - foreach ($this->getResourceMetadata() as $class => $resourceMeta) { - $className = $class->getFullName(); - $representations[$className] = []; - - foreach (['normalization', 'denormalization'] as $normalization) { - $ctx = new Context( - $class, - $this->defaultGroups, - $normalization === 'normalization', - $this->defaultGroups[$normalization][$className] - ); - - $representations[$className][$normalization] = - $this->typeFactory - ->withContext($ctx) - ->get($className); - } - } - - return $representations; - } - /** * @return array */ @@ -326,20 +280,16 @@ final class ModelGenerator foreach ($this->getResourceMetadata() as $class => $resourceMeta) { $repoName = $resourceMeta->getShortName() . 'Repository'; - $ctx = new Context($class, $this->defaultGroups); - $repositories[$repoName] = $this->typeFactory->getOrCreate( $repoName, - function () use ($resourceMeta, $class, &$iriPatterns, $ctx): Type { + function () use ($resourceMeta, &$iriPatterns): Type { $operations = []; foreach ($resourceMeta->getOperations() as $operation) { $mapper = new OperationMapper( - $this->typeFactory->withContext($ctx), - $operation, - $this->resources[$class->getFullName()]['normalization'], - $ctx + $this->typeFactory, + $operation ); $operations[] = $operation = $mapper(); diff --git a/src/Models/PHPClass.php b/src/Models/PHPClass.php index 60954b99d608c178c570cb8c667808891cff887a..a20e719ed27354fbbca2905d31e6668f7dd8c3f6 100644 --- a/src/Models/PHPClass.php +++ b/src/Models/PHPClass.php @@ -26,8 +26,6 @@ use Irstea\NgModelGeneratorBundle\Exceptions\InvalidArgumentException; */ final class PHPClass implements \JsonSerializable { - use MultitonTrait; - /** @var string */ private $namespace; @@ -37,15 +35,13 @@ final class PHPClass implements \JsonSerializable /** * ClassName constructor. * - * @param string $name + * @param string $namespace + * @param string $baseName */ - private function __construct(string $name) + private function __construct(string $namespace, string $baseName) { - $groups = []; - if (!preg_match('/^\\\\?((?:\w+\\\\)*)(\w+)$/i', $name, $groups)) { - throw new InvalidArgumentException("Invalid PHP class name: $name"); - } - [, $this->namespace, $this->baseName] = $groups; + $this->namespace = $namespace; + $this->baseName = $baseName; } /** @@ -120,4 +116,30 @@ final class PHPClass implements \JsonSerializable { return $a->getBaseName() > $b->getBaseName(); } + + /** + * @param self|string $name + * + * @return self + */ + public static function get($name): self + { + if ($name instanceof self) { + return $name; + } + + static $instances = []; + + if (!isset($instances[$name])) { + $groups = []; + if (!preg_match('/^\\\\?((?:\w+\\\\)*)(\w+)$/i', $name, $groups)) { + throw new InvalidArgumentException("Invalid PHP class name: $name"); + } + [, $namespace, $baseName] = $groups; + + $instances[$name] = new self($namespace, $baseName); + } + + return $instances[$name]; + } } diff --git a/src/Models/Types/Generic.php b/src/Models/Types/AbstractCollection.php similarity index 51% rename from src/Models/Types/Generic.php rename to src/Models/Types/AbstractCollection.php index 93f0e69082e6d01f1153475fec045d92555df65c..8817b9630934909f2a67bcbb16c49593f7c8e6a1 100644 --- a/src/Models/Types/Generic.php +++ b/src/Models/Types/AbstractCollection.php @@ -20,82 +20,57 @@ namespace Irstea\NgModelGeneratorBundle\Models\Types; /** - * Class Generic. + * Class AbstractCollection. */ -final class Generic extends AbstractType +abstract class AbstractCollection extends AbstractType { /** * @var Type */ - private $baseType; + protected $valueType; /** - * @var Type[] - */ - private $parameters; - - /** - * Generic constructor. + * Collection constructor. * - * @param Type $baseType - * @param Type[] $parameters + * @param Type $itemType */ - public function __construct(Type $baseType, array $parameters) + public function __construct(Type $itemType) { - $this->baseType = $baseType; - $this->parameters = $parameters; + $this->valueType = $itemType; } /** * {@inheritdoc} */ - public function getUsage(): string + public function getIterator() { - return sprintf( - '%s<%s>', - $this->baseType->getUsage(), - \implode(', ', \array_map( - function (Type $t): string { - return $t->getUsage(); - }, - $this->parameters - )) - ); + yield $this->getKeyType(); + yield $this->valueType; } /** * {@inheritdoc} */ - public function getIterator() + public function getUsage(): string { - yield $this->baseType; - yield from $this->parameters; + return sprintf('%s<%s>', $this->getGenericUsage(), $this->valueType->getUsage()); } /** - * {@inheritdoc} + * @return array */ - public function castToStringOrStringArray(string $expr): string + public function jsonSerialize() { - return $this->baseType->castToStringOrStringArray($expr); + return ['type' => $this->getGenericUsage(), 'keyType' => $this->getKeyType(), 'valueType' => $this->valueType]; } /** - * {@inheritdoc} + * @return Type */ - public function checkType(string $expr): string - { - return $this->baseType->checkType($expr); - } + abstract protected function getKeyType(): Type; /** - * {@inheritdoc} + * @return string */ - public function jsonSerialize() - { - return [ - 'baseType' => $this->baseType, - 'parameters' => $this->parameters, - ]; - } + abstract protected function getGenericUsage(): string; } diff --git a/src/Models/Types/Collection.php b/src/Models/Types/ArrayType.php similarity index 67% rename from src/Models/Types/Collection.php rename to src/Models/Types/ArrayType.php index 1174593df28e025da1c342d12c8856a5897e4970..3527d4c1b3ce73373d85301d168146173ab1db58 100644 --- a/src/Models/Types/Collection.php +++ b/src/Models/Types/ArrayType.php @@ -20,31 +20,24 @@ namespace Irstea\NgModelGeneratorBundle\Models\Types; /** - * Class Collection. + * Class ArrayType. */ -final class Collection extends AbstractType +class ArrayType extends AbstractCollection { /** - * @var Type - */ - private $itemType; - - /** - * Collection constructor. - * - * @param Type $itemType + * {@inheritdoc} */ - public function __construct(Type $itemType) + protected function getKeyType(): Type { - $this->itemType = $itemType; + return BuiltinType::get('number'); } /** * {@inheritdoc} */ - public function getUsage(): string + protected function getGenericUsage(): string { - return sprintf('Array<%s>', $this->itemType->getUsage()); + return 'Array'; } /** @@ -52,7 +45,7 @@ final class Collection extends AbstractType */ public function castToStringOrStringArray(string $expr): string { - $cast = $this->itemType->castToStringOrStringArray('x'); + $cast = $this->valueType->castToStringOrStringArray('x'); if ($cast !== 'x') { return sprintf('%s.map(x => %s)', $expr, $cast); } @@ -67,20 +60,4 @@ final class Collection extends AbstractType { return sprintf('Array.isArray(%s)', $expr); } - - /** - * {@inheritdoc} - */ - public function getIterator() - { - yield $this->itemType; - } - - /** - * @return array - */ - public function jsonSerialize() - { - return ['type' => 'Array', 'itemType' => $this->itemType]; - } } diff --git a/src/Models/Types/BuiltinType.php b/src/Models/Types/BuiltinType.php index 7a9c60eed96a82817a34bc445878c1220ad614ca..37aeaafa71ca1e442bc93c98f93bfa4d5f94c9f6 100644 --- a/src/Models/Types/BuiltinType.php +++ b/src/Models/Types/BuiltinType.php @@ -26,7 +26,7 @@ use Irstea\NgModelGeneratorBundle\TypescriptHelper; /** * Class BuiltinType. */ -final class BuiltinType extends AbstractType +class BuiltinType extends AbstractType { use NamedTrait; use MultitonTrait; diff --git a/src/Models/Types/Resources/AbstractRepresentation.php b/src/Models/Types/Resources/AbstractRepresentation.php index ec423fb4a1c93409a4226e9e8e8825b627ce407f..44d30a194d9667136b428abb29dd98c9a580caba 100644 --- a/src/Models/Types/Resources/AbstractRepresentation.php +++ b/src/Models/Types/Resources/AbstractRepresentation.php @@ -37,9 +37,6 @@ final class AbstractRepresentation extends AbstractType implements Representatio /** @var Type[] */ private $children; - /** @var ResourceMetadata */ - private $resource; - /** * AbstractRepresentation constructor. * @@ -49,13 +46,12 @@ final class AbstractRepresentation extends AbstractType implements Representatio * @param Type|null $parent * @param Type[] $children */ - public function __construct(ResourceMetadata $resource, string $name, string $description, Type $parent = null, array $children = []) + public function __construct(string $name, string $description, Type $parent = null, array $children = []) { $this->name = $name; $this->parent = $parent; $this->children = $children; $this->description = $description; - $this->resource = $resource; } /** @@ -132,28 +128,4 @@ final class AbstractRepresentation extends AbstractType implements Representatio 'children' => $this->children, ]; } - - /** - * {@inheritdoc} - */ - public function getResourceClassName(): string - { - return $this->resource->getClassName(); - } - - /** - * {@inheritdoc} - */ - public function getResourceShortName(): string - { - return $this->resource->getShortName(); - } - - /** - * {@inheritdoc} - */ - public function isAbstractResource(): bool - { - return true; - } } diff --git a/src/Metadata/PaginedTrait.php b/src/Models/Types/Resources/Collection.php similarity index 74% rename from src/Metadata/PaginedTrait.php rename to src/Models/Types/Resources/Collection.php index 78b5133ac92c1f55dd02efbe68efd19b95c7a234..8754e96596e7360fd1453b04259b89557279d62e 100644 --- a/src/Metadata/PaginedTrait.php +++ b/src/Models/Types/Resources/Collection.php @@ -17,23 +17,20 @@ * <https://www.gnu.org/licenses/>. */ -namespace Irstea\NgModelGeneratorBundle\Metadata; +namespace Irstea\NgModelGeneratorBundle\Models\Types\Resources; + +use Irstea\NgModelGeneratorBundle\Models\Types\ArrayType; /** - * Trait PaginedTrait. + * Class Collection. */ -trait PaginedTrait +final class Collection extends ArrayType { - /** @var PaginationMetadata */ - private $pagination; - /** - * Get pagination. - * - * @return PaginationMetadata + * {@inheritdoc} */ - public function getPagination(): PaginationMetadata + protected function getGenericUsage(): string { - return $this->pagination; + return 'Collection'; } } diff --git a/src/Models/Types/Resources/ConcreteRepresentation.php b/src/Models/Types/Resources/ConcreteRepresentation.php index 6f87ea44b5a4e153ca45d871da3d9f7ea58b4c0f..952d3c5ab13cdb2eda58823130ccac3a5b4daeee 100644 --- a/src/Models/Types/Resources/ConcreteRepresentation.php +++ b/src/Models/Types/Resources/ConcreteRepresentation.php @@ -19,8 +19,9 @@ namespace Irstea\NgModelGeneratorBundle\Models\Types\Resources; -use Irstea\NgModelGeneratorBundle\Metadata\ResourceMetadata; +use Irstea\NgModelGeneratorBundle\Models\PHPClass; use Irstea\NgModelGeneratorBundle\Models\Types\Objects\InterfaceType; +use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property; use Irstea\NgModelGeneratorBundle\Models\Types\Type; use Irstea\NgModelGeneratorBundle\TypescriptHelper; @@ -29,54 +30,29 @@ use Irstea\NgModelGeneratorBundle\TypescriptHelper; */ final class ConcreteRepresentation extends InterfaceType implements Representation { - /** @var ResourceMetadata */ - private $resource; - /** @var bool */ private $normalization; /** - * Representation constructor. - * - * @param ResourceMetadata $resource - * @param string $name - * @param Type|null $parent - * @param array $properties - * @param string $description - * @param array $children - * @param bool $normalization + * @var array|PHPClass[] */ - public function __construct(ResourceMetadata $resource, string $name, ?Type $parent, array $properties, string $description, array $children, bool $normalization) - { - parent::__construct($name, $parent, $properties, $description, $children); - $this->normalization = $normalization; - $this->resource = $resource; - } + private $resources; /** - * Get resourceClassName. + * Representation constructor. * - * @return string - */ - public function getResourceClassName(): string - { - return $this->resource->getClassName(); - } - - /** - * @return string - */ - public function getResourceShortName(): string - { - return $this->resource->getShortName(); - } - - /** - * {@inheritdoc} + * @param string $name + * @param Type|null $parent + * @param Property[] $properties + * @param string $description + * @param PHPClass[] $resources + * @param bool $normalization */ - public function isAbstractResource(): bool + public function __construct(string $name, ?Type $parent, array $properties, string $description, array $resources, bool $normalization) { - return false; + parent::__construct($name, $parent, $properties, $description, $resources); + $this->normalization = $normalization; + $this->resources = $resources; } /** @@ -117,19 +93,9 @@ final class ConcreteRepresentation extends InterfaceType implements Representati */ private function getAllResourceNames(): array { - ksort($names); - - return array_keys($names); - } - - /** - * {@inheritdoc} - */ - public function jsonSerialize() - { - $json = parent::jsonSerialize(); - $json['resource'] = $this->getResourceShortName(); - - return $json; + return array_map( + function (PHPClass $c) { return $c->getBaseName(); }, + $this->resources + ); } } diff --git a/src/Models/Types/Resources/IRI.php b/src/Models/Types/Resources/IRI.php new file mode 100644 index 0000000000000000000000000000000000000000..e251a5f937b6104dbf034414a926ec01aa2a2c62 --- /dev/null +++ b/src/Models/Types/Resources/IRI.php @@ -0,0 +1,75 @@ +<?php declare(strict_types=1); +/* + * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata. + * Copyright (C) 2018 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\Models\Types\Resources; + +use Irstea\NgModelGeneratorBundle\Models\MultitonTrait; +use Irstea\NgModelGeneratorBundle\Models\PHPClass; +use Irstea\NgModelGeneratorBundle\Models\Types\AbstractType; +use Irstea\NgModelGeneratorBundle\TypescriptHelper; + +/** + * Class IRI. + */ +final class IRI extends AbstractType +{ + use MultitonTrait; + + /** @var PHPClass */ + private $resource; + + /** + * IRI constructor. + * + * @param PHPClass $resource + */ + public function __construct(string $className) + { + $this->resource = PHPClass::get($className); + } + + /** + * {@inheritdoc} + */ + public function getUsage(): string + { + return sprintf('IRI<%s>', $this->resource->getBaseName()); + } + + /** + * {@inheritdoc} + */ + public function castToStringOrStringArray(string $expr): string + { + return $expr; + } + + /** + * {@inheritdoc} + */ + public function checkType(string $expr): string + { + return sprintf( + 'isIRI<%s>(%s, %s)', + $this->resource->getBaseName(), + $expr, + TypescriptHelper::quoteString($this->resource->getBaseName()) + ); + } +} diff --git a/src/Models/Types/Resources/Representation.php b/src/Models/Types/Resources/Representation.php index 4e269de6684e25430a96e5ef47c4438f7c5def02..375af4d58b293670ce5ed25d7d2d57370f93cae0 100644 --- a/src/Models/Types/Resources/Representation.php +++ b/src/Models/Types/Resources/Representation.php @@ -27,18 +27,4 @@ use Irstea\NgModelGeneratorBundle\Models\Types\Type; */ interface Representation extends Type, Declaration { - /** - * @return string - */ - public function getResourceClassName(): string; - - /** - * @return string - */ - public function getResourceShortName(): string; - - /** - * @return bool - */ - public function isAbstractResource(): bool; } diff --git a/src/NamingStrategy.php b/src/NamingStrategy.php deleted file mode 100644 index 0f8efaec9525a3ff0b7c9a651e402e7e1835a92d..0000000000000000000000000000000000000000 --- a/src/NamingStrategy.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php declare(strict_types=1); -/* - * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata. - * Copyright (C) 2018 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 Irstea\NgModelGeneratorBundle\Models\PHPClass; - -/** - * Interface NamingStrategy. - */ -interface NamingStrategy -{ - /** - * @param ContextInterface $context - * @param PHPClass $resourceClass - * - * @return mixed - */ - public function getName(ContextInterface $context, PHPClass $resourceClass); -} diff --git a/src/OperationMapper.php b/src/OperationMapper.php index b95f99d9fd5df29610c278081f0c30e53f0cb2a3..90639941f4c44d51c4d54538014c03c6d1e56fde 100644 --- a/src/OperationMapper.php +++ b/src/OperationMapper.php @@ -21,37 +21,31 @@ namespace Irstea\NgModelGeneratorBundle; use Doctrine\Common\Inflector\Inflector; use Irstea\NgModelGeneratorBundle\Metadata\OperationMetadata; +use Irstea\NgModelGeneratorBundle\Metadata\SerializationMetadata; +use Irstea\NgModelGeneratorBundle\Models\Types\ArrayType; use Irstea\NgModelGeneratorBundle\Models\Types\BuiltinType; -use Irstea\NgModelGeneratorBundle\Models\Types\Collection; use Irstea\NgModelGeneratorBundle\Models\Types\Objects\InterfaceType; -use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Object; use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property; use Irstea\NgModelGeneratorBundle\Models\Types\Operations\CachedClientCall; +use Irstea\NgModelGeneratorBundle\Models\Types\Operations\ClientCall; use Irstea\NgModelGeneratorBundle\Models\Types\Operations\DirectClientCall; use Irstea\NgModelGeneratorBundle\Models\Types\Operations\Operation; use Irstea\NgModelGeneratorBundle\Models\Types\Operations\Parameter; use Irstea\NgModelGeneratorBundle\Models\Types\Operations\Path; use Irstea\NgModelGeneratorBundle\Models\Types\Operations\PathTemplate; use Irstea\NgModelGeneratorBundle\Models\Types\Operations\PlainPath; -use Irstea\NgModelGeneratorBundle\Models\Types\Resources\ConcreteRepresentation; +use Irstea\NgModelGeneratorBundle\Models\Types\Resources\Collection; use Irstea\NgModelGeneratorBundle\Models\Types\Type; use Irstea\NgModelGeneratorBundle\Models\Types\Union; -use Symfony\Component\PropertyInfo\Type as APIType; /** * Class OperationMapper. */ final class OperationMapper { - /** @var string[] */ - private const NORMALIZING_METHODS = ['POST', 'PUT', 'PATCH', 'GET']; - - /** @var string[] */ - private const DENORMALIZING_METHODS = ['POST', 'PUT', 'PATCH']; - /** @var string[][] */ private const CACHE_DECORATORS = [ - 'item' => [ + 'item' => [ 'GET' => ['get(iri, () => %s)', 'iri'], 'PUT' => ['put(iri, %s)', 'iri'], 'DELETE' => ['delete(iri, %s)', 'iri'], @@ -68,26 +62,16 @@ final class OperationMapper /** @var OperationMetadata */ private $operation; - /** @var ConcreteRepresentation */ - private $baseRepr; - - /** @var ContextInterface */ - private $context; - /** * OperationMapper constructor. * - * @param TypeFactoryInterface $typeFactory - * @param OperationMetadata $operation - * @param ConcreteRepresentation $baseRepr - * @param ContextInterface $context + * @param TypeFactoryInterface $typeFactory + * @param OperationMetadata $operation */ - public function __construct(TypeFactoryInterface $typeFactory, OperationMetadata $operation, ConcreteRepresentation $baseRepr, ContextInterface $context) + public function __construct(TypeFactoryInterface $typeFactory, OperationMetadata $operation) { $this->typeFactory = $typeFactory; $this->operation = $operation; - $this->baseRepr = $baseRepr; - $this->context = $context; } /** @@ -96,101 +80,46 @@ final class OperationMapper public function __invoke(): Operation { $httpMethod = $this->operation->getMethod(); - $normalize = \in_array($httpMethod, self::NORMALIZING_METHODS, true); - $denormalize = \in_array($httpMethod, self::DENORMALIZING_METHODS, true); + $normalization = $this->operation->getNormalization(); + $denormalization = $this->operation->getDenormalization(); - $className = $this->operation->getClassName(); - $name = Inflector::camelize($this->operation->getName()); - $collection = $this->operation->isCollectionOperation(); + $isCollection = $this->operation->isCollectionOperation(); - if ($denormalize && $name === 'post' && $httpMethod === 'POST') { - $collection = false; + if ($this->operation->getName() === 'post' && $httpMethod === 'POST' && $normalization && $denormalization) { + $isCollection = false; } - $pathProperties = $this->baseRepr->getAllProperties(); - $requestBody = $responseBody = null; - $canUseCache = ($httpMethod === 'DELETE'); - - if ($normalize) { - $factory = $this->typeFactory - ->withContext( - $this->context - ->withNormalization(true) - ->withGroups($this->operation->getSerializationGroups(true)) - ); - - $responseBody = $factory->get($className); + if ($normalization) { + $responseBody = $this->mapSerialization($normalization); - if ($collection) { - $responseBody = $factory->getGenericRef('Collection<T>', [$responseBody]); + if ($isCollection) { + $responseBody = new Collection($responseBody); } } - if ($denormalize) { - $requestBody = $this->typeFactory - ->withContext( - $this->context - ->withNormalization(false) - ->withGroups($this->operation->getSerializationGroups(false)) - ) - ->get($className); - - if ($collection) { - $requestBody = new Collection($requestBody); - } - } + if ($denormalization) { + $responseBody = $this->mapSerialization($denormalization); - if ($httpMethod === 'GET' && strpos($name, 'get') === false) { - $name = 'get' . ucfirst($name); - } - if ($collection) { - if (\in_array($name, ['get', 'put', 'delete', 'patch'], true)) { - $name .= 'All'; - } else { - $name = Inflector::pluralize($name); + if ($isCollection) { + $requestBody = new ArrayType($requestBody); } - } else { - $name = Inflector::singularize($name); - } - - $filters = null; - if ($collection && $httpMethod === 'GET') { - $filtersName = $this->operation->getResource()->getShortName() . 'Filters'; - - $filters = $this->typeFactory->getOrCreate( - $filtersName, - function () use ($filtersName): Type { - return new InterfaceType( - $filtersName, - $this->typeFactory->get('CommonFilters'), - $this->getFilters() - ); - } - ); } - $path = $this->parsePath($pathProperties); - - $clientCall = new DirectClientCall( - $httpMethod, - $path, - $filters, - $responseBody, - $requestBody + $clientCall = $this->applyCache( + new DirectClientCall( + $httpMethod, + $this->parsePath(), + $this->getFilters(), + $responseBody, + $requestBody + ), + $isCollection ); - $opType = $collection ? 'collection' : 'item'; - if ($canUseCache && isset(self::CACHE_DECORATORS[$opType][$httpMethod])) { - [$template, $requiredParam] = self::CACHE_DECORATORS[$opType][$httpMethod]; - if ($requiredParam === null || $clientCall->hasParameter($requiredParam)) { - $clientCall = new CachedClientCall($clientCall, 'this.cache.' . $template); - } - } - return new Operation( - $name, + $this->getFancyName($isCollection), $clientCall, sprintf( "Operation: %s\nType: %s\nMethod: %s\nPath: %s", @@ -202,12 +131,22 @@ final class OperationMapper ); } - /** - * @param Property[] $properties + /*** + * @param SerializationMetadata $serializationMetadata * + * @return Type + */ + private function mapSerialization(SerializationMetadata $serializationMetadata): Type + { + $mapper = new SerializationMapper($this->typeFactory, $serializationMetadata); + + return $mapper->get($serializationMetadata->getRoot()->getFullName()); + } + + /** * @return Path */ - private function parsePath(array $properties): Path + private function parsePath(): Path { $path = $this->operation->getPath(); @@ -217,19 +156,18 @@ final class OperationMapper return new PlainPath($parts[0]); } - return $this->parsePathTemplate($parts, $properties); + return $this->parsePathTemplate($parts); } /** - * @param string[] $parts - * @param Property[] $properties + * @param string[] $parts * * @return PathTemplate */ - private function parsePathTemplate(array $parts, array $properties): PathTemplate + private function parsePathTemplate(array $parts): PathTemplate { $requirements = $this->operation->getRequirements(); - $defaultType = $this->typeFactory->get(APIType::BUILTIN_TYPE_STRING); + $type = BuiltinType::get('string'); $template = $pattern = $parameters = []; @@ -250,11 +188,6 @@ final class OperationMapper $pattern[] = '([^\\/]*)'; } - $type = $defaultType; - if (isset($properties[$name])) { - $type = $properties[$name]->getType(); - } - $parameters[$name] = new Parameter($name, $type); } @@ -265,15 +198,90 @@ final class OperationMapper ); } + /** + * @param bool $isCollection + * + * @return string + */ + private function getFancyName(bool $isCollection): string + { + $name = Inflector::camelize($this->operation->getName()); + + if ($this->operation->getMethod() === 'GET' && strpos($name, 'get') === false) { + $name = 'get' . ucfirst($name); + } + + if ($isCollection) { + if (\in_array($name, ['get', 'put', 'delete', 'patch'], true)) { + return $name . 'All'; + } + + return Inflector::pluralize($name); + } + + return Inflector::singularize($name); + } + + /** + * @param ClientCall $clientCall + * + * @return ClientCall + */ + private function applyCache(ClientCall $clientCall, bool $isCollection): ClientCall + { + $decorators = self::CACHE_DECORATORS[$isCollection ? 'collection' : 'item']; + $method = $this->operation->getMethod(); + + if (!isset($decorators[$method])) { + return $clientCall; + } + + $normalization = $this->operation->getNormalization(); + if (!$normalization) { + if ($method !== 'DELETE') { + return $clientCall; + } + } + + [$template, $requiredParam] = $decorators[$method]; + if ($requiredParam !== null && !$clientCall->hasParameter($requiredParam)) { + return $clientCall; + } + + return new CachedClientCall($clientCall, 'this.cache.' . $template); + } + + /** + * @return Type | null + */ + private function getFilters(): ?Type + { + if (!$this->operation->getFilters() && !$this->operation->getPagination()) { + return null; + } + $filtersName = $this->operation->getResource()->getShortName() . 'Filters'; + + return $this->typeFactory->getOrCreate( + $filtersName, + function () use ($filtersName): Type { + return new InterfaceType( + $filtersName, + $this->typeFactory->get('CommonFilters'), + $this->getFilterParameters() + ); + } + ); + } + /** * @return Property[] */ - private function getFilters(): array + private function getFilterParameters(): array { $parameters = []; $pagination = $this->operation->getPagination(); - if ($pagination->isEnabled()) { + if ($pagination && $pagination->isEnabled()) { $intType = BuiltinType::get('number'); $parameters[$pagination->getPageParameterName()] = $intType; @@ -294,7 +302,7 @@ final class OperationMapper if (substr($name, $l - 2) === '[]') { $name = substr($name, 0, $l - 2); - $type = new Collection($type); + $type = new ArrayType($type); } $simplerName = Inflector::camelize(preg_replace('/\W+/', '_', $name)); diff --git a/src/ResourceTypeFactory.php b/src/ResourceTypeFactory.php deleted file mode 100644 index 0e569730d2349cf18be2f53c402c51bc6b04fefc..0000000000000000000000000000000000000000 --- a/src/ResourceTypeFactory.php +++ /dev/null @@ -1,283 +0,0 @@ -<?php declare(strict_types=1); -/* - * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata. - * Copyright (C) 2018 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 Irstea\NgModelGeneratorBundle\Exceptions\DomainException; -use Irstea\NgModelGeneratorBundle\Metadata\ClassHierarchy; -use Irstea\NgModelGeneratorBundle\Metadata\MetadataFactoryInterface; -use Irstea\NgModelGeneratorBundle\Metadata\PropertyMetadata; -use Irstea\NgModelGeneratorBundle\Models\PHPClass; -use Irstea\NgModelGeneratorBundle\Models\Types\Collection; -use Irstea\NgModelGeneratorBundle\Models\Types\Objects\HierarchicalObject; -use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property; -use Irstea\NgModelGeneratorBundle\Models\Types\Placeholder; -use Irstea\NgModelGeneratorBundle\Models\Types\Resources\ConcreteRepresentation; -use Irstea\NgModelGeneratorBundle\Models\Types\Type; -use Irstea\NgModelGeneratorBundle\Models\Types\Union; - -/** - * Class TypeMapper. - */ -final class ResourceTypeFactory implements TypeFactoryInterface -{ - /** @var MetadataFactoryInterface */ - private $metadataFactory; - - /** @var TypeFactoryInterface */ - private $typeFactory; - - /** @var NamingStrategy */ - private $namingStrategy; - - /** @var ClassHierarchy */ - private $classHierarchy; - - /** @var ContextInterface|null */ - private $context; - - /** - * TypeMapper constructor. - * - * @param MetadataFactoryInterface $metadataFactory - * @param TypeFactoryInterface $typeFactory - * @param NamingStrategy $namingStrategy - * @param ContextInterface $context - */ - public function __construct( - MetadataFactoryInterface $metadataFactory, - TypeFactoryInterface $typeFactory, - NamingStrategy $namingStrategy, - ClassHierarchy $classHierarchy, - ContextInterface $context = null - ) { - $this->metadataFactory = $metadataFactory; - $this->typeFactory = $typeFactory; - $this->namingStrategy = $namingStrategy; - $this->context = $context; - $this->classHierarchy = $classHierarchy; - } - - /** - * @param ContextInterface|null $context - * - * @return TypeFactoryInterface - */ - public function withContext(?ContextInterface $context): TypeFactoryInterface - { - if ($context === $this->context) { - return $this; - } - - $clone = clone $this; - $clone->context = $context; - $clone->typeFactory = $clone->typeFactory->withContext($context); - - return $clone; - } - - /** - * {@inheritdoc} - */ - public function has(string $name): bool - { - return $this->typeFactory->has($name); - } - - /** - * {@inheritdoc} - */ - public function getOrCreate(string $name, callable $builder, ...$args): Type - { - return $this->typeFactory->getOrCreate($name, $builder, ...$args); - } - - /** - * {@inheritdoc} - */ - public function get(string $name): Type - { - if (!$this->context || !class_exists($name) || $this->typeFactory->has($name)) { - return $this->typeFactory->get($name); - } - - $class = PHPClass::get($name); - - if ($this->metadataFactory->isResource($class)) { - $reprName = $this->namingStrategy->getName($this->context, $class); - - return $this->typeFactory->getOrCreate( - $reprName, - function () use ($class, $reprName): Type { - return $this->mapRepresentation($class, $reprName); - } - ); - } - - return $this->getOrCreate($name, [$this, 'get'], 'any'); - } - - /** - * {@inheritdoc} - */ - public function addBuiltin(string $name): void - { - $this->typeFactory->addBuiltin($name); - } - - /** - * {@inheritdoc} - */ - public function addAlias(string $alias, string $target): void - { - $this->typeFactory->addAlias($alias, $target); - } - - /** - * @param string $name - * @param PHPClass $resourceClass - * - * @return Type - */ - private function mapRepresentation(PHPClass $resourceClass, string $name): Type - { - $resourceMeta = $this->metadataFactory->getResourceMetadata($resourceClass); - - $normalization = $this->context->isNormalization(); - $groups = $this->context->getGroups(); - - $properties = $this->mapProperties($resourceMeta->getProperties()); - - $parent = null; - $parentClassName = $resourceMeta->getParentClassName(); - if ($parentClassName) { - $parent = $this->get($parentClassName); - - if ($parent instanceof HierarchicalObject) { - $properties = \array_diff_key($properties, $parent->getAllProperties()); - } else { - $parent = null; - } - } - - $desc = explode("\n", $resourceMeta->getDescription()); - $desc[] = 'Resource: ' . $resourceClass; - $desc[] = 'Direction: ' . ($normalization ? 'response' : 'request'); - $desc[] = sprintf('Serialization groups: %s', $groups ? implode(', ', $groups) : '-'); - $desc = trim(implode("\n", $desc)); - - return new ConcreteRepresentation($resourceMeta, $name, $this->context->isNormalization(), $parent, $properties, $desc); - } - - /** - * @param PropertyMetadata[] $propertiesMeta - * - * @return array - */ - private function mapProperties(array $propertiesMeta): array - { - $properties = []; - $identifierCount = 0; - - foreach ($propertiesMeta as $propertyMeta) { - if (!$this->acceptProperty($propertyMeta)) { - continue; - } - - if ($propertyMeta->isIdentifier()) { - ++$identifierCount; - } - - $property = $this->mapProperty($propertyMeta); - $properties[$property->getName()] = $property; - } - - if ($identifierCount > 1) { - throw new DomainException('Cannot handle resource with composite identifier'); - } - - return $properties; - } - - /** - * @param PropertyMetadata $propertyMeta - * - * @return bool - */ - public function acceptProperty(PropertyMetadata $propertyMeta): bool - { - if (!$propertyMeta->getType()) { - return false; - } - if ($this->context->isNormalization()) { - return $propertyMeta->isReadable(); - } - - return ($propertyMeta->getClassName() !== $this->context->getRootClass() && $propertyMeta->isIdentifier()) - || $propertyMeta->isWritable() - || $propertyMeta->isInitializable(); - } - - /** - * @param PropertyMetadata $propertyMeta - * - * @return Property - */ - private function mapProperty(PropertyMetadata $propertyMeta): Property - { - $typeMeta = $propertyMeta->getType(); - \assert($typeMeta !== null); - - $collections = []; - $leafType = $typeMeta; - while ($leafType->getCollectionValueType()) { - $collections[] = $leafType->getCollectionKeyType(); - $leafType = $leafType->getCollectionValueType(); - } - - if ($propertyMeta->isLink()) { - $linkedClassName = $leafType->getClassName(); - $type = $this->typeFactory->getGenericRef('IRI<T>', [Placeholder::get(FQCN::baseName($linkedClassName))]); - - if ($propertyMeta->isEmbedded()) { - $linkedType = $this->get($linkedClassName); - if ($this->context->isNormalization()) { - $type = $linkedType; - } else { - $type = Union::create([$type, $linkedType]); - } - } - } else { - $type = $this->get($leafType->getClassName() ?: $leafType->getBuiltinType()); - } - - foreach (\array_reverse($collections) as $indexType) { - $type = new Collection($type); - } - - return new Property( - $propertyMeta->getName(), - $propertyMeta->getDescription() ?: '', - $type, - $propertyMeta->isIdentifier(), - $propertyMeta->isNullable() || ($propertyMeta->isIdentifier() && !$this->context->isNormalization()), - !$propertyMeta->isWritable() - ); - } -} diff --git a/src/Resources/config/config.xml b/src/Resources/config/config.xml index 1d59f88f98c3bc929ffafbbbdeb7118dea1beb7f..f693d0aae3c671f0ca74e8380c5e8e7dad80b96a 100644 --- a/src/Resources/config/config.xml +++ b/src/Resources/config/config.xml @@ -45,7 +45,6 @@ <service id="irstea_ng_model_generator.model_generator" lazy="true" class="Irstea\NgModelGeneratorBundle\ModelGenerator"> <argument id="ng_model_generator.metadata.factory" type="service"/> <argument id="twig" type="service"/> - <argument id="irstea_ng_model_generator.naming_strategy" type="service"/> </service> <service id="irstea_ng_model_generator.documentation" lazy="true" class="ApiPlatform\Core\Documentation\Documentation"> @@ -61,9 +60,6 @@ <factory service="api_platform.metadata.resource.name_collection_factory" method="create"/> </service> - <service id="irstea_ng_model_generator.naming_strategy" lazy="true" class="Irstea\NgModelGeneratorBundle\DefaultNamingStrategy"> - </service> - </services> </container> diff --git a/src/Resources/views/typescript_models.ts.twig b/src/Resources/views/typescript_models.ts.twig index 08fdbc276369ecb721b569a99a18e60d5038d721..2623c6a697da31b07b5c96a8b0b6bdf0f5448b37 100644 --- a/src/Resources/views/typescript_models.ts.twig +++ b/src/Resources/views/typescript_models.ts.twig @@ -13,10 +13,10 @@ import { Observable } from 'rxjs/Observable'; /** * Union de tous les types de ressources. */ -export type Resource = {% for res in resources -%} - {%- if not loop.first %} | {% endif -%} - {{- res.normalization.usage -}} -{%- endfor -%} +{#export type Resource = {% for res in resources -%}#} + {#{%- if not loop.first %} | {% endif -%}#} + {#{{- res.normalization.usage -}}#} +{#{%- endfor -%}#} ; /** diff --git a/src/SerializationMapper.php b/src/SerializationMapper.php new file mode 100644 index 0000000000000000000000000000000000000000..8aaaf48d9242c150f7ca5e0fd2c640c77146a0e4 --- /dev/null +++ b/src/SerializationMapper.php @@ -0,0 +1,359 @@ +<?php declare(strict_types=1); +/* + * irstea/ng-model-generator-bundle generates Typescript interfaces for Angular using api-platform metadata. + * Copyright (C) 2018 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 Irstea\NgModelGeneratorBundle\Exceptions\DomainException; +use Irstea\NgModelGeneratorBundle\Metadata\PropertyMetadata; +use Irstea\NgModelGeneratorBundle\Metadata\SerializationMetadata; +use Irstea\NgModelGeneratorBundle\Models\PHPClass; +use Irstea\NgModelGeneratorBundle\Models\Types\ArrayType; +use Irstea\NgModelGeneratorBundle\Models\Types\BuiltinType; +use Irstea\NgModelGeneratorBundle\Models\Types\Objects\Property; +use Irstea\NgModelGeneratorBundle\Models\Types\Resources\ConcreteRepresentation; +use Irstea\NgModelGeneratorBundle\Models\Types\Resources\IRI; +use Irstea\NgModelGeneratorBundle\Models\Types\Type; +use Irstea\NgModelGeneratorBundle\Models\Types\Union; + +/** + * Class SerializationMapper. + */ +final class SerializationMapper implements TypeFactoryInterface +{ + /** @var TypeFactoryInterface */ + private $typeFactory; + + /** @var SerializationMetadata */ + private $serialization; + + /** @var int[] */ + private $refCounts; + + /** @var PHPClass[] */ + private $children; + + /** + * SerializationMapper constructor. + * + * @param TypeFactoryInterface $typeFactory + * @param SerializationMetadata $serialization + */ + public function __construct( + TypeFactoryInterface $typeFactory, + SerializationMetadata $serialization + ) { + $this->typeFactory = $typeFactory; + $this->serialization = $serialization; + } + + /** + * {@inheritdoc} + */ + public function has(string $name): bool + { + return $this->typeFactory->has($name); + } + + /** + * {@inheritdoc} + */ + public function getOrCreate(string $name, callable $builder, ...$args): Type + { + return $this->typeFactory->getOrCreate($name, $builder, ...$args); + } + + /** + * {@inheritdoc} + */ + public function get(string $name): Type + { + if (!class_exists($name) || $this->typeFactory->has($name)) { + return $this->typeFactory->get($name); + } + + $class = PHPClass::get($name); + if ($this->serialization->hasRepresentationOf($class)) { + $reprName = $this->getRepresentationName($class); + + return $this->typeFactory->getOrCreate($reprName, function () use ($class, $reprName) { + return $this->mapRepresentation($class, $reprName); + }); + } + + return $this->getOrCreate($name, [$this, 'get'], 'any'); + } + + /** + * {@inheritdoc} + */ + public function addBuiltin(string $name): void + { + $this->typeFactory->addBuiltin($name); + } + + /** + * {@inheritdoc} + */ + public function addAlias(string $alias, string $target): void + { + $this->typeFactory->addAlias($alias, $target); + } + + /** + * @param PHPClass $class + * + * @return string + */ + private function getRepresentationName(PHPClass $class): string + { + $prefix = ($this->serialization->isNormalization() ? 'Get' : 'Put') . $this->serialization->getRoot()->getBaseName(); + + return (implode('', $this->serialization->getGroups()) ?: $prefix) . $class->getBaseName(); + } + + /** + * @param string $name + * @param PHPClass $resourceClass + * + * @return Type + */ + private function mapRepresentation(PHPClass $resourceClass, string $reprName): Type + { + /** + * @var PHPClass + * @var PropertyMetadata[] $propertiesMeta + */ + [, $parentClass, $propertiesMeta] = $this->doMapRepresentation($resourceClass); + + $properties = $this->mapProperties($propertiesMeta); + $parent = $parentClass ? $this->get($parentClass->getFullName()) : null; + +// $repr = $this->serialization->getRepresentationOf($resourceClass); +// +// $parent = $repr->getParent() ? $this->get($repr->getParent()->getFullName()) : null; +// $properties = $this->mapProperties($repr->getProperties()); + + $desc = []; + $desc[] = 'Resource: ' . $resourceClass->getFullName(); +// $desc[] = 'Direction: ' . ($normalization ? 'response' : 'request'); +// $desc[] = sprintf('Serialization groups: %s', $groups ? implode(', ', $groups) : '-'); + $desc = trim(implode("\n", $desc)); + + return new ConcreteRepresentation($reprName, $parent, $properties, $desc, $this->getCoveredResources($resourceClass), $this->serialization->isNormalization()); + } + + /** + * @param PHPClass $class + * + * @return array + */ + private function doMapRepresentation(PHPClass $class): array + { + $repr = $this->serialization->getRepresentationOf($class); + $properties = $repr->getProperties(); + + $parentClass = $repr->getParent(); + if ($parentClass) { + [$keep, , $parentProperties] = $this->doMapRepresentation($parentClass); + if (!$keep) { + $parentClass = null; + $properties = array_replace($parentProperties, $properties); + } + } + + if (!$this->serialization->isRoot($class)) { + if (!$parentClass && $this->getRefCount($class) < 2) { + return [false, null, $properties]; + } + } + + return [true, $parentClass, $properties]; + } + + /** + * @param PropertyMetadata[] $propertiesMeta + * + * @return array + */ + private function mapProperties(array $propertiesMeta): array + { + $properties = []; + $identifierCount = 0; + + foreach ($propertiesMeta as $propertyMeta) { + if (!$this->acceptProperty($propertyMeta)) { + continue; + } + + if ($propertyMeta->isIdentifier()) { + ++$identifierCount; + } + + $property = $this->mapProperty($propertyMeta); + $properties[$property->getName()] = $property; + } + + if ($identifierCount > 1) { + throw new DomainException('Cannot handle resource with composite identifier'); + } + + return $properties; + } + + /** + * @param PropertyMetadata $propertyMeta + * + * @return bool + */ + public function acceptProperty(PropertyMetadata $propertyMeta): bool + { + if (!$propertyMeta->getType()) { + return false; + } + if ($this->serialization->isNormalization()) { + return $propertyMeta->isReadable(); + } + + return $propertyMeta->isWritable() || $propertyMeta->isInitializable(); + } + + /** + * @param PropertyMetadata $propertyMeta + * + * @return Property + */ + private function mapProperty(PropertyMetadata $propertyMeta): Property + { + $typeMeta = $propertyMeta->getType(); + \assert($typeMeta !== null); + + $collections = []; + $leafType = $typeMeta; + while ($leafType->getCollectionValueType()) { + $collections[] = $leafType->getCollectionKeyType(); + $leafType = $leafType->getCollectionValueType(); + } + + if ($propertyMeta->isLink()) { + $linkedClassName = $leafType->getClassName(); + $type = IRI::get($linkedClassName); + + if ($propertyMeta->isEmbedded()) { + $linkedType = $this->get($linkedClassName); + if ($this->serialization->isNormalization()) { + $type = $linkedType; + } else { + $type = Union::create([$type, $linkedType]); + } + } + } else { + $type = $this->get($leafType->getClassName() ?: $leafType->getBuiltinType()); + } + + $number = BuiltinType::get('number'); + foreach (\array_reverse($collections) as $indexType) { + if ($indexType === $number) { + $type = new ArrayType($type); + } + } + + return new Property( + $propertyMeta->getName(), + $propertyMeta->getDescription() ?: '', + $type, + $propertyMeta->isIdentifier(), + $propertyMeta->isNullable() || ($propertyMeta->isIdentifier() && !$this->serialization->isNormalization()), + !$propertyMeta->isWritable() + ); + } + + /** + * @param PHPClass|string $class + * + * @return int + */ + private function getRefCount($class): int + { + if (!$this->refCounts) { + $this->initRefCounts(); + } + + return $this->refCounts[PHPClass::get($class)->getFullName()] ?? 0; + } + + private function initRefCounts(): void + { + $reprs = $this->serialization->getRepresentations(); + $this->refCounts = \array_fill_keys(array_keys($reprs), 0); + + foreach ($reprs as $repr) { + if ($repr->getParent()) { + ++$this->refCounts[$repr->getParent()->getFullName()]; + } + foreach ($repr->getProperties() as $property) { + $className = $property->getLeafType()->getClassName(); + if ($className && isset($this->refCounts[$className])) { + ++$this->refCounts[$className]; + } + } + } + } + + /** + * @param PHPClass|string $class + * + * @return int + */ + private function getCoveredResources($class): array + { + $resources = [$class]; + + for ($i = 0; $i < \count($resources); ++$i) { + $resources = array_merge($resources, $this->getChildren($resources[$i])); + } + + return $resources; + } + + /** + * @param PHPClass|string $class + * + * @return int + */ + private function getChildren($class): array + { + if (!$this->children) { + $this->initChildren(); + } + + return $this->children[PHPClass::get($class)->getFullName()] ?? []; + } + + private function initChildren(): void + { + $reprs = $this->serialization->getRepresentations(); + $this->children = \array_fill_keys(array_keys($reprs), []); + + foreach ($reprs as $repr) { + if ($repr->getParent()) { + $this->children[$repr->getParent()->getFullName()][] = $repr->getClass(); + } + } + } +} diff --git a/src/TypeFactoryInterface.php b/src/TypeFactoryInterface.php index 37a052d1e04df1834b6fbb18c7b0d425740e17ec..760c4f5e237fcad6782200b2ebd7d171bfe310a2 100644 --- a/src/TypeFactoryInterface.php +++ b/src/TypeFactoryInterface.php @@ -26,13 +26,6 @@ use Irstea\NgModelGeneratorBundle\Models\Types\Type; */ interface TypeFactoryInterface { - /** - * @param ContextInterface|null $context - * - * @return TypeFactoryInterface - */ - public function withContext(?ContextInterface $context): self; - /** * @param string $name *