. */ 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; use ApiPlatform\Core\Metadata\Property\PropertyMetadata as APIPropertyMetadata; use Irstea\NgModelGeneratorBundle\Models\ClassName; use Symfony\Component\PropertyInfo\Type; /** * Class PropertyMetadataFactory. */ class PropertyMetadataFactory { public const MODE_CREATE = 'CREATE'; public const MODE_UPDATE = 'UPDATE'; public const MODE_READ = 'READ'; public const MODE_OTHER = 'OTHER'; /** @var PropertyNameCollectionFactoryInterface */ private $propertyNameCollectionFactory; /** @var PropertyMetadataFactoryInterface */ private $propertyMetadataFactory; /** @var ClassName */ private $resource; /** @var string */ private $mode; /** @var array */ private $groups; /** @var array */ private $properties = []; /** @var ResourceClassResolverInterface */ private $resourceClassResolver; /** * PropertyMetadataFactory constructor. * * @param ResourceClassResolverInterface $resourceClassResolver * @param PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory * @param PropertyMetadataFactoryInterface $propertyMetadataFactory * @param ClassName $resource * @param string $mode * @param array $groups */ public function __construct( ResourceClassResolverInterface $resourceClassResolver, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ClassName $resource, string $mode, array $groups ) { $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; $this->propertyMetadataFactory = $propertyMetadataFactory; $this->resource = $resource; $this->mode = $mode; $this->groups = $groups; $this->resourceClassResolver = $resourceClassResolver; } /** * @param ClassName $class * @param string $propertyName * * @throws ResourceClassNotFoundException * @throws \ApiPlatform\Core\Exception\PropertyNotFoundException * * @return PropertyMetadata */ public function create(ClassName $class, string $propertyName): PropertyMetadata { $propertyMeta = $this->getAPIMetadata($class)[$propertyName]; $typeMeta = $propertyMeta->getType(); \assert($typeMeta !== null); [$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(), (bool) $propertyMeta->isInitializable(), $link, $embedded ); } /** * @param $propertyMeta * * @return array */ private function getLinkStatus(APIPropertyMetadata $propertyMeta): array { $typeMeta = $propertyMeta->getType(); \assert($typeMeta !== null); $leafType = $this->getLeafType($typeMeta); if (!$leafType) { return [false, false]; } $leafClassName = $leafType->getClassName(); if (!$leafClassName || !$this->resourceClassResolver->isResourceClass($leafClassName)) { return [false, false]; } $embedded = $this->mode === self::MODE_READ ? $propertyMeta->isReadableLink() : $propertyMeta->isWritableLink(); return [true, (bool) $embedded]; } /** * @param Type $type * * @return Type|null */ private function getLeafType(Type $type): ?Type { while ($type && $type->isCollection()) { $type = $type->getCollectionValueType(); } return $type; } /** * @param ClassName $class * * @throws ResourceClassNotFoundException * @throws \ApiPlatform\Core\Exception\PropertyNotFoundException * * @return APIPropertyMetadata[] */ public function getAPIMetadata(ClassName $class): array { $key = $class->getFullName(); if (!isset($this->properties[$key])) { $this->properties[$key] = $this->doGetAPIMetadata($class); } return $this->properties[$key]; } /** * @param ClassName $class * * @throws \ApiPlatform\Core\Exception\PropertyNotFoundException * @throws ResourceClassNotFoundException * * @return APIPropertyMetadata[] */ private function doGetAPIMetadata(ClassName $class): array { $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)); $propertyMeta = $this->propertyMetadataFactory->create($class->getFullName(), $propertyName); if (!$propertyMeta->getType() || $propertyMeta->isChildInherited()) { continue; } if (!$this->acceptProperty($isResource, $propertyMeta)) { continue; } $properties[$propertyName] = $propertyMeta; } return $properties; } /** * @param string $mode * @param APIPropertyMetadata $propertyMeta * @param mixed $isResource * * @return bool */ private function acceptProperty(bool $isResource, APIPropertyMetadata $propertyMeta): bool { 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(); } } }