vendor/api-platform/core/src/Core/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php line 36

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the API Platform project.
  4. *
  5. * (c) Kévin Dunglas <dunglas@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. declare(strict_types=1);
  11. namespace ApiPlatform\Core\Metadata\Property\Factory;
  12. use ApiPlatform\Core\Exception\PropertyNotFoundException;
  13. use ApiPlatform\Core\Metadata\Extractor\ExtractorInterface;
  14. use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
  15. use ApiPlatform\Core\Metadata\Property\SubresourceMetadata;
  16. use ApiPlatform\Metadata\Extractor\ResourceExtractorInterface;
  17. use Symfony\Component\PropertyInfo\Type;
  18. /**
  19. * Creates properties's metadata using an extractor.
  20. *
  21. * @author Kévin Dunglas <dunglas@gmail.com>
  22. */
  23. final class ExtractorPropertyMetadataFactory implements PropertyMetadataFactoryInterface
  24. {
  25. private $extractor;
  26. private $decorated;
  27. /**
  28. * @param ResourceExtractorInterface|ExtractorInterface $extractor
  29. */
  30. public function __construct($extractor, PropertyMetadataFactoryInterface $decorated = null)
  31. {
  32. $this->extractor = $extractor;
  33. $this->decorated = $decorated;
  34. }
  35. /**
  36. * {@inheritdoc}
  37. */
  38. public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata
  39. {
  40. $parentPropertyMetadata = null;
  41. if ($this->decorated) {
  42. try {
  43. $parentPropertyMetadata = $this->decorated->create($resourceClass, $property, $options);
  44. } catch (PropertyNotFoundException $propertyNotFoundException) {
  45. // Ignore not found exception from decorated factories
  46. }
  47. }
  48. $isInterface = interface_exists($resourceClass);
  49. if (
  50. !property_exists($resourceClass, $property) && !$isInterface ||
  51. null === ($propertyMetadata = $this->extractor->getResources()[$resourceClass]['properties'][$property] ?? null)
  52. ) {
  53. return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property);
  54. }
  55. if ($parentPropertyMetadata) {
  56. return $this->update($parentPropertyMetadata, $propertyMetadata);
  57. }
  58. return ($metadata = new PropertyMetadata(
  59. null,
  60. $propertyMetadata['description'],
  61. $propertyMetadata['readable'],
  62. $propertyMetadata['writable'],
  63. $propertyMetadata['readableLink'],
  64. $propertyMetadata['writableLink'],
  65. $propertyMetadata['required'],
  66. $propertyMetadata['identifier'],
  67. $propertyMetadata['iri'],
  68. null,
  69. $propertyMetadata['attributes']
  70. ))->withSubresource($this->createSubresourceMetadata($propertyMetadata['subresource'], $metadata));
  71. }
  72. /**
  73. * Returns the metadata from the decorated factory if available or throws an exception.
  74. *
  75. * @throws PropertyNotFoundException
  76. */
  77. private function handleNotFound(?PropertyMetadata $parentPropertyMetadata, string $resourceClass, string $property): PropertyMetadata
  78. {
  79. if ($parentPropertyMetadata) {
  80. return $parentPropertyMetadata;
  81. }
  82. throw new PropertyNotFoundException(sprintf('Property "%s" of the resource class "%s" not found.', $property, $resourceClass));
  83. }
  84. /**
  85. * Creates a new instance of metadata if the property is not already set.
  86. */
  87. private function update(PropertyMetadata $propertyMetadata, array $metadata): PropertyMetadata
  88. {
  89. $metadataAccessors = [
  90. 'description' => 'get',
  91. 'readable' => 'is',
  92. 'writable' => 'is',
  93. 'writableLink' => 'is',
  94. 'readableLink' => 'is',
  95. 'required' => 'is',
  96. 'identifier' => 'is',
  97. 'iri' => 'get',
  98. 'attributes' => 'get',
  99. ];
  100. foreach ($metadataAccessors as $metadataKey => $accessorPrefix) {
  101. if (null === $metadata[$metadataKey]) {
  102. continue;
  103. }
  104. $propertyMetadata = $propertyMetadata->{'with'.ucfirst($metadataKey)}($metadata[$metadataKey]);
  105. }
  106. if ($propertyMetadata->hasSubresource()) {
  107. return $propertyMetadata;
  108. }
  109. return $propertyMetadata->withSubresource($this->createSubresourceMetadata($metadata['subresource'], $propertyMetadata));
  110. }
  111. /**
  112. * Creates a SubresourceMetadata.
  113. *
  114. * @param bool|array|null $subresource the subresource metadata coming from XML or YAML
  115. * @param PropertyMetadata $propertyMetadata the current property metadata
  116. */
  117. private function createSubresourceMetadata($subresource, PropertyMetadata $propertyMetadata): ?SubresourceMetadata
  118. {
  119. if (!$subresource) {
  120. return null;
  121. }
  122. $type = $propertyMetadata->getType();
  123. $maxDepth = \is_array($subresource) ? $subresource['maxDepth'] ?? null : null;
  124. if (null !== $type) {
  125. $isCollection = $type->isCollection();
  126. if (
  127. $isCollection &&
  128. $collectionValueType = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType()
  129. ) {
  130. $resourceClass = $collectionValueType->getClassName();
  131. } else {
  132. $resourceClass = $type->getClassName();
  133. }
  134. } elseif (\is_array($subresource) && isset($subresource['resourceClass'])) {
  135. $resourceClass = $subresource['resourceClass'];
  136. $isCollection = $subresource['collection'] ?? true;
  137. } else {
  138. return null;
  139. }
  140. return new SubresourceMetadata($resourceClass, $isCollection, $maxDepth);
  141. }
  142. }