diff --git a/src/Metadata/MetadataFactory.php b/src/Metadata/MetadataFactory.php index 086c03f80d2f2a5231b59e5bd7647c263c669c13..7795cdee8032b629c9fab5cd67e9099d4d6c02c8 100644 --- a/src/Metadata/MetadataFactory.php +++ b/src/Metadata/MetadataFactory.php @@ -358,6 +358,7 @@ final class MetadataFactory implements MetadataFactoryInterface $mode = PropertyMetadataFactory::MODE_OTHER; } $propertyMetadataFactory = new PropertyMetadataFactory( + $this->resourceClassResolver, $this->propertyNameCollectionFactory, $this->propertyMetadataFactory, $class, @@ -418,12 +419,15 @@ final class MetadataFactory implements MetadataFactoryInterface * @param OperationDef|null $opDef * @param array $groups * - * @throws \ApiPlatform\Core\Exception\ResourceClassNotFoundException - * * @return string */ - private function getRepresentationName(ClassName $root, ClassName $class, bool $normalization, ?OperationDef $opDef, array $groups): string - { + private function getRepresentationName( + ClassName $root, + ClassName $class, + bool $normalization, + ?OperationDef $opDef, + array $groups + ): string { if ($normalization && (!$groups || $groups === $this->getDefaultGroups($class))) { return $class->getBaseName(); } diff --git a/src/Metadata/PropertyMetadata.php b/src/Metadata/PropertyMetadata.php index e86def00bf11be1509d5071ea42bd28ba0828d93..3ba802bd7eb6247acbb74f40e90f11ceb4162080 100644 --- a/src/Metadata/PropertyMetadata.php +++ b/src/Metadata/PropertyMetadata.php @@ -54,6 +54,9 @@ class PropertyMetadata implements \JsonSerializable, HasName /** @var bool */ private $embedded; + /** @var bool */ + private $nullable; + /** * PropertyMetadata constructor. * @@ -72,6 +75,7 @@ class PropertyMetadata implements \JsonSerializable, HasName string $description, Type $type, bool $identifier, + bool $nullable, bool $readable, bool $writable, bool $initializable, @@ -82,11 +86,12 @@ class PropertyMetadata implements \JsonSerializable, HasName $this->description = $description; $this->type = $type; $this->identifier = $identifier; + $this->nullable = $nullable; $this->readable = $readable; $this->writable = $writable; $this->initializable = $initializable; $this->link = $link; - $this->embedded = $embedded; + $this->embedded = $link; } /** @@ -167,7 +172,7 @@ class PropertyMetadata implements \JsonSerializable, HasName */ public function isNullable(): bool { - return $this->type ? $this->type->isNullable() : true; + return $this->nullable; } /** diff --git a/src/Metadata/PropertyMetadataFactory.php b/src/Metadata/PropertyMetadataFactory.php index 1c2c1bb18b583bc62ec26ed4ba84f4a80af90716..a6acda8093e9a343e907b04dc7e0c42b347d0374 100644 --- a/src/Metadata/PropertyMetadataFactory.php +++ b/src/Metadata/PropertyMetadataFactory.php @@ -19,6 +19,7 @@ namespace Irstea\NgModelGeneratorBundle\Metadata; +use ApiPlatform\Core\Api\ResourceClassResolverInterface; use ApiPlatform\Core\Exception\ResourceClassNotFoundException; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; @@ -37,16 +38,6 @@ class PropertyMetadataFactory public const MODE_READ = 'READ'; public const MODE_OTHER = 'OTHER'; - /** - * Mode des objets relatifs en fonction du mode de la resource. - */ - private const RELATED_MODES = [ - self::MODE_CREATE => self::MODE_UPDATE, - self::MODE_UPDATE => self::MODE_UPDATE, - self::MODE_READ => self::MODE_READ, - self::MODE_OTHER => self::MODE_OTHER, - ]; - /** @var PropertyNameCollectionFactoryInterface */ private $propertyNameCollectionFactory; @@ -68,9 +59,13 @@ class PropertyMetadataFactory /** @var array */ private $properties = []; + /** @var ResourceClassResolverInterface */ + private $resourceClassResolver; + /** * PropertyMetadataFactory constructor. * + * @param ResourceClassResolverInterface $resourceClassResolver * @param PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory * @param PropertyMetadataFactoryInterface $propertyMetadataFactory * @param ClassName $resource @@ -78,6 +73,7 @@ class PropertyMetadataFactory * @param array $groups */ public function __construct( + ResourceClassResolverInterface $resourceClassResolver, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ClassName $resource, @@ -89,6 +85,7 @@ class PropertyMetadataFactory $this->resource = $resource; $this->mode = $mode; $this->groups = $groups; + $this->resourceClassResolver = $resourceClassResolver; } /** @@ -105,63 +102,72 @@ class PropertyMetadataFactory $typeMeta = $propertyMeta->getType(); \assert($typeMeta !== null); - $mode = $this->getMode($class); - $link = $embedded = $mode === self::MODE_READ ? $propertyMeta->isReadableLink() : $propertyMeta->isWritableLink(); - - if (!$embedded) { - $leafType = $this->getLeafType($typeMeta); - $leafClass = $leafType ? $leafType->getClassName() : null; - if ($leafClass) { - try { - $leafProperties = $this->getAPIMetadata(PHPClass::get($leafClass)); - $embedded = \count($leafProperties) > 0; - $link = true; - } catch (ResourceClassNotFoundException $ex) { - // NOOP - } - } - } + [$link, $embedded] = $this->getLinkStatus($propertyMeta); + + $nullable = $this->mode === self::MODE_UPDATE || $typeMeta->isNullable(); return new PropertyMetadata( $propertyName, '', $typeMeta, $propertyMeta->isIdentifier(), + $nullable, $propertyMeta->isReadable(), $propertyMeta->isWritable(), - $propertyMeta->isInitializable() ?: false, - $link ?: false, - $embedded ?: false + (bool) $propertyMeta->isInitializable(), + $link, + $embedded ); } /** - * @param Type $type + * @param $propertyMeta * - * @return Type|null + * @return array */ - private function getLeafType(Type $type): ?Type + private function getLinkStatus(APIPropertyMetadata $propertyMeta): array { - while ($type && $type->isCollection()) { - $type = $type->getCollectionValueType(); + $typeMeta = $propertyMeta->getType(); + \assert($typeMeta !== null); + + $leafType = $this->getLeafType($typeMeta); + if (!$leafType) { + return [false, false]; } - return $type; + $leafClassName = $leafType->getClassName(); + if (!$leafClassName || !$this->resourceClassResolver->isResourceClass($leafClassName)) { + return [false, false]; + } + + $leafClass = PHPClass::get($leafClassName); + $embedded = $this->mode === self::MODE_READ ? $propertyMeta->isReadableLink() : $propertyMeta->isWritableLink(); + + if (!\is_bool($embedded)) { + $leafProperties = $this->getAPIMetadata($leafClass); + foreach ($leafProperties as $leafProperty) { + if (!$leafProperty->isIdentifier()) { + $embedded = true; + break; + } + } + } + + return [true, (bool) $embedded]; } /** - * @param ClassName $class + * @param Type $type * - * @return string + * @return Type|null */ - private function getMode(ClassName $class): string + private function getLeafType(Type $type): ?Type { - $mode = $this->mode; - if ($class->getFullName() === $this->resource->getFullName()) { - $mode = self::RELATED_MODES[$mode]; + while ($type && $type->isCollection()) { + $type = $type->getCollectionValueType(); } - return $mode; + return $type; } /** @@ -192,9 +198,9 @@ class PropertyMetadataFactory */ private function doGetAPIMetadata(ClassName $class): array { - $mode = $this->getMode($class); $properties = []; $options = $this->groups ? ['serializer_groups' => $this->groups] : []; + $isResource = $class->getFullName() === $this->resource->getFullName(); foreach ($this->propertyNameCollectionFactory->create($class->getFullName(), $options) as $propertyName) { \assert(\is_string($propertyName)); @@ -205,7 +211,7 @@ class PropertyMetadataFactory continue; } - if (!$this->acceptProperty($mode, $propertyMeta)) { + if (!$this->acceptProperty($isResource, $propertyMeta)) { continue; } @@ -218,16 +224,26 @@ class PropertyMetadataFactory /** * @param string $mode * @param APIPropertyMetadata $propertyMeta + * @param mixed $isResource * * @return bool */ - private function acceptProperty(string $mode, APIPropertyMetadata $propertyMeta): bool + private function acceptProperty(bool $isResource, APIPropertyMetadata $propertyMeta): bool { - switch ($mode) { + if (!$isResource && $propertyMeta->isIdentifier()) { + return true; + } + + switch ($this->mode) { case self::MODE_CREATE: return $propertyMeta->isWritable() || $propertyMeta->isInitializable(); + case self::MODE_READ: return $propertyMeta->isReadable(); + + case self::MODE_UPDATE: + return $propertyMeta->isWritable(); + default: return $propertyMeta->isReadable() || $propertyMeta->isWritable(); } diff --git a/src/SerializationMapper.php b/src/SerializationMapper.php index 0954eff82cabfa358d48043b4702283427543b59..a9ac519f5959020069d577a5555a22dedb18d9ce 100644 --- a/src/SerializationMapper.php +++ b/src/SerializationMapper.php @@ -303,9 +303,13 @@ final class SerializationMapper implements TypeFactoryInterface } if ($propertyMeta->isLink()) { - $type = $this->get($leafType->getClassName()); - if (!$this->serialization->isNormalization()) { - $iriType = $this->createIRI([PHPClass::get($leafType->getClassName())]); + $leafClassName = $leafType->getClassName(); + $type = $this->get($leafClassName); + $iriType = $this->createIRI([PHPClass::get($leafClassName)]); + + if (!$propertyMeta->isEmbedded()) { + $type = $iriType; + } elseif (!$this->serialization->isNormalization()) { $type = Union::create([$type, $iriType]); } } else { @@ -328,7 +332,7 @@ final class SerializationMapper implements TypeFactoryInterface $propertyMeta->getDescription() ?: '', $type, $propertyMeta->isIdentifier(), - $propertyMeta->isNullable() || (!$propertyMeta->isWritable() && !$this->serialization->isNormalization()), + $propertyMeta->isNullable(), !$propertyMeta->isWritable() ); }