diff --git a/CHANGELOG.md b/CHANGELOG.md index ac0b54c54ef..04fccd2f605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Changelog -## 2.7.0 +## 2.7.0-alpha.2 + +* Review interfaces (ProcessorInterface, ProviderInterface, TypeConverterInterface, ResolverFactoryInterface etc.) to use `ApiPlatform\Metadata\Operation` instead of `operationName` (#4712) +* Introduce `CollectionOperationInterface` instead of the `collection` flag (#4712) +* Introduce `DeleteOperationInterface` instead of the `delete` flag (#4712) +* The `compositeIdentifier` flag only lives under the `uriVariables` property (#4712) +* The `provider` or `processor` property is specified within the `Operation` and we removed the chain pattern (#4712) + +## 2.7.0-alpha.1 * Swagger UI: Add `usePkceWithAuthorizationCodeGrant` to Swagger UI initOAuth (#4649) * **BC**: `mapping.paths` in configuration should override bundles configuration (#4465) diff --git a/src/Api/IdentifiersExtractor.php b/src/Api/IdentifiersExtractor.php index 79235206683..7de0c6c366e 100644 --- a/src/Api/IdentifiersExtractor.php +++ b/src/Api/IdentifiersExtractor.php @@ -54,7 +54,7 @@ public function getIdentifiersFromItem($item, string $operationName = null, arra { $identifiers = []; $resourceClass = $this->getResourceClass($item, true); - $operation = $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation($operationName); + $operation = $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation($operationName, false, true); $links = $operation instanceof GraphQlOperation ? $operation->getLinks() : $operation->getUriVariables(); foreach ($links ?? [] as $link) { diff --git a/src/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommand.php b/src/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommand.php index 2f038731ecc..d0ffe48856f 100644 --- a/src/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommand.php +++ b/src/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommand.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Core\Bridge\Symfony\Bundle\Command; use ApiPlatform\Core\Annotation\ApiResource; +use ApiPlatform\Core\Api\IdentifiersExtractorInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface; use ApiPlatform\Core\Upgrade\ColorConsoleDiffFormatter; @@ -42,15 +43,17 @@ final class UpgradeApiResourceCommand extends Command private $subresourceOperationFactory; private $subresourceTransformer; private $reader; + private $identifiersExtractor; private $localCache = []; - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, SubresourceOperationFactoryInterface $subresourceOperationFactory, SubresourceTransformer $subresourceTransformer, AnnotationReader $reader) + public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, SubresourceOperationFactoryInterface $subresourceOperationFactory, SubresourceTransformer $subresourceTransformer, AnnotationReader $reader, IdentifiersExtractorInterface $identifiersExtractor) { $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; $this->resourceMetadataFactory = $resourceMetadataFactory; $this->subresourceOperationFactory = $subresourceOperationFactory; $this->subresourceTransformer = $subresourceTransformer; $this->reader = $reader; + $this->identifiersExtractor = $identifiersExtractor; parent::__construct(); } @@ -204,7 +207,7 @@ private function transformApiResource(InputInterface $input, OutputInterface $ou continue; } - $traverser->addVisitor(new UpgradeApiResourceVisitor($attribute, $isAnnotation)); + $traverser->addVisitor(new UpgradeApiResourceVisitor($attribute, $isAnnotation, $this->identifiersExtractor, $resourceClass)); $oldCode = file_get_contents($fileName); $oldStmts = $parser->parse($oldCode); diff --git a/src/Core/EventListener/ReadListener.php b/src/Core/EventListener/ReadListener.php index a4b1138f821..3b50c82f207 100644 --- a/src/Core/EventListener/ReadListener.php +++ b/src/Core/EventListener/ReadListener.php @@ -75,8 +75,9 @@ public function onKernelRequest(RequestEvent $event): void if ( !($attributes = RequestAttributesExtractor::extractAttributes($request)) || !$attributes['receive'] - || $request->isMethod('POST') && isset($attributes['collection_operation_name']) + || ($request->isMethod('POST') && isset($attributes['collection_operation_name'])) || ($operation && !($operation->getExtraProperties()['is_legacy_resource_metadata'] ?? false) && !($operation->getExtraProperties()['is_legacy_subresource'] ?? false)) + || ($operation && false === $operation->canRead()) || $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY) ) { return; diff --git a/src/Core/Metadata/Resource/ApiResourceToLegacyResourceMetadataTrait.php b/src/Core/Metadata/Resource/ApiResourceToLegacyResourceMetadataTrait.php index bad95e5ba4d..482a4b2995e 100644 --- a/src/Core/Metadata/Resource/ApiResourceToLegacyResourceMetadataTrait.php +++ b/src/Core/Metadata/Resource/ApiResourceToLegacyResourceMetadataTrait.php @@ -14,6 +14,8 @@ namespace ApiPlatform\Core\Metadata\Resource; use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\HttpOperation; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; /** @@ -37,12 +39,14 @@ private function transformResourceToResourceMetadata(ApiResource $resource): Res } $arrayOperation['openapi_context']['operationId'] = $name; + $arrayOperation['composite_identifier'] = $this->hasCompositeIdentifier($operation); - if ($operation->getExtraProperties()['is_alternate_resource_metadata'] ?? false) { - $arrayOperation['composite_identifier'] = $operation->getCompositeIdentifier() ?? false; + if (HttpOperation::METHOD_POST === $operation->getMethod() && !$operation->getUriVariables()) { + $collectionOperations[$name] = $arrayOperation; + continue; } - if ($operation->isCollection()) { + if ($operation instanceof CollectionOperationInterface) { $collectionOperations[$name] = $arrayOperation; continue; } @@ -108,4 +112,15 @@ private function transformUriVariablesToIdentifiers(array $arrayOperation): arra return $arrayOperation; } + + private function hasCompositeIdentifier(HttpOperation $operation): bool + { + foreach ($operation->getUriVariables() ?? [] as $parameterName => $uriVariable) { + if ($uriVariable->getCompositeIdentifier()) { + return true; + } + } + + return false; + } } diff --git a/src/Core/Swagger/Serializer/DocumentationNormalizer.php b/src/Core/Swagger/Serializer/DocumentationNormalizer.php index dd7ad7a92e2..bdca7da1d7f 100644 --- a/src/Core/Swagger/Serializer/DocumentationNormalizer.php +++ b/src/Core/Swagger/Serializer/DocumentationNormalizer.php @@ -583,7 +583,7 @@ private function updatePostOperation(bool $v3, \ArrayObject $pathOperation, arra $identifiers = (array) $resourceMetadata ->getTypedOperationAttribute($operationType, $operationName, 'identifiers', [], false); - $pathOperation = $this->addItemOperationParameters($v3, $pathOperation, $operationType, $operationName, $resourceMetadata, $resourceClass); + $pathOperation = $this->addItemOperationParameters($v3, $pathOperation, $operationType, $operationName, $resourceMetadata, $resourceClass, OperationType::ITEM === $operationType ? false : true); $successResponse = ['description' => sprintf('%s resource created', $resourceShortName)]; [$successResponse, $defined] = $this->addSchemas($v3, $successResponse, $definitions, $resourceClass, $operationType, $operationName, $responseMimeTypes); @@ -677,14 +677,14 @@ private function updateDeleteOperation(bool $v3, \ArrayObject $pathOperation, st return $this->addItemOperationParameters($v3, $pathOperation, $operationType, $operationName, $resourceMetadata, $resourceClass); } - private function addItemOperationParameters(bool $v3, \ArrayObject $pathOperation, string $operationType, string $operationName, ResourceMetadata $resourceMetadata, string $resourceClass): \ArrayObject + private function addItemOperationParameters(bool $v3, \ArrayObject $pathOperation, string $operationType, string $operationName, ResourceMetadata $resourceMetadata, string $resourceClass, bool $isPost = false): \ArrayObject { $identifiers = (array) $resourceMetadata ->getTypedOperationAttribute($operationType, $operationName, 'identifiers', [], false); // Auto-generated routes in API Platform < 2.7 are considered as collection, hotfix this as the OpenApi Factory supports new operations anyways. // this also fixes a bug where we could not create POST item operations in API P 2.6 - if (OperationType::ITEM === $operationType && 'post' === substr($operationName, -4)) { + if (OperationType::ITEM === $operationType && $isPost) { $operationType = OperationType::COLLECTION; } diff --git a/src/Core/Upgrade/UpgradeApiResourceVisitor.php b/src/Core/Upgrade/UpgradeApiResourceVisitor.php index ffec1aad519..f88590c74b5 100644 --- a/src/Core/Upgrade/UpgradeApiResourceVisitor.php +++ b/src/Core/Upgrade/UpgradeApiResourceVisitor.php @@ -16,6 +16,7 @@ use ApiPlatform\Api\UrlGeneratorInterface; use ApiPlatform\Core\Annotation\ApiResource as LegacyApiResource; use ApiPlatform\Core\Annotation\ApiSubresource; +use ApiPlatform\Core\Api\IdentifiersExtractorInterface; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; @@ -23,6 +24,7 @@ use ApiPlatform\Metadata\GraphQl\Mutation; use ApiPlatform\Metadata\GraphQl\Query; use ApiPlatform\Metadata\GraphQl\QueryCollection; +use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; @@ -36,12 +38,16 @@ final class UpgradeApiResourceVisitor extends NodeVisitorAbstract use RemoveAnnotationTrait; private LegacyApiResource $resourceAnnotation; + private IdentifiersExtractorInterface $identifiersExtractor; private bool $isAnnotation = false; + private string $resourceClass; - public function __construct(LegacyApiResource $resourceAnnotation, bool $isAnnotation = false) + public function __construct(LegacyApiResource $resourceAnnotation, bool $isAnnotation, IdentifiersExtractorInterface $identifiersExtractor, string $resourceClass) { $this->resourceAnnotation = $resourceAnnotation; $this->isAnnotation = $isAnnotation; + $this->identifiersExtractor = $identifiersExtractor; + $this->resourceClass = $resourceClass; } /** @@ -80,6 +86,10 @@ public function enterNode(Node $node) $this->getGraphQlOperationsNamespaces($this->resourceAnnotation->graphql ?? []) )); + if (true === !($this->resourceAnnotation->attributes['composite_identifier'] ?? true)) { + $namespaces[] = Link::class; + } + foreach ($node->stmts as $k => $stmt) { if (!$stmt instanceof Node\Stmt\Use_) { break; @@ -202,6 +212,40 @@ public function enterNode(Node $node) continue; } + if ('compositeIdentifier' === $key) { + if (false !== $value) { + continue; + } + + $identifiers = $this->identifiersExtractor->getIdentifiersFromResourceClass($this->resourceClass); + $identifierNodeItems = []; + foreach ($identifiers as $identifier) { + $identifierNodes = [ + 'compositeIdentifier' => new Node\Expr\ConstFetch(new Node\Name('false')), + 'fromClass' => new Node\Expr\ClassConstFetch( + new Node\Name( + 'self' + ), + 'class' + ), + 'identifiers' => new Node\Expr\Array_( + [ + new Node\Expr\ArrayItem(new Node\Scalar\String_($identifier)), + ], + ['kind' => Node\Expr\Array_::KIND_SHORT] + ), + ]; + + $identifierNodeItems[] = new Node\Expr\ArrayItem( + new Node\Expr\New_(new Node\Name('Link'), $this->arrayToArguments($identifierNodes)), + new Node\Scalar\String_($identifier) + ); + } + + $arguments['uriVariables'] = new Node\Expr\Array_($identifierNodeItems, ['kind' => Node\Expr\Array_::KIND_SHORT]); + continue; + } + $arguments[$key] = $this->valueToNode($value); } diff --git a/src/Doctrine/Common/State/LinksHandlerTrait.php b/src/Doctrine/Common/State/LinksHandlerTrait.php index 1efd2777868..8e6d4f5ed9b 100644 --- a/src/Doctrine/Common/State/LinksHandlerTrait.php +++ b/src/Doctrine/Common/State/LinksHandlerTrait.php @@ -13,19 +13,22 @@ namespace ApiPlatform\Doctrine\Common\State; +use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; +use ApiPlatform\Metadata\GraphQl\Query; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Operation; trait LinksHandlerTrait { /** - * @param Operation|GraphQlOperation $operation + * @param HttpOperation|GraphQlOperation $operation * * @return Link[] */ - private function getLinks(string $resourceClass, $operation, array $context): array + private function getLinks(string $resourceClass, Operation $operation, array $context): array { $links = ($operation instanceof GraphQlOperation ? $operation->getLinks() : $operation->getUriVariables()) ?? []; @@ -41,8 +44,26 @@ private function getLinks(string $resourceClass, $operation, array $context): ar } } - $operation = $this->resourceMetadataCollectionFactory->create($linkClass)->getOperation($operation->getName()); - foreach ($operation instanceof GraphQlOperation ? $operation->getLinks() : $operation->getUriVariables() as $link) { + // Using graphql, it's possible that we won't find a graphql operation of the same type (eg it is disabled). + try { + $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($linkClass); + $linkedOperation = $resourceMetadataCollection->getOperation($operation->getName()); + } catch (OperationNotFoundException $e) { + if (!$operation instanceof GraphQlOperation) { + throw $e; + } + + // Instead we'll look for the first Query available + foreach ($resourceMetadataCollection as $resourceMetadata) { + foreach ($resourceMetadata->getGraphQlOperations() as $operation) { + if ($operation instanceof Query) { + $linkedOperation = $operation; + } + } + } + } + + foreach ($linkedOperation instanceof GraphQlOperation ? $linkedOperation->getLinks() : $linkedOperation->getUriVariables() as $link) { if ($resourceClass === $link->getToClass()) { $newLinks[] = $link; } diff --git a/src/Doctrine/Common/State/Processor.php b/src/Doctrine/Common/State/PersistProcessor.php similarity index 70% rename from src/Doctrine/Common/State/Processor.php rename to src/Doctrine/Common/State/PersistProcessor.php index bcb414f9e7e..f43a5bcf0b0 100644 --- a/src/Doctrine/Common/State/Processor.php +++ b/src/Doctrine/Common/State/PersistProcessor.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Common\State; +use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProcessorInterface; use ApiPlatform\Util\ClassInfoTrait; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; @@ -20,7 +21,7 @@ use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ObjectManager as DoctrineObjectManager; -final class Processor implements ProcessorInterface +final class PersistProcessor implements ProcessorInterface { use ClassInfoTrait; @@ -31,12 +32,7 @@ public function __construct(ManagerRegistry $managerRegistry) $this->managerRegistry = $managerRegistry; } - public function supports($data, array $uriVariables = [], ?string $operationName = null, array $context = []): bool - { - return null !== $this->getManager($data); - } - - private function persist($data, array $context = []) + public function process($data, Operation $operation, array $uriVariables = [], array $context = []) { if (!$manager = $this->getManager($data)) { return $data; @@ -52,25 +48,6 @@ private function persist($data, array $context = []) return $data; } - private function remove($data, array $context = []) - { - if (!$manager = $this->getManager($data)) { - return; - } - - $manager->remove($data); - $manager->flush(); - } - - public function process($data, array $uriVariables = [], ?string $operationName = null, array $context = []) - { - if (\array_key_exists('operation', $context) && $context['operation']->isDelete()) { - return $this->remove($data); - } - - return $this->persist($data); - } - /** * Gets the Doctrine object manager associated with given data. * diff --git a/src/Doctrine/Common/State/RemoveProcessor.php b/src/Doctrine/Common/State/RemoveProcessor.php new file mode 100644 index 00000000000..f149b13e8cf --- /dev/null +++ b/src/Doctrine/Common/State/RemoveProcessor.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Doctrine\Common\State; + +use ApiPlatform\Metadata\Operation; +use ApiPlatform\State\ProcessorInterface; +use ApiPlatform\Util\ClassInfoTrait; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\ObjectManager as DoctrineObjectManager; + +final class RemoveProcessor implements ProcessorInterface +{ + use ClassInfoTrait; + + private $managerRegistry; + + public function __construct(ManagerRegistry $managerRegistry) + { + $this->managerRegistry = $managerRegistry; + } + + public function process($data, Operation $operation, array $uriVariables = [], array $context = []) + { + if (!$manager = $this->getManager($data)) { + return; + } + + $manager->remove($data); + $manager->flush(); + } + + /** + * Gets the Doctrine object manager associated with given data. + * + * @param mixed $data + */ + private function getManager($data): ?DoctrineObjectManager + { + return \is_object($data) ? $this->managerRegistry->getManagerForClass($this->getObjectClass($data)) : null; + } +} diff --git a/src/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactory.php b/src/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactory.php new file mode 100644 index 00000000000..ec2ea373e40 --- /dev/null +++ b/src/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactory.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Doctrine\Odm\Metadata\Resource; + +use ApiPlatform\Doctrine\Common\State\PersistProcessor; +use ApiPlatform\Doctrine\Common\State\RemoveProcessor; +use ApiPlatform\Doctrine\Odm\State\CollectionProvider; +use ApiPlatform\Doctrine\Odm\State\ItemProvider; +use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\DeleteOperationInterface; +use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use Doctrine\ODM\MongoDB\DocumentManager; +use Doctrine\Persistence\ManagerRegistry; + +final class DoctrineMongoDbOdmResourceCollectionMetadataFactory implements ResourceMetadataCollectionFactoryInterface +{ + /** + * @var ManagerRegistry + */ + private $managerRegistry; + + /** + * @var ResourceMetadataCollectionFactoryInterface + */ + private $decorated; + + public function __construct(ManagerRegistry $managerRegistry, ResourceMetadataCollectionFactoryInterface $decorated) + { + $this->decorated = $decorated; + $this->managerRegistry = $managerRegistry; + } + + /** + * {@inheritDoc} + */ + public function create(string $resourceClass): ResourceMetadataCollection + { + $resourceMetadataCollection = $this->decorated->create($resourceClass); + + foreach ($resourceMetadataCollection as $i => $resourceMetadata) { + $operations = $resourceMetadata->getOperations(); + + if ($operations) { + foreach ($resourceMetadata->getOperations() as $operationName => $operation) { + if (!$this->managerRegistry->getManagerForClass($operation->getClass()) instanceof DocumentManager) { + continue; + } + + $operations->add($operationName, $this->addDefaults($operation)); + } + + $resourceMetadata = $resourceMetadata->withOperations($operations); + } + + $graphQlOperations = $resourceMetadata->getGraphQlOperations(); + + if ($graphQlOperations) { + foreach ($graphQlOperations as $operationName => $graphQlOperation) { + if (!$this->managerRegistry->getManagerForClass($graphQlOperation->getClass()) instanceof DocumentManager) { + continue; + } + + $graphQlOperations[$operationName] = $this->addDefaults($graphQlOperation); + } + + $resourceMetadata = $resourceMetadata->withGraphQlOperations($graphQlOperations); + } + + $resourceMetadataCollection[$i] = $resourceMetadata; + } + + return $resourceMetadataCollection; + } + + private function addDefaults($operation): Operation + { + if (null === $operation->getProvider()) { + $operation = $operation->withProvider($this->getProvider($operation)); + } + + if (null === $operation->getProcessor()) { + $operation = $operation->withProcessor($this->getProcessor($operation)); + } + + return $operation; + } + + private function getProvider(Operation $operation): string + { + if ($operation instanceof CollectionOperationInterface) { + return CollectionProvider::class; + } + + return ItemProvider::class; + } + + private function getProcessor(Operation $operation): string + { + if ($operation instanceof DeleteOperationInterface) { + return RemoveProcessor::class; + } + + return PersistProcessor::class; + } +} diff --git a/src/Doctrine/Odm/State/CollectionProvider.php b/src/Doctrine/Odm/State/CollectionProvider.php index f296e6b319f..c4153607a7c 100644 --- a/src/Doctrine/Odm/State/CollectionProvider.php +++ b/src/Doctrine/Odm/State/CollectionProvider.php @@ -17,7 +17,7 @@ use ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface; use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; +use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\State\ProviderInterface; use Doctrine\ODM\MongoDB\DocumentManager; @@ -46,8 +46,9 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource $this->collectionExtensions = $collectionExtensions; } - public function provide(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []) { + $resourceClass = $operation->getClass(); /** @var DocumentManager $manager */ $manager = $this->managerRegistry->getManagerForClass($resourceClass); @@ -59,19 +60,19 @@ public function provide(string $resourceClass, array $uriVariables = [], ?string $aggregationBuilder = $repository->createAggregationBuilder(); - $this->handleLinks($aggregationBuilder, $uriVariables, $context, $resourceClass, $operationName); + $this->handleLinks($aggregationBuilder, $uriVariables, $context, $resourceClass, $operation); foreach ($this->collectionExtensions as $extension) { - $extension->applyToCollection($aggregationBuilder, $resourceClass, $operationName, $context); + $extension->applyToCollection($aggregationBuilder, $resourceClass, $operation->getName(), $context); - if ($extension instanceof AggregationResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) { - return $extension->getResult($aggregationBuilder, $resourceClass, $operationName, $context); + if ($extension instanceof AggregationResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operation->getName(), $context)) { + return $extension->getResult($aggregationBuilder, $resourceClass, $operation->getName(), $context); } } $resourceMetadata = $this->resourceMetadataCollectionFactory->create($resourceClass); try { - $operation = $context['operation'] ?? $resourceMetadata->getOperation($operationName); + $operation = $context['operation'] ?? $resourceMetadata->getOperation($operation->getName()); $attribute = $operation->getExtraProperties()['doctrine_mongodb'] ?? []; } catch (OperationNotFoundException $e) { $attribute = $resourceMetadata->getOperation(null, true)->getExtraProperties()['doctrine_mongodb'] ?? []; @@ -80,19 +81,4 @@ public function provide(string $resourceClass, array $uriVariables = [], ?string return $aggregationBuilder->hydrate($resourceClass)->execute($executeOptions); } - - public function supports(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []): bool - { - if (!$this->managerRegistry->getManagerForClass($resourceClass) instanceof DocumentManager) { - return false; - } - - $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operationName); - - if ($operation instanceof GraphQlOperation) { - return true; - } - - return $operation->isCollection() ?? false; - } } diff --git a/src/Doctrine/Odm/State/ItemProvider.php b/src/Doctrine/Odm/State/ItemProvider.php index e9f5f6d98ff..0549a032c4f 100644 --- a/src/Doctrine/Odm/State/ItemProvider.php +++ b/src/Doctrine/Odm/State/ItemProvider.php @@ -16,8 +16,8 @@ use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface; use ApiPlatform\Exception\RuntimeException; +use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\State\ProviderInterface; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; @@ -48,8 +48,9 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource $this->itemExtensions = $itemExtensions; } - public function provide(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []) { + $resourceClass = $operation->getClass(); /** @var DocumentManager $manager */ $manager = $this->managerRegistry->getManagerForClass($resourceClass); @@ -66,35 +67,18 @@ public function provide(string $resourceClass, array $uriVariables = [], ?string $aggregationBuilder = $repository->createAggregationBuilder(); - $this->handleLinks($aggregationBuilder, $uriVariables, $context, $resourceClass, $operationName); + $this->handleLinks($aggregationBuilder, $uriVariables, $context, $resourceClass, $operation); foreach ($this->itemExtensions as $extension) { - $extension->applyToItem($aggregationBuilder, $resourceClass, $uriVariables, $operationName, $context); + $extension->applyToItem($aggregationBuilder, $resourceClass, $uriVariables, $operation->getName(), $context); - if ($extension instanceof AggregationResultItemExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) { - return $extension->getResult($aggregationBuilder, $resourceClass, $operationName, $context); + if ($extension instanceof AggregationResultItemExtensionInterface && $extension->supportsResult($resourceClass, $operation->getName(), $context)) { + return $extension->getResult($aggregationBuilder, $resourceClass, $operation->getName(), $context); } } - $resourceMetadata = $this->resourceMetadataCollectionFactory->create($resourceClass); - - if ($resourceMetadata instanceof ResourceMetadataCollection) { - $attribute = $resourceMetadata->getOperation()->getExtraProperties()['doctrine_mongodb'] ?? []; - } - - $executeOptions = $attribute['execute_options'] ?? []; + $executeOptions = $operation->getExtraProperties()['doctrine_mongodb']['execute_options'] ?? []; return $aggregationBuilder->hydrate($resourceClass)->execute($executeOptions)->current() ?: null; } - - public function supports(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []): bool - { - if (!$this->managerRegistry->getManagerForClass($resourceClass) instanceof DocumentManager) { - return false; - } - - $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operationName); - - return !($operation->isCollection() ?? false); - } } diff --git a/src/Doctrine/Odm/State/LinksHandlerTrait.php b/src/Doctrine/Odm/State/LinksHandlerTrait.php index 87b8f686be4..dcaac93c0cc 100644 --- a/src/Doctrine/Odm/State/LinksHandlerTrait.php +++ b/src/Doctrine/Odm/State/LinksHandlerTrait.php @@ -16,6 +16,7 @@ use ApiPlatform\Doctrine\Common\State\LinksHandlerTrait as CommonLinksHandlerTrait; use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Operation; use Doctrine\ODM\MongoDB\Aggregation\Builder; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; @@ -24,13 +25,12 @@ trait LinksHandlerTrait { use CommonLinksHandlerTrait; - private function handleLinks(Builder $aggregationBuilder, array $identifiers, array $context, string $resourceClass, ?string $operationName = null): void + private function handleLinks(Builder $aggregationBuilder, array $identifiers, array $context, string $resourceClass, Operation $operation): void { if (!$identifiers) { return; } - $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operationName); $links = $this->getLinks($resourceClass, $operation, $context); if (!$links) { diff --git a/src/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php b/src/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php index 066935af371..b18150f21df 100644 --- a/src/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php +++ b/src/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php @@ -21,7 +21,7 @@ use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; @@ -58,7 +58,7 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator $em = $queryBuilder->getEntityManager(); $classMetadata = $em->getClassMetadata($resourceClass); - /** @var Operation|GraphQlOperation|null */ + /** @var HttpOperation|GraphQlOperation|null */ $operation = null; $forceEager = $this->forceEager; diff --git a/src/Doctrine/Orm/Extension/OrderExtension.php b/src/Doctrine/Orm/Extension/OrderExtension.php index 4f40e7af215..894ccfb2884 100644 --- a/src/Doctrine/Orm/Extension/OrderExtension.php +++ b/src/Doctrine/Orm/Extension/OrderExtension.php @@ -80,7 +80,6 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator $defaultOrder = $this->resourceMetadataFactory->create($resourceClass)->getCollectionOperationAttribute($operationName, 'order', [], true); } - // TODO: 3.0 default value is [] not null if (null !== $defaultOrder && [] !== $defaultOrder) { foreach ($defaultOrder as $field => $order) { if (\is_int($field)) { diff --git a/src/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php b/src/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php new file mode 100644 index 00000000000..7f50d12977b --- /dev/null +++ b/src/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Doctrine\Orm\Metadata\Resource; + +use ApiPlatform\Doctrine\Common\State\PersistProcessor; +use ApiPlatform\Doctrine\Common\State\RemoveProcessor; +use ApiPlatform\Doctrine\Orm\State\CollectionProvider; +use ApiPlatform\Doctrine\Orm\State\ItemProvider; +use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\DeleteOperationInterface; +use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ManagerRegistry; + +final class DoctrineOrmResourceCollectionMetadataFactory implements ResourceMetadataCollectionFactoryInterface +{ + /** + * @var ManagerRegistry + */ + private $managerRegistry; + + /** + * @var ResourceMetadataCollectionFactoryInterface + */ + private $decorated; + + public function __construct(ManagerRegistry $managerRegistry, ResourceMetadataCollectionFactoryInterface $decorated) + { + $this->decorated = $decorated; + $this->managerRegistry = $managerRegistry; + } + + /** + * {@inheritDoc} + */ + public function create(string $resourceClass): ResourceMetadataCollection + { + $resourceMetadataCollection = $this->decorated->create($resourceClass); + + foreach ($resourceMetadataCollection as $i => $resourceMetadata) { + $operations = $resourceMetadata->getOperations(); + + if ($operations) { + foreach ($resourceMetadata->getOperations() as $operationName => $operation) { + if (!$this->managerRegistry->getManagerForClass($operation->getClass()) instanceof EntityManagerInterface) { + continue; + } + + $operations->add($operationName, $this->addDefaults($operation)); + } + + $resourceMetadata = $resourceMetadata->withOperations($operations); + } + + $graphQlOperations = $resourceMetadata->getGraphQlOperations(); + + if ($graphQlOperations) { + foreach ($graphQlOperations as $operationName => $graphQlOperation) { + if (!$this->managerRegistry->getManagerForClass($graphQlOperation->getClass()) instanceof EntityManagerInterface) { + continue; + } + + $graphQlOperations[$operationName] = $this->addDefaults($graphQlOperation); + } + + $resourceMetadata = $resourceMetadata->withGraphQlOperations($graphQlOperations); + } + + $resourceMetadataCollection[$i] = $resourceMetadata; + } + + return $resourceMetadataCollection; + } + + private function addDefaults($operation): Operation + { + if (null === $operation->getProvider()) { + $operation = $operation->withProvider($this->getProvider($operation)); + } + + if (null === $operation->getProcessor()) { + $operation = $operation->withProcessor($this->getProcessor($operation)); + } + + return $operation; + } + + private function getProvider(Operation $operation): string + { + if ($operation instanceof CollectionOperationInterface) { + return CollectionProvider::class; + } + + return ItemProvider::class; + } + + private function getProcessor(Operation $operation): string + { + if ($operation instanceof DeleteOperationInterface) { + return RemoveProcessor::class; + } + + return PersistProcessor::class; + } +} diff --git a/src/Doctrine/Orm/State/CollectionProvider.php b/src/Doctrine/Orm/State/CollectionProvider.php index f625ff4c680..a652020df42 100644 --- a/src/Doctrine/Orm/State/CollectionProvider.php +++ b/src/Doctrine/Orm/State/CollectionProvider.php @@ -17,7 +17,7 @@ use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; +use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\State\ProviderInterface; use Doctrine\ORM\EntityManagerInterface; @@ -48,8 +48,9 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource $this->collectionExtensions = $collectionExtensions; } - public function provide(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []) { + $resourceClass = $operation->getClass(); /** @var EntityManagerInterface $manager */ $manager = $this->managerRegistry->getManagerForClass($resourceClass); @@ -62,31 +63,16 @@ public function provide(string $resourceClass, array $uriVariables = [], ?string $queryBuilder = $repository->createQueryBuilder('o'); $queryNameGenerator = new QueryNameGenerator(); - $this->handleLinks($queryBuilder, $uriVariables, $queryNameGenerator, $context, $resourceClass, $operationName); + $this->handleLinks($queryBuilder, $uriVariables, $queryNameGenerator, $context, $resourceClass, $operation); foreach ($this->collectionExtensions as $extension) { - $extension->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); + $extension->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $operation->getName(), $context); - if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) { - return $extension->getResult($queryBuilder, $resourceClass, $operationName, $context); + if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operation->getName(), $context)) { + return $extension->getResult($queryBuilder, $resourceClass, $operation->getName(), $context); } } return $queryBuilder->getQuery()->getResult(); } - - public function supports(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []): bool - { - if (!$this->managerRegistry->getManagerForClass($resourceClass) instanceof EntityManagerInterface) { - return false; - } - - $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operationName); - - if ($operation instanceof GraphQlOperation) { - return true; - } - - return $operation->isCollection() ?? false; - } } diff --git a/src/Doctrine/Orm/State/ItemProvider.php b/src/Doctrine/Orm/State/ItemProvider.php index 9d4ec84dbaf..b61191b30a3 100644 --- a/src/Doctrine/Orm/State/ItemProvider.php +++ b/src/Doctrine/Orm/State/ItemProvider.php @@ -17,6 +17,7 @@ use ApiPlatform\Doctrine\Orm\Extension\QueryResultItemExtensionInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; use ApiPlatform\Exception\RuntimeException; +use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\State\ProviderInterface; use Doctrine\ORM\EntityManagerInterface; @@ -47,8 +48,9 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource $this->itemExtensions = $itemExtensions; } - public function provide(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []) { + $resourceClass = $operation->getClass(); /** @var EntityManagerInterface $manager */ $manager = $this->managerRegistry->getManagerForClass($resourceClass); @@ -66,27 +68,16 @@ public function provide(string $resourceClass, array $uriVariables = [], ?string $queryBuilder = $repository->createQueryBuilder('o'); $queryNameGenerator = new QueryNameGenerator(); - $this->handleLinks($queryBuilder, $uriVariables, $queryNameGenerator, $context, $resourceClass, $operationName); + $this->handleLinks($queryBuilder, $uriVariables, $queryNameGenerator, $context, $resourceClass, $operation); foreach ($this->itemExtensions as $extension) { - $extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $uriVariables, $operationName, $context); + $extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $uriVariables, $operation->getName(), $context); - if ($extension instanceof QueryResultItemExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) { - return $extension->getResult($queryBuilder, $resourceClass, $operationName, $context); + if ($extension instanceof QueryResultItemExtensionInterface && $extension->supportsResult($resourceClass, $operation->getName(), $context)) { + return $extension->getResult($queryBuilder, $resourceClass, $operation->getName(), $context); } } return $queryBuilder->getQuery()->getOneOrNullResult(); } - - public function supports(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []): bool - { - if (!$this->managerRegistry->getManagerForClass($resourceClass) instanceof EntityManagerInterface) { - return false; - } - - $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operationName); - - return !($operation->getExtraProperties()['is_legacy_subresource'] ?? false) && !($operation->isCollection() ?? false); - } } diff --git a/src/Doctrine/Orm/State/LinksHandlerTrait.php b/src/Doctrine/Orm/State/LinksHandlerTrait.php index 76e0ed14b1d..6299e2fb9fa 100644 --- a/src/Doctrine/Orm/State/LinksHandlerTrait.php +++ b/src/Doctrine/Orm/State/LinksHandlerTrait.php @@ -15,6 +15,7 @@ use ApiPlatform\Doctrine\Common\State\LinksHandlerTrait as CommonLinksHandlerTrait; use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; +use ApiPlatform\Metadata\Operation; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\QueryBuilder; @@ -22,7 +23,7 @@ trait LinksHandlerTrait { use CommonLinksHandlerTrait; - private function handleLinks(QueryBuilder $queryBuilder, array $identifiers, QueryNameGenerator $queryNameGenerator, array $context, string $resourceClass, ?string $operationName = null): void + private function handleLinks(QueryBuilder $queryBuilder, array $identifiers, QueryNameGenerator $queryNameGenerator, array $context, string $resourceClass, Operation $operation): void { if (!$identifiers) { return; @@ -32,7 +33,6 @@ private function handleLinks(QueryBuilder $queryBuilder, array $identifiers, Que $doctrineClassMetadata = $manager->getClassMetadata($resourceClass); $alias = $queryBuilder->getRootAliases()[0]; - $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operationName); $links = $this->getLinks($resourceClass, $operation, $context); if (!$links) { diff --git a/src/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php b/src/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php index 314bd5b6b0b..0bd6c20fa44 100644 --- a/src/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php +++ b/src/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php @@ -14,8 +14,11 @@ namespace ApiPlatform\Elasticsearch\Metadata\Document\Factory; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Elasticsearch\Exception\IndexNotFoundException; use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; /** * Creates document's metadata using the attribute configuration. @@ -26,10 +29,16 @@ */ final class AttributeDocumentMetadataFactory implements DocumentMetadataFactoryInterface { + /** + * @var ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface + */ private $resourceMetadataFactory; private $decorated; - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ?DocumentMetadataFactoryInterface $decorated = null) + /** + * @param ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory + */ + public function __construct($resourceMetadataFactory, ?DocumentMetadataFactoryInterface $decorated = null) { $this->resourceMetadataFactory = $resourceMetadataFactory; $this->decorated = $decorated; @@ -52,17 +61,22 @@ public function create(string $resourceClass): DocumentMetadata $resourceMetadata = null; if (!$documentMetadata || null === $documentMetadata->getIndex()) { + /** @var ResourceMetadata|ResourceMetadataCollection */ $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if (null !== $index = $resourceMetadata->getAttribute('elasticsearch_index')) { + $index = $resourceMetadata instanceof ResourceMetadata ? $resourceMetadata->getAttribute('elasticsearch_index') : ($resourceMetadata->getOperation()->getExtraProperties()['elasticsearch_index'] ?? null); + + if (null !== $index) { $documentMetadata = $documentMetadata ? $documentMetadata->withIndex($index) : new DocumentMetadata($index); } } if (!$documentMetadata || DocumentMetadata::DEFAULT_TYPE === $documentMetadata->getType()) { + /** @var ResourceMetadata|ResourceMetadataCollection */ $resourceMetadata = $resourceMetadata ?? $this->resourceMetadataFactory->create($resourceClass); + $type = $resourceMetadata instanceof ResourceMetadata ? $resourceMetadata->getAttribute('elasticsearch_type') : ($resourceMetadata->getOperation()->getExtraProperties()['elasticsearch_type'] ?? null); - if (null !== $type = $resourceMetadata->getAttribute('elasticsearch_type')) { + if (null !== $type) { $documentMetadata = $documentMetadata ? $documentMetadata->withType($type) : new DocumentMetadata(null, $type); } } diff --git a/src/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php b/src/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php index 740c5bcf75b..6f89047ff93 100644 --- a/src/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php +++ b/src/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php @@ -14,8 +14,11 @@ namespace ApiPlatform\Elasticsearch\Metadata\Document\Factory; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Elasticsearch\Exception\IndexNotFoundException; use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Util\Inflector; use Elasticsearch\Client; use Elasticsearch\Common\Exceptions\Missing404Exception; @@ -32,10 +35,16 @@ final class CatDocumentMetadataFactory implements DocumentMetadataFactoryInterface { private $client; + /** + * @var ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface + */ private $resourceMetadataFactory; private $decorated; - public function __construct(Client $client, ResourceMetadataFactoryInterface $resourceMetadataFactory, ?DocumentMetadataFactoryInterface $decorated = null) + /** + * @param ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory + */ + public function __construct(Client $client, $resourceMetadataFactory, ?DocumentMetadataFactoryInterface $decorated = null) { $this->client = $client; $this->resourceMetadataFactory = $resourceMetadataFactory; @@ -60,7 +69,15 @@ public function create(string $resourceClass): DocumentMetadata return $documentMetadata; } - if (null === $resourceShortName = $this->resourceMetadataFactory->create($resourceClass)->getShortName()) { + /** @var ResourceMetadata|ResourceMetadataCollection */ + $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + if ($resourceMetadata instanceof ResourceMetadata) { + $resourceShortName = $resourceMetadata->getShortName(); + } else { + $resourceShortName = $resourceMetadata->getOperation()->getShortName(); + } + + if (null === $resourceShortName) { return $this->handleNotFound($documentMetadata, $resourceClass); } diff --git a/src/Elasticsearch/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactory.php b/src/Elasticsearch/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactory.php new file mode 100644 index 00000000000..f61ab4179e7 --- /dev/null +++ b/src/Elasticsearch/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactory.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Elasticsearch\Metadata\Resource\Factory; + +use ApiPlatform\Elasticsearch\State\CollectionProvider; +use ApiPlatform\Elasticsearch\State\ItemProvider; +use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use ApiPlatform\Util\Inflector; +use Elasticsearch\Client; +use Elasticsearch\Common\Exceptions\Missing404Exception; + +final class ElasticsearchProviderResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface +{ + /** + * @var ResourceMetadataCollectionFactoryInterface + */ + private $decorated; + + private $client; + + public function __construct(Client $client, ResourceMetadataCollectionFactoryInterface $decorated) + { + $this->decorated = $decorated; + $this->client = $client; + } + + /** + * {@inheritDoc} + */ + public function create(string $resourceClass): ResourceMetadataCollection + { + $resourceMetadataCollection = $this->decorated->create($resourceClass); + + foreach ($resourceMetadataCollection as $i => $resourceMetadata) { + $operations = $resourceMetadata->getOperations(); + + if ($operations) { + foreach ($resourceMetadata->getOperations() as $operationName => $operation) { + if ($this->hasIndices($operation->getShortName())) { + $operation = $operation->withElasticsearch(true); + } + + if (null !== $operation->getProvider() || false === ($operation->getElasticsearch() ?? false)) { + continue; + } + + $operations->add($operationName, $operation->withProvider($operation instanceof CollectionOperationInterface ? CollectionProvider::class : ItemProvider::class)); + } + + $resourceMetadata = $resourceMetadata->withOperations($operations); + } + + $graphQlOperations = $resourceMetadata->getGraphQlOperations(); + + if ($graphQlOperations) { + foreach ($graphQlOperations as $operationName => $graphQlOperation) { + if ($this->hasIndices($graphQlOperation->getShortName())) { + $graphQlOperation = $graphQlOperation->withElasticsearch(true); + } + + if (null !== $graphQlOperation->getProvider() || false === ($graphQlOperation->getElasticsearch() ?? false)) { + continue; + } + + $graphQlOperations[$operationName] = $graphQlOperation->withProvider($graphQlOperation instanceof CollectionOperationInterface ? CollectionProvider::class : ItemProvider::class); + } + + $resourceMetadata = $resourceMetadata->withGraphQlOperations($graphQlOperations); + } + + $resourceMetadataCollection[$i] = $resourceMetadata; + } + + return $resourceMetadataCollection; + } + + private function hasIndices(string $shortName): bool + { + $index = Inflector::tableize($shortName); + + try { + $this->client->cat()->indices(['index' => $index]); + + return true; + } catch (Missing404Exception $e) { + return false; + } + } +} diff --git a/src/Elasticsearch/State/CollectionProvider.php b/src/Elasticsearch/State/CollectionProvider.php index ab85db7b71c..fa3aa576348 100644 --- a/src/Elasticsearch/State/CollectionProvider.php +++ b/src/Elasticsearch/State/CollectionProvider.php @@ -13,12 +13,10 @@ namespace ApiPlatform\Elasticsearch\State; -use ApiPlatform\Elasticsearch\Exception\IndexNotFoundException; use ApiPlatform\Elasticsearch\Extension\RequestBodySearchCollectionExtensionInterface; use ApiPlatform\Elasticsearch\Metadata\Document\Factory\DocumentMetadataFactoryInterface; use ApiPlatform\Elasticsearch\Paginator; -use ApiPlatform\Exception\OperationNotFoundException; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Operation; use ApiPlatform\State\Pagination\Pagination; use ApiPlatform\State\ProviderInterface; use Elasticsearch\Client; @@ -38,13 +36,12 @@ final class CollectionProvider implements ProviderInterface private $documentMetadataFactory; private $denormalizer; private $pagination; - private $resourceMetadataCollectionFactory; private $collectionExtensions; /** * @param RequestBodySearchCollectionExtensionInterface[] $collectionExtensions */ - public function __construct(Client $client, DocumentMetadataFactoryInterface $documentMetadataFactory, DenormalizerInterface $denormalizer, Pagination $pagination, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, iterable $collectionExtensions = []) + public function __construct(Client $client, DocumentMetadataFactoryInterface $documentMetadataFactory, DenormalizerInterface $denormalizer, Pagination $pagination, iterable $collectionExtensions = []) { $this->client = $client; $this->documentMetadataFactory = $documentMetadataFactory; @@ -52,39 +49,16 @@ public function __construct(Client $client, DocumentMetadataFactoryInterface $do $this->denormalizer = $denormalizer; $this->pagination = $pagination; - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->collectionExtensions = $collectionExtensions; } /** * {@inheritdoc} */ - public function supports(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []): bool - { - try { - $resourceMetadata = $this->resourceMetadataCollectionFactory->create($resourceClass); - $operation = $context['operation'] ?? $resourceMetadata->getOperation($operationName, true); - if (false === $operation->getElasticsearch() || !($operation->isCollection() ?? false)) { - return false; - } - } catch (OperationNotFoundException $e) { - return false; - } - - try { - $this->documentMetadataFactory->create($resourceClass); - } catch (IndexNotFoundException $e) { - return false; - } - - return true; - } - - /** - * {@inheritdoc} - */ - public function provide(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []) { + $resourceClass = $operation->getClass(); + $operationName = $operation->getName(); $documentMetadata = $this->documentMetadataFactory->create($resourceClass); $body = []; diff --git a/src/Elasticsearch/State/ItemProvider.php b/src/Elasticsearch/State/ItemProvider.php index 36b10300d84..0df291e0ffa 100644 --- a/src/Elasticsearch/State/ItemProvider.php +++ b/src/Elasticsearch/State/ItemProvider.php @@ -13,11 +13,9 @@ namespace ApiPlatform\Elasticsearch\State; -use ApiPlatform\Elasticsearch\Exception\IndexNotFoundException; use ApiPlatform\Elasticsearch\Metadata\Document\Factory\DocumentMetadataFactoryInterface; use ApiPlatform\Elasticsearch\Serializer\DocumentNormalizer; -use ApiPlatform\Exception\OperationNotFoundException; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use Elasticsearch\Client; use Elasticsearch\Common\Exceptions\Missing404Exception; @@ -37,45 +35,20 @@ final class ItemProvider implements ProviderInterface private $client; private $documentMetadataFactory; private $denormalizer; - private $resourceMetadataCollectionFactory; - public function __construct(Client $client, DocumentMetadataFactoryInterface $documentMetadataFactory, DenormalizerInterface $denormalizer, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory) + public function __construct(Client $client, DocumentMetadataFactoryInterface $documentMetadataFactory, DenormalizerInterface $denormalizer) { $this->client = $client; $this->documentMetadataFactory = $documentMetadataFactory; $this->denormalizer = $denormalizer; - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; } /** * {@inheritdoc} */ - public function supports(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []): bool - { - try { - $resourceMetadata = $this->resourceMetadataCollectionFactory->create($resourceClass); - $operation = $context['operation'] ?? $resourceMetadata->getOperation($operationName); - if (false === $operation->getElasticsearch() || true === ($operation->isCollection() ?? false)) { - return false; - } - } catch (OperationNotFoundException $e) { - return false; - } - - try { - $this->documentMetadataFactory->create($resourceClass); - } catch (IndexNotFoundException $e) { - return false; - } - - return true; - } - - /** - * {@inheritdoc} - */ - public function provide(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []) { + $resourceClass = $operation->getClass(); $documentMetadata = $this->documentMetadataFactory->create($resourceClass); try { diff --git a/src/GraphQl/Resolver/Factory/CollectionResolverFactory.php b/src/GraphQl/Resolver/Factory/CollectionResolverFactory.php index b71effe2dc0..2510d061382 100644 --- a/src/GraphQl/Resolver/Factory/CollectionResolverFactory.php +++ b/src/GraphQl/Resolver/Factory/CollectionResolverFactory.php @@ -13,13 +13,12 @@ namespace ApiPlatform\GraphQl\Resolver\Factory; -use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\GraphQl\Resolver\QueryCollectionResolverInterface; use ApiPlatform\GraphQl\Resolver\Stage\ReadStageInterface; use ApiPlatform\GraphQl\Resolver\Stage\SecurityPostDenormalizeStageInterface; use ApiPlatform\GraphQl\Resolver\Stage\SecurityStageInterface; use ApiPlatform\GraphQl\Resolver\Stage\SerializeStageInterface; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\GraphQl\Operation; use ApiPlatform\Util\CloneTrait; use GraphQL\Type\Definition\ResolveInfo; use Psr\Container\ContainerInterface; @@ -42,9 +41,8 @@ final class CollectionResolverFactory implements ResolverFactoryInterface private $serializeStage; private $queryResolverLocator; private $requestStack; - private $resourceMetadataCollectionFactory; - public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, ContainerInterface $queryResolverLocator, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, RequestStack $requestStack = null) + public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, ContainerInterface $queryResolverLocator, RequestStack $requestStack = null) { $this->readStage = $readStage; $this->securityStage = $securityStage; @@ -52,12 +50,11 @@ public function __construct(ReadStageInterface $readStage, SecurityStageInterfac $this->serializeStage = $serializeStage; $this->queryResolverLocator = $queryResolverLocator; $this->requestStack = $requestStack; - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; } - public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?string $operationName = null): callable + public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null): callable { - return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operationName) { + return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operation) { // If authorization has failed for a relation field (e.g. via ApiProperty security), the field is not present in the source: null can be returned directly to ensure the collection isn't in the response. if (null === $resourceClass || null === $rootClass || (null !== $source && !\array_key_exists($info->fieldName, $source))) { return null; @@ -70,22 +67,14 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul ); } - $operationName = $operationName ?? 'collection_query'; $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false]; - $collection = ($this->readStage)($resourceClass, $rootClass, $operationName, $resolverContext); + $collection = ($this->readStage)($resourceClass, $rootClass, $operation, $resolverContext); if (!is_iterable($collection)) { throw new \LogicException('Collection from read stage should be iterable.'); } - $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); - try { - $operation = $resourceMetadataCollection->getOperation($operationName); - $queryResolverId = $operation->getResolver(); - } catch (OperationNotFoundException $e) { - $queryResolverId = null; - } - + $queryResolverId = $operation->getResolver(); if (null !== $queryResolverId) { /** @var QueryCollectionResolverInterface $queryResolver */ $queryResolver = $this->queryResolverLocator->get($queryResolverId); @@ -94,12 +83,12 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul // Only perform security stage on the top-level query if (null === $source) { - ($this->securityStage)($resourceClass, $operationName, $resolverContext + [ + ($this->securityStage)($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $collection, ], ]); - ($this->securityPostDenormalizeStage)($resourceClass, $operationName, $resolverContext + [ + ($this->securityPostDenormalizeStage)($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $collection, 'previous_object' => $this->clone($collection), @@ -107,7 +96,7 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul ]); } - return ($this->serializeStage)($collection, $resourceClass, $operationName, $resolverContext); + return ($this->serializeStage)($collection, $resourceClass, $operation, $resolverContext); }; } } diff --git a/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php b/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php index 6ce2473660c..e69d2b1d5f8 100644 --- a/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php +++ b/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php @@ -22,7 +22,8 @@ use ApiPlatform\GraphQl\Resolver\Stage\SerializeStageInterface; use ApiPlatform\GraphQl\Resolver\Stage\ValidateStageInterface; use ApiPlatform\GraphQl\Resolver\Stage\WriteStageInterface; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\DeleteOperationInterface; +use ApiPlatform\Metadata\GraphQl\Operation; use ApiPlatform\Util\ClassInfoTrait; use ApiPlatform\Util\CloneTrait; use GraphQL\Type\Definition\ResolveInfo; @@ -47,10 +48,9 @@ final class ItemMutationResolverFactory implements ResolverFactoryInterface private $writeStage; private $validateStage; private $mutationResolverLocator; - private $resourceMetadataCollectionFactory; private $securityPostValidationStage; - public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, DeserializeStageInterface $deserializeStage, WriteStageInterface $writeStage, ValidateStageInterface $validateStage, ContainerInterface $mutationResolverLocator, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, SecurityPostValidationStageInterface $securityPostValidationStage) + public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, DeserializeStageInterface $deserializeStage, WriteStageInterface $writeStage, ValidateStageInterface $validateStage, ContainerInterface $mutationResolverLocator, SecurityPostValidationStageInterface $securityPostValidationStage) { $this->readStage = $readStage; $this->securityStage = $securityStage; @@ -60,46 +60,42 @@ public function __construct(ReadStageInterface $readStage, SecurityStageInterfac $this->writeStage = $writeStage; $this->validateStage = $validateStage; $this->mutationResolverLocator = $mutationResolverLocator; - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->securityPostValidationStage = $securityPostValidationStage; } - public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?string $operationName = null): callable + public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null): callable { - return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operationName) { - if (null === $resourceClass || null === $operationName) { + return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operation) { + if (null === $resourceClass || null === $operation) { return null; } $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => true, 'is_subscription' => false]; - $item = ($this->readStage)($resourceClass, $rootClass, $operationName, $resolverContext); + $item = ($this->readStage)($resourceClass, $rootClass, $operation, $resolverContext); if (null !== $item && !\is_object($item)) { throw new \LogicException('Item from read stage should be a nullable object.'); } - ($this->securityStage)($resourceClass, $operationName, $resolverContext + [ + ($this->securityStage)($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $item, ], ]); $previousItem = $this->clone($item); - if ('delete' === $operationName) { - ($this->securityPostDenormalizeStage)($resourceClass, $operationName, $resolverContext + [ + if ('delete' === $operation->getName() || $operation instanceof DeleteOperationInterface) { + ($this->securityPostDenormalizeStage)($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $item, 'previous_object' => $previousItem, ], ]); - $item = ($this->writeStage)($item, $resourceClass, $operationName, $resolverContext); + $item = ($this->writeStage)($item, $resourceClass, $operation, $resolverContext); - return ($this->serializeStage)($item, $resourceClass, $operationName, $resolverContext); + return ($this->serializeStage)($item, $resourceClass, $operation, $resolverContext); } - $item = ($this->deserializeStage)($item, $resourceClass, $operationName, $resolverContext); - - $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); - $operation = $resourceMetadataCollection->getOperation($operationName); + $item = ($this->deserializeStage)($item, $resourceClass, $operation, $resolverContext); $mutationResolverId = $operation->getResolver(); if (null !== $mutationResolverId) { @@ -111,7 +107,7 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul } } - ($this->securityPostDenormalizeStage)($resourceClass, $operationName, $resolverContext + [ + ($this->securityPostDenormalizeStage)($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $item, 'previous_object' => $previousItem, @@ -119,19 +115,19 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul ]); if (null !== $item) { - ($this->validateStage)($item, $resourceClass, $operationName, $resolverContext); + ($this->validateStage)($item, $resourceClass, $operation, $resolverContext); - ($this->securityPostValidationStage)($resourceClass, $operationName, $resolverContext + [ + ($this->securityPostValidationStage)($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $item, 'previous_object' => $previousItem, ], ]); - $persistResult = ($this->writeStage)($item, $resourceClass, $operationName, $resolverContext); + $persistResult = ($this->writeStage)($item, $resourceClass, $operation, $resolverContext); } - return ($this->serializeStage)($persistResult ?? $item, $resourceClass, $operationName, $resolverContext); + return ($this->serializeStage)($persistResult ?? $item, $resourceClass, $operation, $resolverContext); }; } } diff --git a/src/GraphQl/Resolver/Factory/ItemResolverFactory.php b/src/GraphQl/Resolver/Factory/ItemResolverFactory.php index 18d8b21fcef..e8eaf460119 100644 --- a/src/GraphQl/Resolver/Factory/ItemResolverFactory.php +++ b/src/GraphQl/Resolver/Factory/ItemResolverFactory.php @@ -18,6 +18,8 @@ use ApiPlatform\GraphQl\Resolver\Stage\SecurityPostDenormalizeStageInterface; use ApiPlatform\GraphQl\Resolver\Stage\SecurityStageInterface; use ApiPlatform\GraphQl\Resolver\Stage\SerializeStageInterface; +use ApiPlatform\Metadata\GraphQl\Operation; +use ApiPlatform\Metadata\GraphQl\Query; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Util\ClassInfoTrait; use ApiPlatform\Util\CloneTrait; @@ -53,26 +55,27 @@ public function __construct(ReadStageInterface $readStage, SecurityStageInterfac $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; } - public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?string $operationName = null): callable + public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null): callable { - return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operationName) { + return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operation) { // Data already fetched and normalized (field or nested resource) if ($source && \array_key_exists($info->fieldName, $source)) { return $source[$info->fieldName]; } - $operationName = $operationName ?? 'item_query'; $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => false]; - $item = ($this->readStage)($resourceClass, $rootClass, $operationName, $resolverContext); + if (!$operation) { + $operation = new Query(); + } + + $item = ($this->readStage)($resourceClass, $rootClass, $operation, $resolverContext); if (null !== $item && !\is_object($item)) { throw new \LogicException('Item from read stage should be a nullable object.'); } + // The item retrieved can be of another type when using an identifier (see Relay Nodes at query.feature:23) $resourceClass = $this->getResourceClass($item, $resourceClass); - $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); - $operation = $resourceMetadataCollection->getOperation($operationName); - $queryResolverId = $operation->getResolver(); if (null !== $queryResolverId) { /** @var QueryItemResolverInterface $queryResolver */ @@ -81,19 +84,19 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul $resourceClass = $this->getResourceClass($item, $resourceClass, sprintf('Custom query resolver "%s"', $queryResolverId).' has to return an item of class %s but returned an item of class %s.'); } - ($this->securityStage)($resourceClass, $operationName, $resolverContext + [ + ($this->securityStage)($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $item, ], ]); - ($this->securityPostDenormalizeStage)($resourceClass, $operationName, $resolverContext + [ + ($this->securityPostDenormalizeStage)($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $item, 'previous_object' => $this->clone($item), ], ]); - return ($this->serializeStage)($item, $resourceClass, $operationName, $resolverContext); + return ($this->serializeStage)($item, $resourceClass, $operation, $resolverContext); }; } diff --git a/src/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactory.php b/src/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactory.php index f17dea6fa20..2019dc13f6a 100644 --- a/src/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactory.php +++ b/src/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactory.php @@ -18,7 +18,7 @@ use ApiPlatform\GraphQl\Resolver\Stage\SerializeStageInterface; use ApiPlatform\GraphQl\Subscription\MercureSubscriptionIriGeneratorInterface; use ApiPlatform\GraphQl\Subscription\SubscriptionManagerInterface; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\GraphQl\Operation; use ApiPlatform\Util\ClassInfoTrait; use ApiPlatform\Util\CloneTrait; use GraphQL\Type\Definition\ResolveInfo; @@ -36,46 +36,41 @@ final class ItemSubscriptionResolverFactory implements ResolverFactoryInterface private $readStage; private $securityStage; private $serializeStage; - private $resourceMetadataCollectionFactory; private $subscriptionManager; private $mercureSubscriptionIriGenerator; - public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SerializeStageInterface $serializeStage, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, SubscriptionManagerInterface $subscriptionManager, ?MercureSubscriptionIriGeneratorInterface $mercureSubscriptionIriGenerator) + public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SerializeStageInterface $serializeStage, SubscriptionManagerInterface $subscriptionManager, ?MercureSubscriptionIriGeneratorInterface $mercureSubscriptionIriGenerator) { $this->readStage = $readStage; $this->securityStage = $securityStage; $this->serializeStage = $serializeStage; - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->subscriptionManager = $subscriptionManager; $this->mercureSubscriptionIriGenerator = $mercureSubscriptionIriGenerator; } - public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?string $operationName = null): callable + public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null): callable { - return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operationName) { - if (null === $resourceClass || null === $operationName) { + return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operation) { + if (null === $resourceClass || null === $operation) { return null; } $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => true]; - $item = ($this->readStage)($resourceClass, $rootClass, $operationName, $resolverContext); + $item = ($this->readStage)($resourceClass, $rootClass, $operation, $resolverContext); if (null !== $item && !\is_object($item)) { throw new \LogicException('Item from read stage should be a nullable object.'); } - ($this->securityStage)($resourceClass, $operationName, $resolverContext + [ + ($this->securityStage)($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $item, ], ]); - $result = ($this->serializeStage)($item, $resourceClass, $operationName, $resolverContext); + $result = ($this->serializeStage)($item, $resourceClass, $operation, $resolverContext); $subscriptionId = $this->subscriptionManager->retrieveSubscriptionId($resolverContext, $result); - $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); - $operation = $resourceMetadataCollection->getOperation($operationName); - if ($subscriptionId && ($mercure = $operation->getMercure())) { if (!$this->mercureSubscriptionIriGenerator) { throw new \LogicException('Cannot use Mercure for subscriptions when MercureBundle is not installed. Try running "composer require mercure".'); diff --git a/src/GraphQl/Resolver/Factory/ResolverFactoryInterface.php b/src/GraphQl/Resolver/Factory/ResolverFactoryInterface.php index 3a051a9111b..c65e8f16682 100644 --- a/src/GraphQl/Resolver/Factory/ResolverFactoryInterface.php +++ b/src/GraphQl/Resolver/Factory/ResolverFactoryInterface.php @@ -13,6 +13,8 @@ namespace ApiPlatform\GraphQl\Resolver\Factory; +use ApiPlatform\Metadata\GraphQl\Operation; + /** * Builds a GraphQL resolver. * @@ -20,5 +22,5 @@ */ interface ResolverFactoryInterface { - public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?string $operationName = null): callable; + public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?Operation $operation = null): callable; } diff --git a/src/GraphQl/Resolver/Stage/DeserializeStage.php b/src/GraphQl/Resolver/Stage/DeserializeStage.php index 49e36d57775..d1262199c76 100644 --- a/src/GraphQl/Resolver/Stage/DeserializeStage.php +++ b/src/GraphQl/Resolver/Stage/DeserializeStage.php @@ -15,7 +15,7 @@ use ApiPlatform\GraphQl\Serializer\ItemNormalizer; use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\GraphQl\Operation; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -26,13 +26,11 @@ */ final class DeserializeStage implements DeserializeStageInterface { - private $resourceMetadataCollectionFactory; private $denormalizer; private $serializerContextBuilder; - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, DenormalizerInterface $denormalizer, SerializerContextBuilderInterface $serializerContextBuilder) + public function __construct(DenormalizerInterface $denormalizer, SerializerContextBuilderInterface $serializerContextBuilder) { - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->denormalizer = $denormalizer; $this->serializerContextBuilder = $serializerContextBuilder; } @@ -40,15 +38,13 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource /** * {@inheritdoc} */ - public function __invoke($objectToPopulate, string $resourceClass, string $operationName, array $context) + public function __invoke($objectToPopulate, string $resourceClass, Operation $operation, array $context) { - $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); - $operation = $resourceMetadataCollection->getOperation($operationName); if (!($operation->canDeserialize() ?? true)) { return $objectToPopulate; } - $denormalizationContext = $this->serializerContextBuilder->create($resourceClass, $operationName, $context, false); + $denormalizationContext = $this->serializerContextBuilder->create($resourceClass, $operation->getName(), $context, false); if (null !== $objectToPopulate) { $denormalizationContext[AbstractNormalizer::OBJECT_TO_POPULATE] = $objectToPopulate; } diff --git a/src/GraphQl/Resolver/Stage/DeserializeStageInterface.php b/src/GraphQl/Resolver/Stage/DeserializeStageInterface.php index 5bb08160d6a..6b1ca70f402 100644 --- a/src/GraphQl/Resolver/Stage/DeserializeStageInterface.php +++ b/src/GraphQl/Resolver/Stage/DeserializeStageInterface.php @@ -13,6 +13,8 @@ namespace ApiPlatform\GraphQl\Resolver\Stage; +use ApiPlatform\Metadata\GraphQl\Operation; + /** * Deserialize stage of GraphQL resolvers. * @@ -25,5 +27,5 @@ interface DeserializeStageInterface * * @return object|null */ - public function __invoke($objectToPopulate, string $resourceClass, string $operationName, array $context); + public function __invoke($objectToPopulate, string $resourceClass, Operation $operation, array $context); } diff --git a/src/GraphQl/Resolver/Stage/ReadStage.php b/src/GraphQl/Resolver/Stage/ReadStage.php index 1fe1ce01d36..f60f9bd5942 100644 --- a/src/GraphQl/Resolver/Stage/ReadStage.php +++ b/src/GraphQl/Resolver/Stage/ReadStage.php @@ -15,12 +15,10 @@ use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Exception\ItemNotFoundException; -use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\GraphQl\Resolver\Util\IdentifierTrait; use ApiPlatform\GraphQl\Serializer\ItemNormalizer; use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface; -use ApiPlatform\Metadata\GraphQl\QueryCollection; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\GraphQl\Operation; use ApiPlatform\State\ProviderInterface; use ApiPlatform\Util\ArrayTrait; use ApiPlatform\Util\ClassInfoTrait; @@ -38,15 +36,13 @@ final class ReadStage implements ReadStageInterface use ClassInfoTrait; use IdentifierTrait; - private $resourceMetadataCollectionFactory; private $iriConverter; private $provider; private $serializerContextBuilder; private $nestingSeparator; - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, IriConverterInterface $iriConverter, ProviderInterface $provider, SerializerContextBuilderInterface $serializerContextBuilder, string $nestingSeparator) + public function __construct(IriConverterInterface $iriConverter, ProviderInterface $provider, SerializerContextBuilderInterface $serializerContextBuilder, string $nestingSeparator) { - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->iriConverter = $iriConverter; $this->provider = $provider; $this->serializerContextBuilder = $serializerContextBuilder; @@ -56,21 +52,14 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource /** * {@inheritdoc} */ - public function __invoke(?string $resourceClass, ?string $rootClass, string $operationName, array $context) + public function __invoke(?string $resourceClass, ?string $rootClass, Operation $operation, array $context) { - $operation = null; - try { - $operation = $resourceClass ? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operationName) : null; - } catch (OperationNotFoundException $e) { - // ReadStage may be invoked without an existing operation - } - - if ($operation && !($operation->canRead() ?? true)) { + if (!($operation->canRead() ?? true)) { return $context['is_collection'] ? [] : null; } $args = $context['args']; - $normalizationContext = $this->serializerContextBuilder->create($resourceClass, $operationName, $context, true); + $normalizationContext = $this->serializerContextBuilder->create($resourceClass, $operation->getName(), $context, true); if (!$context['is_collection']) { $identifier = $this->getIdentifierFromContext($context); @@ -95,12 +84,7 @@ public function __invoke(?string $resourceClass, ?string $rootClass, string $ope $uriVariables = []; $normalizationContext['filters'] = $this->getNormalizedFilters($args); - - if (!$operation && $resourceClass) { - $operation = (new QueryCollection())->withOperation($this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation(null, true)); - } - - $normalizationContext['operation'] = $operation ?? new QueryCollection(); + $normalizationContext['operation'] = $operation; $source = $context['source']; /** @var ResolveInfo $info */ @@ -110,7 +94,7 @@ public function __invoke(?string $resourceClass, ?string $rootClass, string $ope $normalizationContext['linkClass'] = $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY]; } - return $this->provider->provide($resourceClass, $uriVariables, $operationName, $normalizationContext); + return $this->provider->provide($operation, $uriVariables, $normalizationContext); } /** diff --git a/src/GraphQl/Resolver/Stage/ReadStageInterface.php b/src/GraphQl/Resolver/Stage/ReadStageInterface.php index 3c3d90e2fcd..04afe523157 100644 --- a/src/GraphQl/Resolver/Stage/ReadStageInterface.php +++ b/src/GraphQl/Resolver/Stage/ReadStageInterface.php @@ -13,6 +13,8 @@ namespace ApiPlatform\GraphQl\Resolver\Stage; +use ApiPlatform\Metadata\GraphQl\Operation; + /** * Read stage of GraphQL resolvers. * @@ -23,5 +25,5 @@ interface ReadStageInterface /** * @return object|iterable|null */ - public function __invoke(?string $resourceClass, ?string $rootClass, string $operationName, array $context); + public function __invoke(?string $resourceClass, ?string $rootClass, Operation $operation, array $context); } diff --git a/src/GraphQl/Resolver/Stage/SecurityPostDenormalizeStage.php b/src/GraphQl/Resolver/Stage/SecurityPostDenormalizeStage.php index fed98ef6808..cc8864fbc2a 100644 --- a/src/GraphQl/Resolver/Stage/SecurityPostDenormalizeStage.php +++ b/src/GraphQl/Resolver/Stage/SecurityPostDenormalizeStage.php @@ -13,7 +13,7 @@ namespace ApiPlatform\GraphQl\Resolver\Stage; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\GraphQl\Operation; use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -24,22 +24,18 @@ */ final class SecurityPostDenormalizeStage implements SecurityPostDenormalizeStageInterface { - private $resourceMetadataCollectionFactory; private $resourceAccessChecker; - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ?ResourceAccessCheckerInterface $resourceAccessChecker) + public function __construct(?ResourceAccessCheckerInterface $resourceAccessChecker) { - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->resourceAccessChecker = $resourceAccessChecker; } /** * {@inheritdoc} */ - public function __invoke(string $resourceClass, string $operationName, array $context): void + public function __invoke(string $resourceClass, Operation $operation, array $context): void { - $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); - $operation = $resourceMetadataCollection->getOperation($operationName); $isGranted = $operation->getSecurityPostDenormalize(); if (null !== $isGranted && null === $this->resourceAccessChecker) { diff --git a/src/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageInterface.php b/src/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageInterface.php index 98658c906d8..a62ba52731d 100644 --- a/src/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageInterface.php +++ b/src/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageInterface.php @@ -13,6 +13,7 @@ namespace ApiPlatform\GraphQl\Resolver\Stage; +use ApiPlatform\Metadata\GraphQl\Operation; use GraphQL\Error\Error; /** @@ -25,5 +26,5 @@ interface SecurityPostDenormalizeStageInterface /** * @throws Error */ - public function __invoke(string $resourceClass, string $operationName, array $context): void; + public function __invoke(string $resourceClass, Operation $operation, array $context): void; } diff --git a/src/GraphQl/Resolver/Stage/SecurityPostValidationStage.php b/src/GraphQl/Resolver/Stage/SecurityPostValidationStage.php index e387c03a79c..e58d5a8562a 100644 --- a/src/GraphQl/Resolver/Stage/SecurityPostValidationStage.php +++ b/src/GraphQl/Resolver/Stage/SecurityPostValidationStage.php @@ -13,7 +13,7 @@ namespace ApiPlatform\GraphQl\Resolver\Stage; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\GraphQl\Operation; use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -25,22 +25,18 @@ */ final class SecurityPostValidationStage implements SecurityPostValidationStageInterface { - private $resourceMetadataCollectionFactory; private $resourceAccessChecker; - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ?ResourceAccessCheckerInterface $resourceAccessChecker) + public function __construct(?ResourceAccessCheckerInterface $resourceAccessChecker) { - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->resourceAccessChecker = $resourceAccessChecker; } /** * {@inheritdoc} */ - public function __invoke(string $resourceClass, string $operationName, array $context): void + public function __invoke(string $resourceClass, Operation $operation, array $context): void { - $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); - $operation = $resourceMetadataCollection->getOperation($operationName); $isGranted = $operation->getSecurityPostValidation(); if (null !== $isGranted && null === $this->resourceAccessChecker) { diff --git a/src/GraphQl/Resolver/Stage/SecurityPostValidationStageInterface.php b/src/GraphQl/Resolver/Stage/SecurityPostValidationStageInterface.php index 5aec0d3bfdb..bbbbf7d4d05 100644 --- a/src/GraphQl/Resolver/Stage/SecurityPostValidationStageInterface.php +++ b/src/GraphQl/Resolver/Stage/SecurityPostValidationStageInterface.php @@ -13,6 +13,7 @@ namespace ApiPlatform\GraphQl\Resolver\Stage; +use ApiPlatform\Metadata\GraphQl\Operation; use GraphQL\Error\Error; /** @@ -26,5 +27,5 @@ interface SecurityPostValidationStageInterface /** * @throws Error */ - public function __invoke(string $resourceClass, string $operationName, array $context): void; + public function __invoke(string $resourceClass, Operation $operation, array $context): void; } diff --git a/src/GraphQl/Resolver/Stage/SecurityStage.php b/src/GraphQl/Resolver/Stage/SecurityStage.php index 6c798fb3063..1a0a16d5a05 100644 --- a/src/GraphQl/Resolver/Stage/SecurityStage.php +++ b/src/GraphQl/Resolver/Stage/SecurityStage.php @@ -13,7 +13,7 @@ namespace ApiPlatform\GraphQl\Resolver\Stage; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\GraphQl\Operation; use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -24,22 +24,18 @@ */ final class SecurityStage implements SecurityStageInterface { - private $resourceMetadataCollectionFactory; private $resourceAccessChecker; - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ?ResourceAccessCheckerInterface $resourceAccessChecker) + public function __construct(?ResourceAccessCheckerInterface $resourceAccessChecker) { - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->resourceAccessChecker = $resourceAccessChecker; } /** * {@inheritdoc} */ - public function __invoke(string $resourceClass, string $operationName, array $context): void + public function __invoke(string $resourceClass, Operation $operation, array $context): void { - $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); - $operation = $resourceMetadataCollection->getOperation($operationName); $isGranted = $operation->getSecurity(); if (null !== $isGranted && null === $this->resourceAccessChecker) { diff --git a/src/GraphQl/Resolver/Stage/SecurityStageInterface.php b/src/GraphQl/Resolver/Stage/SecurityStageInterface.php index 2a24fc486d2..a1f47fad9bf 100644 --- a/src/GraphQl/Resolver/Stage/SecurityStageInterface.php +++ b/src/GraphQl/Resolver/Stage/SecurityStageInterface.php @@ -13,6 +13,7 @@ namespace ApiPlatform\GraphQl\Resolver\Stage; +use ApiPlatform\Metadata\GraphQl\Operation; use GraphQL\Error\Error; /** @@ -26,5 +27,5 @@ interface SecurityStageInterface /** * @throws Error */ - public function __invoke(string $resourceClass, string $operationName, array $context): void; + public function __invoke(string $resourceClass, Operation $operation, array $context): void; } diff --git a/src/GraphQl/Resolver/Stage/SerializeStage.php b/src/GraphQl/Resolver/Stage/SerializeStage.php index d95f2ca7992..7032950a1f2 100644 --- a/src/GraphQl/Resolver/Stage/SerializeStage.php +++ b/src/GraphQl/Resolver/Stage/SerializeStage.php @@ -13,11 +13,10 @@ namespace ApiPlatform\GraphQl\Resolver\Stage; -use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\GraphQl\Resolver\Util\IdentifierTrait; use ApiPlatform\GraphQl\Serializer\ItemNormalizer; use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\GraphQl\Operation; use ApiPlatform\State\Pagination\Pagination; use ApiPlatform\State\Pagination\PaginatorInterface; use ApiPlatform\State\Pagination\PartialPaginatorInterface; @@ -32,14 +31,12 @@ final class SerializeStage implements SerializeStageInterface { use IdentifierTrait; - private $resourceMetadataCollectionFactory; private $normalizer; private $serializerContextBuilder; private $pagination; - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, NormalizerInterface $normalizer, SerializerContextBuilderInterface $serializerContextBuilder, Pagination $pagination) + public function __construct(NormalizerInterface $normalizer, SerializerContextBuilderInterface $serializerContextBuilder, Pagination $pagination) { - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->normalizer = $normalizer; $this->serializerContextBuilder = $serializerContextBuilder; $this->pagination = $pagination; @@ -48,23 +45,16 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource /** * {@inheritdoc} */ - public function __invoke($itemOrCollection, string $resourceClass, string $operationName, array $context): ?array + public function __invoke($itemOrCollection, string $resourceClass, Operation $operation, array $context): ?array { - // TODO: replace by $operation->isCollection and $operation instanceof Mutation + // TODO: replace by $operation->isCollection and $operation instanceof CollectionOperationInterface $isCollection = $context['is_collection']; $isMutation = $context['is_mutation']; $isSubscription = $context['is_subscription']; + $shortName = $operation->getShortName(); + $operationName = $operation->getName(); - $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); - $operation = null; - try { - $operation = $resourceMetadataCollection->getOperation($operationName); - $shortName = $operation->getShortName(); - } catch (OperationNotFoundException $e) { - $shortName = $resourceMetadataCollection->getOperation()->getShortName(); - } - - if ($operation && !($operation->canSerialize() ?? true)) { + if (!($operation->canSerialize() ?? true)) { if ($isCollection) { if ($this->pagination->isGraphQlEnabled($resourceClass, $operationName, $context)) { return 'cursor' === $this->pagination->getGraphQlPaginationType($resourceClass, $operationName) ? diff --git a/src/GraphQl/Resolver/Stage/SerializeStageInterface.php b/src/GraphQl/Resolver/Stage/SerializeStageInterface.php index 810b637f7a3..5f6640eca2a 100644 --- a/src/GraphQl/Resolver/Stage/SerializeStageInterface.php +++ b/src/GraphQl/Resolver/Stage/SerializeStageInterface.php @@ -13,6 +13,8 @@ namespace ApiPlatform\GraphQl\Resolver\Stage; +use ApiPlatform\Metadata\GraphQl\Operation; + /** * Serialize stage of GraphQL resolvers. * @@ -23,5 +25,5 @@ interface SerializeStageInterface /** * @param object|iterable|null $itemOrCollection */ - public function __invoke($itemOrCollection, string $resourceClass, string $operationName, array $context): ?array; + public function __invoke($itemOrCollection, string $resourceClass, Operation $operation, array $context): ?array; } diff --git a/src/GraphQl/Resolver/Stage/ValidateStage.php b/src/GraphQl/Resolver/Stage/ValidateStage.php index 3cd27402284..8de5125d093 100644 --- a/src/GraphQl/Resolver/Stage/ValidateStage.php +++ b/src/GraphQl/Resolver/Stage/ValidateStage.php @@ -13,7 +13,7 @@ namespace ApiPlatform\GraphQl\Resolver\Stage; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\GraphQl\Operation; use ApiPlatform\Validator\ValidatorInterface; /** @@ -23,23 +23,18 @@ */ final class ValidateStage implements ValidateStageInterface { - private $resourceMetadataCollectionFactory; private $validator; - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ValidatorInterface $validator) + public function __construct(ValidatorInterface $validator) { - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->validator = $validator; } /** * {@inheritdoc} */ - public function __invoke($object, string $resourceClass, string $operationName, array $context): void + public function __invoke($object, string $resourceClass, Operation $operation, array $context): void { - $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); - $operation = $resourceMetadataCollection->getOperation($operationName); - if (!($operation->canValidate() ?? true)) { return; } diff --git a/src/GraphQl/Resolver/Stage/ValidateStageInterface.php b/src/GraphQl/Resolver/Stage/ValidateStageInterface.php index 9c832920127..4d2cf4b4c0e 100644 --- a/src/GraphQl/Resolver/Stage/ValidateStageInterface.php +++ b/src/GraphQl/Resolver/Stage/ValidateStageInterface.php @@ -13,6 +13,7 @@ namespace ApiPlatform\GraphQl\Resolver\Stage; +use ApiPlatform\Metadata\GraphQl\Operation; use GraphQL\Error\Error; /** @@ -27,5 +28,5 @@ interface ValidateStageInterface * * @throws Error */ - public function __invoke($object, string $resourceClass, string $operationName, array $context): void; + public function __invoke($object, string $resourceClass, Operation $operation, array $context): void; } diff --git a/src/GraphQl/Resolver/Stage/WriteStage.php b/src/GraphQl/Resolver/Stage/WriteStage.php index b583f1999d7..b0a466488bd 100644 --- a/src/GraphQl/Resolver/Stage/WriteStage.php +++ b/src/GraphQl/Resolver/Stage/WriteStage.php @@ -14,7 +14,7 @@ namespace ApiPlatform\GraphQl\Resolver\Stage; use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\GraphQl\Operation; use ApiPlatform\State\ProcessorInterface; /** @@ -24,13 +24,11 @@ */ final class WriteStage implements WriteStageInterface { - private $resourceMetadataCollectionFactory; private $processor; private $serializerContextBuilder; - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ProcessorInterface $processor, SerializerContextBuilderInterface $serializerContextBuilder) + public function __construct(ProcessorInterface $processor, SerializerContextBuilderInterface $serializerContextBuilder) { - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->processor = $processor; $this->serializerContextBuilder = $serializerContextBuilder; } @@ -38,16 +36,14 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource /** * {@inheritdoc} */ - public function __invoke($data, string $resourceClass, string $operationName, array $context) + public function __invoke($data, string $resourceClass, Operation $operation, array $context) { - $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); - $operation = $resourceMetadataCollection->getOperation($operationName); if (null === $data || !($operation->canWrite() ?? true)) { return $data; } - $denormalizationContext = $this->serializerContextBuilder->create($resourceClass, $operationName, $context, false); + $denormalizationContext = $this->serializerContextBuilder->create($resourceClass, $operation->getName(), $context, false); - return $this->processor->process($data, [], $operation->getName(), ['operation' => $operation] + $denormalizationContext); + return $this->processor->process($data, $operation, [], ['operation' => $operation] + $denormalizationContext); } } diff --git a/src/GraphQl/Resolver/Stage/WriteStageInterface.php b/src/GraphQl/Resolver/Stage/WriteStageInterface.php index f6240a14540..049c5cfb494 100644 --- a/src/GraphQl/Resolver/Stage/WriteStageInterface.php +++ b/src/GraphQl/Resolver/Stage/WriteStageInterface.php @@ -13,6 +13,8 @@ namespace ApiPlatform\GraphQl\Resolver\Stage; +use ApiPlatform\Metadata\GraphQl\Operation; + /** * Write stage of GraphQL resolvers. * @@ -25,5 +27,5 @@ interface WriteStageInterface * * @return object|null */ - public function __invoke($data, string $resourceClass, string $operationName, array $context); + public function __invoke($data, string $resourceClass, Operation $operation, array $context); } diff --git a/src/GraphQl/Subscription/SubscriptionManager.php b/src/GraphQl/Subscription/SubscriptionManager.php index c979d63a1fb..ce195c9c47a 100644 --- a/src/GraphQl/Subscription/SubscriptionManager.php +++ b/src/GraphQl/Subscription/SubscriptionManager.php @@ -16,6 +16,9 @@ use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\GraphQl\Resolver\Stage\SerializeStageInterface; use ApiPlatform\GraphQl\Resolver\Util\IdentifierTrait; +use ApiPlatform\Metadata\GraphQl\Operation; +use ApiPlatform\Metadata\GraphQl\Subscription; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Util\ResourceClassInfoTrait; use ApiPlatform\Util\SortTrait; use GraphQL\Type\Definition\ResolveInfo; @@ -37,13 +40,15 @@ final class SubscriptionManager implements SubscriptionManagerInterface private $subscriptionIdentifierGenerator; private $serializeStage; private $iriConverter; + private $resourceMetadataCollectionFactory; - public function __construct(CacheItemPoolInterface $subscriptionsCache, SubscriptionIdentifierGeneratorInterface $subscriptionIdentifierGenerator, SerializeStageInterface $serializeStage, IriConverterInterface $iriConverter) + public function __construct(CacheItemPoolInterface $subscriptionsCache, SubscriptionIdentifierGeneratorInterface $subscriptionIdentifierGenerator, SerializeStageInterface $serializeStage, IriConverterInterface $iriConverter, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory) { $this->subscriptionsCache = $subscriptionsCache; $this->subscriptionIdentifierGenerator = $subscriptionIdentifierGenerator; $this->serializeStage = $serializeStage; $this->iriConverter = $iriConverter; + $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; } public function retrieveSubscriptionId(array $context, ?array $result): ?string @@ -85,12 +90,15 @@ public function getPushPayloads($object): array $subscriptions = $this->getSubscriptionsFromIri($iri); $resourceClass = $this->getObjectClass($object); + $resourceMetadata = $this->resourceMetadataCollectionFactory->create($resourceClass); + $shortName = $resourceMetadata->getOperation()->getShortName(); $payloads = []; foreach ($subscriptions as [$subscriptionId, $subscriptionFields, $subscriptionResult]) { $resolverContext = ['fields' => $subscriptionFields, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => true]; - - $data = ($this->serializeStage)($object, $resourceClass, 'update_subscription', $resolverContext); + /** @var Operation */ + $operation = (new Subscription())->withName('update_subscription')->withShortName($shortName); + $data = ($this->serializeStage)($object, $resourceClass, $operation, $resolverContext); unset($data['clientSubscriptionId']); if ($data !== $subscriptionResult) { diff --git a/src/GraphQl/Type/FieldsBuilder.php b/src/GraphQl/Type/FieldsBuilder.php index 82d7331e021..ac3de4ecd55 100644 --- a/src/GraphQl/Type/FieldsBuilder.php +++ b/src/GraphQl/Type/FieldsBuilder.php @@ -13,13 +13,16 @@ namespace ApiPlatform\GraphQl\Type; +use ApiPlatform\Api\ResourceClassResolverInterface; use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\GraphQl\Resolver\Factory\ResolverFactoryInterface; use ApiPlatform\GraphQl\Type\Definition\TypeInterface; use ApiPlatform\Metadata\GraphQl\Mutation; use ApiPlatform\Metadata\GraphQl\Operation; +use ApiPlatform\Metadata\GraphQl\Query; +use ApiPlatform\Metadata\GraphQl\QueryCollection; use ApiPlatform\Metadata\GraphQl\Subscription; -use ApiPlatform\Metadata\Operation as ApiOperation; +use ApiPlatform\Metadata\Operation as AbstractOperation; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; @@ -45,6 +48,7 @@ final class FieldsBuilder implements FieldsBuilderInterface private $propertyNameCollectionFactory; private $propertyMetadataFactory; private $resourceMetadataCollectionFactory; + private $resourceClassResolver; private $typesContainer; private $typeBuilder; private $typeConverter; @@ -57,11 +61,12 @@ final class FieldsBuilder implements FieldsBuilderInterface private $nameConverter; private $nestingSeparator; - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, TypesContainerInterface $typesContainer, TypeBuilderInterface $typeBuilder, TypeConverterInterface $typeConverter, ResolverFactoryInterface $itemResolverFactory, ResolverFactoryInterface $collectionResolverFactory, ResolverFactoryInterface $itemMutationResolverFactory, ResolverFactoryInterface $itemSubscriptionResolverFactory, ContainerInterface $filterLocator, Pagination $pagination, ?NameConverterInterface $nameConverter, string $nestingSeparator) + public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ResourceClassResolverInterface $resourceClassResolver, TypesContainerInterface $typesContainer, TypeBuilderInterface $typeBuilder, TypeConverterInterface $typeConverter, ResolverFactoryInterface $itemResolverFactory, ResolverFactoryInterface $collectionResolverFactory, ResolverFactoryInterface $itemMutationResolverFactory, ResolverFactoryInterface $itemSubscriptionResolverFactory, ContainerInterface $filterLocator, Pagination $pagination, ?NameConverterInterface $nameConverter, string $nestingSeparator) { $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; $this->propertyMetadataFactory = $propertyMetadataFactory; $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; + $this->resourceClassResolver = $resourceClassResolver; $this->typesContainer = $typesContainer; $this->typeBuilder = $typeBuilder; $this->typeConverter = $typeConverter; @@ -269,8 +274,10 @@ public function resolveResourceArgs(array $args, Operation $operation): array private function getResourceFieldConfiguration(?string $property, ?string $fieldDescription, ?string $deprecationReason, Type $type, string $rootResource, bool $input, Operation $rootOperation, int $depth = 0, bool $forceNullable = false): ?array { try { + $isCollectionType = $this->typeBuilder->isCollection($type); + if ( - $this->typeBuilder->isCollection($type) && + $isCollectionType && $collectionValueType = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType() ) { $resourceClass = $collectionValueType->getClassName(); @@ -293,40 +300,45 @@ private function getResourceFieldConfiguration(?string $property, ?string $field return null; } - $resourceMetadataCollection = $operation = null; - if (!empty($resourceClass)) { + $args = []; + + $resolverOperation = $rootOperation; + + if ($resourceClass && $this->resourceClassResolver->isResourceClass($resourceClass) && $rootOperation->getClass() !== $resourceClass) { $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); - try { - $operation = $resourceMetadataCollection->getOperation($rootOperation->getName()); - } catch (OperationNotFoundException $e) { + $resolverOperation = $resourceMetadataCollection->getOperation(null, $isCollectionType); + + if (!$resolverOperation instanceof Operation) { + $resolverOperation = ($isCollectionType ? new QueryCollection() : new Query())->withOperation($resolverOperation); } } - $args = []; - if (!$input && !$rootOperation instanceof Mutation && !$rootOperation instanceof Subscription && !$isStandardGraphqlType && $this->typeBuilder->isCollection($type)) { + if (!$input && !$rootOperation instanceof Mutation && !$rootOperation instanceof Subscription && !$isStandardGraphqlType && $isCollectionType) { if ($this->pagination->isGraphQlEnabled($resourceClass, $rootOperation->getName())) { $args = $this->getGraphQlPaginationArgs($resourceClass, $rootOperation->getName()); } - // Look for the collection operation if it exists - if (!$operation && $resourceMetadataCollection) { + // Find the collection operation to get filters, there might be a smarter way to do this + $operation = null; + if (!empty($resourceClass)) { + $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); try { $operation = $resourceMetadataCollection->getOperation(null, true); } catch (OperationNotFoundException $e) { } } - $args = $this->getFilterArgs($args, $resourceClass, $operation, $rootResource, $rootOperation, $property, $depth); + $args = $this->getFilterArgs($args, $resourceClass, $rootResource, $rootOperation, $property, $depth, $operation); } if ($isStandardGraphqlType || $input) { $resolve = null; } elseif (($rootOperation instanceof Mutation || $rootOperation instanceof Subscription) && $depth <= 0) { - $resolve = $rootOperation instanceof Mutation ? ($this->itemMutationResolverFactory)($resourceClass, $rootResource, $rootOperation->getName()) : ($this->itemSubscriptionResolverFactory)($resourceClass, $rootResource, $rootOperation->getName()); + $resolve = $rootOperation instanceof Mutation ? ($this->itemMutationResolverFactory)($resourceClass, $rootResource, $resolverOperation) : ($this->itemSubscriptionResolverFactory)($resourceClass, $rootResource, $resolverOperation); } elseif ($this->typeBuilder->isCollection($type)) { - $resolve = ($this->collectionResolverFactory)($resourceClass, $rootResource, $rootOperation->getName()); + $resolve = ($this->collectionResolverFactory)($resourceClass, $rootResource, $resolverOperation); } else { - $resolve = ($this->itemResolverFactory)($resourceClass, $rootResource, $rootOperation->getName()); + $resolve = ($this->itemResolverFactory)($resourceClass, $rootResource, $resolverOperation); } return [ @@ -387,10 +399,7 @@ private function getGraphQlPaginationArgs(string $resourceClass, string $queryNa return $args; } - /** - * @param Operation|ApiOperation|null $operation - */ - private function getFilterArgs(array $args, ?string $resourceClass, $operation, string $rootResource, Operation $rootOperation, ?string $property, int $depth): array + private function getFilterArgs(array $args, ?string $resourceClass, string $rootResource, Operation $rootOperation, ?string $property, int $depth, ?AbstractOperation $operation = null): array { if (null === $operation || null === $resourceClass) { return $args; @@ -429,8 +438,8 @@ private function getFilterArgs(array $args, ?string $resourceClass, $operation, } /** - * @param Operation|ApiOperation|null $operation - * @param mixed $original + * @param AbstractOperation|null $operation + * @param mixed $original */ private function mergeFilterArgs(array $args, array $parsed, $operation = null, $original = ''): array { diff --git a/src/GraphQl/Type/SchemaBuilder.php b/src/GraphQl/Type/SchemaBuilder.php index 80376f73f1b..11041e9b241 100644 --- a/src/GraphQl/Type/SchemaBuilder.php +++ b/src/GraphQl/Type/SchemaBuilder.php @@ -13,6 +13,7 @@ namespace ApiPlatform\GraphQl\Type; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\GraphQl\Query; use ApiPlatform\Metadata\GraphQl\Subscription; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; @@ -74,14 +75,14 @@ public function getSchema(): Schema continue; } - if ($operation instanceof Query && !$operation->isCollection()) { - $queryFields += $this->fieldsBuilder->getItemQueryFields($resourceClass, $operation, $configuration); + if ($operation instanceof Query && $operation instanceof CollectionOperationInterface) { + $queryFields += $this->fieldsBuilder->getCollectionQueryFields($resourceClass, $operation, $configuration); continue; } - if ($operation instanceof Query && $operation->isCollection()) { - $queryFields += $this->fieldsBuilder->getCollectionQueryFields($resourceClass, $operation, $configuration); + if ($operation instanceof Query) { + $queryFields += $this->fieldsBuilder->getItemQueryFields($resourceClass, $operation, $configuration); continue; } diff --git a/src/GraphQl/Type/TypeBuilder.php b/src/GraphQl/Type/TypeBuilder.php index 53760e71bbf..3b64f786bda 100644 --- a/src/GraphQl/Type/TypeBuilder.php +++ b/src/GraphQl/Type/TypeBuilder.php @@ -15,6 +15,7 @@ use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\GraphQl\Serializer\ItemNormalizer; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\GraphQl\Mutation; use ApiPlatform\Metadata\GraphQl\Operation; use ApiPlatform\Metadata\GraphQl\Query; @@ -80,8 +81,8 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadataCo if ('item_query' === $operationName || 'collection_query' === $operationName) { // Test if the collection/item operation exists and it has different groups try { - if ($resourceMetadataCollection->getOperation($operation->isCollection() ? 'item_query' : 'collection_query')->getNormalizationContext() !== $operation->getNormalizationContext()) { - $shortName .= $operation->isCollection() ? 'Collection' : 'Item'; + if ($resourceMetadataCollection->getOperation($operation instanceof CollectionOperationInterface ? 'item_query' : 'collection_query')->getNormalizationContext() !== $operation->getNormalizationContext()) { + $shortName .= $operation instanceof CollectionOperationInterface ? 'Collection' : 'Item'; } } catch (OperationNotFoundException $e) { } @@ -137,10 +138,9 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadataCo try { $wrappedOperation = $resourceMetadataCollection->getOperation($wrappedOperationName); } catch (OperationNotFoundException $e) { - $wrappedOperation = (new Query()) + $wrappedOperation = ('collection_query' === $wrappedOperationName ? new QueryCollection() : new Query()) ->withResource($resourceMetadataCollection[0]) - ->withName($wrappedOperationName) - ->withCollection('collection_query' === $wrappedOperationName); + ->withName($wrappedOperationName); } $fields = [ diff --git a/src/GraphQl/Type/TypeConverter.php b/src/GraphQl/Type/TypeConverter.php index 55314697ea5..0b2e9afa6c3 100644 --- a/src/GraphQl/Type/TypeConverter.php +++ b/src/GraphQl/Type/TypeConverter.php @@ -16,8 +16,10 @@ use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\Exception\ResourceClassNotFoundException; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\GraphQl\Operation; use ApiPlatform\Metadata\GraphQl\Query; +use ApiPlatform\Metadata\GraphQl\QueryCollection; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use GraphQL\Error\SyntaxError; @@ -155,7 +157,7 @@ private function getResourceType(Type $type, bool $input, Operation $rootOperati } $operationName = $rootOperation->getName(); - $isCollection = 'collection_query' === $operationName; + $isCollection = $rootOperation instanceof CollectionOperationInterface || 'collection_query' === $operationName; // We're retrieving the type of a property which is a relation to the rootResource if ($resourceClass !== $rootResource && $property && $rootOperation instanceof Query) { @@ -165,11 +167,15 @@ private function getResourceType(Type $type, bool $input, Operation $rootOperati try { $operation = $resourceMetadataCollection->getOperation($operationName); + + if (!$operation instanceof Operation) { + throw new OperationNotFoundException(); + } } catch (OperationNotFoundException $e) { - $operation = (new Query()) + /** @var Operation */ + $operation = ($isCollection ? new QueryCollection() : new Query()) ->withResource($resourceMetadataCollection[0]) - ->withName($operationName) - ->withCollection($isCollection); + ->withName($operationName); } return $this->typeBuilder->getResourceObjectType($resourceClass, $resourceMetadataCollection, $operation, $input, false, $depth); diff --git a/src/Hal/Serializer/EntrypointNormalizer.php b/src/Hal/Serializer/EntrypointNormalizer.php index 04c8b3b9468..49d50a2c7ae 100644 --- a/src/Hal/Serializer/EntrypointNormalizer.php +++ b/src/Hal/Serializer/EntrypointNormalizer.php @@ -20,6 +20,7 @@ use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; @@ -82,7 +83,7 @@ public function normalize($object, $format = null, array $context = []): array foreach ($resourceMetadata as $resource) { foreach ($resource->getOperations() as $operationName => $operation) { - if (!$operation->isCollection()) { + if (!$operation instanceof CollectionOperationInterface) { continue; } diff --git a/src/HttpCache/EventListener/AddTagsListener.php b/src/HttpCache/EventListener/AddTagsListener.php index 065355a8434..4066e29ffe5 100644 --- a/src/HttpCache/EventListener/AddTagsListener.php +++ b/src/HttpCache/EventListener/AddTagsListener.php @@ -17,6 +17,7 @@ use ApiPlatform\Api\UrlGeneratorInterface; use ApiPlatform\Core\HttpCache\PurgerInterface as LegacyPurgerInterface; use ApiPlatform\HttpCache\PurgerInterface; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\State\UriVariablesResolverTrait; use ApiPlatform\Util\OperationRequestInitiatorTrait; @@ -76,7 +77,7 @@ public function onKernelResponse(ResponseEvent $event): void } $resources = $request->attributes->get('_resources'); - if (isset($attributes['collection_operation_name']) || ($attributes['subresource_context']['collection'] ?? false) || ($operation && $operation->isCollection())) { + if (isset($attributes['collection_operation_name']) || ($attributes['subresource_context']['collection'] ?? false) || ($operation && $operation instanceof CollectionOperationInterface)) { // Allows to purge collections $uriVariables = $this->getOperationUriVariables($operation, $request->attributes->all(), $attributes['resource_class']); $iri = $this->iriConverter instanceof IriConverterInterface ? $this->iriConverter->getIriFromResourceClass($attributes['resource_class'], $attributes['operation_name'] ?? null, UrlGeneratorInterface::ABS_PATH, ['identifiers_values' => $uriVariables]) : $this->iriConverter->getIriFromResourceClass($attributes['resource_class'], UrlGeneratorInterface::ABS_PATH); diff --git a/src/Hydra/Serializer/DocumentationNormalizer.php b/src/Hydra/Serializer/DocumentationNormalizer.php index 01a66028e22..87fd7c65ee3 100644 --- a/src/Hydra/Serializer/DocumentationNormalizer.php +++ b/src/Hydra/Serializer/DocumentationNormalizer.php @@ -28,7 +28,8 @@ use ApiPlatform\JsonLd\ContextBuilderInterface; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; @@ -285,7 +286,7 @@ private function getHydraProperties(string $resourceClass, $resourceMetadata, st } else { $classes[$resourceClass] = true; foreach ($resourceMetadata->getOperations() as $operation) { - if (!$operation->isCollection()) { + if (!$operation instanceof CollectionOperationInterface) { continue; } @@ -350,7 +351,7 @@ private function getHydraOperations(string $resourceClass, $resourceMetadata, st $hydraOperations = []; foreach ($resourceMetadataCollection as $resourceMetadata) { foreach ($resourceMetadata->getOperations() as $operationName => $operation) { - if (($operation instanceof Post || ($operation->isCollection() ?? false)) !== $collection) { + if (($operation instanceof Post || $operation instanceof CollectionOperationInterface) !== $collection) { continue; } @@ -375,12 +376,12 @@ private function getHydraOperations(string $resourceClass, $resourceMetadata, st * * @param ResourceMetadata|ApiResource $resourceMetadata * @param SubresourceMetadata $subresourceMetadata - * @param array|Operation $operation + * @param array|HttpOperation $operation */ private function getHydraOperation(string $resourceClass, $resourceMetadata, string $operationName, $operation, string $prefixedShortName, ?string $operationType = null, SubresourceMetadata $subresourceMetadata = null): array { - if ($operation instanceof Operation) { - $method = $operation->getMethod() ?: Operation::METHOD_GET; + if ($operation instanceof HttpOperation) { + $method = $operation->getMethod() ?: HttpOperation::METHOD_GET; } elseif ($this->operationMethodResolver) { if (OperationType::COLLECTION === $operationType) { $method = $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName); @@ -393,16 +394,16 @@ private function getHydraOperation(string $resourceClass, $resourceMetadata, str $method = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'method', 'GET'); } - $hydraOperation = $operation instanceof Operation ? ($operation->getHydraContext() ?? []) : ($operation['hydra_context'] ?? []); - if ($operation instanceof Operation ? $operation->getDeprecationReason() : $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'deprecation_reason', null, true)) { + $hydraOperation = $operation instanceof HttpOperation ? ($operation->getHydraContext() ?? []) : ($operation['hydra_context'] ?? []); + if ($operation instanceof HttpOperation ? $operation->getDeprecationReason() : $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'deprecation_reason', null, true)) { $hydraOperation['owl:deprecated'] = true; } - if ($operation instanceof Operation) { + if ($operation instanceof HttpOperation) { $shortName = $operation->getShortName(); $inputMetadata = $operation->getInput() ?? []; $outputMetadata = $operation->getOutput() ?? []; - $operationType = $operation->isCollection() ? OperationType::COLLECTION : OperationType::ITEM; + $operationType = $operation instanceof CollectionOperationInterface ? OperationType::COLLECTION : OperationType::ITEM; } else { $shortName = $resourceMetadata->getShortName(); $inputMetadata = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'input', ['class' => false]); @@ -516,6 +517,10 @@ private function getRange($propertyMetadata): ?string if ($resourceMetadata instanceof ResourceMetadataCollection) { $operation = $resourceMetadata->getOperation(); + if (!$operation instanceof HttpOperation) { + return "#{$operation->getShortName()}"; + } + return $operation->getTypes()[0] ?? "#{$operation->getShortName()}"; } diff --git a/src/Hydra/Serializer/EntrypointNormalizer.php b/src/Hydra/Serializer/EntrypointNormalizer.php index cc0245d841a..8bbb56b1de6 100644 --- a/src/Hydra/Serializer/EntrypointNormalizer.php +++ b/src/Hydra/Serializer/EntrypointNormalizer.php @@ -21,6 +21,7 @@ use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\Exception\OperationNotFoundException; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; @@ -88,7 +89,7 @@ public function normalize($object, $format = null, array $context = []): array foreach ($resource->getOperations() as $operationName => $operation) { $key = lcfirst($resource->getShortName()); - if (!$operation->isCollection() || isset($entrypoint[$key])) { + if (!$operation instanceof CollectionOperationInterface || isset($entrypoint[$key])) { continue; } diff --git a/src/JsonApi/Serializer/EntrypointNormalizer.php b/src/JsonApi/Serializer/EntrypointNormalizer.php index 72af487e557..e06c8d8998c 100644 --- a/src/JsonApi/Serializer/EntrypointNormalizer.php +++ b/src/JsonApi/Serializer/EntrypointNormalizer.php @@ -20,6 +20,7 @@ use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; @@ -81,7 +82,7 @@ public function normalize($object, $format = null, array $context = []) foreach ($resourceMetadata as $resource) { foreach ($resource->getOperations() as $operationName => $operation) { - if (!$operation->isCollection() || $operation->getUriVariables()) { + if (!$operation instanceof CollectionOperationInterface || $operation->getUriVariables()) { continue; } diff --git a/src/JsonLd/ContextBuilder.php b/src/JsonLd/ContextBuilder.php index 23f6ea187d1..812205a27b9 100644 --- a/src/JsonLd/ContextBuilder.php +++ b/src/JsonLd/ContextBuilder.php @@ -19,7 +19,7 @@ use ApiPlatform\Core\Metadata\Property\PropertyMetadata; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Metadata\ApiProperty; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; @@ -204,7 +204,7 @@ public function getAnonymousResourceContext($object, array $context = [], int $r return $jsonLdContext; } - private function getResourceContextWithShortname(string $resourceClass, int $referenceType, string $shortName, ?Operation $operation = null): array + private function getResourceContextWithShortname(string $resourceClass, int $referenceType, string $shortName, ?HttpOperation $operation = null): array { $context = $this->getBaseContext($referenceType); if ($this->propertyMetadataFactory instanceof LegacyPropertyMetadataFactoryInterface) { diff --git a/src/JsonLd/Serializer/ItemNormalizer.php b/src/JsonLd/Serializer/ItemNormalizer.php index 7e5f95696f3..49f049ca1bc 100644 --- a/src/JsonLd/Serializer/ItemNormalizer.php +++ b/src/JsonLd/Serializer/ItemNormalizer.php @@ -20,6 +20,7 @@ use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\JsonLd\ContextBuilderInterface; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Serializer\AbstractItemNormalizer; use ApiPlatform\Serializer\ContextTrait; use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface; @@ -110,7 +111,7 @@ public function normalize($object, $format = null, array $context = []) $metadata['@type'] = $resourceMetadata->getIri() ?: $resourceMetadata->getShortName(); } elseif ($this->resourceMetadataFactory) { $operation = $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation(); - $types = $operation->getTypes(); + $types = $operation instanceof HttpOperation ? $operation->getTypes() : null; if (null === $types) { $types = [$operation->getShortName()]; } diff --git a/src/JsonSchema/SchemaFactory.php b/src/JsonSchema/SchemaFactory.php index 3e2cc5bc457..dc98d883f49 100644 --- a/src/JsonSchema/SchemaFactory.php +++ b/src/JsonSchema/SchemaFactory.php @@ -22,7 +22,7 @@ use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer; use ApiPlatform\Metadata\ApiProperty; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; @@ -379,7 +379,7 @@ private function getSerializerContext($resourceMetadata, string $type = Schema:: } /** - * @param Operation|ResourceMetadata|null $resourceMetadata + * @param HttpOperation|ResourceMetadata|null $resourceMetadata */ private function getValidationGroups($resourceMetadata, ?string $operationType, ?string $operationName): array { @@ -401,7 +401,7 @@ private function getValidationGroups($resourceMetadata, ?string $operationType, /** * Gets the options for the property name collection / property metadata factories. */ - private function getFactoryOptions(array $serializerContext, array $validationGroups, ?string $operationType, ?string $operationName, ?Operation $operation = null): array + private function getFactoryOptions(array $serializerContext, array $validationGroups, ?string $operationType, ?string $operationName, ?HttpOperation $operation = null): array { $options = [ /* @see https://github.com/symfony/symfony/blob/v5.1.0/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php */ diff --git a/src/Metadata/ApiResource.php b/src/Metadata/ApiResource.php index 4d36a484d8f..9f76f0993de 100644 --- a/src/Metadata/ApiResource.php +++ b/src/Metadata/ApiResource.php @@ -13,6 +13,8 @@ namespace ApiPlatform\Metadata; +use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; + /** * Resource metadata attribute. * @@ -20,96 +22,104 @@ * @experimental */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] -final class ApiResource +class ApiResource { use WithResourceTrait; - private $operations; - private $uriTemplate; - private $shortName; - private $description; - private $types; + protected $operations; + protected $uriTemplate; + protected $shortName; + protected $description; + protected $types; /** * @var array|mixed|string|null */ - private $formats; + protected $formats; /** * @var array|mixed|string|null */ - private $inputFormats; + protected $inputFormats; /** * @var array|mixed|string|null */ - private $outputFormats; + protected $outputFormats; /** * @var array|array|string[]|string|null */ - private $uriVariables; - private $routePrefix; - private $defaults; - private $requirements; - private $options; - private $stateless; - private $sunset; - private $acceptPatch; - - private $status; - private $host; - private $schemes; - private $condition; - private $controller; - private $class; - private $urlGenerationStrategy; - private $deprecationReason; - private $cacheHeaders; - private $normalizationContext; - private $denormalizationContext; + protected $uriVariables; + protected $routePrefix; + protected $defaults; + protected $requirements; + protected $options; + protected $stateless; + protected $sunset; + protected $acceptPatch; + + protected $status; + protected $host; + protected $schemes; + protected $condition; + protected $controller; + protected $class; + protected $urlGenerationStrategy; + protected $deprecationReason; + protected $cacheHeaders; + protected $normalizationContext; + protected $denormalizationContext; /** * @var string[]|null */ - private $hydraContext; - private $openapiContext; - private $validationContext; + protected $hydraContext; + protected $openapiContext; + protected $validationContext; /** * @var string[] */ - private $filters; - private $elasticsearch; + protected $filters; + protected $elasticsearch; /** * @var array|bool|mixed|null */ - private $mercure; + protected $mercure; /** * @var bool|mixed|null */ - private $messenger; - private $input; - private $output; - private $order; - private $fetchPartial; - private $forceEager; - private $paginationClientEnabled; - private $paginationClientItemsPerPage; - private $paginationClientPartial; - private $paginationViaCursor; - private $paginationEnabled; - private $paginationFetchJoinCollection; - private $paginationUseOutputWalkers; - private $paginationItemsPerPage; - private $paginationMaximumItemsPerPage; - private $paginationPartial; - private $paginationType; - private $security; - private $securityMessage; - private $securityPostDenormalize; - private $securityPostDenormalizeMessage; - private $securityPostValidation; - private $securityPostValidationMessage; - private $compositeIdentifier; - private $exceptionToStatus; - private $queryParameterValidationEnabled; - private $graphQlOperations; - private $extraProperties; + protected $messenger; + protected $input; + protected $output; + protected $order; + protected $fetchPartial; + protected $forceEager; + protected $paginationClientEnabled; + protected $paginationClientItemsPerPage; + protected $paginationClientPartial; + protected $paginationViaCursor; + protected $paginationEnabled; + protected $paginationFetchJoinCollection; + protected $paginationUseOutputWalkers; + protected $paginationItemsPerPage; + protected $paginationMaximumItemsPerPage; + protected $paginationPartial; + protected $paginationType; + protected $security; + protected $securityMessage; + protected $securityPostDenormalize; + protected $securityPostDenormalizeMessage; + protected $securityPostValidation; + protected $securityPostValidationMessage; + protected $compositeIdentifier; + protected $exceptionToStatus; + protected $queryParameterValidationEnabled; + protected $graphQlOperations; + /** + * @var string|callable|null + */ + protected $provider; + /** + * @var string|callable|null + */ + protected $processor; + protected $extraProperties; /** * @param array|null $types The RDF types of this resource @@ -152,6 +162,8 @@ final class ApiResource * @param string|null $securityPostDenormalizeMessage https://api-platform.com/docs/core/security/#configuring-the-access-control-error-message * @param string $securityPostValidation https://api-platform.com/docs/core/security/#executing-access-control-rules-after-validtion * @param string $securityPostValidationMessage https://api-platform.com/docs/core/security/#configuring-the-access-control-error-message + * @param mixed|null $provider + * @param mixed|null $processor */ public function __construct( ?string $uriTemplate = null, @@ -214,6 +226,8 @@ public function __construct( ?array $exceptionToStatus = null, ?bool $queryParameterValidationEnabled = null, ?array $graphQlOperations = null, + $provider = null, + $processor = null, array $extraProperties = [] ) { $this->operations = null === $operations ? null : new Operations($operations); @@ -276,6 +290,8 @@ public function __construct( $this->exceptionToStatus = $exceptionToStatus; $this->queryParameterValidationEnabled = $queryParameterValidationEnabled; $this->graphQlOperations = $graphQlOperations; + $this->provider = $provider; + $this->processor = $processor; $this->extraProperties = $extraProperties; } @@ -1043,19 +1059,6 @@ public function withSecurityPostValidationMessage(?string $securityPostValidatio return $self; } - public function getCompositeIdentifier(): ?bool - { - return $this->compositeIdentifier; - } - - public function withCompositeIdentifier(bool $compositeIdentifier): self - { - $self = clone $this; - $self->compositeIdentifier = $compositeIdentifier; - - return $self; - } - public function getExceptionToStatus(): ?array { return $this->exceptionToStatus; @@ -1082,6 +1085,9 @@ public function withQueryParameterValidationEnabled(bool $queryParameterValidati return $self; } + /** + * @return GraphQlOperation + */ public function getGraphQlOperations(): ?array { return $this->graphQlOperations; @@ -1095,6 +1101,38 @@ public function withGraphQlOperations(array $graphQlOperations): self return $self; } + /** + * @return string|callable|null + */ + public function getProcessor() + { + return $this->processor; + } + + public function withProcessor($processor): self + { + $self = clone $this; + $self->processor = $processor; + + return $self; + } + + /** + * @return string|callable|null + */ + public function getProvider() + { + return $this->provider; + } + + public function withProvider($provider): self + { + $self = clone $this; + $self->provider = $provider; + + return $self; + } + public function getExtraProperties(): array { return $this->extraProperties; diff --git a/src/Metadata/CollectionOperationInterface.php b/src/Metadata/CollectionOperationInterface.php new file mode 100644 index 00000000000..8bc7c7a81c5 --- /dev/null +++ b/src/Metadata/CollectionOperationInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Metadata; + +/** + * The Operation returns a collection. + */ +interface CollectionOperationInterface +{ +} diff --git a/src/Metadata/Delete.php b/src/Metadata/Delete.php index 28eb27be255..e4adf300b95 100644 --- a/src/Metadata/Delete.php +++ b/src/Metadata/Delete.php @@ -14,7 +14,7 @@ namespace ApiPlatform\Metadata; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] -final class Delete extends Operation +final class Delete extends HttpOperation implements DeleteOperationInterface { /** * {@inheritdoc} @@ -90,6 +90,8 @@ public function __construct( ?bool $queryParameterValidate = null, ?int $priority = null, ?string $name = null, + ?string $provider = null, + ?string $processor = null, array $extraProperties = [] ) { parent::__construct(self::METHOD_DELETE, ...\func_get_args()); diff --git a/src/Metadata/DeleteOperationInterface.php b/src/Metadata/DeleteOperationInterface.php new file mode 100644 index 00000000000..16573aa60ab --- /dev/null +++ b/src/Metadata/DeleteOperationInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Metadata; + +/** + * The Operation deletes content. + */ +interface DeleteOperationInterface +{ +} diff --git a/src/Metadata/Extractor/XmlResourceExtractor.php b/src/Metadata/Extractor/XmlResourceExtractor.php index 3b32446c442..5b815bdfaf8 100644 --- a/src/Metadata/Extractor/XmlResourceExtractor.php +++ b/src/Metadata/Extractor/XmlResourceExtractor.php @@ -85,7 +85,6 @@ private function buildExtendedBase(\SimpleXMLElement $resource): array 'hydraContext' => isset($resource->hydraContext->values) ? $this->buildValues($resource->hydraContext->values) : null, 'openapiContext' => isset($resource->openapiContext->values) ? $this->buildValues($resource->openapiContext->values) : null, 'paginationViaCursor' => $this->buildPaginationViaCursor($resource), - 'compositeIdentifier' => $this->phpize($resource, 'compositeIdentifier', 'bool'), 'exceptionToStatus' => $this->buildExceptionToStatus($resource), 'queryParameterValidationEnabled' => $this->phpize($resource, 'queryParameterValidationEnabled', 'bool'), ]); diff --git a/src/Metadata/Extractor/YamlResourceExtractor.php b/src/Metadata/Extractor/YamlResourceExtractor.php index 8c4a7de4d0e..eae09e49ae1 100644 --- a/src/Metadata/Extractor/YamlResourceExtractor.php +++ b/src/Metadata/Extractor/YamlResourceExtractor.php @@ -14,8 +14,10 @@ namespace ApiPlatform\Metadata\Extractor; use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\GraphQl\DeleteMutation; use ApiPlatform\Metadata\GraphQl\Mutation; use ApiPlatform\Metadata\GraphQl\Query; +use ApiPlatform\Metadata\GraphQl\QueryCollection; use ApiPlatform\Metadata\GraphQl\Subscription; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Yaml; @@ -98,7 +100,6 @@ private function buildExtendedBase(array $resource): array 'host' => $this->phpize($resource, 'host', 'string'), 'condition' => $this->phpize($resource, 'condition', 'string'), 'controller' => $this->phpize($resource, 'controller', 'string'), - 'compositeIdentifier' => $this->phpize($resource, 'compositeIdentifier', 'bool'), 'queryParameterValidationEnabled' => $this->phpize($resource, 'queryParameterValidationEnabled', 'bool'), 'types' => $this->buildArrayValue($resource, 'types'), 'cacheHeaders' => $this->buildArrayValue($resource, 'cacheHeaders'), @@ -292,10 +293,19 @@ private function buildGraphQlOperations(array $resource, array $root): ?array } } + $collection = $this->phpize($operation, 'collection', 'bool', false); + if (Query::class === $class && $collection) { + $class = QueryCollection::class; + } + + $delete = $this->phpize($operation, 'delete', 'bool', false); + if (Mutation::class === $class && $delete) { + $class = DeleteMutation::class; + } + $data[] = array_merge($datum, [ 'graphql_operation_class' => $class, 'resolver' => $this->phpize($operation, 'resolver', 'string'), - 'collection' => $this->phpize($operation, 'collection', 'bool'), 'args' => $operation['args'] ?? null, 'class' => $this->phpize($operation, 'class', 'string'), 'read' => $this->phpize($operation, 'read', 'bool'), diff --git a/src/Metadata/Extractor/schema/resources.xsd b/src/Metadata/Extractor/schema/resources.xsd index 30ebd76eca3..7202feb23c7 100644 --- a/src/Metadata/Extractor/schema/resources.xsd +++ b/src/Metadata/Extractor/schema/resources.xsd @@ -44,11 +44,8 @@ - - - @@ -74,7 +71,6 @@ - @@ -346,6 +342,8 @@ + + @@ -365,6 +363,5 @@ - diff --git a/src/Metadata/Get.php b/src/Metadata/Get.php index 30f2853fe31..67736c646da 100644 --- a/src/Metadata/Get.php +++ b/src/Metadata/Get.php @@ -14,20 +14,18 @@ namespace ApiPlatform\Metadata; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] -final class Get extends Operation +final class Get extends HttpOperation { /** * {@inheritdoc} */ public function __construct( ?string $uriTemplate = null, - ?string $shortName = null, - ?string $description = null, ?array $types = null, - $formats = null, - $inputFormats = null, - $outputFormats = null, - $uriVariables = null, + $formats = null, + $inputFormats = null, + $outputFormats = null, + $uriVariables = null, ?string $routePrefix = null, ?string $routeName = null, ?array $defaults = null, @@ -36,60 +34,62 @@ public function __construct( ?bool $stateless = null, ?string $sunset = null, ?string $acceptPatch = null, - $status = null, + $status = null, ?string $host = null, ?array $schemes = null, ?string $condition = null, ?string $controller = null, - ?string $class = null, - ?int $urlGenerationStrategy = null, - ?bool $collection = null, - ?string $deprecationReason = null, ?array $cacheHeaders = null, - ?array $normalizationContext = null, - ?array $denormalizationContext = null, + ?array $hydraContext = null, ?array $openapiContext = null, - ?array $swaggerContext = null, - ?array $validationContext = null, - ?array $filters = null, - ?bool $elasticsearch = null, - $mercure = null, - $messenger = null, - $input = null, - $output = null, - ?array $order = null, - ?bool $fetchPartial = null, - ?bool $forceEager = null, + ?array $exceptionToStatus = null, + + ?bool $queryParameterValidationEnabled = null, + + ?string $shortName = null, + ?string $class = null, + ?bool $paginationEnabled = null, + ?string $paginationType = null, + ?int $paginationItemsPerPage = null, + ?int $paginationMaximumItemsPerPage = null, + ?bool $paginationPartial = null, ?bool $paginationClientEnabled = null, ?bool $paginationClientItemsPerPage = null, ?bool $paginationClientPartial = null, - ?array $paginationViaCursor = null, - ?bool $paginationEnabled = null, ?bool $paginationFetchJoinCollection = null, ?bool $paginationUseOutputWalkers = null, - ?int $paginationItemsPerPage = null, - ?int $paginationMaximumItemsPerPage = null, - ?bool $paginationPartial = null, - ?string $paginationType = null, + ?array $paginationViaCursor = null, + ?array $order = null, + ?string $description = null, + ?array $normalizationContext = null, + ?array $denormalizationContext = null, ?string $security = null, ?string $securityMessage = null, ?string $securityPostDenormalize = null, ?string $securityPostDenormalizeMessage = null, ?string $securityPostValidation = null, ?string $securityPostValidationMessage = null, - ?bool $compositeIdentifier = null, - ?array $exceptionToStatus = null, - ?bool $queryParameterValidationEnabled = null, + ?string $deprecationReason = null, + ?array $filters = null, + ?array $validationContext = null, + $input = null, + $output = null, + $mercure = null, + $messenger = null, + ?bool $elasticsearch = null, + ?int $urlGenerationStrategy = null, ?bool $read = null, ?bool $deserialize = null, ?bool $validate = null, ?bool $write = null, ?bool $serialize = null, - // TODO: replace by queryParameterValidationEnabled? - ?bool $queryParameterValidate = null, + ?bool $fetchPartial = null, + ?bool $forceEager = null, ?int $priority = null, ?string $name = null, + ?string $provider = null, + ?string $processor = null, array $extraProperties = [] ) { parent::__construct(self::METHOD_GET, ...\func_get_args()); diff --git a/src/Metadata/GetCollection.php b/src/Metadata/GetCollection.php index 3db8aa295f9..00bd77205f1 100644 --- a/src/Metadata/GetCollection.php +++ b/src/Metadata/GetCollection.php @@ -14,20 +14,18 @@ namespace ApiPlatform\Metadata; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] -final class GetCollection extends Operation +final class GetCollection extends HttpOperation implements CollectionOperationInterface { /** * {@inheritdoc} */ public function __construct( ?string $uriTemplate = null, - ?string $shortName = null, - ?string $description = null, ?array $types = null, - $formats = null, - $inputFormats = null, - $outputFormats = null, - $uriVariables = null, + $formats = null, + $inputFormats = null, + $outputFormats = null, + $uriVariables = null, ?string $routePrefix = null, ?string $routeName = null, ?array $defaults = null, @@ -36,63 +34,64 @@ public function __construct( ?bool $stateless = null, ?string $sunset = null, ?string $acceptPatch = null, - $status = null, + $status = null, ?string $host = null, ?array $schemes = null, ?string $condition = null, ?string $controller = null, - ?string $class = null, - ?int $urlGenerationStrategy = null, - ?bool $collection = null, - ?string $deprecationReason = null, ?array $cacheHeaders = null, - ?array $normalizationContext = null, - ?array $denormalizationContext = null, + ?array $hydraContext = null, ?array $openapiContext = null, - ?array $swaggerContext = null, - ?array $validationContext = null, - ?array $filters = null, - ?bool $elasticsearch = null, - $mercure = null, - $messenger = null, - $input = null, - $output = null, - ?array $order = null, - ?bool $fetchPartial = null, - ?bool $forceEager = null, + ?array $exceptionToStatus = null, + + ?bool $queryParameterValidationEnabled = null, + + ?string $shortName = null, + ?string $class = null, + ?bool $paginationEnabled = null, + ?string $paginationType = null, + ?int $paginationItemsPerPage = null, + ?int $paginationMaximumItemsPerPage = null, + ?bool $paginationPartial = null, ?bool $paginationClientEnabled = null, ?bool $paginationClientItemsPerPage = null, ?bool $paginationClientPartial = null, - ?array $paginationViaCursor = null, - ?bool $paginationEnabled = null, ?bool $paginationFetchJoinCollection = null, ?bool $paginationUseOutputWalkers = null, - ?int $paginationItemsPerPage = null, - ?int $paginationMaximumItemsPerPage = null, - ?bool $paginationPartial = null, - ?string $paginationType = null, + ?array $paginationViaCursor = null, + ?array $order = null, + ?string $description = null, + ?array $normalizationContext = null, + ?array $denormalizationContext = null, ?string $security = null, ?string $securityMessage = null, ?string $securityPostDenormalize = null, ?string $securityPostDenormalizeMessage = null, ?string $securityPostValidation = null, ?string $securityPostValidationMessage = null, - ?bool $compositeIdentifier = null, - ?array $exceptionToStatus = null, - ?bool $queryParameterValidationEnabled = null, + ?string $deprecationReason = null, + ?array $filters = null, + ?array $validationContext = null, + $input = null, + $output = null, + $mercure = null, + $messenger = null, + ?bool $elasticsearch = null, + ?int $urlGenerationStrategy = null, ?bool $read = null, ?bool $deserialize = null, ?bool $validate = null, ?bool $write = null, ?bool $serialize = null, - // TODO: replace by queryParameterValidationEnabled? - ?bool $queryParameterValidate = null, + ?bool $fetchPartial = null, + ?bool $forceEager = null, ?int $priority = null, ?string $name = null, + ?string $provider = null, + ?string $processor = null, array $extraProperties = [] ) { parent::__construct(self::METHOD_GET, ...\func_get_args()); - $this->collection = $collection ?? true; } } diff --git a/src/Metadata/GraphQl/DeleteMutation.php b/src/Metadata/GraphQl/DeleteMutation.php new file mode 100644 index 00000000000..31768c814a3 --- /dev/null +++ b/src/Metadata/GraphQl/DeleteMutation.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Metadata\GraphQl; + +use ApiPlatform\Metadata\DeleteOperationInterface; + +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +class DeleteMutation extends Operation implements DeleteOperationInterface +{ + /** + * {@inheritdoc} + */ + public function __construct( + ?string $resolver = null, + ?array $args = null, + ?array $links = null, + + // abstract operation arguments + ?string $shortName = null, + ?string $class = null, + ?bool $paginationEnabled = null, + ?string $paginationType = null, + ?int $paginationItemsPerPage = null, + ?int $paginationMaximumItemsPerPage = null, + ?bool $paginationPartial = null, + ?bool $paginationClientEnabled = null, + ?bool $paginationClientItemsPerPage = null, + ?bool $paginationClientPartial = null, + ?bool $paginationFetchJoinCollection = null, + ?bool $paginationUseOutputWalkers = null, + ?bool $paginationViaCursor = null, + ?array $order = null, + ?string $description = null, + ?array $normalizationContext = null, + ?array $denormalizationContext = null, + ?string $security = null, + ?string $securityMessage = null, + ?string $securityPostDenormalize = null, + ?string $securityPostDenormalizeMessage = null, + ?string $securityPostValidation = null, + ?string $securityPostValidationMessage = null, + ?string $deprecationReason = null, + ?array $filters = null, + ?array $validationContext = null, + $input = null, + $output = null, + $mercure = null, + $messenger = null, + ?bool $elasticsearch = null, + ?int $urlGenerationStrategy = null, + ?bool $read = null, + ?bool $deserialize = null, + ?bool $validate = null, + ?bool $write = null, + ?bool $serialize = null, + ?bool $fetchPartial = null, + ?bool $forceEager = null, + ?int $priority = null, + ?string $name = null, + ?string $provider = null, + ?string $processor = null, + array $extraProperties = [] +) { + parent::__construct(...\func_get_args()); + } +} diff --git a/src/Metadata/GraphQl/Mutation.php b/src/Metadata/GraphQl/Mutation.php index 75b9c40ad24..b83df385d3c 100644 --- a/src/Metadata/GraphQl/Mutation.php +++ b/src/Metadata/GraphQl/Mutation.php @@ -20,10 +20,11 @@ class Mutation extends Operation * {@inheritdoc} */ public function __construct( - ?bool $delete = null, ?string $resolver = null, - ?bool $collection = null, ?array $args = null, + ?array $links = null, + + // abstract operation arguments ?string $shortName = null, ?string $class = null, ?bool $paginationEnabled = null, @@ -36,6 +37,7 @@ public function __construct( ?bool $paginationClientPartial = null, ?bool $paginationFetchJoinCollection = null, ?bool $paginationUseOutputWalkers = null, + ?bool $paginationViaCursor = null, ?array $order = null, ?string $description = null, ?array $normalizationContext = null, @@ -64,6 +66,8 @@ public function __construct( ?bool $forceEager = null, ?int $priority = null, ?string $name = null, + ?string $provider = null, + ?string $processor = null, array $extraProperties = [] ) { parent::__construct(...\func_get_args()); diff --git a/src/Metadata/GraphQl/Operation.php b/src/Metadata/GraphQl/Operation.php index 2c5347f04a1..d2b3ae5a0b1 100644 --- a/src/Metadata/GraphQl/Operation.php +++ b/src/Metadata/GraphQl/Operation.php @@ -14,113 +14,28 @@ namespace ApiPlatform\Metadata\GraphQl; use ApiPlatform\Metadata\Link; -use ApiPlatform\Metadata\WithResourceTrait; +use ApiPlatform\Metadata\Operation as AbstractOperation; -class Operation +class Operation extends AbstractOperation { - use WithResourceTrait; - protected $resolver; - protected $collection; protected $args; - protected $shortName; - protected $class; /** @var Link[]|null */ protected $links; - protected $paginationEnabled; - protected $paginationType; - protected $paginationItemsPerPage; - protected $paginationMaximumItemsPerPage; - protected $paginationPartial; - protected $paginationClientEnabled; - protected $paginationClientItemsPerPage; - protected $paginationClientPartial; - protected $paginationFetchJoinCollection; - protected $paginationUseOutputWalkers; - protected $order; - protected $description; - protected $normalizationContext; - protected $denormalizationContext; - protected $security; - protected $securityMessage; - protected $securityPostDenormalize; - protected $securityPostDenormalizeMessage; - protected $securityPostValidation; - protected $securityPostValidationMessage; - protected $deprecationReason; - /** - * @var string[] - */ - protected $filters; - protected $validationContext; - /** - * @var null - */ - protected $input; - /** - * @var null - */ - protected $output; - /** - * @var string|array|bool|null - */ - protected $mercure; - /** - * @var string|bool|null - */ - protected $messenger; - protected $elasticsearch; - protected $urlGenerationStrategy; - protected $read; - protected $deserialize; - protected $validate; - protected $write; - protected $serialize; - protected $delete; - protected $fetchPartial; - protected $forceEager; - protected $priority; - protected $name; - protected $extraProperties; /** - * @param string $resolver - * @param string $shortName - * @param string $class - * @param bool $paginationEnabled - * @param string $paginationType - * @param int $paginationItemsPerPage - * @param int $paginationMaximumItemsPerPage - * @param bool $paginationPartial - * @param bool $paginationClientEnabled - * @param bool $paginationClientItemsPerPage - * @param bool $paginationClientPartial - * @param bool $paginationFetchJoinCollection - * @param bool $paginationUseOutputWalkers - * @param string $description - * @param string $security - * @param string $securityMessage - * @param string $securityPostDenormalize - * @param string $securityPostDenormalizeMessage - * @param string $securityPostValidation - * @param string $securityPostValidationMessage - * @param string $deprecationReason - * @param string[] $filters - * @param bool|string|array $mercure - * @param bool|string $messenger - * @param bool $elasticsearch - * @param int $urlGenerationStrategy - * @param bool $delete - * @param bool $fetchPartial - * @param bool $forceEager - * @param mixed|null $input - * @param mixed|null $output + * @param string $resolver + * @param mixed|null $input + * @param mixed|null $output + * @param mixed|null $mercure + * @param mixed|null $messenger */ public function __construct( - ?bool $delete = null, ?string $resolver = null, - ?bool $collection = null, ?array $args = null, + ?array $links = null, + + // abstract operation arguments ?string $shortName = null, ?string $class = null, ?bool $paginationEnabled = null, @@ -133,6 +48,7 @@ public function __construct( ?bool $paginationClientPartial = null, ?bool $paginationFetchJoinCollection = null, ?bool $paginationUseOutputWalkers = null, + ?bool $paginationViaCursor = null, ?array $order = null, ?string $description = null, ?array $normalizationContext = null, @@ -161,11 +77,15 @@ public function __construct( ?bool $forceEager = null, ?int $priority = null, ?string $name = null, + ?string $provider = null, + ?string $processor = null, array $extraProperties = [] ) { $this->resolver = $resolver; - $this->collection = $collection; $this->args = $args; + $this->links = $links; + + // Abstract operation properties $this->shortName = $shortName; $this->class = $class; $this->paginationEnabled = $paginationEnabled; @@ -178,6 +98,7 @@ public function __construct( $this->paginationClientPartial = $paginationClientPartial; $this->paginationFetchJoinCollection = $paginationFetchJoinCollection; $this->paginationUseOutputWalkers = $paginationUseOutputWalkers; + $this->paginationViaCursor = $paginationViaCursor; $this->order = $order; $this->description = $description; $this->normalizationContext = $normalizationContext; @@ -202,19 +123,15 @@ public function __construct( $this->validate = $validate; $this->write = $write; $this->serialize = $serialize; - $this->delete = $delete; $this->fetchPartial = $fetchPartial; $this->forceEager = $forceEager; $this->priority = $priority; $this->name = $name; + $this->provider = $provider; + $this->processor = $processor; $this->extraProperties = $extraProperties; } - public function withOperation($operation) - { - return $this->copyFrom($operation); - } - public function getResolver(): ?string { return $this->resolver; @@ -228,19 +145,6 @@ public function withResolver(?string $resolver = null): self return $self; } - public function isCollection(): ?bool - { - return $this->collection; - } - - public function withCollection(bool $collection = false): self - { - $self = clone $this; - $self->collection = $collection; - - return $self; - } - public function getArgs(): ?array { return $this->args; @@ -254,32 +158,6 @@ public function withArgs(?array $args = null): self return $self; } - public function getShortName(): ?string - { - return $this->shortName; - } - - public function withShortName(?string $shortName = null): self - { - $self = clone $this; - $self->shortName = $shortName; - - return $self; - } - - public function getClass(): ?string - { - return $this->class; - } - - public function withClass(?string $class = null): self - { - $self = clone $this; - $self->class = $class; - - return $self; - } - /** * @return Link[]|null */ @@ -298,540 +176,4 @@ public function withLinks(array $links): self return $self; } - - public function getPaginationEnabled(): ?bool - { - return $this->paginationEnabled; - } - - public function withPaginationEnabled(?bool $paginationEnabled = null): self - { - $self = clone $this; - $self->paginationEnabled = $paginationEnabled; - - return $self; - } - - public function getPaginationType(): ?string - { - return $this->paginationType; - } - - public function withPaginationType(?string $paginationType = null): self - { - $self = clone $this; - $self->paginationType = $paginationType; - - return $self; - } - - public function getPaginationItemsPerPage(): ?int - { - return $this->paginationItemsPerPage; - } - - public function withPaginationItemsPerPage(?int $paginationItemsPerPage = null): self - { - $self = clone $this; - $self->paginationItemsPerPage = $paginationItemsPerPage; - - return $self; - } - - public function getPaginationMaximumItemsPerPage(): ?int - { - return $this->paginationMaximumItemsPerPage; - } - - public function withPaginationMaximumItemsPerPage(?int $paginationMaximumItemsPerPage = null): self - { - $self = clone $this; - $self->paginationMaximumItemsPerPage = $paginationMaximumItemsPerPage; - - return $self; - } - - public function getPaginationPartial(): ?bool - { - return $this->paginationPartial; - } - - public function withPaginationPartial(?bool $paginationPartial = null): self - { - $self = clone $this; - $self->paginationPartial = $paginationPartial; - - return $self; - } - - public function getPaginationClientEnabled(): ?bool - { - return $this->paginationClientEnabled; - } - - public function withPaginationClientEnabled(?bool $paginationClientEnabled = null): self - { - $self = clone $this; - $self->paginationClientEnabled = $paginationClientEnabled; - - return $self; - } - - public function getPaginationClientItemsPerPage(): ?bool - { - return $this->paginationClientItemsPerPage; - } - - public function withPaginationClientItemsPerPage(?bool $paginationClientItemsPerPage = null): self - { - $self = clone $this; - $self->paginationClientItemsPerPage = $paginationClientItemsPerPage; - - return $self; - } - - public function getPaginationClientPartial(): ?bool - { - return $this->paginationClientPartial; - } - - public function withPaginationClientPartial(?bool $paginationClientPartial = null): self - { - $self = clone $this; - $self->paginationClientPartial = $paginationClientPartial; - - return $self; - } - - public function getPaginationFetchJoinCollection(): ?bool - { - return $this->paginationFetchJoinCollection; - } - - public function withPaginationFetchJoinCollection(?bool $paginationFetchJoinCollection = null): self - { - $self = clone $this; - $self->paginationFetchJoinCollection = $paginationFetchJoinCollection; - - return $self; - } - - public function getPaginationUseOutputWalkers(): ?bool - { - return $this->paginationUseOutputWalkers; - } - - public function withPaginationUseOutputWalkers(?bool $paginationUseOutputWalkers = null): self - { - $self = clone $this; - $self->paginationUseOutputWalkers = $paginationUseOutputWalkers; - - return $self; - } - - public function getOrder(): ?array - { - return $this->order; - } - - public function withOrder(array $order = []): self - { - $self = clone $this; - $self->order = $order; - - return $self; - } - - public function getDescription(): ?string - { - return $this->description; - } - - public function withDescription(?string $description = null): self - { - $self = clone $this; - $self->description = $description; - - return $self; - } - - public function getNormalizationContext(): ?array - { - return $this->normalizationContext; - } - - public function withNormalizationContext(array $normalizationContext = []): self - { - $self = clone $this; - $self->normalizationContext = $normalizationContext; - - return $self; - } - - public function getDenormalizationContext(): ?array - { - return $this->denormalizationContext; - } - - public function withDenormalizationContext(array $denormalizationContext = []): self - { - $self = clone $this; - $self->denormalizationContext = $denormalizationContext; - - return $self; - } - - public function getSecurity(): ?string - { - return $this->security; - } - - public function withSecurity(?string $security = null): self - { - $self = clone $this; - $self->security = $security; - - return $self; - } - - public function getSecurityMessage(): ?string - { - return $this->securityMessage; - } - - public function withSecurityMessage(?string $securityMessage = null): self - { - $self = clone $this; - $self->securityMessage = $securityMessage; - - return $self; - } - - public function getSecurityPostDenormalize(): ?string - { - return $this->securityPostDenormalize; - } - - public function withSecurityPostDenormalize(?string $securityPostDenormalize = null): self - { - $self = clone $this; - $self->securityPostDenormalize = $securityPostDenormalize; - - return $self; - } - - public function getSecurityPostDenormalizeMessage(): ?string - { - return $this->securityPostDenormalizeMessage; - } - - public function withSecurityPostDenormalizeMessage(?string $securityPostDenormalizeMessage = null): self - { - $self = clone $this; - $self->securityPostDenormalizeMessage = $securityPostDenormalizeMessage; - - return $self; - } - - public function getSecurityPostValidation(): ?string - { - return $this->securityPostValidation; - } - - public function withSecurityPostValidation(?string $securityPostValidation = null): self - { - $self = clone $this; - $self->securityPostValidation = $securityPostValidation; - - return $self; - } - - public function getSecurityPostValidationMessage(): ?string - { - return $this->securityPostValidationMessage; - } - - public function withSecurityPostValidationMessage(?string $securityPostValidationMessage = null): self - { - $self = clone $this; - $self->securityPostValidationMessage = $securityPostValidationMessage; - - return $self; - } - - public function getDeprecationReason(): ?string - { - return $this->deprecationReason; - } - - public function withDeprecationReason(?string $deprecationReason = null): self - { - $self = clone $this; - $self->deprecationReason = $deprecationReason; - - return $self; - } - - public function getFilters(): ?array - { - return $this->filters; - } - - public function withFilters(array $filters = []): self - { - $self = clone $this; - $self->filters = $filters; - - return $self; - } - - public function getValidationContext(): ?array - { - return $this->validationContext; - } - - public function withValidationContext(array $validationContext = []): self - { - $self = clone $this; - $self->validationContext = $validationContext; - - return $self; - } - - public function getInput() - { - return $this->input; - } - - public function withInput($input = null): self - { - $self = clone $this; - $self->input = $input; - - return $self; - } - - public function getOutput() - { - return $this->output; - } - - public function withOutput($output = null): self - { - $self = clone $this; - $self->output = $output; - - return $self; - } - - /** - * @return bool|string|array|null - */ - public function getMercure() - { - return $this->mercure; - } - - /** - * @param bool|string|array|null $mercure - * - * @return $this - */ - public function withMercure($mercure = null): self - { - $self = clone $this; - $self->mercure = $mercure; - - return $self; - } - - /** - * @return bool|string|null - */ - public function getMessenger() - { - return $this->messenger; - } - - /** - * @param bool|string|null $messenger - * - * @return $this - */ - public function withMessenger($messenger = null): self - { - $self = clone $this; - $self->messenger = $messenger; - - return $self; - } - - public function getElasticsearch(): ?bool - { - return $this->elasticsearch; - } - - public function withElasticsearch(?bool $elasticsearch = null): self - { - $self = clone $this; - $self->elasticsearch = $elasticsearch; - - return $self; - } - - public function getUrlGenerationStrategy(): ?int - { - return $this->urlGenerationStrategy; - } - - public function withUrlGenerationStrategy(?int $urlGenerationStrategy = null): self - { - $self = clone $this; - $self->urlGenerationStrategy = $urlGenerationStrategy; - - return $self; - } - - public function canRead(): ?bool - { - return $this->read; - } - - public function withRead(bool $read = true): self - { - $self = clone $this; - $self->read = $read; - - return $self; - } - - public function canDeserialize(): ?bool - { - return $this->deserialize; - } - - public function withDeserialize(bool $deserialize = true): self - { - $self = clone $this; - $self->deserialize = $deserialize; - - return $self; - } - - public function canValidate(): ?bool - { - return $this->validate; - } - - public function withValidate(bool $validate = true): self - { - $self = clone $this; - $self->validate = $validate; - - return $self; - } - - public function canWrite(): ?bool - { - return $this->write; - } - - public function withWrite(bool $write = true): self - { - $self = clone $this; - $self->write = $write; - - return $self; - } - - public function canSerialize(): ?bool - { - return $this->serialize; - } - - public function withSerialize(bool $serialize = true): self - { - $self = clone $this; - $self->serialize = $serialize; - - return $self; - } - - public function isDelete(): ?bool - { - return $this->delete; - } - - public function withDelete(?bool $delete = null): self - { - $self = clone $this; - $self->delete = $delete; - - return $self; - } - - public function getFetchPartial(): ?bool - { - return $this->fetchPartial; - } - - public function withFetchPartial(?bool $fetchPartial = null): self - { - $self = clone $this; - $self->fetchPartial = $fetchPartial; - - return $self; - } - - public function getForceEager(): ?bool - { - return $this->forceEager; - } - - public function withForceEager(?bool $forceEager = null): self - { - $self = clone $this; - $self->forceEager = $forceEager; - - return $self; - } - - public function getPriority(): ?int - { - return $this->priority; - } - - public function withPriority(int $priority = 0): self - { - $self = clone $this; - $self->priority = $priority; - - return $self; - } - - public function getName(): ?string - { - return $this->name; - } - - public function withName(string $name = ''): self - { - $self = clone $this; - $self->name = $name; - - return $self; - } - - public function getExtraProperties(): array - { - return $this->extraProperties; - } - - public function withExtraProperties(array $extraProperties = []): self - { - $self = clone $this; - $self->extraProperties = $extraProperties; - - return $self; - } } diff --git a/src/Metadata/GraphQl/Query.php b/src/Metadata/GraphQl/Query.php index 5aa2b0a7b49..b58ed1842f3 100644 --- a/src/Metadata/GraphQl/Query.php +++ b/src/Metadata/GraphQl/Query.php @@ -21,8 +21,10 @@ class Query extends Operation */ public function __construct( ?string $resolver = null, - ?bool $collection = null, ?array $args = null, + ?array $links = null, + + // abstract operation arguments ?string $shortName = null, ?string $class = null, ?bool $paginationEnabled = null, @@ -35,6 +37,7 @@ public function __construct( ?bool $paginationClientPartial = null, ?bool $paginationFetchJoinCollection = null, ?bool $paginationUseOutputWalkers = null, + ?bool $paginationViaCursor = null, ?array $order = null, ?string $description = null, ?array $normalizationContext = null, @@ -63,9 +66,11 @@ public function __construct( ?bool $forceEager = null, ?int $priority = null, ?string $name = null, + ?string $provider = null, + ?string $processor = null, array $extraProperties = [] ) { - parent::__construct(false, ...\func_get_args()); + parent::__construct(...\func_get_args()); $this->name = $name ?: 'item_query'; } } diff --git a/src/Metadata/GraphQl/QueryCollection.php b/src/Metadata/GraphQl/QueryCollection.php index 4a84d92f95a..08fa564e4f8 100644 --- a/src/Metadata/GraphQl/QueryCollection.php +++ b/src/Metadata/GraphQl/QueryCollection.php @@ -13,16 +13,20 @@ namespace ApiPlatform\Metadata\GraphQl; +use ApiPlatform\Metadata\CollectionOperationInterface; + #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] -final class QueryCollection extends Query +final class QueryCollection extends Query implements CollectionOperationInterface { /** * {@inheritdoc} */ public function __construct( ?string $resolver = null, - ?bool $collection = null, ?array $args = null, + ?array $links = null, + + // abstract operation arguments ?string $shortName = null, ?string $class = null, ?bool $paginationEnabled = null, @@ -35,6 +39,7 @@ public function __construct( ?bool $paginationClientPartial = null, ?bool $paginationFetchJoinCollection = null, ?bool $paginationUseOutputWalkers = null, + ?bool $paginationViaCursor = null, ?array $order = null, ?string $description = null, ?array $normalizationContext = null, @@ -63,10 +68,11 @@ public function __construct( ?bool $forceEager = null, ?int $priority = null, ?string $name = null, + ?string $provider = null, + ?string $processor = null, array $extraProperties = [] ) { parent::__construct(...\func_get_args()); - $this->collection = true; $this->name = $name ?: 'collection_query'; } } diff --git a/src/Metadata/GraphQl/Subscription.php b/src/Metadata/GraphQl/Subscription.php index 55289ee5349..7ab1088cb9a 100644 --- a/src/Metadata/GraphQl/Subscription.php +++ b/src/Metadata/GraphQl/Subscription.php @@ -21,8 +21,10 @@ final class Subscription extends Operation */ public function __construct( ?string $resolver = null, - ?bool $collection = null, ?array $args = null, + ?array $links = null, + + // abstract operation arguments ?string $shortName = null, ?string $class = null, ?bool $paginationEnabled = null, @@ -35,10 +37,11 @@ public function __construct( ?bool $paginationClientPartial = null, ?bool $paginationFetchJoinCollection = null, ?bool $paginationUseOutputWalkers = null, + ?bool $paginationViaCursor = null, ?array $order = null, ?string $description = null, - array $normalizationContext = [], - array $denormalizationContext = [], + ?array $normalizationContext = null, + ?array $denormalizationContext = null, ?string $security = null, ?string $securityMessage = null, ?string $securityPostDenormalize = null, @@ -63,9 +66,11 @@ public function __construct( ?bool $forceEager = null, ?int $priority = null, ?string $name = null, + ?string $provider = null, + ?string $processor = null, array $extraProperties = [] ) { - parent::__construct(false, ...\func_get_args()); + parent::__construct(...\func_get_args()); $this->name = $name ?: 'update_subscription'; } } diff --git a/src/Metadata/HttpOperation.php b/src/Metadata/HttpOperation.php new file mode 100644 index 00000000000..9dc314cfa73 --- /dev/null +++ b/src/Metadata/HttpOperation.php @@ -0,0 +1,612 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Metadata; + +class HttpOperation extends Operation +{ + public const METHOD_GET = 'GET'; + public const METHOD_POST = 'POST'; + public const METHOD_PUT = 'PUT'; + public const METHOD_PATCH = 'PATCH'; + public const METHOD_DELETE = 'DELETE'; + public const METHOD_HEAD = 'HEAD'; + public const METHOD_OPTIONS = 'OPTIONS'; + + protected $method; + protected $uriTemplate; + protected $types; + + /** + * @var array|mixed|string|null + */ + protected $formats; + + /** + * @var array|mixed|string|null + */ + protected $inputFormats; + + /** + * @var array|mixed|string|null + */ + protected $outputFormats; + + /** + * @var array|array|string[]|string|null + */ + protected $uriVariables; + + protected $routePrefix; + protected $routeName; + protected $defaults; + protected $requirements; + protected $options; + protected $stateless; + protected $sunset; + protected $acceptPatch; + protected $cacheHeaders; + /** + * @var string|int|null + */ + protected $status; + protected $host; + protected $schemes; + protected $condition; + protected $controller; + /** + * @var string[] + */ + protected $hydraContext; + protected $openapiContext; + + protected $exceptionToStatus; + + protected $queryParameterValidationEnabled; + + /** + * @param array|null $types the RDF types of this property + * @param array|string|null $formats https://api-platform.com/docs/core/content-negotiation/#configuring-formats-for-a-specific-resource-or-operation + * @param array|string|null $inputFormats https://api-platform.com/docs/core/content-negotiation/#configuring-formats-for-a-specific-resource-or-operation + * @param array|string|null $outputFormats https://api-platform.com/docs/core/content-negotiation/#configuring-formats-for-a-specific-resource-or-operation + * @param mixed|null $uriVariables + * @param string|null $routePrefix https://api-platform.com/docs/core/operations/#prefixing-all-routes-of-all-operations + * @param string|null $sunset https://api-platform.com/docs/core/deprecations/#setting-the-sunset-http-header-to-indicate-when-a-resource-or-an-operation-will-be-removed + * @param string|int|null $status + * @param string|null $deprecationReason https://api-platform.com/docs/core/deprecations/#deprecating-resource-classes-operations-and-properties + * @param array|null $cacheHeaders https://api-platform.com/docs/core/performance/#setting-custom-http-cache-headers + * @param array|null $normalizationContext https://api-platform.com/docs/core/serialization/#using-serialization-groups + * @param array|null $denormalizationContext https://api-platform.com/docs/core/serialization/#using-serialization-groups + * @param string[]|null $hydraContext https://api-platform.com/docs/core/extending-jsonld-context/#hydra + * @param array|null $openapiContext https://api-platform.com/docs/core/openapi/#using-the-openapi-and-swagger-contexts + * @param string[]|null $filters https://api-platform.com/docs/core/filters/#doctrine-orm-and-mongodb-odm-filters + * @param bool|null $elasticsearch https://api-platform.com/docs/core/elasticsearch/ + * @param mixed|null $mercure https://api-platform.com/docs/core/mercure + * @param mixed|null $messenger https://api-platform.com/docs/core/messenger/#dispatching-a-resource-through-the-message-bus + * @param mixed|null $input https://api-platform.com/docs/core/dto/#specifying-an-input-or-an-output-data-representation + * @param mixed|null $output https://api-platform.com/docs/core/dto/#specifying-an-input-or-an-output-data-representation + * @param array|null $order https://api-platform.com/docs/core/default-order/#overriding-default-order + * @param bool|null $fetchPartial https://api-platform.com/docs/core/performance/#fetch-partial + * @param bool|null $forceEager https://api-platform.com/docs/core/performance/#force-eager + * @param bool|null $paginationClientEnabled https://api-platform.com/docs/core/pagination/#for-a-specific-resource-1 + * @param bool|null $paginationClientItemsPerPage https://api-platform.com/docs/core/pagination/#for-a-specific-resource-3 + * @param bool|null $paginationClientPartial https://api-platform.com/docs/core/pagination/#for-a-specific-resource-6 + * @param array|null $paginationViaCursor https://api-platform.com/docs/core/pagination/#cursor-based-pagination + * @param bool|null $paginationEnabled https://api-platform.com/docs/core/pagination/#for-a-specific-resource + * @param bool|null $paginationFetchJoinCollection https://api-platform.com/docs/core/pagination/#controlling-the-behavior-of-the-doctrine-orm-paginator + * @param int|null $paginationItemsPerPage https://api-platform.com/docs/core/pagination/#changing-the-number-of-items-per-page + * @param int|null $paginationMaximumItemsPerPage https://api-platform.com/docs/core/pagination/#changing-maximum-items-per-page + * @param bool|null $paginationPartial https://api-platform.com/docs/core/performance/#partial-pagination + * @param string|null $paginationType https://api-platform.com/docs/core/graphql/#using-the-page-based-pagination + * @param string|null $security https://api-platform.com/docs/core/security + * @param string|null $securityMessage https://api-platform.com/docs/core/security/#configuring-the-access-control-error-message + * @param string|null $securityPostDenormalize https://api-platform.com/docs/core/security/#executing-access-control-rules-after-denormalization + * @param string|null $securityPostDenormalizeMessage https://api-platform.com/docs/core/security/#configuring-the-access-control-error-message + * @param bool|null $read https://api-platform.com/docs/core/events/#the-event-system + * @param bool|null $deserialize https://api-platform.com/docs/core/events/#the-event-system + * @param bool|null $validate https://api-platform.com/docs/core/events/#the-event-system + * @param bool|null $write https://api-platform.com/docs/core/events/#the-event-system + * @param bool|null $serialize https://api-platform.com/docs/core/events/#the-event-system + */ + public function __construct( + string $method = self::METHOD_GET, + ?string $uriTemplate = null, + ?array $types = null, + $formats = null, + $inputFormats = null, + $outputFormats = null, + $uriVariables = null, + ?string $routePrefix = null, + ?string $routeName = null, + ?array $defaults = null, + ?array $requirements = null, + ?array $options = null, + ?bool $stateless = null, + ?string $sunset = null, + ?string $acceptPatch = null, + $status = null, + ?string $host = null, + ?array $schemes = null, + ?string $condition = null, + ?string $controller = null, + ?array $cacheHeaders = null, + + ?array $hydraContext = null, + ?array $openapiContext = null, + ?array $exceptionToStatus = null, + + ?bool $queryParameterValidationEnabled = null, + + // abstract operation arguments + ?string $shortName = null, + ?string $class = null, + ?bool $paginationEnabled = null, + ?string $paginationType = null, + ?int $paginationItemsPerPage = null, + ?int $paginationMaximumItemsPerPage = null, + ?bool $paginationPartial = null, + ?bool $paginationClientEnabled = null, + ?bool $paginationClientItemsPerPage = null, + ?bool $paginationClientPartial = null, + ?bool $paginationFetchJoinCollection = null, + ?bool $paginationUseOutputWalkers = null, + ?array $paginationViaCursor = null, + ?array $order = null, + ?string $description = null, + ?array $normalizationContext = null, + ?array $denormalizationContext = null, + ?string $security = null, + ?string $securityMessage = null, + ?string $securityPostDenormalize = null, + ?string $securityPostDenormalizeMessage = null, + ?string $securityPostValidation = null, + ?string $securityPostValidationMessage = null, + ?string $deprecationReason = null, + ?array $filters = null, + ?array $validationContext = null, + $input = null, + $output = null, + $mercure = null, + $messenger = null, + ?bool $elasticsearch = null, + ?int $urlGenerationStrategy = null, + ?bool $read = null, + ?bool $deserialize = null, + ?bool $validate = null, + ?bool $write = null, + ?bool $serialize = null, + ?bool $fetchPartial = null, + ?bool $forceEager = null, + ?int $priority = null, + ?string $name = null, + ?string $provider = null, + ?string $processor = null, + array $extraProperties = [] + ) { + $this->method = $method; + $this->uriTemplate = $uriTemplate; + $this->shortName = $shortName; + $this->description = $description; + $this->types = $types; + $this->formats = $formats; + $this->inputFormats = $inputFormats; + $this->outputFormats = $outputFormats; + $this->uriVariables = $uriVariables; + $this->routePrefix = $routePrefix; + $this->routeName = $routeName; + $this->defaults = $defaults; + $this->requirements = $requirements; + $this->options = $options; + $this->stateless = $stateless; + $this->sunset = $sunset; + $this->acceptPatch = $acceptPatch; + $this->status = $status; + $this->host = $host; + $this->schemes = $schemes; + $this->condition = $condition; + $this->controller = $controller; + $this->class = $class; + $this->urlGenerationStrategy = $urlGenerationStrategy; + $this->deprecationReason = $deprecationReason; + $this->cacheHeaders = $cacheHeaders; + $this->normalizationContext = $normalizationContext; + $this->denormalizationContext = $denormalizationContext; + $this->hydraContext = $hydraContext; + $this->openapiContext = $openapiContext; + $this->validationContext = $validationContext; + $this->filters = $filters; + $this->elasticsearch = $elasticsearch; + $this->mercure = $mercure; + $this->messenger = $messenger; + $this->input = $input; + $this->output = $output; + $this->order = $order; + $this->fetchPartial = $fetchPartial; + $this->forceEager = $forceEager; + $this->paginationClientEnabled = $paginationClientEnabled; + $this->paginationClientItemsPerPage = $paginationClientItemsPerPage; + $this->paginationClientPartial = $paginationClientPartial; + $this->paginationViaCursor = $paginationViaCursor; + $this->paginationEnabled = $paginationEnabled; + $this->paginationFetchJoinCollection = $paginationFetchJoinCollection; + $this->paginationUseOutputWalkers = $paginationUseOutputWalkers; + $this->paginationItemsPerPage = $paginationItemsPerPage; + $this->paginationMaximumItemsPerPage = $paginationMaximumItemsPerPage; + $this->paginationPartial = $paginationPartial; + $this->paginationType = $paginationType; + $this->security = $security; + $this->securityMessage = $securityMessage; + $this->securityPostDenormalize = $securityPostDenormalize; + $this->securityPostDenormalizeMessage = $securityPostDenormalizeMessage; + $this->securityPostValidation = $securityPostValidation; + $this->securityPostValidationMessage = $securityPostValidationMessage; + $this->exceptionToStatus = $exceptionToStatus; + $this->queryParameterValidationEnabled = $queryParameterValidationEnabled; + $this->read = $read; + $this->deserialize = $deserialize; + $this->validate = $validate; + $this->write = $write; + $this->serialize = $serialize; + $this->priority = $priority; + $this->name = $name; + $this->provider = $provider; + $this->processor = $processor; + $this->extraProperties = $extraProperties; + } + + public function getMethod(): string + { + return $this->method; + } + + public function withMethod(string $method): self + { + $self = clone $this; + $self->method = $method; + + return $self; + } + + public function getUriTemplate(): ?string + { + return $this->uriTemplate; + } + + public function withUriTemplate(?string $uriTemplate = null) + { + $self = clone $this; + $self->uriTemplate = $uriTemplate; + + return $self; + } + + public function getTypes(): ?array + { + return $this->types; + } + + /** + * @param string[]|string $types + */ + public function withTypes($types): self + { + $self = clone $this; + $self->types = (array) $types; + + return $self; + } + + /** + * @return array|mixed|string|null + */ + public function getFormats() + { + return $this->formats; + } + + public function withFormats($formats = null): self + { + $self = clone $this; + $self->formats = $formats; + + return $self; + } + + /** + * @return array|mixed|string|null + */ + public function getInputFormats() + { + return $this->inputFormats; + } + + public function withInputFormats($inputFormats = null): self + { + $self = clone $this; + $self->inputFormats = $inputFormats; + + return $self; + } + + /** + * @return array|mixed|string|null + */ + public function getOutputFormats() + { + return $this->outputFormats; + } + + public function withOutputFormats($outputFormats = null): self + { + $self = clone $this; + $self->outputFormats = $outputFormats; + + return $self; + } + + /** + * @return array|array|string[]|string|null + */ + public function getUriVariables() + { + return $this->uriVariables; + } + + /** + * @param array|array|string[]|string $uriVariables + */ + public function withUriVariables($uriVariables): self + { + $self = clone $this; + $self->uriVariables = $uriVariables; + + return $self; + } + + public function getRoutePrefix(): ?string + { + return $this->routePrefix; + } + + public function withRoutePrefix(string $routePrefix): self + { + $self = clone $this; + $self->routePrefix = $routePrefix; + + return $self; + } + + public function getRouteName(): ?string + { + return $this->routeName; + } + + public function withRouteName(?string $routeName): self + { + $self = clone $this; + $self->routeName = $routeName; + + return $self; + } + + public function getDefaults(): ?array + { + return $this->defaults; + } + + public function withDefaults(array $defaults): self + { + $self = clone $this; + $self->defaults = $defaults; + + return $self; + } + + public function getRequirements(): ?array + { + return $this->requirements; + } + + public function withRequirements(array $requirements): self + { + $self = clone $this; + $self->requirements = $requirements; + + return $self; + } + + public function getOptions(): ?array + { + return $this->options; + } + + public function withOptions(array $options): self + { + $self = clone $this; + $self->options = $options; + + return $self; + } + + public function getStateless(): ?bool + { + return $this->stateless; + } + + public function withStateless($stateless): self + { + $self = clone $this; + $self->stateless = $stateless; + + return $self; + } + + public function getSunset(): ?string + { + return $this->sunset; + } + + public function withSunset(string $sunset): self + { + $self = clone $this; + $self->sunset = $sunset; + + return $self; + } + + public function getAcceptPatch(): ?string + { + return $this->acceptPatch; + } + + public function withAcceptPatch(string $acceptPatch): self + { + $self = clone $this; + $self->acceptPatch = $acceptPatch; + + return $self; + } + + public function getStatus(): ?int + { + return $this->status; + } + + public function withStatus(int $status): self + { + $self = clone $this; + $self->status = $status; + + return $self; + } + + public function getHost(): ?string + { + return $this->host; + } + + public function withHost(string $host): self + { + $self = clone $this; + $self->host = $host; + + return $self; + } + + public function getSchemes(): ?array + { + return $this->schemes; + } + + public function withSchemes(array $schemes): self + { + $self = clone $this; + $self->schemes = $schemes; + + return $self; + } + + public function getCondition(): ?string + { + return $this->condition; + } + + public function withCondition(string $condition): self + { + $self = clone $this; + $self->condition = $condition; + + return $self; + } + + public function getController(): ?string + { + return $this->controller; + } + + public function withController(string $controller): self + { + $self = clone $this; + $self->controller = $controller; + + return $self; + } + + public function getCacheHeaders(): ?array + { + return $this->cacheHeaders; + } + + public function withCacheHeaders(array $cacheHeaders): self + { + $self = clone $this; + $self->cacheHeaders = $cacheHeaders; + + return $self; + } + + /** + * @return string[] + */ + public function getHydraContext(): ?array + { + return $this->hydraContext; + } + + public function withHydraContext(array $hydraContext): self + { + $self = clone $this; + $self->hydraContext = $hydraContext; + + return $self; + } + + public function getOpenapiContext(): ?array + { + return $this->openapiContext; + } + + public function withOpenapiContext(array $openapiContext): self + { + $self = clone $this; + $self->openapiContext = $openapiContext; + + return $self; + } + + public function getExceptionToStatus(): ?array + { + return $this->exceptionToStatus; + } + + public function withExceptionToStatus(array $exceptionToStatus): self + { + $self = clone $this; + $self->exceptionToStatus = $exceptionToStatus; + + return $self; + } + + public function getQueryParameterValidationEnabled(): ?bool + { + return $this->queryParameterValidationEnabled; + } + + public function withQueryParameterValidationEnabled(bool $queryParameterValidationEnabled): self + { + $self = clone $this; + $self->queryParameterValidationEnabled = $queryParameterValidationEnabled; + + return $self; + } +} diff --git a/src/Metadata/Operation.php b/src/Metadata/Operation.php index 9f896a70ec5..d9d42259ce5 100644 --- a/src/Metadata/Operation.php +++ b/src/Metadata/Operation.php @@ -13,859 +13,338 @@ namespace ApiPlatform\Metadata; -class Operation +abstract class Operation { use WithResourceTrait; - public const METHOD_GET = 'GET'; - public const METHOD_POST = 'POST'; - public const METHOD_PUT = 'PUT'; - public const METHOD_PATCH = 'PATCH'; - public const METHOD_DELETE = 'DELETE'; - public const METHOD_HEAD = 'HEAD'; - public const METHOD_OPTIONS = 'OPTIONS'; - protected $method; - protected $uriTemplate; protected $shortName; - protected $description; - protected $types; - /** - * @var array|mixed|string|null - */ - protected $formats; - /** - * @var array|mixed|string|null - */ - protected $inputFormats; - /** - * @var array|mixed|string|null - */ - protected $outputFormats; - /** - * @var array|array|string[]|string|null - */ - protected $uriVariables; - protected $routePrefix; - protected $routeName; - protected $defaults; - protected $requirements; - protected $options; - protected $stateless; - protected $sunset; - protected $acceptPatch; - /** - * @var string|int|null - */ - protected $status; - protected $host; - protected $schemes; - protected $condition; - protected $controller; protected $class; - protected $urlGenerationStrategy; - protected $collection; - protected $deprecationReason; - protected $cacheHeaders; + protected $paginationEnabled; + protected $paginationType; + protected $paginationItemsPerPage; + protected $paginationMaximumItemsPerPage; + protected $paginationPartial; + protected $paginationClientEnabled; + protected $paginationClientItemsPerPage; + protected $paginationClientPartial; + protected $paginationFetchJoinCollection; + protected $paginationUseOutputWalkers; + protected $paginationViaCursor; + protected $order; + protected $description; protected $normalizationContext; protected $denormalizationContext; + protected $security; + protected $securityMessage; + protected $securityPostDenormalize; + protected $securityPostDenormalizeMessage; + protected $securityPostValidation; + protected $securityPostValidationMessage; + protected $deprecationReason; /** * @var string[] */ - protected $hydraContext; - protected $openapiContext; - protected $swaggerContext; + protected $filters; protected $validationContext; /** - * @var string[] + * @var null */ - protected $filters; - protected $elasticsearch; + protected $input; /** - * @var array|bool|mixed|null + * @var null + */ + protected $output; + /** + * @var string|array|bool|null */ protected $mercure; /** - * @var bool|mixed|null + * @var string|bool|null */ protected $messenger; - protected $input; - protected $output; - protected $order; - protected $fetchPartial; - protected $forceEager; - protected $paginationClientEnabled; - protected $paginationClientItemsPerPage; - protected $paginationClientPartial; - protected $paginationViaCursor; - protected $paginationEnabled; - protected $paginationFetchJoinCollection; - protected $paginationUseOutputWalkers; - protected $paginationItemsPerPage; - protected $paginationMaximumItemsPerPage; - protected $paginationPartial; - protected $paginationType; - protected $security; - protected $securityMessage; - protected $securityPostDenormalize; - protected $securityPostDenormalizeMessage; - protected $securityPostValidation; - protected $securityPostValidationMessage; - protected $compositeIdentifier; - protected $exceptionToStatus; - protected $queryParameterValidationEnabled; + protected $elasticsearch; + protected $urlGenerationStrategy; protected $read; protected $deserialize; protected $validate; protected $write; protected $serialize; - protected $queryParameterValidate; + protected $fetchPartial; + protected $forceEager; protected $priority; protected $name; - protected $extraProperties; - /** - * @param array|null $types the RDF types of this property - * @param array|string|null $formats https://api-platform.com/docs/core/content-negotiation/#configuring-formats-for-a-specific-resource-or-operation - * @param array|string|null $inputFormats https://api-platform.com/docs/core/content-negotiation/#configuring-formats-for-a-specific-resource-or-operation - * @param array|string|null $outputFormats https://api-platform.com/docs/core/content-negotiation/#configuring-formats-for-a-specific-resource-or-operation - * @param mixed|null $uriVariables - * @param string|null $routePrefix https://api-platform.com/docs/core/operations/#prefixing-all-routes-of-all-operations - * @param string|null $sunset https://api-platform.com/docs/core/deprecations/#setting-the-sunset-http-header-to-indicate-when-a-resource-or-an-operation-will-be-removed - * @param string|int|null $status - * @param string|null $deprecationReason https://api-platform.com/docs/core/deprecations/#deprecating-resource-classes-operations-and-properties - * @param array|null $cacheHeaders https://api-platform.com/docs/core/performance/#setting-custom-http-cache-headers - * @param array|null $normalizationContext https://api-platform.com/docs/core/serialization/#using-serialization-groups - * @param array|null $denormalizationContext https://api-platform.com/docs/core/serialization/#using-serialization-groups - * @param string[]|null $hydraContext https://api-platform.com/docs/core/extending-jsonld-context/#hydra - * @param array|null $openapiContext https://api-platform.com/docs/core/openapi/#using-the-openapi-and-swagger-contexts - * @param array|null $swaggerContext https://api-platform.com/docs/core/openapi/#using-the-openapi-and-swagger-contexts - * @param string[]|null $filters https://api-platform.com/docs/core/filters/#doctrine-orm-and-mongodb-odm-filters - * @param bool|null $elasticsearch https://api-platform.com/docs/core/elasticsearch/ - * @param mixed|null $mercure https://api-platform.com/docs/core/mercure - * @param mixed|null $messenger https://api-platform.com/docs/core/messenger/#dispatching-a-resource-through-the-message-bus - * @param mixed|null $input https://api-platform.com/docs/core/dto/#specifying-an-input-or-an-output-data-representation - * @param mixed|null $output https://api-platform.com/docs/core/dto/#specifying-an-input-or-an-output-data-representation - * @param array|null $order https://api-platform.com/docs/core/default-order/#overriding-default-order - * @param bool|null $fetchPartial https://api-platform.com/docs/core/performance/#fetch-partial - * @param bool|null $forceEager https://api-platform.com/docs/core/performance/#force-eager - * @param bool|null $paginationClientEnabled https://api-platform.com/docs/core/pagination/#for-a-specific-resource-1 - * @param bool|null $paginationClientItemsPerPage https://api-platform.com/docs/core/pagination/#for-a-specific-resource-3 - * @param bool|null $paginationClientPartial https://api-platform.com/docs/core/pagination/#for-a-specific-resource-6 - * @param array|null $paginationViaCursor https://api-platform.com/docs/core/pagination/#cursor-based-pagination - * @param bool|null $paginationEnabled https://api-platform.com/docs/core/pagination/#for-a-specific-resource - * @param bool|null $paginationFetchJoinCollection https://api-platform.com/docs/core/pagination/#controlling-the-behavior-of-the-doctrine-orm-paginator - * @param int|null $paginationItemsPerPage https://api-platform.com/docs/core/pagination/#changing-the-number-of-items-per-page - * @param int|null $paginationMaximumItemsPerPage https://api-platform.com/docs/core/pagination/#changing-maximum-items-per-page - * @param bool|null $paginationPartial https://api-platform.com/docs/core/performance/#partial-pagination - * @param string|null $paginationType https://api-platform.com/docs/core/graphql/#using-the-page-based-pagination - * @param string|null $security https://api-platform.com/docs/core/security - * @param string|null $securityMessage https://api-platform.com/docs/core/security/#configuring-the-access-control-error-message - * @param string|null $securityPostDenormalize https://api-platform.com/docs/core/security/#executing-access-control-rules-after-denormalization - * @param string|null $securityPostDenormalizeMessage https://api-platform.com/docs/core/security/#configuring-the-access-control-error-message - * @param bool|null $read https://api-platform.com/docs/core/events/#the-event-system - * @param bool|null $deserialize https://api-platform.com/docs/core/events/#the-event-system - * @param bool|null $validate https://api-platform.com/docs/core/events/#the-event-system - * @param bool|null $write https://api-platform.com/docs/core/events/#the-event-system - * @param bool|null $serialize https://api-platform.com/docs/core/events/#the-event-system + * @var string|callable|null */ + protected $provider; + /** + * @var string|callable|null + */ + protected $processor; + protected $extraProperties; + public function __construct( - string $method = self::METHOD_GET, - ?string $uriTemplate = null, ?string $shortName = null, - ?string $description = null, - ?array $types = null, - $formats = null, - $inputFormats = null, - $outputFormats = null, - $uriVariables = null, - ?string $routePrefix = null, - ?string $routeName = null, - ?array $defaults = null, - ?array $requirements = null, - ?array $options = null, - ?bool $stateless = null, - ?string $sunset = null, - ?string $acceptPatch = null, - $status = null, - ?string $host = null, - ?array $schemes = null, - ?string $condition = null, - ?string $controller = null, ?string $class = null, - ?int $urlGenerationStrategy = null, - ?bool $collection = null, - ?string $deprecationReason = null, - ?array $cacheHeaders = null, - ?array $normalizationContext = null, - ?array $denormalizationContext = null, - ?array $hydraContext = null, - ?array $openapiContext = null, - ?array $swaggerContext = null, - ?array $validationContext = null, - ?array $filters = null, - ?bool $elasticsearch = null, - $mercure = null, - $messenger = null, - $input = null, - $output = null, - ?array $order = null, - ?bool $fetchPartial = null, - ?bool $forceEager = null, + ?bool $paginationEnabled = null, + ?string $paginationType = null, + ?int $paginationItemsPerPage = null, + ?int $paginationMaximumItemsPerPage = null, + ?bool $paginationPartial = null, ?bool $paginationClientEnabled = null, ?bool $paginationClientItemsPerPage = null, ?bool $paginationClientPartial = null, - ?array $paginationViaCursor = null, - ?bool $paginationEnabled = null, ?bool $paginationFetchJoinCollection = null, ?bool $paginationUseOutputWalkers = null, - ?int $paginationItemsPerPage = null, - ?int $paginationMaximumItemsPerPage = null, - ?bool $paginationPartial = null, - ?string $paginationType = null, + ?array $paginationViaCursor = null, + ?array $order = null, + ?string $description = null, + ?array $normalizationContext = null, + ?array $denormalizationContext = null, ?string $security = null, ?string $securityMessage = null, ?string $securityPostDenormalize = null, ?string $securityPostDenormalizeMessage = null, ?string $securityPostValidation = null, ?string $securityPostValidationMessage = null, - ?bool $compositeIdentifier = null, - ?array $exceptionToStatus = null, - ?bool $queryParameterValidationEnabled = null, + ?string $deprecationReason = null, + ?array $filters = null, + ?array $validationContext = null, + $input = null, + $output = null, + $mercure = null, + $messenger = null, + ?bool $elasticsearch = null, + ?int $urlGenerationStrategy = null, ?bool $read = null, ?bool $deserialize = null, ?bool $validate = null, ?bool $write = null, ?bool $serialize = null, - // TODO: replace by queryParameterValidationEnabled? - ?bool $queryParameterValidate = null, + ?bool $fetchPartial = null, + ?bool $forceEager = null, ?int $priority = null, ?string $name = null, + $provider = null, + $processor = null, array $extraProperties = [] ) { - $this->method = $method; - $this->uriTemplate = $uriTemplate; $this->shortName = $shortName; - $this->description = $description; - $this->types = $types; - $this->formats = $formats; - $this->inputFormats = $inputFormats; - $this->outputFormats = $outputFormats; - $this->uriVariables = $uriVariables; - $this->routePrefix = $routePrefix; - $this->routeName = $routeName; - $this->defaults = $defaults; - $this->requirements = $requirements; - $this->options = $options; - $this->stateless = $stateless; - $this->sunset = $sunset; - $this->acceptPatch = $acceptPatch; - $this->status = $status; - $this->host = $host; - $this->schemes = $schemes; - $this->condition = $condition; - $this->controller = $controller; $this->class = $class; - $this->urlGenerationStrategy = $urlGenerationStrategy; - $this->collection = $collection; - $this->deprecationReason = $deprecationReason; - $this->cacheHeaders = $cacheHeaders; - $this->normalizationContext = $normalizationContext; - $this->denormalizationContext = $denormalizationContext; - $this->hydraContext = $hydraContext; - $this->openapiContext = $openapiContext; - $this->swaggerContext = $swaggerContext; - $this->validationContext = $validationContext; - $this->filters = $filters; - $this->elasticsearch = $elasticsearch; - $this->mercure = $mercure; - $this->messenger = $messenger; - $this->input = $input; - $this->output = $output; - $this->order = $order; - $this->fetchPartial = $fetchPartial; - $this->forceEager = $forceEager; + $this->paginationEnabled = $paginationEnabled; + $this->paginationType = $paginationType; + $this->paginationItemsPerPage = $paginationItemsPerPage; + $this->paginationMaximumItemsPerPage = $paginationMaximumItemsPerPage; + $this->paginationPartial = $paginationPartial; $this->paginationClientEnabled = $paginationClientEnabled; $this->paginationClientItemsPerPage = $paginationClientItemsPerPage; $this->paginationClientPartial = $paginationClientPartial; - $this->paginationViaCursor = $paginationViaCursor; - $this->paginationEnabled = $paginationEnabled; $this->paginationFetchJoinCollection = $paginationFetchJoinCollection; $this->paginationUseOutputWalkers = $paginationUseOutputWalkers; - $this->paginationItemsPerPage = $paginationItemsPerPage; - $this->paginationMaximumItemsPerPage = $paginationMaximumItemsPerPage; - $this->paginationPartial = $paginationPartial; - $this->paginationType = $paginationType; + $this->paginationViaCursor = $paginationViaCursor; + $this->order = $order; + $this->description = $description; + $this->normalizationContext = $normalizationContext; + $this->denormalizationContext = $denormalizationContext; $this->security = $security; $this->securityMessage = $securityMessage; $this->securityPostDenormalize = $securityPostDenormalize; $this->securityPostDenormalizeMessage = $securityPostDenormalizeMessage; $this->securityPostValidation = $securityPostValidation; $this->securityPostValidationMessage = $securityPostValidationMessage; - $this->compositeIdentifier = $compositeIdentifier; - $this->exceptionToStatus = $exceptionToStatus; - $this->queryParameterValidationEnabled = $queryParameterValidationEnabled; + $this->deprecationReason = $deprecationReason; + $this->filters = $filters; + $this->validationContext = $validationContext; + $this->input = $input; + $this->output = $output; + $this->mercure = $mercure; + $this->messenger = $messenger; + $this->elasticsearch = $elasticsearch; + $this->urlGenerationStrategy = $urlGenerationStrategy; $this->read = $read; $this->deserialize = $deserialize; $this->validate = $validate; $this->write = $write; $this->serialize = $serialize; - $this->queryParameterValidate = $queryParameterValidate; + $this->fetchPartial = $fetchPartial; + $this->forceEager = $forceEager; $this->priority = $priority; $this->name = $name; + $this->provider = $provider; + $this->processor = $processor; $this->extraProperties = $extraProperties; } public function withOperation($operation) { - return $this->copyFrom($operation); - } - - public function getName(): ?string - { - return $this->name; - } - - public function withName(string $name): self - { - $self = clone $this; - $self->name = $name; - - return $self; - } - - public function getMethod(): string - { - return $this->method; - } - - public function withMethod(string $method): self - { - $self = clone $this; - $self->method = $method; - - return $self; - } - - public function getUriTemplate(): ?string - { - return $this->uriTemplate; - } - - public function withUriTemplate(?string $uriTemplate = null) - { - $self = clone $this; - $self->uriTemplate = $uriTemplate; - - return $self; - } - - public function getShortName(): ?string - { - return $this->shortName; - } - - public function withShortName(?string $shortName = null): self - { - $self = clone $this; - $self->shortName = $shortName; - - return $self; - } - - public function getDescription(): ?string - { - return $this->description; - } - - public function withDescription(?string $description = null): self - { - $self = clone $this; - $self->description = $description; - - return $self; - } - - public function getTypes(): ?array - { - return $this->types; - } - - /** - * @param string[]|string $types - */ - public function withTypes($types): self - { - $self = clone $this; - $self->types = (array) $types; - - return $self; - } - - /** - * @return array|mixed|string|null - */ - public function getFormats() - { - return $this->formats; - } - - public function withFormats($formats = null): self - { - $self = clone $this; - $self->formats = $formats; - - return $self; - } - - /** - * @return array|mixed|string|null - */ - public function getInputFormats() - { - return $this->inputFormats; - } - - public function withInputFormats($inputFormats = null): self - { - $self = clone $this; - $self->inputFormats = $inputFormats; - - return $self; - } - - /** - * @return array|mixed|string|null - */ - public function getOutputFormats() - { - return $this->outputFormats; - } - - public function withOutputFormats($outputFormats = null): self - { - $self = clone $this; - $self->outputFormats = $outputFormats; - - return $self; - } - - /** - * @return array|array|string[]|string|null - */ - public function getUriVariables() - { - return $this->uriVariables; - } - - /** - * @param array|array|string[]|string $uriVariables - */ - public function withUriVariables($uriVariables): self - { - $self = clone $this; - $self->uriVariables = $uriVariables; - - return $self; - } - - public function getRoutePrefix(): ?string - { - return $this->routePrefix; - } - - public function withRoutePrefix(string $routePrefix): self - { - $self = clone $this; - $self->routePrefix = $routePrefix; - - return $self; - } - - public function getRouteName(): ?string - { - return $this->routeName; - } - - public function withRouteName(?string $routeName): self - { - $self = clone $this; - $self->routeName = $routeName; - - return $self; - } - - public function getDefaults(): ?array - { - return $this->defaults; - } - - public function withDefaults(array $defaults): self - { - $self = clone $this; - $self->defaults = $defaults; - - return $self; - } - - public function getRequirements(): ?array - { - return $this->requirements; - } - - public function withRequirements(array $requirements): self - { - $self = clone $this; - $self->requirements = $requirements; - - return $self; - } - - public function getOptions(): ?array - { - return $this->options; - } - - public function withOptions(array $options): self - { - $self = clone $this; - $self->options = $options; - - return $self; - } - - public function getStateless(): ?bool - { - return $this->stateless; - } - - public function withStateless($stateless): self - { - $self = clone $this; - $self->stateless = $stateless; - - return $self; - } - - public function getSunset(): ?string - { - return $this->sunset; - } - - public function withSunset(string $sunset): self - { - $self = clone $this; - $self->sunset = $sunset; - - return $self; - } - - public function getAcceptPatch(): ?string - { - return $this->acceptPatch; - } - - public function withAcceptPatch(string $acceptPatch): self - { - $self = clone $this; - $self->acceptPatch = $acceptPatch; - - return $self; - } - - public function getStatus(): ?int - { - return $this->status; - } - - public function withStatus(int $status): self - { - $self = clone $this; - $self->status = $status; - - return $self; - } - - public function getHost(): ?string - { - return $this->host; - } - - public function withHost(string $host): self - { - $self = clone $this; - $self->host = $host; - - return $self; - } - - public function getSchemes(): ?array - { - return $this->schemes; - } - - public function withSchemes(array $schemes): self - { - $self = clone $this; - $self->schemes = $schemes; - - return $self; - } - - public function getCondition(): ?string - { - return $this->condition; - } - - public function withCondition(string $condition): self - { - $self = clone $this; - $self->condition = $condition; - - return $self; - } - - public function getController(): ?string - { - return $this->controller; - } - - public function withController(string $controller): self - { - $self = clone $this; - $self->controller = $controller; - - return $self; - } - - public function getClass(): ?string - { - return $this->class; - } - - public function withClass(string $class): self - { - $self = clone $this; - $self->class = $class; - - return $self; - } - - public function getUrlGenerationStrategy(): ?int - { - return $this->urlGenerationStrategy; - } - - public function withUrlGenerationStrategy(int $urlGenerationStrategy): self - { - $self = clone $this; - $self->urlGenerationStrategy = $urlGenerationStrategy; - - return $self; - } - - public function isCollection(): ?bool - { - return $this->collection; - } - - public function withCollection(bool $collection): self - { - $self = clone $this; - $self->collection = $collection; - - return $self; - } - - public function getDeprecationReason(): ?string - { - return $this->deprecationReason; - } - - public function withDeprecationReason(string $deprecationReason): self - { - $self = clone $this; - $self->deprecationReason = $deprecationReason; - - return $self; + return $this->copyFrom($operation); } - public function getCacheHeaders(): ?array + public function getShortName(): ?string { - return $this->cacheHeaders; + return $this->shortName; } - public function withCacheHeaders(array $cacheHeaders): self + public function withShortName(?string $shortName = null): self { $self = clone $this; - $self->cacheHeaders = $cacheHeaders; + $self->shortName = $shortName; return $self; } - public function getNormalizationContext(): ?array + public function getClass(): ?string { - return $this->normalizationContext; + return $this->class; } - public function withNormalizationContext(array $normalizationContext): self + public function withClass(?string $class = null): self { $self = clone $this; - $self->normalizationContext = $normalizationContext; + $self->class = $class; return $self; } - public function getDenormalizationContext(): ?array + public function getPaginationEnabled(): ?bool { - return $this->denormalizationContext; + return $this->paginationEnabled; } - public function withDenormalizationContext(array $denormalizationContext): self + public function withPaginationEnabled(?bool $paginationEnabled = null): self { $self = clone $this; - $self->denormalizationContext = $denormalizationContext; + $self->paginationEnabled = $paginationEnabled; return $self; } - /** - * @return string[] - */ - public function getHydraContext(): ?array + public function getPaginationType(): ?string { - return $this->hydraContext; + return $this->paginationType; } - public function withHydraContext(array $hydraContext): self + public function withPaginationType(?string $paginationType = null): self { $self = clone $this; - $self->hydraContext = $hydraContext; + $self->paginationType = $paginationType; return $self; } - public function getOpenapiContext(): ?array + public function getPaginationItemsPerPage(): ?int { - return $this->openapiContext; + return $this->paginationItemsPerPage; } - public function withOpenapiContext(array $openapiContext): self + public function withPaginationItemsPerPage(?int $paginationItemsPerPage = null): self { $self = clone $this; - $self->openapiContext = $openapiContext; + $self->paginationItemsPerPage = $paginationItemsPerPage; return $self; } - public function getSwaggerContext(): ?array + public function getPaginationMaximumItemsPerPage(): ?int { - return $this->swaggerContext; + return $this->paginationMaximumItemsPerPage; } - public function withSwaggerContext(array $swaggerContext): self + public function withPaginationMaximumItemsPerPage(?int $paginationMaximumItemsPerPage = null): self { $self = clone $this; - $self->swaggerContext = $swaggerContext; + $self->paginationMaximumItemsPerPage = $paginationMaximumItemsPerPage; return $self; } - public function getValidationContext(): ?array + public function getPaginationPartial(): ?bool { - return $this->validationContext; + return $this->paginationPartial; } - public function withValidationContext(array $validationContext): self + public function withPaginationPartial(?bool $paginationPartial = null): self { $self = clone $this; - $self->validationContext = $validationContext; + $self->paginationPartial = $paginationPartial; return $self; } - /** - * @return string[] - */ - public function getFilters(): ?array + public function getPaginationClientEnabled(): ?bool { - return $this->filters; + return $this->paginationClientEnabled; } - public function withFilters(array $filters): self + public function withPaginationClientEnabled(?bool $paginationClientEnabled = null): self { $self = clone $this; - $self->filters = $filters; + $self->paginationClientEnabled = $paginationClientEnabled; return $self; } - public function getElasticsearch(): ?bool + public function getPaginationClientItemsPerPage(): ?bool { - return $this->elasticsearch; + return $this->paginationClientItemsPerPage; } - public function withElasticsearch(bool $elasticsearch): self + public function withPaginationClientItemsPerPage(?bool $paginationClientItemsPerPage = null): self { $self = clone $this; - $self->elasticsearch = $elasticsearch; + $self->paginationClientItemsPerPage = $paginationClientItemsPerPage; return $self; } - /** - * @return array|bool|mixed|null - */ - public function getMercure() + public function getPaginationClientPartial(): ?bool { - return $this->mercure; + return $this->paginationClientPartial; } - public function withMercure($mercure): self + public function withPaginationClientPartial(?bool $paginationClientPartial = null): self { $self = clone $this; - $self->mercure = $mercure; + $self->paginationClientPartial = $paginationClientPartial; return $self; } - /** - * @return bool|mixed|null - */ - public function getMessenger() + public function getPaginationFetchJoinCollection(): ?bool { - return $this->messenger; + return $this->paginationFetchJoinCollection; } - public function withMessenger($messenger): self + public function withPaginationFetchJoinCollection(?bool $paginationFetchJoinCollection = null): self { $self = clone $this; - $self->messenger = $messenger; + $self->paginationFetchJoinCollection = $paginationFetchJoinCollection; return $self; } - public function getInput() + public function getPaginationUseOutputWalkers(): ?bool { - return $this->input; + return $this->paginationUseOutputWalkers; } - public function withInput($input): self + public function withPaginationUseOutputWalkers(?bool $paginationUseOutputWalkers = null): self { $self = clone $this; - $self->input = $input; + $self->paginationUseOutputWalkers = $paginationUseOutputWalkers; return $self; } - public function getOutput() + public function getPaginationViaCursor(): ?array { - return $this->output; + return $this->paginationViaCursor; } - public function withOutput($output): self + public function withPaginationViaCursor(array $paginationViaCursor): self { $self = clone $this; - $self->output = $output; + $self->paginationViaCursor = $paginationViaCursor; return $self; } @@ -875,7 +354,7 @@ public function getOrder(): ?array return $this->order; } - public function withOrder(array $order): self + public function withOrder(array $order = []): self { $self = clone $this; $self->order = $order; @@ -883,397 +362,414 @@ public function withOrder(array $order): self return $self; } - public function isDelete(): bool - { - return self::METHOD_DELETE === $this->getMethod(); - } - - public function getFetchPartial(): ?bool + public function getDescription(): ?string { - return $this->fetchPartial; + return $this->description; } - public function withFetchPartial(bool $fetchPartial): self + public function withDescription(?string $description = null): self { $self = clone $this; - $self->fetchPartial = $fetchPartial; + $self->description = $description; return $self; } - public function getForceEager(): ?bool + public function getNormalizationContext(): ?array { - return $this->forceEager; + return $this->normalizationContext; } - public function withForceEager(bool $forceEager): self + public function withNormalizationContext(array $normalizationContext = []): self { $self = clone $this; - $self->forceEager = $forceEager; + $self->normalizationContext = $normalizationContext; return $self; } - public function getPaginationClientEnabled(): ?bool + public function getDenormalizationContext(): ?array { - return $this->paginationClientEnabled; + return $this->denormalizationContext; } - public function withPaginationClientEnabled(bool $paginationClientEnabled): self + public function withDenormalizationContext(array $denormalizationContext = []): self { $self = clone $this; - $self->paginationClientEnabled = $paginationClientEnabled; + $self->denormalizationContext = $denormalizationContext; return $self; } - public function getPaginationClientItemsPerPage(): ?bool + public function getSecurity(): ?string { - return $this->paginationClientItemsPerPage; + return $this->security; } - public function withPaginationClientItemsPerPage(bool $paginationClientItemsPerPage): self + public function withSecurity(?string $security = null): self { $self = clone $this; - $self->paginationClientItemsPerPage = $paginationClientItemsPerPage; + $self->security = $security; return $self; } - public function getPaginationClientPartial(): ?bool + public function getSecurityMessage(): ?string { - return $this->paginationClientPartial; + return $this->securityMessage; } - public function withPaginationClientPartial(bool $paginationClientPartial): self + public function withSecurityMessage(?string $securityMessage = null): self { $self = clone $this; - $self->paginationClientPartial = $paginationClientPartial; + $self->securityMessage = $securityMessage; return $self; } - public function getPaginationViaCursor(): ?array + public function getSecurityPostDenormalize(): ?string { - return $this->paginationViaCursor; + return $this->securityPostDenormalize; } - public function withPaginationViaCursor(array $paginationViaCursor): self + public function withSecurityPostDenormalize(?string $securityPostDenormalize = null): self { $self = clone $this; - $self->paginationViaCursor = $paginationViaCursor; + $self->securityPostDenormalize = $securityPostDenormalize; return $self; } - public function getPaginationEnabled(): ?bool + public function getSecurityPostDenormalizeMessage(): ?string { - return $this->paginationEnabled; + return $this->securityPostDenormalizeMessage; } - public function withPaginationEnabled(bool $paginationEnabled): self + public function withSecurityPostDenormalizeMessage(?string $securityPostDenormalizeMessage = null): self { $self = clone $this; - $self->paginationEnabled = $paginationEnabled; + $self->securityPostDenormalizeMessage = $securityPostDenormalizeMessage; return $self; } - public function getPaginationFetchJoinCollection(): ?bool + public function getSecurityPostValidation(): ?string { - return $this->paginationFetchJoinCollection; + return $this->securityPostValidation; } - public function withPaginationFetchJoinCollection(bool $paginationFetchJoinCollection): self + public function withSecurityPostValidation(?string $securityPostValidation = null): self { $self = clone $this; - $self->paginationFetchJoinCollection = $paginationFetchJoinCollection; + $self->securityPostValidation = $securityPostValidation; return $self; } - public function getPaginationUseOutputWalkers(): ?bool + public function getSecurityPostValidationMessage(): ?string { - return $this->paginationUseOutputWalkers; + return $this->securityPostValidationMessage; } - public function withPaginationUseOutputWalkers(bool $paginationUseOutputWalkers): self + public function withSecurityPostValidationMessage(?string $securityPostValidationMessage = null): self { $self = clone $this; - $self->paginationUseOutputWalkers = $paginationUseOutputWalkers; + $self->securityPostValidationMessage = $securityPostValidationMessage; return $self; } - public function getPaginationItemsPerPage(): ?int + public function getDeprecationReason(): ?string { - return $this->paginationItemsPerPage; + return $this->deprecationReason; } - public function withPaginationItemsPerPage(int $paginationItemsPerPage): self + public function withDeprecationReason(?string $deprecationReason = null): self { $self = clone $this; - $self->paginationItemsPerPage = $paginationItemsPerPage; + $self->deprecationReason = $deprecationReason; return $self; } - public function getPaginationMaximumItemsPerPage(): ?int + public function getFilters(): ?array { - return $this->paginationMaximumItemsPerPage; + return $this->filters; } - public function withPaginationMaximumItemsPerPage(int $paginationMaximumItemsPerPage): self + public function withFilters(array $filters = []): self { $self = clone $this; - $self->paginationMaximumItemsPerPage = $paginationMaximumItemsPerPage; + $self->filters = $filters; return $self; } - public function getPaginationPartial(): ?bool + public function getValidationContext(): ?array { - return $this->paginationPartial; + return $this->validationContext; } - public function withPaginationPartial(bool $paginationPartial): self + public function withValidationContext(array $validationContext = []): self { $self = clone $this; - $self->paginationPartial = $paginationPartial; + $self->validationContext = $validationContext; return $self; } - public function getPaginationType(): ?string + public function getInput() { - return $this->paginationType; + return $this->input; } - public function withPaginationType(string $paginationType): self + public function withInput($input = null): self { $self = clone $this; - $self->paginationType = $paginationType; + $self->input = $input; return $self; } - public function getSecurity(): ?string + public function getOutput() { - return $this->security; + return $this->output; } - public function withSecurity(string $security): self + public function withOutput($output = null): self { $self = clone $this; - $self->security = $security; + $self->output = $output; return $self; } - public function getSecurityMessage(): ?string + /** + * @return bool|string|array|null + */ + public function getMercure() { - return $this->securityMessage; + return $this->mercure; } - public function withSecurityMessage(string $securityMessage): self + /** + * @param bool|string|array|null $mercure + * + * @return $this + */ + public function withMercure($mercure = null): self { $self = clone $this; - $self->securityMessage = $securityMessage; + $self->mercure = $mercure; return $self; } - public function getSecurityPostDenormalize(): ?string + /** + * @return bool|string|null + */ + public function getMessenger() { - return $this->securityPostDenormalize; + return $this->messenger; } - public function withSecurityPostDenormalize(string $securityPostDenormalize): self + /** + * @param bool|string|null $messenger + * + * @return $this + */ + public function withMessenger($messenger = null): self { $self = clone $this; - $self->securityPostDenormalize = $securityPostDenormalize; + $self->messenger = $messenger; return $self; } - public function getSecurityPostDenormalizeMessage(): ?string + public function getElasticsearch(): ?bool { - return $this->securityPostDenormalizeMessage; + return $this->elasticsearch; } - public function withSecurityPostDenormalizeMessage(string $securityPostDenormalizeMessage): self + public function withElasticsearch(?bool $elasticsearch = null): self { $self = clone $this; - $self->securityPostDenormalizeMessage = $securityPostDenormalizeMessage; + $self->elasticsearch = $elasticsearch; return $self; } - public function getSecurityPostValidation(): ?string + public function getUrlGenerationStrategy(): ?int { - return $this->securityPostValidation; + return $this->urlGenerationStrategy; } - public function withSecurityPostValidation(string $securityPostValidation): self + public function withUrlGenerationStrategy(?int $urlGenerationStrategy = null): self { $self = clone $this; - $self->securityPostValidation = $securityPostValidation; + $self->urlGenerationStrategy = $urlGenerationStrategy; return $self; } - public function getSecurityPostValidationMessage(): ?string + public function canRead(): ?bool { - return $this->securityPostValidationMessage; + return $this->read; } - public function withSecurityPostValidationMessage(string $securityPostValidationMessage): self + public function withRead(bool $read = true): self { $self = clone $this; - $self->securityPostValidationMessage = $securityPostValidationMessage; + $self->read = $read; return $self; } - public function getCompositeIdentifier(): ?bool + public function canDeserialize(): ?bool { - return $this->compositeIdentifier; + return $this->deserialize; } - public function withCompositeIdentifier(bool $compositeIdentifier): self + public function withDeserialize(bool $deserialize = true): self { $self = clone $this; - $self->compositeIdentifier = $compositeIdentifier; + $self->deserialize = $deserialize; return $self; } - public function getExceptionToStatus(): ?array + public function canValidate(): ?bool { - return $this->exceptionToStatus; + return $this->validate; } - public function withExceptionToStatus(array $exceptionToStatus): self + public function withValidate(bool $validate = true): self { $self = clone $this; - $self->exceptionToStatus = $exceptionToStatus; + $self->validate = $validate; return $self; } - public function getQueryParameterValidationEnabled(): ?bool + public function canWrite(): ?bool { - return $this->queryParameterValidationEnabled; + return $this->write; } - public function withQueryParameterValidationEnabled(bool $queryParameterValidationEnabled): self + public function withWrite(bool $write = true): self { $self = clone $this; - $self->queryParameterValidationEnabled = $queryParameterValidationEnabled; + $self->write = $write; return $self; } - public function getExtraProperties(): ?array + public function canSerialize(): ?bool { - return $this->extraProperties; + return $this->serialize; } - public function withExtraProperties(array $extraProperties): self + public function withSerialize(bool $serialize = true): self { $self = clone $this; - $self->extraProperties = $extraProperties; + $self->serialize = $serialize; return $self; } - public function canRead(): ?bool + public function getFetchPartial(): ?bool { - return $this->read; + return $this->fetchPartial; } - public function withRead(bool $read): self + public function withFetchPartial(?bool $fetchPartial = null): self { $self = clone $this; - $self->read = $read; + $self->fetchPartial = $fetchPartial; return $self; } - public function canDeserialize(): ?bool + public function getForceEager(): ?bool { - return $this->deserialize; + return $this->forceEager; } - public function withDeserialize(bool $deserialize): self + public function withForceEager(?bool $forceEager = null): self { $self = clone $this; - $self->deserialize = $deserialize; + $self->forceEager = $forceEager; return $self; } - public function canValidate(): ?bool + public function getPriority(): ?int { - return $this->validate; + return $this->priority; } - public function withValidate(bool $validate): self + public function withPriority(int $priority = 0): self { $self = clone $this; - $self->validate = $validate; + $self->priority = $priority; return $self; } - public function canWrite(): ?bool + public function getName(): ?string { - return $this->write; + return $this->name; } - public function withWrite(bool $write): self + public function withName(string $name = ''): self { $self = clone $this; - $self->write = $write; + $self->name = $name; return $self; } - public function canSerialize(): ?bool + /** + * @return string|callable|null + */ + public function getProcessor() { - return $this->serialize; + return $this->processor; } - public function withSerialize(bool $serialize): self + public function withProcessor($processor): self { $self = clone $this; - $self->serialize = $serialize; + $self->processor = $processor; return $self; } - public function canQueryParameterValidate(): ?bool + /** + * @return string|callable|null + */ + public function getProvider() { - return $this->validate; + return $this->provider; } - public function withQueryParameterValidate(bool $validate): self + public function withProvider($provider): self { $self = clone $this; - $self->validate = $validate; + $self->provider = $provider; return $self; } - public function getPriority(): ?int + public function getExtraProperties(): array { - return $this->priority; + return $this->extraProperties; } - public function withPriority(int $priority): self + public function withExtraProperties(array $extraProperties = []): self { $self = clone $this; - $self->priority = $priority; + $self->extraProperties = $extraProperties; return $self; } diff --git a/src/Metadata/Operations.php b/src/Metadata/Operations.php index f23da43cf14..10a7dc24581 100644 --- a/src/Metadata/Operations.php +++ b/src/Metadata/Operations.php @@ -23,7 +23,7 @@ final class Operations implements \IteratorAggregate, \Countable private $operations; /** - * @param Operation[] $operations + * @param array $operations */ public function __construct(array $operations = []) { diff --git a/src/Metadata/Patch.php b/src/Metadata/Patch.php index dc6b662741a..23e9fe17128 100644 --- a/src/Metadata/Patch.php +++ b/src/Metadata/Patch.php @@ -14,20 +14,18 @@ namespace ApiPlatform\Metadata; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] -final class Patch extends Operation +final class Patch extends HttpOperation { /** * {@inheritdoc} */ public function __construct( ?string $uriTemplate = null, - ?string $shortName = null, - ?string $description = null, ?array $types = null, - $formats = null, - $inputFormats = null, - $outputFormats = null, - $uriVariables = null, + $formats = null, + $inputFormats = null, + $outputFormats = null, + $uriVariables = null, ?string $routePrefix = null, ?string $routeName = null, ?array $defaults = null, @@ -36,60 +34,63 @@ public function __construct( ?bool $stateless = null, ?string $sunset = null, ?string $acceptPatch = null, - $status = null, + $status = null, ?string $host = null, ?array $schemes = null, ?string $condition = null, ?string $controller = null, - ?string $class = null, - ?int $urlGenerationStrategy = null, - ?bool $collection = null, - ?string $deprecationReason = null, ?array $cacheHeaders = null, - ?array $normalizationContext = null, - ?array $denormalizationContext = null, + ?array $hydraContext = null, ?array $openapiContext = null, - ?array $swaggerContext = null, - ?array $validationContext = null, - ?array $filters = null, - ?bool $elasticsearch = null, - $mercure = null, - $messenger = null, - $input = null, - $output = null, - ?array $order = null, - ?bool $fetchPartial = null, - ?bool $forceEager = null, + ?array $exceptionToStatus = null, + + ?bool $queryParameterValidationEnabled = null, + + // abstract operation arguments + ?string $shortName = null, + ?string $class = null, + ?bool $paginationEnabled = null, + ?string $paginationType = null, + ?int $paginationItemsPerPage = null, + ?int $paginationMaximumItemsPerPage = null, + ?bool $paginationPartial = null, ?bool $paginationClientEnabled = null, ?bool $paginationClientItemsPerPage = null, ?bool $paginationClientPartial = null, - ?array $paginationViaCursor = null, - ?bool $paginationEnabled = null, ?bool $paginationFetchJoinCollection = null, ?bool $paginationUseOutputWalkers = null, - ?int $paginationItemsPerPage = null, - ?int $paginationMaximumItemsPerPage = null, - ?bool $paginationPartial = null, - ?string $paginationType = null, + ?array $paginationViaCursor = null, + ?array $order = null, + ?string $description = null, + ?array $normalizationContext = null, + ?array $denormalizationContext = null, ?string $security = null, ?string $securityMessage = null, ?string $securityPostDenormalize = null, ?string $securityPostDenormalizeMessage = null, ?string $securityPostValidation = null, ?string $securityPostValidationMessage = null, - ?bool $compositeIdentifier = null, - ?array $exceptionToStatus = null, - ?bool $queryParameterValidationEnabled = null, + ?string $deprecationReason = null, + ?array $filters = null, + ?array $validationContext = null, + $input = null, + $output = null, + $mercure = null, + $messenger = null, + ?bool $elasticsearch = null, + ?int $urlGenerationStrategy = null, ?bool $read = null, ?bool $deserialize = null, ?bool $validate = null, ?bool $write = null, ?bool $serialize = null, - // TODO: replace by queryParameterValidationEnabled? - ?bool $queryParameterValidate = null, + ?bool $fetchPartial = null, + ?bool $forceEager = null, ?int $priority = null, ?string $name = null, + ?string $provider = null, + ?string $processor = null, array $extraProperties = [] ) { parent::__construct(self::METHOD_PATCH, ...\func_get_args()); diff --git a/src/Metadata/Post.php b/src/Metadata/Post.php index 10ac988398c..06ba2a4f1db 100644 --- a/src/Metadata/Post.php +++ b/src/Metadata/Post.php @@ -14,20 +14,18 @@ namespace ApiPlatform\Metadata; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] -final class Post extends Operation +final class Post extends HttpOperation { /** * {@inheritdoc} */ public function __construct( ?string $uriTemplate = null, - ?string $shortName = null, - ?string $description = null, ?array $types = null, - $formats = null, - $inputFormats = null, - $outputFormats = null, - $uriVariables = null, + $formats = null, + $inputFormats = null, + $outputFormats = null, + $uriVariables = null, ?string $routePrefix = null, ?string $routeName = null, ?array $defaults = null, @@ -36,60 +34,63 @@ public function __construct( ?bool $stateless = null, ?string $sunset = null, ?string $acceptPatch = null, - $status = null, + $status = null, ?string $host = null, ?array $schemes = null, ?string $condition = null, ?string $controller = null, - ?string $class = null, - ?int $urlGenerationStrategy = null, - ?bool $collection = null, - ?string $deprecationReason = null, ?array $cacheHeaders = null, - ?array $normalizationContext = null, - ?array $denormalizationContext = null, + ?array $hydraContext = null, ?array $openapiContext = null, - ?array $swaggerContext = null, - ?array $validationContext = null, - ?array $filters = null, - ?bool $elasticsearch = null, - $mercure = null, - $messenger = null, - $input = null, - $output = null, - ?array $order = null, - ?bool $fetchPartial = null, - ?bool $forceEager = null, + ?array $exceptionToStatus = null, + + ?bool $queryParameterValidationEnabled = null, + + // abstract operation arguments + ?string $shortName = null, + ?string $class = null, + ?bool $paginationEnabled = null, + ?string $paginationType = null, + ?int $paginationItemsPerPage = null, + ?int $paginationMaximumItemsPerPage = null, + ?bool $paginationPartial = null, ?bool $paginationClientEnabled = null, ?bool $paginationClientItemsPerPage = null, ?bool $paginationClientPartial = null, - ?array $paginationViaCursor = null, - ?bool $paginationEnabled = null, ?bool $paginationFetchJoinCollection = null, ?bool $paginationUseOutputWalkers = null, - ?int $paginationItemsPerPage = null, - ?int $paginationMaximumItemsPerPage = null, - ?bool $paginationPartial = null, - ?string $paginationType = null, + ?array $paginationViaCursor = null, + ?array $order = null, + ?string $description = null, + ?array $normalizationContext = null, + ?array $denormalizationContext = null, ?string $security = null, ?string $securityMessage = null, ?string $securityPostDenormalize = null, ?string $securityPostDenormalizeMessage = null, ?string $securityPostValidation = null, ?string $securityPostValidationMessage = null, - ?bool $compositeIdentifier = null, - ?array $exceptionToStatus = null, - ?bool $queryParameterValidationEnabled = null, + ?string $deprecationReason = null, + ?array $filters = null, + ?array $validationContext = null, + $input = null, + $output = null, + $mercure = null, + $messenger = null, + ?bool $elasticsearch = null, + ?int $urlGenerationStrategy = null, ?bool $read = null, ?bool $deserialize = null, ?bool $validate = null, ?bool $write = null, ?bool $serialize = null, - // TODO: replace by queryParameterValidationEnabled? - ?bool $queryParameterValidate = null, + ?bool $fetchPartial = null, + ?bool $forceEager = null, ?int $priority = null, ?string $name = null, + ?string $provider = null, + ?string $processor = null, array $extraProperties = [] ) { parent::__construct(self::METHOD_POST, ...\func_get_args()); diff --git a/src/Metadata/Put.php b/src/Metadata/Put.php index cbb85309ae9..f76b6bad927 100644 --- a/src/Metadata/Put.php +++ b/src/Metadata/Put.php @@ -14,20 +14,18 @@ namespace ApiPlatform\Metadata; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] -final class Put extends Operation +final class Put extends HttpOperation { /** * {@inheritdoc} */ public function __construct( ?string $uriTemplate = null, - ?string $shortName = null, - ?string $description = null, ?array $types = null, - $formats = null, - $inputFormats = null, - $outputFormats = null, - $uriVariables = null, + $formats = null, + $inputFormats = null, + $outputFormats = null, + $uriVariables = null, ?string $routePrefix = null, ?string $routeName = null, ?array $defaults = null, @@ -36,60 +34,63 @@ public function __construct( ?bool $stateless = null, ?string $sunset = null, ?string $acceptPatch = null, - $status = null, + $status = null, ?string $host = null, ?array $schemes = null, ?string $condition = null, ?string $controller = null, - ?string $class = null, - ?int $urlGenerationStrategy = null, - ?bool $collection = null, - ?string $deprecationReason = null, ?array $cacheHeaders = null, - ?array $normalizationContext = null, - ?array $denormalizationContext = null, + ?array $hydraContext = null, ?array $openapiContext = null, - ?array $swaggerContext = null, - ?array $validationContext = null, - ?array $filters = null, - ?bool $elasticsearch = null, - $mercure = null, - $messenger = null, - $input = null, - $output = null, - ?array $order = null, - ?bool $fetchPartial = null, - ?bool $forceEager = null, + ?array $exceptionToStatus = null, + + ?bool $queryParameterValidationEnabled = null, + + // abstract operation arguments + ?string $shortName = null, + ?string $class = null, + ?bool $paginationEnabled = null, + ?string $paginationType = null, + ?int $paginationItemsPerPage = null, + ?int $paginationMaximumItemsPerPage = null, + ?bool $paginationPartial = null, ?bool $paginationClientEnabled = null, ?bool $paginationClientItemsPerPage = null, ?bool $paginationClientPartial = null, - ?array $paginationViaCursor = null, - ?bool $paginationEnabled = null, ?bool $paginationFetchJoinCollection = null, ?bool $paginationUseOutputWalkers = null, - ?int $paginationItemsPerPage = null, - ?int $paginationMaximumItemsPerPage = null, - ?bool $paginationPartial = null, - ?string $paginationType = null, + ?array $paginationViaCursor = null, + ?array $order = null, + ?string $description = null, + ?array $normalizationContext = null, + ?array $denormalizationContext = null, ?string $security = null, ?string $securityMessage = null, ?string $securityPostDenormalize = null, ?string $securityPostDenormalizeMessage = null, ?string $securityPostValidation = null, ?string $securityPostValidationMessage = null, - ?bool $compositeIdentifier = null, - ?array $exceptionToStatus = null, - ?bool $queryParameterValidationEnabled = null, + ?string $deprecationReason = null, + ?array $filters = null, + ?array $validationContext = null, + $input = null, + $output = null, + $mercure = null, + $messenger = null, + ?bool $elasticsearch = null, + ?int $urlGenerationStrategy = null, ?bool $read = null, ?bool $deserialize = null, ?bool $validate = null, ?bool $write = null, ?bool $serialize = null, - // TODO: replace by queryParameterValidationEnabled? - ?bool $queryParameterValidate = null, + ?bool $fetchPartial = null, + ?bool $forceEager = null, ?int $priority = null, ?string $name = null, + ?string $provider = null, + ?string $processor = null, array $extraProperties = [] ) { parent::__construct(self::METHOD_PUT, ...\func_get_args()); diff --git a/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php index 89cd298fb72..367958190d1 100644 --- a/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php @@ -16,6 +16,7 @@ use ApiPlatform\Exception\ResourceClassNotFoundException; use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; @@ -24,7 +25,7 @@ use ApiPlatform\Metadata\GraphQl\Query; use ApiPlatform\Metadata\GraphQl\QueryCollection; use ApiPlatform\Metadata\GraphQl\Subscription; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; @@ -107,7 +108,7 @@ private function buildResourceOperations(array $attributes, string $resourceClas continue; } - if (!is_subclass_of($attribute->getName(), Operation::class) && !is_subclass_of($attribute->getName(), GraphQlOperation::class)) { + if (!is_subclass_of($attribute->getName(), HttpOperation::class) && !is_subclass_of($attribute->getName(), GraphQlOperation::class)) { continue; } @@ -167,7 +168,7 @@ private function buildResourceOperations(array $attributes, string $resourceClas } /** - * @param Operation|GraphQlOperation $operation + * @param HttpOperation|GraphQlOperation $operation */ private function getOperationWithDefaults(ApiResource $resource, $operation): array { @@ -186,7 +187,7 @@ private function getOperationWithDefaults(ApiResource $resource, $operation): ar } // TODO: remove in 3.0 - if ($operation instanceof Operation && 'getUriVariables' === $methodName && !$operation->getUriTemplate() && $operation->isCollection() && !$operation->getUriVariables()) { + if ($operation instanceof HttpOperation && 'getUriVariables' === $methodName && !$operation->getUriTemplate() && $operation instanceof CollectionOperationInterface && !$operation->getUriVariables()) { trigger_deprecation('api-platform', '2.7', 'Identifiers are declared on the default #[ApiResource] but you did not specify identifiers on the collection operation. In 3.0 the collection operations can have identifiers, you should specify identifiers on the operation not on the resource to avoid unwanted behavior.'); continue; } @@ -206,9 +207,9 @@ private function getOperationWithDefaults(ApiResource $resource, $operation): ar $operation = $operation->withDescription(ucfirst("{$operation->getName()}s a {$resource->getShortName()}.")); } - if ('delete' === $operation->getName()) { - $operation = $operation->withDelete(true); - } + // if ('delete' === $operation->getName()) { + // $operation = $operation->withDelete(true); + // } return [$operation->getName(), $operation]; } @@ -228,13 +229,13 @@ private function getOperationWithDefaults(ApiResource $resource, $operation): ar } return [ - sprintf('_api_%s_%s%s', $operation->getUriTemplate() ?: $operation->getShortName(), strtolower($operation->getMethod() ?? Operation::METHOD_GET), $operation instanceof GetCollection ? '_collection' : ''), + sprintf('_api_%s_%s%s', $operation->getUriTemplate() ?: $operation->getShortName(), strtolower($operation->getMethod() ?? HttpOperation::METHOD_GET), $operation instanceof GetCollection ? '_collection' : ''), $operation, ]; } /** - * @param ApiResource|Operation|GraphQlOperation $operation + * @param ApiResource|HttpOperation|GraphQlOperation $operation */ private function addGlobalDefaults($operation) { @@ -262,7 +263,7 @@ private function getResourceWithDefaults(string $resourceClass, string $shortNam private function hasResourceAttributes(\ReflectionClass $reflectionClass): bool { foreach ($reflectionClass->getAttributes() as $attribute) { - if (ApiResource::class === $attribute->getName() || is_subclass_of($attribute->getName(), Operation::class) || is_subclass_of($attribute->getName(), GraphQlOperation::class)) { + if (ApiResource::class === $attribute->getName() || is_subclass_of($attribute->getName(), HttpOperation::class) || is_subclass_of($attribute->getName(), GraphQlOperation::class)) { return true; } } @@ -278,7 +279,7 @@ private function hasResourceAttributes(\ReflectionClass $reflectionClass): bool * #[Get(uriTemplate: '/alternate')] * class Example {} */ - private function hasSameOperation(ApiResource $resource, string $operationClass, Operation $operation): bool + private function hasSameOperation(ApiResource $resource, string $operationClass, HttpOperation $operation): bool { foreach ($resource->getOperations() ?? [] as $o) { if ($o instanceof $operationClass && $operation->getUriTemplate() === $o->getUriTemplate() && $operation->getName() === $o->getName() && $operation->getRouteName() === $o->getRouteName()) { diff --git a/src/Metadata/Resource/Factory/AttributesResourceNameCollectionFactory.php b/src/Metadata/Resource/Factory/AttributesResourceNameCollectionFactory.php index 627723cb504..7848ec7a073 100644 --- a/src/Metadata/Resource/Factory/AttributesResourceNameCollectionFactory.php +++ b/src/Metadata/Resource/Factory/AttributesResourceNameCollectionFactory.php @@ -15,7 +15,7 @@ use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Resource\ResourceNameCollection; use ApiPlatform\Util\ReflectionClassRecursiveIterator; @@ -66,7 +66,7 @@ private function isResource(\ReflectionClass $reflectionClass): bool return true; } - if ($reflectionClass->getAttributes(Operation::class, \ReflectionAttribute::IS_INSTANCEOF) || $reflectionClass->getAttributes(GraphQlOperation::class, \ReflectionAttribute::IS_INSTANCEOF)) { + if ($reflectionClass->getAttributes(HttpOperation::class, \ReflectionAttribute::IS_INSTANCEOF) || $reflectionClass->getAttributes(GraphQlOperation::class, \ReflectionAttribute::IS_INSTANCEOF)) { return true; } diff --git a/src/Metadata/Resource/Factory/ExtractorResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/ExtractorResourceMetadataCollectionFactory.php index 9eed43dfda3..6ba2c98ce24 100644 --- a/src/Metadata/Resource/Factory/ExtractorResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/ExtractorResourceMetadataCollectionFactory.php @@ -14,11 +14,12 @@ namespace ApiPlatform\Metadata\Resource\Factory; use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Extractor\ResourceExtractorInterface; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; @@ -109,7 +110,7 @@ private function buildOperations(?array $data, ApiResource $resource): array if (null === $data) { foreach ([new Get(), new GetCollection(), new Post(), new Put(), new Patch(), new Delete()] as $operation) { - $operationName = sprintf('_api_%s_%s%s', $resource->getShortName(), strtolower($operation->getMethod()), $operation->isCollection() ? '_collection' : ''); + $operationName = sprintf('_api_%s_%s%s', $resource->getShortName(), strtolower($operation->getMethod()), $operation instanceof CollectionOperationInterface ? '_collection' : ''); $operations[$operationName] = $this->getOperationWithDefaults($resource, $operation)->withName($operationName); } @@ -121,7 +122,7 @@ private function buildOperations(?array $data, ApiResource $resource): array throw new \InvalidArgumentException(sprintf('Operation "%s" does not exist.', $attributes['class'])); } - /** @var Operation $operation */ + /** @var HttpOperation $operation */ $operation = (new $attributes['class']())->withShortName($resource->getShortName()); unset($attributes['class']); foreach ($attributes as $key => $value) { @@ -141,7 +142,7 @@ private function buildOperations(?array $data, ApiResource $resource): array } if (empty($attributes['name'])) { - $attributes['name'] = sprintf('_api_%s_%s%s', $operation->getUriTemplate() ?: $operation->getShortName(), strtolower($operation->getMethod()), $operation->isCollection() ? '_collection' : ''); + $attributes['name'] = sprintf('_api_%s_%s%s', $operation->getUriTemplate() ?: $operation->getShortName(), strtolower($operation->getMethod()), $operation instanceof CollectionOperationInterface ? '_collection' : ''); } $operations[$attributes['name']] = $this->getOperationWithDefaults($resource, $operation)->withName($attributes['name']); } @@ -154,7 +155,7 @@ private function buildGraphQlOperations(?array $data, ApiResource $resource): ar $operations = []; foreach ($data as $attributes) { - /** @var Operation $operation */ + /** @var HttpOperation $operation */ $operation = (new $attributes['graphql_operation_class']())->withShortName($resource->getShortName()); unset($attributes['graphql_operation_class']); @@ -180,7 +181,7 @@ private function buildGraphQlOperations(?array $data, ApiResource $resource): ar return $operations; } - private function getOperationWithDefaults(ApiResource $resource, Operation $operation): Operation + private function getOperationWithDefaults(ApiResource $resource, HttpOperation $operation): HttpOperation { foreach (($this->defaults['attributes'] ?? []) as $key => $value) { [$key, $value] = $this->getKeyValue($key, $value); diff --git a/src/Metadata/Resource/Factory/FormatsResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/FormatsResourceMetadataCollectionFactory.php index 48421c64690..dc57b5f2adc 100644 --- a/src/Metadata/Resource/Factory/FormatsResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/FormatsResourceMetadataCollectionFactory.php @@ -15,7 +15,8 @@ use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; @@ -76,7 +77,7 @@ private function normalize(array $resourceInputFormats, array $resourceOutputFor $operation = $operation->withFormats($this->normalizeFormats($operation->getFormats())); } - if (($isPatch = Operation::METHOD_PATCH === $operation->getMethod()) && !$operation->getFormats() && !$operation->getInputFormats()) { + if (($isPatch = HttpOperation::METHOD_PATCH === $operation->getMethod()) && !$operation->getFormats() && !$operation->getInputFormats()) { $operation = $operation->withInputFormats($this->patchFormats); } @@ -96,7 +97,7 @@ private function normalize(array $resourceInputFormats, array $resourceOutputFor // Prepare an Accept-Patch header foreach ($newOperations as $operationName => $operation) { - if ($operation->isCollection()) { + if ($operation instanceof CollectionOperationInterface) { continue; } diff --git a/src/Metadata/Resource/Factory/InputOutputResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/InputOutputResourceMetadataCollectionFactory.php index 949ac8b371a..89c0e4da276 100644 --- a/src/Metadata/Resource/Factory/InputOutputResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/InputOutputResourceMetadataCollectionFactory.php @@ -14,7 +14,7 @@ namespace ApiPlatform\Metadata\Resource\Factory; use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; @@ -79,7 +79,7 @@ private function getTransformedOperations($operations, ApiResource $resourceMeta } if ( - $operation instanceof Operation + $operation instanceof HttpOperation && $operation->getOutput() && \array_key_exists('class', $operation->getOutput()) && null === $operation->getOutput()['class'] diff --git a/src/Metadata/Resource/Factory/LegacyResourceMetadataResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/LegacyResourceMetadataResourceMetadataCollectionFactory.php index 686f5f1653e..920a8aade01 100644 --- a/src/Metadata/Resource/Factory/LegacyResourceMetadataResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/LegacyResourceMetadataResourceMetadataCollectionFactory.php @@ -20,13 +20,18 @@ use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Exception\ResourceClassNotFoundException; use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\GraphQl\Mutation; use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; use ApiPlatform\Metadata\GraphQl\Query; +use ApiPlatform\Metadata\GraphQl\QueryCollection; use ApiPlatform\Metadata\GraphQl\Subscription; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Link; -use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Operations; +use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Resource\DeprecationMetadataTrait; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; @@ -77,7 +82,6 @@ public function create(string $resourceClass): ResourceMetadataCollection $resource = (new ApiResource()) ->withShortName($resourceMetadata->getShortName()) ->withClass($resourceClass) - ->withCompositeIdentifier($resourceMetadata->getAttribute('composite_identifier', true)) ->withExtraProperties(['is_legacy_resource_metadata' => true]); if ($description = $resourceMetadata->getDescription()) { @@ -115,9 +119,8 @@ public function create(string $resourceClass): ResourceMetadataCollection $graphQlOperations = []; foreach ($resourceMetadata->getGraphql() as $operationName => $operation) { if (false !== strpos($operationName, 'query') || isset($operation['item_query']) || isset($operation['collection_query'])) { - $graphQlOperation = (new Query()) - ->withCollection(isset($operation['collection_query']) || false !== strpos($operationName, 'collection')) - ->withName($operationName); + $graphQlOperation = isset($operation['collection_query']) || false !== strpos($operationName, 'collection') ? new QueryCollection() : new Query(); + $graphQlOperation = $graphQlOperation->withName($operationName); } else { $graphQlOperation = (new Mutation()) ->withDescription(ucfirst("{$operationName}s a {$resourceMetadata->getShortName()}.")) @@ -154,20 +157,25 @@ private function createOperations(array $operations, string $type, ApiResource $ { $priority = 0; foreach ($operations as $operationName => $operation) { - $newOperation = (new Operation()) - ->withMethod($operation['method']) - ->withCollection(OperationType::COLLECTION === $type) - ->withCompositeIdentifier($resource->getCompositeIdentifier()) + $newOperation = OperationType::COLLECTION === $type ? new GetCollection() : new HttpOperation(); + + $newOperation = $newOperation->withMethod($operation['method']) ->withClass($resource->getClass()) ->withPriority($priority++); + if (HttpOperation::METHOD_DELETE === $operation['method']) { + $newOperation = (new Delete())->withOperation($newOperation); + } elseif (HttpOperation::METHOD_POST === $operation['method'] && !isset($operation['path'])) { + $newOperation = (new Post())->withOperation($newOperation)->withUriVariables([])->withRead(false); + } + foreach ($operation as $key => $value) { $newOperation = $this->setAttributeValue($newOperation, $key, $value); } $newOperation = $newOperation->withResource($resource); - if ($newOperation->isCollection()) { + if ($newOperation instanceof CollectionOperationInterface) { $newOperation = $newOperation->withUriVariables([]); } @@ -178,15 +186,15 @@ private function createOperations(array $operations, string $type, ApiResource $ } /** - * @param Operation|GraphQlOperation|ApiResource $operation - * @param mixed $value + * @param HttpOperation|GraphQlOperation|ApiResource $operation + * @param mixed $value * - * @return Operation|GraphQlOperation|ApiResource + * @return HttpOperation|GraphQlOperation|ApiResource */ private function setAttributeValue($operation, string $key, $value) { if ('identifiers' === $key) { - if (!$operation instanceof ApiResource && $operation->isCollection()) { + if (!$operation instanceof ApiResource && $operation instanceof CollectionOperationInterface) { return $operation; } @@ -218,9 +226,9 @@ private function setAttributeValue($operation, string $key, $value) } /** - * @param ApiResource|Operation $resource + * @param ApiResource|HttpOperation $resource * - * @return ApiResource|Operation + * @return ApiResource|HttpOperation */ private function identifiersToUriVariables(ResourceMetadata $resourceMetadata, $resource) { diff --git a/src/Metadata/Resource/Factory/LegacySubresourceMetadataResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/LegacySubresourceMetadataResourceMetadataCollectionFactory.php index 934c2c3385a..57314f426ce 100644 --- a/src/Metadata/Resource/Factory/LegacySubresourceMetadataResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/LegacySubresourceMetadataResourceMetadataCollectionFactory.php @@ -16,9 +16,10 @@ use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Link; -use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Resource\DeprecationMetadataTrait; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; @@ -74,7 +75,7 @@ public function create(string $resourceClass): ResourceMetadataCollection } $operationValue = $operation->{$methodName}(); - if (null !== $operationValue && [] !== $operationValue) { + if (null !== $operationValue) { continue; } @@ -103,12 +104,18 @@ private function computeSubresourceCache() $identifiers = []; // Removing the third tuple element + $previousParameterName = null; foreach ($subresourceMetadata['identifiers'] as $parameterName => [$class, $property, $isPresent]) { if (!$isPresent) { continue; } - $identifiers[$parameterName] = (new Link())->withFromClass($class)->withIdentifiers([$property])->withParameterName($parameterName); + $identifiers[$parameterName] = (new Link())->withFromClass($class)->withIdentifiers([$property])->withParameterName($parameterName)->withCompositeIdentifier(false); + if ($previousParameterName) { + $identifiers[$previousParameterName] = $identifiers[$previousParameterName]->withFromProperty($parameterName); + } + + $previousParameterName = $parameterName; } $extraProperties = ['is_legacy_subresource' => true]; @@ -121,8 +128,8 @@ private function computeSubresourceCache() unset($subresourceMetadata['identifiers']); } - $resource = (new ApiResource())->withExtraProperties($extraProperties)->withCompositeIdentifier(false)->withUriVariables($identifiers)->withStateless(false); - $operation = (new Get())->withExtraProperties($extraProperties + ['legacy_subresource_operation_name' => $subresourceMetadata['route_name']])->withCompositeIdentifier(false)->withUriVariables($identifiers); + $resource = (new ApiResource())->withExtraProperties($extraProperties)->withUriVariables($identifiers)->withStateless(false); + $operation = ($subresourceMetadata['collection'] ? new GetCollection() : new Get())->withExtraProperties($extraProperties + ['legacy_subresource_operation_name' => $subresourceMetadata['route_name']])->withUriVariables($identifiers); if ($subresourceMetadata['path']) { $resource = $resource->withUriTemplate($subresourceMetadata['path']); @@ -139,10 +146,6 @@ private function computeSubresourceCache() $operation = $operation->withClass($subresourceMetadata['resource_class']); } - if ($subresourceMetadata['collection']) { - $operation = $operation->withCollection($subresourceMetadata['collection']); - } - foreach ($subresourceMetadata as $key => $value) { if ('route_name' === $key) { continue; @@ -165,10 +168,10 @@ private function computeSubresourceCache() } /** - * @param Operation|GraphQlOperation|ApiResource $operation - * @param mixed $value + * @param HttpOperation|GraphQlOperation|ApiResource $operation + * @param mixed $value * - * @return Operation|GraphQlOperation|ApiResource + * @return HttpOperation|GraphQlOperation|ApiResource */ private function setAttributeValue($operation, string $key, $value) { diff --git a/src/Metadata/Resource/Factory/LinkFactory.php b/src/Metadata/Resource/Factory/LinkFactory.php index c1352bcaa32..5a156c3b0d2 100644 --- a/src/Metadata/Resource/Factory/LinkFactory.php +++ b/src/Metadata/Resource/Factory/LinkFactory.php @@ -46,19 +46,6 @@ public function createLinksFromIdentifiers($operation): array return []; } - if (!($operation->getCompositeIdentifier() ?? true)) { - $links = []; - foreach ($identifiers as $identifier) { - $links[] = (new Link()) - ->withFromClass($resourceClass) - ->withParameterName($identifier) - ->withIdentifiers([$identifier]) - ->withCompositeIdentifier(false); - } - - return $links; - } - $link = (new Link())->withFromClass($resourceClass)->withIdentifiers($identifiers); $parameterName = $identifiers[0]; diff --git a/src/Metadata/Resource/Factory/LinkFactoryInterface.php b/src/Metadata/Resource/Factory/LinkFactoryInterface.php index a1abce47a64..f2bbdbb31fe 100644 --- a/src/Metadata/Resource/Factory/LinkFactoryInterface.php +++ b/src/Metadata/Resource/Factory/LinkFactoryInterface.php @@ -15,8 +15,8 @@ use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Link; -use ApiPlatform\Metadata\Operation; /** * @internal @@ -26,7 +26,7 @@ interface LinkFactoryInterface /** * Create Links by using the resource class identifiers. * - * @param ApiResource|Operation|GraphQlOperation $operation + * @param ApiResource|HttpOperation|GraphQlOperation $operation * * @return Link[] */ @@ -35,7 +35,7 @@ public function createLinksFromIdentifiers($operation); /** * Create Links from the relations metadata information. * - * @param ApiResource|Operation|GraphQlOperation $operation + * @param ApiResource|HttpOperation|GraphQlOperation $operation * * @return Link[] */ @@ -44,7 +44,7 @@ public function createLinksFromRelations($operation); /** * Create Links by using PHP attribute Links found on properties. * - * @param ApiResource|Operation|GraphQlOperation $operation + * @param ApiResource|HttpOperation|GraphQlOperation $operation * * @return Link[] */ diff --git a/src/Metadata/Resource/Factory/OperationNameResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/OperationNameResourceMetadataCollectionFactory.php index 910dc00efa7..f7b9f7a5e8e 100644 --- a/src/Metadata/Resource/Factory/OperationNameResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/OperationNameResourceMetadataCollectionFactory.php @@ -13,7 +13,8 @@ namespace ApiPlatform\Metadata\Resource\Factory; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; /** @@ -55,7 +56,7 @@ public function create(string $resourceClass): ResourceMetadataCollection continue; } - $newOperationName = sprintf('_api_%s_%s%s', $operation->getUriTemplate() ?: $operation->getShortName(), strtolower($operation->getMethod() ?? Operation::METHOD_GET), $operation->isCollection() ? '_collection' : ''); + $newOperationName = sprintf('_api_%s_%s%s', $operation->getUriTemplate() ?: $operation->getShortName(), strtolower($operation->getMethod() ?? HttpOperation::METHOD_GET), $operation instanceof CollectionOperationInterface ? '_collection' : ''); // TODO: remove in 3.0 this is used in the IRI converter to avoid a bc break if (($extraProperties = $operation->getExtraProperties()) && isset($extraProperties['is_legacy_subresource'])) { diff --git a/src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.php index 6e015dc4f89..43abbee0f52 100644 --- a/src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.php @@ -14,8 +14,9 @@ namespace ApiPlatform\Metadata\Resource\Factory; use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Link; -use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; @@ -58,7 +59,7 @@ public function create(string $resourceClass): ResourceMetadataCollection $operations = new Operations(); foreach ($resource->getOperations() ?? new Operations() as $key => $operation) { - /** @var Operation */ + /** @var HttpOperation */ $operation = $this->configureUriVariables($operation); if ($operation->getUriTemplate()) { @@ -81,7 +82,7 @@ public function create(string $resourceClass): ResourceMetadataCollection } $operation = $operation->withUriTemplate($this->generateUriTemplate($operation)); - $operationName = $operation->getName() ?: sprintf('_api_%s_%s%s', $operation->getUriTemplate(), strtolower($operation->getMethod() ?? Operation::METHOD_GET), $operation->isCollection() ? '_collection' : ''); + $operationName = $operation->getName() ?: sprintf('_api_%s_%s%s', $operation->getUriTemplate(), strtolower($operation->getMethod() ?? HttpOperation::METHOD_GET), $operation instanceof CollectionOperationInterface ? '_collection' : ''); if (!$operation->getName()) { $operation = $operation->withName($operationName); } @@ -96,7 +97,7 @@ public function create(string $resourceClass): ResourceMetadataCollection return $resourceMetadataCollection; } - private function generateUriTemplate(Operation $operation): string + private function generateUriTemplate(HttpOperation $operation): string { $uriTemplate = sprintf('/%s', $this->pathSegmentNameGenerator->getSegmentName($operation->getShortName())); $uriVariables = $operation->getUriVariables() ?? []; @@ -115,14 +116,17 @@ private function generateUriTemplate(Operation $operation): string } /** - * @param ApiResource|Operation $operation + * @param ApiResource|HttpOperation $operation * - * @return ApiResource|Operation + * @return ApiResource|HttpOperation */ private function configureUriVariables($operation) { // We will generate the collection route, don't initialize variables here - if ($operation instanceof Operation && $operation->isCollection() && !$operation->getUriTemplate()) { + if ($operation instanceof HttpOperation && ( + [] === $operation->getUriVariables() || + ($operation instanceof CollectionOperationInterface && null === $operation->getUriTemplate()) + )) { return $operation; } @@ -168,9 +172,9 @@ private function configureUriVariables($operation) } /** - * @param ApiResource|Operation $operation + * @param ApiResource|HttpOperation $operation * - * @return ApiResource|Operation + * @return ApiResource|HttpOperation */ private function normalizeUriVariables($operation) { @@ -198,9 +202,6 @@ private function normalizeUriVariables($operation) $normalizedUriVariable = new Link($normalizedParameterName, $normalizedUriVariable['from_property'] ?? null, $normalizedUriVariable['to_property'] ?? null, $normalizedUriVariable['from_class'] ?? null, $normalizedUriVariable['to_class'] ?? null, $normalizedUriVariable['identifiers'] ?? null, $normalizedUriVariable['composite_identifier'] ?? null, $normalizedUriVariable['expanded_value'] ?? null); } } - if (null !== ($hasCompositeIdentifier = $operation->getCompositeIdentifier())) { - $normalizedUriVariable = $normalizedUriVariable->withCompositeIdentifier($hasCompositeIdentifier); - } $normalizedUriVariable = $normalizedUriVariable->withParameterName($normalizedParameterName); $normalizedUriVariables[$normalizedParameterName] = $normalizedUriVariable; diff --git a/src/Metadata/Resource/ResourceMetadataCollection.php b/src/Metadata/Resource/ResourceMetadataCollection.php index 07a18b3d098..401aae0f5ae 100644 --- a/src/Metadata/Resource/ResourceMetadataCollection.php +++ b/src/Metadata/Resource/ResourceMetadataCollection.php @@ -15,7 +15,8 @@ use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; +use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operation; /** @@ -34,9 +35,9 @@ public function __construct(string $resourceClass, array $input = []) } /** - * @return Operation|GraphQlOperation + * @return Operation */ - public function getOperation(?string $operationName = null, bool $forceCollection = false) + public function getOperation(?string $operationName = null, bool $forceCollection = false, bool $httpOperation = false) { if (isset($this->operationCache[$operationName ?? ''])) { return $this->operationCache[$operationName ?? '']; @@ -55,7 +56,8 @@ public function getOperation(?string $operationName = null, bool $forceCollectio $metadata = $it->current(); foreach ($metadata->getOperations() ?? [] as $name => $operation) { - if ('' === $operationName && \in_array($operation->getMethod() ?? Operation::METHOD_GET, [Operation::METHOD_GET, Operation::METHOD_OPTIONS, Operation::METHOD_HEAD], true) && ($forceCollection ? $operation->isCollection() : !$operation->isCollection())) { + $isCollection = $operation instanceof CollectionOperationInterface; + if ('' === $operationName && \in_array($operation->getMethod() ?? HttpOperation::METHOD_GET, [HttpOperation::METHOD_GET, HttpOperation::METHOD_OPTIONS, HttpOperation::METHOD_HEAD], true) && ($forceCollection ? $isCollection : !$isCollection)) { return $this->operationCache[$operationName] = $operation; } @@ -65,6 +67,11 @@ public function getOperation(?string $operationName = null, bool $forceCollectio } foreach ($metadata->getGraphQlOperations() ?? [] as $name => $operation) { + $isCollection = $operation instanceof CollectionOperationInterface; + if ('' === $operationName && ($forceCollection ? $isCollection : !$isCollection) && false === $httpOperation) { + return $this->operationCache['graphql_'.$operationName] = $operation; + } + if ($name === $operationName) { return $this->operationCache['graphql_'.$operationName] = $operation; } diff --git a/src/Metadata/WithResourceTrait.php b/src/Metadata/WithResourceTrait.php index 0c0b9ebfbe6..78714bd03e7 100644 --- a/src/Metadata/WithResourceTrait.php +++ b/src/Metadata/WithResourceTrait.php @@ -13,8 +13,6 @@ namespace ApiPlatform\Metadata; -use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; - trait WithResourceTrait { public function withResource(ApiResource $resource): self @@ -23,11 +21,11 @@ public function withResource(ApiResource $resource): self } /** - * @param ApiResource|Operation|GraphQlOperation $resource + * @param ApiResource|Operation $resource * - * @return ApiResource|Operation|GraphQlOperation + * @return ApiResource|Operation */ - private function copyFrom($resource) + protected function copyFrom($resource) { $self = clone $this; foreach (get_class_methods($resource) as $method) { diff --git a/src/OpenApi/Factory/OpenApiFactory.php b/src/OpenApi/Factory/OpenApiFactory.php index b0f7163201f..86441fdadc9 100644 --- a/src/OpenApi/Factory/OpenApiFactory.php +++ b/src/OpenApi/Factory/OpenApiFactory.php @@ -20,7 +20,8 @@ use ApiPlatform\JsonSchema\SchemaFactoryInterface; use ApiPlatform\JsonSchema\TypeFactoryInterface; use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; @@ -149,7 +150,7 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection } $path = $this->getPath($routeName && $this->routeCollection ? $this->routeCollection->get($routeName)->getPath() : ($operation->getRoutePrefix() ?? '').$operation->getUriTemplate()); - $method = $operation->getMethod() ?? Operation::METHOD_GET; + $method = $operation->getMethod() ?? HttpOperation::METHOD_GET; if (!\in_array($method, Model\PathItem::$methods, true)) { continue; @@ -165,7 +166,7 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection $pathItem = new Model\PathItem(); } - $forceSchemaCollection = $operation->isCollection() && 'GET' === $method ? true : false; + $forceSchemaCollection = $operation instanceof CollectionOperationInterface && 'GET' === $method ? true : false; $schema = new Schema('openapi'); $schema->setDefinitions($schemas); @@ -196,7 +197,7 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection $parameters[] = $parameter; } - if ($operation->isCollection() && Operation::METHOD_POST !== $method) { + if ($operation instanceof CollectionOperationInterface && HttpOperation::METHOD_POST !== $method) { foreach (array_merge($this->getPaginationParameters($operation), $this->getFiltersParameters($operation)) as $parameter) { if ($this->hasParameter($parameter, $parameters)) { continue; @@ -208,12 +209,12 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection // Create responses switch ($method) { - case Operation::METHOD_GET: + case HttpOperation::METHOD_GET: $successStatus = (string) $operation->getStatus() ?: 200; $responseContent = $this->buildContent($responseMimeTypes, $operationOutputSchemas); - $responses[$successStatus] = new Model\Response(sprintf('%s %s', $resourceShortName, $operation->isCollection() ? 'collection' : 'resource'), $responseContent); + $responses[$successStatus] = new Model\Response(sprintf('%s %s', $resourceShortName, $operation instanceof CollectionOperationInterface ? 'collection' : 'resource'), $responseContent); break; - case Operation::METHOD_POST: + case HttpOperation::METHOD_POST: $responseLinks = $this->getLinks($resourceMetadataCollection, $operation); $responseContent = $this->buildContent($responseMimeTypes, $operationOutputSchemas); $successStatus = (string) $operation->getStatus() ?: 201; @@ -221,8 +222,8 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection $responses['400'] = new Model\Response('Invalid input'); $responses['422'] = new Model\Response('Unprocessable entity'); break; - case Operation::METHOD_PATCH: - case Operation::METHOD_PUT: + case HttpOperation::METHOD_PATCH: + case HttpOperation::METHOD_PUT: $responseLinks = $this->getLinks($resourceMetadataCollection, $operation); $successStatus = (string) $operation->getStatus() ?: 200; $responseContent = $this->buildContent($responseMimeTypes, $operationOutputSchemas); @@ -230,13 +231,13 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection $responses['400'] = new Model\Response('Invalid input'); $responses['422'] = new Model\Response('Unprocessable entity'); break; - case Operation::METHOD_DELETE: + case HttpOperation::METHOD_DELETE: $successStatus = (string) $operation->getStatus() ?: 204; $responses[$successStatus] = new Model\Response(sprintf('%s resource deleted', $resourceShortName)); break; } - if (!$operation->isCollection() && !$operation instanceof Post) { + if (!$operation instanceof CollectionOperationInterface && !$operation instanceof Post) { $responses['404'] = new Model\Response('Resource not found'); } @@ -253,7 +254,7 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection $requestBody = null; if ($contextRequestBody = $operation->getOpenapiContext()['requestBody'] ?? false) { $requestBody = new Model\RequestBody($contextRequestBody['description'] ?? '', new \ArrayObject($contextRequestBody['content']), $contextRequestBody['required'] ?? false); - } elseif (\in_array($method, [Operation::METHOD_PATCH, Operation::METHOD_PUT, Operation::METHOD_POST], true)) { + } elseif (\in_array($method, [HttpOperation::METHOD_PATCH, HttpOperation::METHOD_PUT, HttpOperation::METHOD_POST], true)) { $operationInputSchemas = []; foreach ($requestMimeTypes as $operationFormat) { $operationInputSchema = $this->jsonSchemaFactory->buildSchema($resourceClass, $operationFormat, Schema::TYPE_INPUT, null, $operationName, $schema, null, $forceSchemaCollection); @@ -261,15 +262,15 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection $this->appendSchemaDefinitions($schemas, $operationInputSchema->getDefinitions()); } - $requestBody = new Model\RequestBody(sprintf('The %s %s resource', Operation::METHOD_POST === $method ? 'new' : 'updated', $resourceShortName), $this->buildContent($requestMimeTypes, $operationInputSchemas), true); + $requestBody = new Model\RequestBody(sprintf('The %s %s resource', HttpOperation::METHOD_POST === $method ? 'new' : 'updated', $resourceShortName), $this->buildContent($requestMimeTypes, $operationInputSchemas), true); } $pathItem = $pathItem->{'with'.ucfirst($method)}(new Model\Operation( $operationId, $operation->getOpenapiContext()['tags'] ?? [$operation->getShortName() ?: $resourceShortName], $responses, - $operation->getOpenapiContext()['summary'] ?? $this->getPathDescription($resourceShortName, $method, $operation->isCollection() ?? false), - $operation->getOpenapiContext()['description'] ?? $this->getPathDescription($resourceShortName, $method, $operation->isCollection() ?? false), + $operation->getOpenapiContext()['summary'] ?? $this->getPathDescription($resourceShortName, $method, $operation instanceof CollectionOperationInterface), + $operation->getOpenapiContext()['description'] ?? $this->getPathDescription($resourceShortName, $method, $operation instanceof CollectionOperationInterface), isset($operation->getOpenapiContext()['externalDocs']) ? new ExternalDocumentation($operation->getOpenapiContext()['externalDocs']['description'] ?? null, $operation->getOpenapiContext()['externalDocs']['url']) : null, $parameters, $requestBody, @@ -297,7 +298,7 @@ private function buildContent(array $responseMimeTypes, array $operationSchemas) return $content; } - private function getMimeTypes(Operation $operation): array + private function getMimeTypes(HttpOperation $operation): array { $requestFormats = $operation->getInputFormats() ?: []; $responseFormats = $operation->getOutputFormats() ?: []; @@ -367,7 +368,7 @@ private function getPathDescription(string $resourceShortName, string $method, b * * return Model\Link[] */ - private function getLinks(ResourceMetadataCollection $resourceMetadataCollection, Operation $currentOperation): \ArrayObject + private function getLinks(ResourceMetadataCollection $resourceMetadataCollection, HttpOperation $currentOperation): \ArrayObject { $links = new \ArrayObject(); @@ -376,7 +377,7 @@ private function getLinks(ResourceMetadataCollection $resourceMetadataCollection foreach ($resourceMetadataCollection as $resource) { foreach ($resource->getOperations() as $operationName => $operation) { $parameters = []; - if ($operationName === $operation->getName() || isset($links[$operationName]) || $operation->isCollection() || Operation::METHOD_GET !== ($operation->getMethod() ?: Operation::METHOD_GET)) { + if ($operationName === $operation->getName() || isset($links[$operationName]) || $operation instanceof CollectionOperationInterface || HttpOperation::METHOD_GET !== ($operation->getMethod() ?: HttpOperation::METHOD_GET)) { continue; } @@ -416,7 +417,7 @@ private function getLinks(ResourceMetadataCollection $resourceMetadataCollection /** * Gets parameters corresponding to enabled filters. */ - private function getFiltersParameters(Operation $operation): array + private function getFiltersParameters(HttpOperation $operation): array { $parameters = []; @@ -454,7 +455,7 @@ private function getFiltersParameters(Operation $operation): array return $parameters; } - private function getPaginationParameters(Operation $operation): array + private function getPaginationParameters(HttpOperation $operation): array { if (!$this->paginationOptions->isPaginationEnabled()) { return []; diff --git a/src/Serializer/SerializerContextBuilder.php b/src/Serializer/SerializerContextBuilder.php index 350b9924b50..b2502023ca0 100644 --- a/src/Serializer/SerializerContextBuilder.php +++ b/src/Serializer/SerializerContextBuilder.php @@ -18,6 +18,7 @@ use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer; use ApiPlatform\Exception\RuntimeException; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Util\RequestAttributesExtractor; use Symfony\Component\HttpFoundation\Request; @@ -63,7 +64,7 @@ public function createFromRequest(Request $request, bool $normalization, array $ // TODO: 3.0 becomes true by default $context['skip_null_values'] = $context['skip_null_values'] ?? $this->shouldSkipNullValues($attributes['resource_class'], $context['operation_name']); // TODO: remove in 3.0, operation type will not exist anymore - $context['operation_type'] = $operation->isCollection() ? OperationType::COLLECTION : OperationType::ITEM; + $context['operation_type'] = $operation instanceof CollectionOperationInterface ? OperationType::COLLECTION : OperationType::ITEM; $context['iri_only'] = $context['iri_only'] ?? false; $context['request_uri'] = $request->getRequestUri(); $context['uri'] = $request->getUri(); diff --git a/src/State/CallableProcessor.php b/src/State/CallableProcessor.php new file mode 100644 index 00000000000..3a950f8fedf --- /dev/null +++ b/src/State/CallableProcessor.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\State; + +use ApiPlatform\Core\Exception\RuntimeException; +use ApiPlatform\Metadata\Operation; +use Psr\Container\ContainerInterface; + +final class CallableProcessor implements ProcessorInterface +{ + private $locator; + + public function __construct(ContainerInterface $locator) + { + $this->locator = $locator; + } + + /** + * {@inheritDoc} + */ + public function process($data, Operation $operation, array $uriVariables = [], array $context = []) + { + if (!($processor = $operation->getProcessor())) { + return; + } + + if (\is_callable($processor)) { + return $processor($data, $operation, $uriVariables, $context); + } + + if (\is_string($processor)) { + if (!$this->locator->has($processor)) { + throw new RuntimeException(sprintf('Processor "%s" not found on operation "%s"', $processor, $operation->getName())); + } + + /** @var ProcessorInterface */ + $processor = $this->locator->get($processor); + + return $processor->process($data, $operation, $uriVariables, $context); + } + } +} diff --git a/src/State/CallableProvider.php b/src/State/CallableProvider.php new file mode 100644 index 00000000000..96318ef865e --- /dev/null +++ b/src/State/CallableProvider.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\State; + +use ApiPlatform\Core\Exception\RuntimeException; +use ApiPlatform\Metadata\Operation; +use Psr\Container\ContainerInterface; + +final class CallableProvider implements ProviderInterface +{ + private $locator; + + public function __construct(ContainerInterface $locator) + { + $this->locator = $locator; + } + + /** + * {@inheritDoc} + */ + public function provide(Operation $operation, array $uriVariables = [], array $context = []) + { + if (\is_callable($provider = $operation->getProvider())) { + return $provider($operation, $uriVariables, $context); + } + + if (\is_string($provider)) { + if (!$this->locator->has($provider)) { + throw new RuntimeException(sprintf('Provider "%s" not found on operation "%s"', $provider, $operation->getName())); + } + + /** @var ProviderInterface */ + $provider = $this->locator->get($provider); + + return $provider->provide($operation, $uriVariables, $context); + } + + throw new RuntimeException(sprintf('Provider not found on operation "%s"', $operation->getName())); + } +} diff --git a/src/State/ChainProcessor.php b/src/State/ChainProcessor.php deleted file mode 100644 index da28d790ddf..00000000000 --- a/src/State/ChainProcessor.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\State; - -/** - * Tries each configured data processors and returns the result of the first able to handle the resource class. - * - * @experimental - */ -final class ChainProcessor implements ProcessorInterface -{ - /** - * @var iterable - * - * @internal - */ - public $processors; - - /** - * @param ProcessorInterface[] $processors - */ - public function __construct(iterable $processors) - { - $this->processors = $processors; - } - - public function supports($data, array $uriVariables = [], ?string $operationName = null, array $context = []): bool - { - foreach ($this->processors as $processor) { - if ($supports = $processor->supports($data, $uriVariables, $operationName, $context)) { - return $supports; - } - } - - return false; - } - - public function process($data, array $uriVariables = [], ?string $operationName = null, array $context = []) - { - foreach ($this->processors as $processor) { - if ($processor->supports($data, $uriVariables, $operationName, $context)) { - return $processor->process($data, $uriVariables, $operationName, $context); - } - } - - return null; - } -} diff --git a/src/State/ChainProvider.php b/src/State/ChainProvider.php deleted file mode 100644 index e803ec2cb44..00000000000 --- a/src/State/ChainProvider.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\State; - -/** - * Tries each configured data provider and returns the result of the first able to handle the resource class. - * - * @experimental - */ -final class ChainProvider implements ProviderInterface -{ - /** - * @var iterable - * - * @internal - */ - public $providers; - - /** - * @param ProviderInterface[] $providers - */ - public function __construct(iterable $providers) - { - $this->providers = $providers; - } - - public function provide(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []) - { - foreach ($this->providers as $provider) { - if ($provider->supports($resourceClass, $uriVariables, $operationName, $context)) { - return $provider->provide($resourceClass, $uriVariables, $operationName, $context); - } - } - - return \count($uriVariables) ? null : []; - } - - public function supports(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []): bool - { - foreach ($this->providers as $provider) { - if ($provider->supports($resourceClass, $uriVariables, $operationName, $context)) { - return true; - } - } - - return false; - } -} diff --git a/src/State/LegacyDataProviderState.php b/src/State/LegacyDataProviderState.php deleted file mode 100644 index 1cf37598f96..00000000000 --- a/src/State/LegacyDataProviderState.php +++ /dev/null @@ -1,88 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\State; - -use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; -use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; - -/** - * @internal - * - * @deprecated - */ -final class LegacyDataProviderState implements ProviderInterface -{ - private $itemDataProvider; - private $collectionDataProvider; - private $subresourceDataProvider; - private $resourceMetadataCollectionFactory; - - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ItemDataProviderInterface $itemDataProvider, CollectionDataProviderInterface $collectionDataProvider, SubresourceDataProviderInterface $subresourceDataProvider) - { - $this->itemDataProvider = $itemDataProvider; - $this->collectionDataProvider = $collectionDataProvider; - $this->subresourceDataProvider = $subresourceDataProvider; - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; - } - - public function provide(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []) - { - $operation = $context['operation'] ?? null; - if ($operation && ( - ($operation->getExtraProperties()['is_legacy_subresource'] ?? false) || - ($operation->getExtraProperties()['is_alternate_resource_metadata'] ?? false) - )) { - $subresourceContext = ['filters' => $context['filters'] ?? [], 'identifiers' => $operation->getExtraProperties()['legacy_subresource_identifiers'] ?? [], 'property' => $operation->getExtraProperties()['legacy_subresource_property'] ?? null, 'collection' => $operation->isCollection()] + $context; - $subresourceIdentifiers = []; - foreach ($operation->getUriVariables() as $parameterName => $uriTemplateDefinition) { - $subresourceIdentifiers[$parameterName] = [$uriTemplateDefinition->getIdentifiers()[0] => $identifiers[$parameterName]]; - } - - return $this->subresourceDataProvider->getSubresource($resourceClass, $subresourceIdentifiers, $subresourceContext, $operationName); - } - - if ($identifiers) { - return $this->itemDataProvider->getItem($resourceClass, $identifiers, $operationName, $context); - } - - if ($this->collectionDataProvider instanceof ContextAwareCollectionDataProviderInterface) { - return $this->collectionDataProvider->getCollection($resourceClass, $operationName, $context); - } - - return $this->collectionDataProvider->getCollection($resourceClass, $operationName); - } - - public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool - { - $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operationName); - if (!($operation->getExtraProperties()['is_legacy_resource_metadata'] ?? false) && !($operation->getExtraProperties()['is_legacy_subresource'] ?? false)) { - // FIXME: uncomment the following line when all providers will be ported - return false; - } - - if ($identifiers && $this->itemDataProvider instanceof RestrictedDataProviderInterface) { - return $this->itemDataProvider->supports($resourceClass, $operationName, $context); - } - - if ($this->collectionDataProvider instanceof RestrictedDataProviderInterface) { - return $this->collectionDataProvider->supports($resourceClass, $operationName, $context); - } - - return false; - } -} diff --git a/src/State/ProcessorInterface.php b/src/State/ProcessorInterface.php index 7195442d4ae..dc33da7c01b 100644 --- a/src/State/ProcessorInterface.php +++ b/src/State/ProcessorInterface.php @@ -13,24 +13,22 @@ namespace ApiPlatform\State; +use ApiPlatform\Metadata\Operation; + /** * Process data: send an email, persist to storage, add to queue etc. * + * @author Antoine Bluchet * @experimental */ interface ProcessorInterface { - /** - * Whether this state handler supports the class/identifier tuple. - * - * @param mixed $data - */ - public function supports($data, array $uriVariables = [], ?string $operationName = null, array $context = []): bool; - /** * Handle the state. * * @param mixed $data + * + * @return mixed */ - public function process($data, array $uriVariables = [], ?string $operationName = null, array $context = []); + public function process($data, Operation $operation, array $uriVariables = [], array $context = []); } diff --git a/src/State/ProviderInterface.php b/src/State/ProviderInterface.php index 3f44de9dd7c..31eca895433 100644 --- a/src/State/ProviderInterface.php +++ b/src/State/ProviderInterface.php @@ -13,6 +13,8 @@ namespace ApiPlatform\State; +use ApiPlatform\Metadata\Operation; + /** * Retrieves data from a persistence layer. * @@ -26,10 +28,5 @@ interface ProviderInterface * * @return object|array|null */ - public function provide(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []); - - /** - * Whether this state provider supports the class/identifier tuple. - */ - public function supports(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []): bool; + public function provide(Operation $operation, array $uriVariables = [], array $context = []); } diff --git a/src/State/UriVariablesResolverTrait.php b/src/State/UriVariablesResolverTrait.php index d2b0efc3dd7..3f693e6033f 100644 --- a/src/State/UriVariablesResolverTrait.php +++ b/src/State/UriVariablesResolverTrait.php @@ -18,7 +18,7 @@ use ApiPlatform\Core\Identifier\ContextAwareIdentifierConverterInterface; use ApiPlatform\Core\Identifier\IdentifierConverterInterface; use ApiPlatform\Exception\InvalidIdentifierException; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\HttpOperation; /** * @experimental @@ -31,7 +31,7 @@ trait UriVariablesResolverTrait /** * Resolves an operation's UriVariables to their identifiers values. * - * @param Operation|null $operation + * @param HttpOperation|null $operation */ private function getOperationUriVariables($operation, array $parameters, string $resourceClass): array { diff --git a/src/Symfony/Bundle/DataCollector/RequestDataCollector.php b/src/Symfony/Bundle/DataCollector/RequestDataCollector.php index 0028c9d3b11..6f03d58a868 100644 --- a/src/Symfony/Bundle/DataCollector/RequestDataCollector.php +++ b/src/Symfony/Bundle/DataCollector/RequestDataCollector.php @@ -27,8 +27,6 @@ use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\State\ProcessorInterface; use ApiPlatform\State\ProviderInterface; -use ApiPlatform\Symfony\Bundle\Processor\TraceableChainProcessor; -use ApiPlatform\Symfony\Bundle\Provider\TraceableChainProvider; use ApiPlatform\Util\RequestAttributesExtractor; use PackageVersions\Versions; use Psr\Container\ContainerInterface; @@ -143,17 +141,6 @@ public function collect(Request $request, Response $response, \Throwable $except if ($this->dataPersister instanceof TraceableChainDataPersister) { $this->data['dataPersisters']['responses'] = $this->dataPersister->getPersistersResponse(); } - - if ($this->provider instanceof TraceableChainProvider) { - $this->data['providers'] = [ - 'context' => $this->cloneVar($this->provider->getContext()), - 'responses' => $this->provider->getProvidersResponse(), - ]; - } - - if ($this->processor instanceof TraceableChainProcessor) { - $this->data['processors']['responses'] = $this->processor->getProcessorsResponse(); - } } private function setFilters(ApiResource $resourceMetadata, int $index, array &$filters, array &$counters): void @@ -219,16 +206,6 @@ public function getDataPersisters(): array return $this->data['dataPersisters'] ?? ['responses' => []]; } - public function getProviders(): array - { - return $this->data['providers'] ?? ['responses' => []]; - } - - public function getProcessors(): array - { - return $this->data['processors'] ?? ['responses' => []]; - } - public function getVersion(): ?string { if (!class_exists(Versions::class)) { diff --git a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index cd81e7164ce..59b910c7a77 100644 --- a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -340,6 +340,7 @@ private function registerMetadataConfiguration(ContainerBuilder $container, arra $loader->load('metadata/links.xml'); $loader->load('metadata/property.xml'); $loader->load('metadata/resource.xml'); + $loader->load('v3/metadata.xml'); $container->getDefinition('api_platform.metadata.resource_extractor.xml')->replaceArgument(0, $xmlResources); $container->getDefinition('api_platform.metadata.property_extractor.xml')->replaceArgument(0, $xmlResources); @@ -835,6 +836,10 @@ private function registerMessengerConfiguration(ContainerBuilder $container, arr } $loader->load('messenger.xml'); + + if (!$config['metadata_backward_compatibility_layer']) { + $loader->load('v3/messenger.xml'); + } } private function registerElasticsearchConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void @@ -849,6 +854,10 @@ private function registerElasticsearchConfiguration(ContainerBuilder $container, $loader->load('elasticsearch.xml'); + if (!$config['metadata_backward_compatibility_layer']) { + $loader->load('v3/elasticsearch.xml'); + } + $container->registerForAutoconfiguration(RequestBodySearchCollectionExtensionInterface::class) ->addTag('api_platform.elasticsearch.request_body_search_extension.collection'); diff --git a/src/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DependencyInjection/Configuration.php index 864e62b4b32..0fa4a16c7c1 100644 --- a/src/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DependencyInjection/Configuration.php @@ -13,7 +13,8 @@ namespace ApiPlatform\Symfony\Bundle\DependencyInjection; -use ApiPlatform\Core\Annotation\ApiResource; +use ApiPlatform\Core\Annotation\ApiResource as LegacyApiResource; +use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Doctrine\Common\Filter\OrderFilterInterface; use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; use ApiPlatform\Exception\FilterValidationException; @@ -638,8 +639,18 @@ private function addDefaultsSection(ArrayNodeDefinition $rootNode): void return $normalizedDefaults; }); - [$publicProperties, $configurableAttributes] = ApiResource::getConfigMetadata(); - foreach (array_merge($publicProperties, $configurableAttributes) as $attribute => $_) { + // TODO: test defaults with things that are no in the constructor + if (class_exists(ApiResource::class)) { + $reflection = new \ReflectionClass(ApiResource::class); + foreach ($reflection->getConstructor()->getParameters() as $parameter) { + $defaultsNode->children()->variableNode($nameConverter->normalize($parameter->getName())); + } + + return; + } + + [$publicProperties, $configurableAttributes] = LegacyApiResource::getConfigMetadata(); + foreach (array_merge($publicProperties, $configurableAttributpies) as $attribute => $_) { $snakeCased = $nameConverter->normalize($attribute); $defaultsNode->children()->variableNode($snakeCased); } @@ -663,4 +674,4 @@ private function buildDeprecationArgs(string $version, string $message): array } } -class_alias(Configuration::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Configuration::class); \ No newline at end of file +class_alias(Configuration::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Configuration::class); diff --git a/src/Symfony/Bundle/Processor/TraceableChainProcessor.php b/src/Symfony/Bundle/Processor/TraceableChainProcessor.php deleted file mode 100644 index dc6906f44c0..00000000000 --- a/src/Symfony/Bundle/Processor/TraceableChainProcessor.php +++ /dev/null @@ -1,65 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Symfony\Bundle\Processor; - -use ApiPlatform\State\ChainProcessor; -use ApiPlatform\State\ProcessorInterface; - -final class TraceableChainProcessor implements ProcessorInterface -{ - private $processors = []; - private $processorsResponse = []; - private $decorated; - - public function __construct(ProcessorInterface $processor) - { - if ($processor instanceof ChainProcessor) { - $this->decorated = $processor; - $this->processors = $processor->processors; - } - } - - public function getProcessorsResponse(): array - { - return $this->processorsResponse; - } - - private function traceProcessors($data, array $context = []) - { - $found = false; - foreach ($this->processors as $processor) { - if ( - ($this->processorsResponse[\get_class($processor)] = $found ? false : $processor->supports($data, $context)) - && - !$found - ) { - $found = true; - } - } - } - - public function process($data, array $uriVariables = [], ?string $operationName = null, array $context = []) - { - $this->traceProcessors($data, $context); - - return $this->decorated->process($data, $uriVariables, $operationName, $context); - } - - public function supports($data, array $uriVariables = [], ?string $operationName = null, array $context = []): bool - { - return $this->decorated->supports($data, $uriVariables, $operationName, $context); - } -} - -class_alias(TraceableChainProcessor::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\Processor\TraceableChainProcessor::class); diff --git a/src/Symfony/Bundle/Provider/TraceableChainProvider.php b/src/Symfony/Bundle/Provider/TraceableChainProvider.php deleted file mode 100644 index dc5b12dc51c..00000000000 --- a/src/Symfony/Bundle/Provider/TraceableChainProvider.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Symfony\Bundle\Provider; - -use ApiPlatform\State\ChainProvider; -use ApiPlatform\State\ProviderInterface; - -final class TraceableChainProvider implements ProviderInterface -{ - private $providers; - private $context; - private $providersResponse = []; - private $decorated; - - public function __construct(ProviderInterface $provider) - { - if ($provider instanceof ChainProvider) { - $this->decorated = $provider; - $this->providers = $provider->providers; - } - } - - public function getProvidersResponse() - { - return $this->providersResponse; - } - - public function getContext() - { - return $this->context; - } - - private function traceProviders(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []) - { - foreach ($this->providers as $provider) { - $this->providersResponse[\get_class($provider)] = $provider->supports($resourceClass, $uriVariables, $operationName, $context); - } - } - - private function traceContext(array $context) - { - $this->context = $context; - } - - public function provide(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []) - { - $this->traceProviders($resourceClass, $uriVariables, $operationName, $context); - $this->traceContext($context); - - return $this->decorated->provide($resourceClass, $uriVariables, $operationName, $context); - } - - public function supports(string $resourceClass, array $uriVariables = [], ?string $operationName = null, array $context = []): bool - { - return $this->decorated->supports($resourceClass, $uriVariables, $operationName, $context); - } -} diff --git a/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml b/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml index 26ba533ba97..2a850fae14f 100644 --- a/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml +++ b/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml @@ -22,20 +22,16 @@ - - + - - + - - + - - + diff --git a/src/Symfony/Bundle/Resources/config/doctrine_orm.xml b/src/Symfony/Bundle/Resources/config/doctrine_orm.xml index 0e1a65a23d1..3b783508aa8 100644 --- a/src/Symfony/Bundle/Resources/config/doctrine_orm.xml +++ b/src/Symfony/Bundle/Resources/config/doctrine_orm.xml @@ -9,26 +9,16 @@ - + - - + - - - - - - - - - - + diff --git a/src/Symfony/Bundle/Resources/config/elasticsearch.xml b/src/Symfony/Bundle/Resources/config/elasticsearch.xml index 1600cd2f340..910d80ea5ff 100644 --- a/src/Symfony/Bundle/Resources/config/elasticsearch.xml +++ b/src/Symfony/Bundle/Resources/config/elasticsearch.xml @@ -24,13 +24,13 @@ - + - + @@ -64,15 +64,6 @@ - - - - - - - - - @@ -83,17 +74,6 @@ - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/http_cache_purger.xml b/src/Symfony/Bundle/Resources/config/http_cache_purger.xml index 5a7fcdc3060..7a10a5155a8 100644 --- a/src/Symfony/Bundle/Resources/config/http_cache_purger.xml +++ b/src/Symfony/Bundle/Resources/config/http_cache_purger.xml @@ -5,6 +5,8 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> + + diff --git a/src/Symfony/Bundle/Resources/config/legacy/upgrade.xml b/src/Symfony/Bundle/Resources/config/legacy/upgrade.xml index f3d0eeeb54a..a44d410c108 100644 --- a/src/Symfony/Bundle/Resources/config/legacy/upgrade.xml +++ b/src/Symfony/Bundle/Resources/config/legacy/upgrade.xml @@ -13,6 +13,7 @@ + diff --git a/src/Symfony/Bundle/Resources/config/messenger.xml b/src/Symfony/Bundle/Resources/config/messenger.xml index 917c8a059cc..ca63b5dc9b9 100644 --- a/src/Symfony/Bundle/Resources/config/messenger.xml +++ b/src/Symfony/Bundle/Resources/config/messenger.xml @@ -14,7 +14,7 @@ - + diff --git a/src/Symfony/Bundle/Resources/config/metadata/resource_name.xml b/src/Symfony/Bundle/Resources/config/metadata/resource_name.xml index 51ed6a5287a..2613336d50d 100644 --- a/src/Symfony/Bundle/Resources/config/metadata/resource_name.xml +++ b/src/Symfony/Bundle/Resources/config/metadata/resource_name.xml @@ -11,11 +11,6 @@ - - %api_platform.resource_class_directories% - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/backward_compatibility.xml b/src/Symfony/Bundle/Resources/config/v3/backward_compatibility.xml index 95bd543a079..70d421f1283 100644 --- a/src/Symfony/Bundle/Resources/config/v3/backward_compatibility.xml +++ b/src/Symfony/Bundle/Resources/config/v3/backward_compatibility.xml @@ -22,17 +22,6 @@ - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/debug.xml b/src/Symfony/Bundle/Resources/config/v3/debug.xml index a31ea6a6fbd..412f179d7d8 100644 --- a/src/Symfony/Bundle/Resources/config/v3/debug.xml +++ b/src/Symfony/Bundle/Resources/config/v3/debug.xml @@ -11,13 +11,5 @@ - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/doctrine_odm.xml b/src/Symfony/Bundle/Resources/config/v3/doctrine_odm.xml index 171a339c172..26604a5b2b5 100644 --- a/src/Symfony/Bundle/Resources/config/v3/doctrine_odm.xml +++ b/src/Symfony/Bundle/Resources/config/v3/doctrine_odm.xml @@ -24,5 +24,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/Resources/config/v3/doctrine_orm.xml b/src/Symfony/Bundle/Resources/config/v3/doctrine_orm.xml index 24905a5c534..e3d8c6da9a3 100644 --- a/src/Symfony/Bundle/Resources/config/v3/doctrine_orm.xml +++ b/src/Symfony/Bundle/Resources/config/v3/doctrine_orm.xml @@ -13,6 +13,22 @@ + + + + + + + + + + + + + + + + null @@ -24,5 +40,9 @@ + + + + diff --git a/src/Symfony/Bundle/Resources/config/v3/elasticsearch.xml b/src/Symfony/Bundle/Resources/config/v3/elasticsearch.xml new file mode 100644 index 00000000000..c04b5eaa882 --- /dev/null +++ b/src/Symfony/Bundle/Resources/config/v3/elasticsearch.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/Resources/config/v3/graphql.xml b/src/Symfony/Bundle/Resources/config/v3/graphql.xml index 4c1ab4a162f..180cba84b0e 100644 --- a/src/Symfony/Bundle/Resources/config/v3/graphql.xml +++ b/src/Symfony/Bundle/Resources/config/v3/graphql.xml @@ -5,12 +5,10 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - @@ -35,6 +33,7 @@ + @@ -77,7 +76,6 @@ - @@ -90,7 +88,6 @@ - @@ -98,7 +95,6 @@ - @@ -106,35 +102,29 @@ - - - - - - @@ -179,6 +169,7 @@ + diff --git a/src/Symfony/Bundle/Resources/config/v3/messenger.xml b/src/Symfony/Bundle/Resources/config/v3/messenger.xml new file mode 100644 index 00000000000..de581cca87e --- /dev/null +++ b/src/Symfony/Bundle/Resources/config/v3/messenger.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/src/Symfony/Bundle/Resources/config/v3/metadata.xml b/src/Symfony/Bundle/Resources/config/v3/metadata.xml index 4f0cd84fa79..76885ef83ea 100644 --- a/src/Symfony/Bundle/Resources/config/v3/metadata.xml +++ b/src/Symfony/Bundle/Resources/config/v3/metadata.xml @@ -5,5 +5,9 @@ + + %api_platform.resource_class_directories% + + diff --git a/src/Symfony/Bundle/Resources/config/v3/state.xml b/src/Symfony/Bundle/Resources/config/v3/state.xml index 8ac0efe016e..9004bdd9d5d 100644 --- a/src/Symfony/Bundle/Resources/config/v3/state.xml +++ b/src/Symfony/Bundle/Resources/config/v3/state.xml @@ -4,6 +4,10 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> + + + + @@ -13,16 +17,9 @@ - - + + - - - - - - - - + diff --git a/src/Symfony/Bundle/Resources/views/DataCollector/request.html.twig b/src/Symfony/Bundle/Resources/views/DataCollector/request.html.twig index ecca24b5739..92fc737b905 100644 --- a/src/Symfony/Bundle/Resources/views/DataCollector/request.html.twig +++ b/src/Symfony/Bundle/Resources/views/DataCollector/request.html.twig @@ -200,20 +200,6 @@ {{ apiPlatform.providerTable(collector.dataPersisters, 'data persister') }} - -
-

Providers

-
- {{ apiPlatform.providerTable(collector.providers, 'provider') }} -
-
- -
-

Processors

-
- {{ apiPlatform.providerTable(collector.processors, 'processors') }} -
-
{% endif %} {% endblock %} diff --git a/src/Symfony/EventListener/QueryParameterValidateListener.php b/src/Symfony/EventListener/QueryParameterValidateListener.php index 43be666d345..17c1ec5a4c7 100644 --- a/src/Symfony/EventListener/QueryParameterValidateListener.php +++ b/src/Symfony/EventListener/QueryParameterValidateListener.php @@ -17,6 +17,7 @@ use ApiPlatform\Core\Filter\QueryParameterValidator as LegacyQueryParameterValidator; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Util\OperationRequestInitiatorTrait; use ApiPlatform\Util\RequestAttributesExtractor; @@ -72,7 +73,7 @@ public function onKernelRequest(RequestEvent $event) } if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface && - (!$operation || !($operation->canQueryParameterValidate() ?? true) || !$operation->isCollection()) + (!$operation || !($operation->getQueryParameterValidationEnabled() ?? true) || !$operation instanceof CollectionOperationInterface) ) { return; } diff --git a/src/Symfony/EventListener/ReadListener.php b/src/Symfony/EventListener/ReadListener.php index 4b91515fe6a..17ffd741a33 100644 --- a/src/Symfony/EventListener/ReadListener.php +++ b/src/Symfony/EventListener/ReadListener.php @@ -90,7 +90,7 @@ public function onKernelRequest(RequestEvent $event): void $resourceClass = $operation->getClass() ?? $attributes['resource_class']; try { $uriVariables = $this->getOperationUriVariables($operation, $parameters, $resourceClass); - $data = $this->provider->provide($resourceClass, $uriVariables, $operation->getName(), $context); + $data = $this->provider->provide($operation, $uriVariables, $context); } catch (InvalidIdentifierException $e) { throw new NotFoundHttpException('Invalid identifier value or configuration.', $e); } catch (InvalidUriVariableException $e) { diff --git a/src/Symfony/EventListener/RespondListener.php b/src/Symfony/EventListener/RespondListener.php index 9ffe26e1874..a47cd298eed 100644 --- a/src/Symfony/EventListener/RespondListener.php +++ b/src/Symfony/EventListener/RespondListener.php @@ -17,6 +17,7 @@ use ApiPlatform\Api\UrlGeneratorInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Util\OperationRequestInitiatorTrait; use ApiPlatform\Util\RequestAttributesExtractor; @@ -111,7 +112,7 @@ public function onKernelView(ViewEvent $event): void ) { $status = 301; - if ($operation->isCollection()) { + if ($operation instanceof CollectionOperationInterface) { $headers['Location'] = $this->iriConverter->getIriFromResourceClass($operation->getClass(), $operation->getName(), UrlGeneratorInterface::ABS_PATH, ['operation' => $operation]); } else { $headers['Location'] = $this->iriConverter->getIriFromItem($request->attributes->get('data'), $operation->getName(), UrlGeneratorInterface::ABS_PATH, ['operation' => $operation]); diff --git a/src/Symfony/EventListener/WriteListener.php b/src/Symfony/EventListener/WriteListener.php index 4cd4f76f459..d0ae668e26e 100644 --- a/src/Symfony/EventListener/WriteListener.php +++ b/src/Symfony/EventListener/WriteListener.php @@ -75,6 +75,10 @@ public function onKernelView(ViewEvent $event): void return; } + if (!$operation->getProcessor()) { + return; + } + $context = ['operation' => $operation, 'resource_class' => $attributes['resource_class']]; try { $uriVariables = $this->getOperationUriVariables($operation, $request->attributes->all(), $attributes['resource_class']); @@ -82,15 +86,11 @@ public function onKernelView(ViewEvent $event): void throw new NotFoundHttpException('Invalid identifier value or configuration.', $e); } - if (!$this->processor->supports($controllerResult, $uriVariables, $operation->getName(), $context)) { - return; - } - switch ($request->getMethod()) { case 'PUT': case 'PATCH': case 'POST': - $persistResult = $this->processor->process($controllerResult, $uriVariables, $operation->getName(), $context); + $persistResult = $this->processor->process($controllerResult, $operation, $uriVariables, $context); if ($persistResult) { $controllerResult = $persistResult; @@ -113,7 +113,7 @@ public function onKernelView(ViewEvent $event): void break; case 'DELETE': - $this->processor->process($controllerResult, $uriVariables, $operation->getName(), $context); + $this->processor->process($controllerResult, $operation, $uriVariables, $context); $event->setControllerResult(null); break; } diff --git a/src/Symfony/Messenger/Metadata/MessengerResourceMetadataCollectionFactory.php b/src/Symfony/Messenger/Metadata/MessengerResourceMetadataCollectionFactory.php new file mode 100644 index 00000000000..61e3dc0dc5f --- /dev/null +++ b/src/Symfony/Messenger/Metadata/MessengerResourceMetadataCollectionFactory.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Symfony\Messenger\Metadata; + +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use ApiPlatform\Symfony\Messenger\Processor; + +final class MessengerResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface +{ + /** + * @var ResourceMetadataCollectionFactoryInterface + */ + private $decorated; + + public function __construct(ResourceMetadataCollectionFactoryInterface $decorated) + { + $this->decorated = $decorated; + } + + /** + * {@inheritDoc} + */ + public function create(string $resourceClass): ResourceMetadataCollection + { + $resourceMetadataCollection = $this->decorated->create($resourceClass); + + foreach ($resourceMetadataCollection as $i => $resourceMetadata) { + $operations = $resourceMetadata->getOperations(); + + if ($operations) { + foreach ($resourceMetadata->getOperations() as $operationName => $operation) { + if (null !== $operation->getProcessor() || false === ($operation->getMessenger() ?? false)) { + continue; + } + + $operations->add($operationName, $operation->withProcessor(Processor::class)); + } + + $resourceMetadata = $resourceMetadata->withOperations($operations); + } + + $graphQlOperations = $resourceMetadata->getGraphQlOperations(); + + if ($graphQlOperations) { + foreach ($graphQlOperations as $operationName => $graphQlOperation) { + if (null !== $graphQlOperation->getProcessor() || false === ($graphQlOperation->getMessenger() ?? false)) { + continue; + } + + $graphQlOperations[$operationName] = $graphQlOperation->withProcessor(Processor::class); + } + + $resourceMetadata = $resourceMetadata->withGraphQlOperations($graphQlOperations); + } + + $resourceMetadataCollection[$i] = $resourceMetadata; + } + + return $resourceMetadataCollection; + } +} diff --git a/src/Symfony/Messenger/Processor.php b/src/Symfony/Messenger/Processor.php index c4feb7accd1..499722ceeb5 100644 --- a/src/Symfony/Messenger/Processor.php +++ b/src/Symfony/Messenger/Processor.php @@ -14,7 +14,8 @@ namespace ApiPlatform\Symfony\Messenger; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Exception\OperationNotFoundException; +use ApiPlatform\Metadata\DeleteOperationInterface; +use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\State\ProcessorInterface; use ApiPlatform\Util\ClassInfoTrait; @@ -67,28 +68,12 @@ private function remove($data, array $context = []) ); } - public function process($data, array $uriVariables = [], ?string $operationName = null, array $context = []) + public function process($data, Operation $operation, array $uriVariables = [], array $context = []) { - if (\array_key_exists('operation', $context) && $context['operation']->isDelete()) { + if ($operation instanceof DeleteOperationInterface) { return $this->remove($data); } return $this->persist($data); } - - public function supports($data, array $uriVariables = [], ?string $operationName = null, array $context = []): bool - { - try { - if (isset($context['operation'])) { - $operation = $context['operation']; - } else { - $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($context['resource_class'] ?? $this->getObjectClass($data)); - $operation = $resourceMetadataCollection->getOperation($operationName); - } - - return false !== ($operation->getMessenger() ?? false); - } catch (OperationNotFoundException $e) { - return false; - } - } } diff --git a/src/Symfony/Routing/ApiLoader.php b/src/Symfony/Routing/ApiLoader.php index a53996d0167..5a7459efdd6 100644 --- a/src/Symfony/Routing/ApiLoader.php +++ b/src/Symfony/Routing/ApiLoader.php @@ -21,7 +21,8 @@ use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface; use ApiPlatform\Exception\InvalidResourceException; use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; use ApiPlatform\PathResolver\OperationPathResolverInterface; @@ -117,20 +118,22 @@ public function load($data, $type = null): RouteCollection $legacyDefaults['_api_subresource_context'] = [ 'property' => $operation->getExtraProperties()['legacy_subresource_property'], 'identifiers' => $operation->getExtraProperties()['legacy_subresource_identifiers'], - 'collection' => $operation->isCollection(), + 'collection' => $operation instanceof CollectionOperationInterface, 'operationId' => $operation->getExtraProperties()['legacy_subresource_operation_name'] ?? null, ]; $legacyDefaults['_api_identifiers'] = $operation->getExtraProperties()['legacy_subresource_identifiers']; } elseif ($operation->getExtraProperties()['is_legacy_resource_metadata'] ?? false) { - $legacyDefaults[sprintf('_api_%s_operation_name', $operation->isCollection() ? OperationType::COLLECTION : OperationType::ITEM)] = $operationName; + $legacyDefaults[sprintf('_api_%s_operation_name', $operation instanceof CollectionOperationInterface ? OperationType::COLLECTION : OperationType::ITEM)] = $operationName; $legacyDefaults['_api_identifiers'] = []; - $legacyDefaults['_api_has_composite_identifier'] = $operation->getCompositeIdentifier(); // Legacy identifiers + $hasCompositeIdentifier = false; foreach ($operation->getUriVariables() ?? [] as $parameterName => $identifiedBy) { + $hasCompositeIdentifier = $identifiedBy->getCompositeIdentifier(); foreach ($identifiedBy->getIdentifiers() ?? [] as $identifier) { $legacyDefaults['_api_identifiers'][] = $identifier; } } + $legacyDefaults['_api_has_composite_identifier'] = $hasCompositeIdentifier; } $path = ($operation->getRoutePrefix() ?? '').$operation->getUriTemplate(); @@ -155,7 +158,7 @@ public function load($data, $type = null): RouteCollection $operation->getOptions() ?? [], $operation->getHost() ?? '', $operation->getSchemes() ?? [], - [$operation->getMethod() ?? Operation::METHOD_GET], + [$operation->getMethod() ?? HttpOperation::METHOD_GET], $operation->getCondition() ?? '' ); diff --git a/src/Symfony/Routing/IriConverter.php b/src/Symfony/Routing/IriConverter.php index 6d0e1b0be4e..2b93de5407b 100644 --- a/src/Symfony/Routing/IriConverter.php +++ b/src/Symfony/Routing/IriConverter.php @@ -23,6 +23,7 @@ use ApiPlatform\Exception\ItemNotFoundException; use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\Exception\RuntimeException; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\State\ProviderInterface; @@ -44,14 +45,14 @@ final class IriConverter implements IriConverterInterface use ResourceClassInfoTrait; use UriVariablesResolverTrait; - private $stateProvider; + private $provider; private $router; private $identifiersExtractor; private $resourceMetadataCollectionFactory; - public function __construct(ProviderInterface $stateProvider, RouterInterface $router, IdentifiersExtractorInterface $identifiersExtractor, ResourceClassResolverInterface $resourceClassResolver, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, UriVariablesConverterInterface $uriVariablesConverter = null) + public function __construct(ProviderInterface $provider, RouterInterface $router, IdentifiersExtractorInterface $identifiersExtractor, ResourceClassResolverInterface $resourceClassResolver, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, UriVariablesConverterInterface $uriVariablesConverter = null) { - $this->stateProvider = $stateProvider; + $this->provider = $provider; $this->router = $router; $this->uriVariablesConverter = $uriVariablesConverter; $this->identifiersExtractor = $identifiersExtractor; @@ -81,7 +82,7 @@ public function getItemFromIri(string $iri, array $context = []) $context['operation'] = $operation = $parameters['_api_operation'] = $this->resourceMetadataCollectionFactory->create($parameters['_api_resource_class'])->getOperation($parameters['_api_operation_name']); - if ($operation->isCollection()) { + if ($operation instanceof CollectionOperationInterface) { throw new InvalidArgumentException(sprintf('The iri "%s" references a collection not an item.', $iri)); } @@ -93,7 +94,7 @@ public function getItemFromIri(string $iri, array $context = []) throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } - if ($item = $this->stateProvider->provide($attributes['resource_class'], $uriVariables, $attributes['operation_name'], $context)) { + if ($item = $this->provider->provide($operation, $uriVariables, $context)) { return $item; } @@ -118,7 +119,7 @@ public function getIriFromItem($item, string $operationName = null, int $referen ($operation->getExtraProperties()['is_alternate_resource_metadata'] ?? false) || ($operation->getExtraProperties()['legacy_subresource_behavior'] ?? false) || // When we want the Iri from an object, we don't want the collection uriTemplate, for this we use getIriFromResourceClass - $operation->isCollection() || $operation instanceof Post + $operation instanceof CollectionOperationInterface || $operation instanceof Post ) { unset($context['operation']); $operationName = null; @@ -126,12 +127,12 @@ public function getIriFromItem($item, string $operationName = null, int $referen } try { - $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operationName); + $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operationName, false, true); } catch (OperationNotFoundException $e) { $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); foreach ($resourceMetadataCollection as $resource) { foreach ($resource->getOperations() as $name => $operation) { - if ($operationName === $name && !$operation->isCollection()) { + if ($operationName === $name && !$operation instanceof CollectionOperationInterface) { break 2; } } diff --git a/src/Util/OperationRequestInitiatorTrait.php b/src/Util/OperationRequestInitiatorTrait.php index 56b2cf29238..cbcaccaf4f1 100644 --- a/src/Util/OperationRequestInitiatorTrait.php +++ b/src/Util/OperationRequestInitiatorTrait.php @@ -13,7 +13,7 @@ namespace ApiPlatform\Util; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Symfony\Component\HttpFoundation\Request; @@ -30,7 +30,7 @@ trait OperationRequestInitiatorTrait /** * TODO: Kernel terminate remove the _api_operation attribute? */ - private function initializeOperation(Request $request): ?Operation + private function initializeOperation(Request $request): ?HttpOperation { if ($request->attributes->get('_api_operation')) { return $request->attributes->get('_api_operation'); diff --git a/tests/Api/IdentifiersExtractorTest.php b/tests/Api/IdentifiersExtractorTest.php index ad0ad7fe15e..e90f548bbf4 100644 --- a/tests/Api/IdentifiersExtractorTest.php +++ b/tests/Api/IdentifiersExtractorTest.php @@ -17,8 +17,8 @@ use ApiPlatform\Api\ResourceClassResolverInterface; use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Link; -use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; @@ -48,7 +48,7 @@ public function testGetIdentifiersFromItem() $propertyMetadataFactoryProphecy->reveal() ); - $operation = $this->prophesize(Operation::class); + $operation = $this->prophesize(HttpOperation::class); $item = new Dummy(); $resourceClass = Dummy::class; $context = [ diff --git a/tests/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommandTest.php b/tests/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommandTest.php index 2bf07ff53e0..bc1b88225bc 100644 --- a/tests/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommandTest.php +++ b/tests/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommandTest.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Tests\Bridge\Symfony\Bundle\Command; +use ApiPlatform\Core\Api\IdentifiersExtractorInterface; use ApiPlatform\Core\Bridge\Symfony\Bundle\Command\UpgradeApiResourceCommand; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; @@ -33,11 +34,13 @@ class UpgradeApiResourceCommandTest extends TestCase private function getCommandTester(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, SubresourceOperationFactoryInterface $subresourceOperationFactory): CommandTester { + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $application = new Application(); $application->setCatchExceptions(false); $application->setAutoExit(false); - $application->add(new UpgradeApiResourceCommand($resourceNameCollectionFactory, $resourceMetadataFactory, $subresourceOperationFactory, new SubresourceTransformer(), new AnnotationReader())); + $application->add(new UpgradeApiResourceCommand($resourceNameCollectionFactory, $resourceMetadataFactory, $subresourceOperationFactory, new SubresourceTransformer(), new AnnotationReader(), $identifiersExtractor->reveal())); $command = $application->find('api:upgrade-resource'); diff --git a/tests/Doctrine/Common/State/ProcessorTest.php b/tests/Doctrine/Common/State/PersistProcessorTest.php similarity index 68% rename from tests/Doctrine/Common/State/ProcessorTest.php rename to tests/Doctrine/Common/State/PersistProcessorTest.php index 034437f2d52..bd10fdce00d 100644 --- a/tests/Doctrine/Common/State/ProcessorTest.php +++ b/tests/Doctrine/Common/State/PersistProcessorTest.php @@ -14,8 +14,8 @@ namespace ApiPlatform\Tests\Doctrine\Common\State; use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Doctrine\Common\State\Processor; -use ApiPlatform\Metadata\Delete; +use ApiPlatform\Doctrine\Common\State\PersistProcessor; +use ApiPlatform\Metadata\Get; use ApiPlatform\State\ProcessorInterface; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; @@ -26,26 +26,13 @@ use Prophecy\Prediction\CallPrediction; use Prophecy\Prediction\NoCallsPrediction; -class ProcessorTest extends TestCase +class PersistProcessorTest extends TestCase { use ProphecyTrait; public function testConstruct() { - $this->assertInstanceOf(ProcessorInterface::class, new Processor($this->prophesize(ManagerRegistry::class)->reveal())); - } - - public function testSupports() - { - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($this->prophesize(ObjectManager::class)->reveal())->shouldBeCalled(); - - $this->assertTrue((new Processor($managerRegistryProphecy->reveal()))->supports(new Dummy())); - } - - public function testDoesNotSupport() - { - $this->assertFalse((new Processor($this->prophesize(ManagerRegistry::class)->reveal()))->supports('dummy')); + $this->assertInstanceOf(ProcessorInterface::class, new PersistProcessor($this->prophesize(ManagerRegistry::class)->reveal())); } public function testPersist() @@ -61,7 +48,7 @@ public function testPersist() $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($objectManagerProphecy->reveal())->shouldBeCalled(); - $result = (new Processor($managerRegistryProphecy->reveal()))->process($dummy); + $result = (new PersistProcessor($managerRegistryProphecy->reveal()))->process($dummy, new Get()); $this->assertSame($dummy, $result); } @@ -79,7 +66,7 @@ public function testPersistIfEntityAlreadyManaged() $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($objectManagerProphecy->reveal())->shouldBeCalled(); - $result = (new Processor($managerRegistryProphecy->reveal()))->process($dummy); + $result = (new PersistProcessor($managerRegistryProphecy->reveal()))->process($dummy, new Get()); $this->assertSame($dummy, $result); } @@ -90,32 +77,10 @@ public function testPersistWithNullManager() $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); - $result = (new Processor($managerRegistryProphecy->reveal()))->process($dummy); + $result = (new PersistProcessor($managerRegistryProphecy->reveal()))->process($dummy, new Get()); $this->assertSame($dummy, $result); } - public function testRemove() - { - $dummy = new Dummy(); - - $objectManagerProphecy = $this->prophesize(ObjectManager::class); - $objectManagerProphecy->remove($dummy)->shouldBeCalled(); - $objectManagerProphecy->flush()->shouldBeCalled(); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($objectManagerProphecy->reveal())->shouldBeCalled(); - - (new Processor($managerRegistryProphecy->reveal()))->process($dummy, [], null, ['operation' => new Delete()]); - } - - public function testRemoveWithNullManager() - { - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); - - (new Processor($managerRegistryProphecy->reveal()))->process(new Dummy(), [], null, ['operation' => new Delete()]); - } - public function getTrackingPolicyParameters() { return [ @@ -154,7 +119,7 @@ public function testTrackingPolicy($metadataClass, $deferredExplicit, $persisted $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($objectManagerProphecy)->shouldBeCalled(); - $result = (new Processor($managerRegistryProphecy->reveal()))->process($dummy); + $result = (new PersistProcessor($managerRegistryProphecy->reveal()))->process($dummy, new Get()); $this->assertSame($dummy, $result); } } diff --git a/tests/Doctrine/Common/State/RemoveProcessorTest.php b/tests/Doctrine/Common/State/RemoveProcessorTest.php new file mode 100644 index 00000000000..c63d425693d --- /dev/null +++ b/tests/Doctrine/Common/State/RemoveProcessorTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Doctrine\Common\State; + +use ApiPlatform\Core\Tests\ProphecyTrait; +use ApiPlatform\Doctrine\Common\State\RemoveProcessor; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\State\ProcessorInterface; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\ObjectManager; +use PHPUnit\Framework\TestCase; + +class RemoveProcessorTest extends TestCase +{ + use ProphecyTrait; + + public function testConstruct() + { + $this->assertInstanceOf(ProcessorInterface::class, new RemoveProcessor($this->prophesize(ManagerRegistry::class)->reveal())); + } + + public function testRemove() + { + $dummy = new Dummy(); + + $objectManagerProphecy = $this->prophesize(ObjectManager::class); + $objectManagerProphecy->remove($dummy)->shouldBeCalled(); + $objectManagerProphecy->flush()->shouldBeCalled(); + + $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); + $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($objectManagerProphecy->reveal())->shouldBeCalled(); + + (new RemoveProcessor($managerRegistryProphecy->reveal()))->process($dummy, new Delete(), []); + } + + public function testRemoveWithNullManager() + { + $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); + $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); + + (new RemoveProcessor($managerRegistryProphecy->reveal()))->process(new Dummy(), new Delete(), []); + } +} diff --git a/tests/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest.php b/tests/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest.php new file mode 100644 index 00000000000..5793373b06a --- /dev/null +++ b/tests/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Doctrine\Odm\Metadata\Resource; + +use ApiPlatform\Core\Tests\ProphecyTrait; +use ApiPlatform\Doctrine\Common\State\PersistProcessor; +use ApiPlatform\Doctrine\Common\State\RemoveProcessor; +use ApiPlatform\Doctrine\Odm\Metadata\Resource\DoctrineMongoDbOdmResourceCollectionMetadataFactory; +use ApiPlatform\Doctrine\Odm\State\CollectionProvider; +use ApiPlatform\Doctrine\Odm\State\ItemProvider; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Operations; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy; +use Doctrine\ODM\MongoDB\DocumentManager; +use Doctrine\Persistence\ManagerRegistry; +use PHPUnit\Framework\TestCase; + +final class DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest extends TestCase +{ + use ProphecyTrait; + + private function getResourceMetadataCollectionFactory(Operation $operation) + { + if (!class_exists(DocumentManager::class)) { + $this->markTestSkipped('ODM not installed'); + } + + $resourceMetadataCollectionFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); + $resourceMetadataCollectionFactory->create($operation->getClass())->willReturn(new ResourceMetadataCollection($operation->getClass(), [ + (new ApiResource()) + ->withOperations( + new Operations([$operation->getName() => $operation]) + )->withGraphQlOperations([ + 'graphql_'.$operation->getName() => $operation->withName('graphql_'.$operation->getName()), + ]), + ])); + + return $resourceMetadataCollectionFactory->reveal(); + } + + public function testWithoutManager() + { + if (!class_exists(DocumentManager::class)) { + $this->markTestSkipped('ODM not installed'); + } + + $operation = (new Get())->withClass(Dummy::class)->withName('get'); + $managerRegistry = $this->prophesize(ManagerRegistry::class); + $managerRegistry->getManagerForClass(Dummy::class)->willReturn(null); + + $resourceMetadataCollectionFactory = new DoctrineMongoDbOdmResourceCollectionMetadataFactory($managerRegistry->reveal(), $this->getResourceMetadataCollectionFactory($operation)); + $resourceMetadataCollection = $resourceMetadataCollectionFactory->create(Dummy::class); + + $this->assertNull($resourceMetadataCollection->getOperation('get')->getProvider()); + $this->assertNull($resourceMetadataCollection->getOperation('graphql_get')->getProvider()); + } + + /** + * @dataProvider operationProvider + */ + public function testWithProvider(Operation $operation, string $expectedProvider = null, string $expectedProcessor = null) + { + if (!class_exists(DocumentManager::class)) { + $this->markTestSkipped('ODM not installed'); + } + + $objectManager = $this->prophesize(DocumentManager::class); + $managerRegistry = $this->prophesize(ManagerRegistry::class); + $managerRegistry->getManagerForClass($operation->getClass())->willReturn($objectManager->reveal()); + $resourceMetadataCollectionFactory = new DoctrineMongoDbOdmResourceCollectionMetadataFactory($managerRegistry->reveal(), $this->getResourceMetadataCollectionFactory($operation)); + $resourceMetadataCollection = $resourceMetadataCollectionFactory->create($operation->getClass()); + $this->assertEquals($expectedProvider, $resourceMetadataCollection->getOperation($operation->getName())->getProvider()); + $this->assertEquals($expectedProvider, $resourceMetadataCollection->getOperation('graphql_'.$operation->getName())->getProvider()); + $this->assertEquals($expectedProcessor, $resourceMetadataCollection->getOperation($operation->getName())->getProcessor()); + $this->assertEquals($expectedProcessor, $resourceMetadataCollection->getOperation('graphql_'.$operation->getName())->getProcessor()); + } + + public function operationProvider(): iterable + { + $default = (new Get())->withName('get')->withClass(Dummy::class); + + yield [(new Get())->withProvider('has a provider')->withProcessor('and a processor')->withOperation($default), 'has a provider', 'and a processor']; + yield [(new Get())->withOperation($default), ItemProvider::class, PersistProcessor::class]; + yield [(new GetCollection())->withOperation($default), CollectionProvider::class, PersistProcessor::class]; + yield [(new Delete())->withOperation($default), ItemProvider::class, RemoveProcessor::class]; + } +} diff --git a/tests/Doctrine/Odm/State/CollectionProviderTest.php b/tests/Doctrine/Odm/State/CollectionProviderTest.php index 7a39d1af6ac..c8e8920a376 100644 --- a/tests/Doctrine/Odm/State/CollectionProviderTest.php +++ b/tests/Doctrine/Odm/State/CollectionProviderTest.php @@ -19,12 +19,10 @@ use ApiPlatform\Doctrine\Odm\State\CollectionProvider; use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; -use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy; use ApiPlatform\Tests\Fixtures\TestBundle\Document\ProviderEntity; use Doctrine\ODM\MongoDB\Aggregation\Builder; use Doctrine\ODM\MongoDB\DocumentManager; @@ -71,14 +69,14 @@ public function testGetCollection() $managerProphecy->getRepository(ProviderEntity::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled(); $this->managerRegistryProphecy->getManagerForClass(ProviderEntity::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); - - $this->resourceMetadataFactoryProphecy->create(ProviderEntity::class)->willReturn(new ResourceMetadataCollection(ProviderEntity::class, [(new ApiResource())->withOperations(new Operations(['foo' => new GetCollection()]))])); + $operation = (new GetCollection())->withName('foo')->withClass(ProviderEntity::class); + $this->resourceMetadataFactoryProphecy->create(ProviderEntity::class)->willReturn(new ResourceMetadataCollection(ProviderEntity::class, [(new ApiResource())->withOperations(new Operations(['foo' => $operation]))])); $extensionProphecy = $this->prophesize(AggregationCollectionExtensionInterface::class); $extensionProphecy->applyToCollection($aggregationBuilder, ProviderEntity::class, 'foo', [])->shouldBeCalled(); $dataProvider = new CollectionProvider($this->resourceMetadataFactoryProphecy->reveal(), $this->managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals($iterator, $dataProvider->provide(ProviderEntity::class, [], 'foo')); + $this->assertEquals($iterator, $dataProvider->provide($operation, [])); } public function testGetCollectionWithExecuteOptions() @@ -98,13 +96,17 @@ public function testGetCollectionWithExecuteOptions() $this->managerRegistryProphecy->getManagerForClass(ProviderEntity::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); - $this->resourceMetadataFactoryProphecy->create(ProviderEntity::class)->willReturn(new ResourceMetadataCollection(ProviderEntity::class, [(new ApiResource())->withOperations(new Operations(['foo' => (new GetCollection())->withExtraProperties(['doctrine_mongodb' => ['execute_options' => ['allowDiskUse' => true]]])]))])); + $operation = (new GetCollection())->withExtraProperties(['doctrine_mongodb' => ['execute_options' => ['allowDiskUse' => true]]])->withName('foo')->withClass(ProviderEntity::class); + $this->resourceMetadataFactoryProphecy->create(ProviderEntity::class) + ->willReturn(new ResourceMetadataCollection(ProviderEntity::class, [ + (new ApiResource())->withOperations(new Operations(['foo' => $operation])), + ])); $extensionProphecy = $this->prophesize(AggregationCollectionExtensionInterface::class); $extensionProphecy->applyToCollection($aggregationBuilder, ProviderEntity::class, 'foo', [])->shouldBeCalled(); $dataProvider = new CollectionProvider($this->resourceMetadataFactoryProphecy->reveal(), $this->managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals($iterator, $dataProvider->provide(ProviderEntity::class, [], 'foo')); + $this->assertEquals($iterator, $dataProvider->provide($operation, [])); } public function testAggregationResultExtension() @@ -119,6 +121,7 @@ public function testAggregationResultExtension() $managerProphecy->getRepository(ProviderEntity::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled(); $this->managerRegistryProphecy->getManagerForClass(ProviderEntity::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); + $operation = (new GetCollection())->withName('foo')->withClass(ProviderEntity::class); $extensionProphecy = $this->prophesize(AggregationResultCollectionExtensionInterface::class); $extensionProphecy->applyToCollection($aggregationBuilder, ProviderEntity::class, 'foo', [])->shouldBeCalled(); @@ -126,7 +129,7 @@ public function testAggregationResultExtension() $extensionProphecy->getResult($aggregationBuilder, ProviderEntity::class, 'foo', [])->willReturn([])->shouldBeCalled(); $dataProvider = new CollectionProvider($this->resourceMetadataFactoryProphecy->reveal(), $this->managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals([], $dataProvider->provide(ProviderEntity::class, [], 'foo')); + $this->assertEquals([], $dataProvider->provide($operation, [])); } public function testCannotCreateAggregationBuilder() @@ -142,43 +145,8 @@ public function testCannotCreateAggregationBuilder() $this->managerRegistryProphecy->getManagerForClass(ProviderEntity::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); $dataProvider = new CollectionProvider($this->resourceMetadataFactoryProphecy->reveal(), $this->managerRegistryProphecy->reveal()); - $this->assertEquals([], $dataProvider->provide(ProviderEntity::class, [], 'foo')); - } - - public function testSupportedClass() - { - $documentManagerProphecy = $this->prophesize(DocumentManager::class); - - $this->resourceMetadataFactoryProphecy->create(ProviderEntity::class)->willReturn(new ResourceMetadataCollection(ProviderEntity::class, [(new ApiResource())->withOperations(new Operations(['foo' => new GetCollection()]))])); - $this->managerRegistryProphecy->getManagerForClass(ProviderEntity::class)->willReturn($documentManagerProphecy->reveal())->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(AggregationResultCollectionExtensionInterface::class); - - $dataProvider = new CollectionProvider($this->resourceMetadataFactoryProphecy->reveal(), $this->managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertTrue($dataProvider->supports(ProviderEntity::class, [], 'foo')); - } - - public function testUnsupportedClass() - { - $this->managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(AggregationResultCollectionExtensionInterface::class); - - $dataProvider = new CollectionProvider($this->resourceMetadataFactoryProphecy->reveal(), $this->managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertFalse($dataProvider->supports(Dummy::class, [], 'foo')); - } - - public function testNotCollectionOperation() - { - $documentManagerProphecy = $this->prophesize(DocumentManager::class); - - $this->resourceMetadataFactoryProphecy->create(ProviderEntity::class)->willReturn(new ResourceMetadataCollection(ProviderEntity::class, [(new ApiResource())->withOperations(new Operations(['foo' => new Get()]))])); - $this->managerRegistryProphecy->getManagerForClass(ProviderEntity::class)->willReturn($documentManagerProphecy->reveal())->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(AggregationResultCollectionExtensionInterface::class); - - $dataProvider = new CollectionProvider($this->resourceMetadataFactoryProphecy->reveal(), $this->managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertFalse($dataProvider->supports(ProviderEntity::class, [], 'foo')); + $operation = (new GetCollection())->withName('foo')->withClass(ProviderEntity::class); + $this->assertEquals([], $dataProvider->provide($operation, [])); } public function testOperationNotFound() @@ -204,6 +172,6 @@ public function testOperationNotFound() $extensionProphecy->applyToCollection($aggregationBuilder, ProviderEntity::class, 'bar', [])->shouldBeCalled(); $dataProvider = new CollectionProvider($this->resourceMetadataFactoryProphecy->reveal(), $this->managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals($iterator, $dataProvider->provide(ProviderEntity::class, [], 'bar')); + $this->assertEquals($iterator, $dataProvider->provide((new GetCollection())->withName('bar')->withClass(ProviderEntity::class), [])); } } diff --git a/tests/Doctrine/Odm/State/ItemProviderTest.php b/tests/Doctrine/Odm/State/ItemProviderTest.php index 21529ba0f08..eda8b7bf0f4 100644 --- a/tests/Doctrine/Odm/State/ItemProviderTest.php +++ b/tests/Doctrine/Odm/State/ItemProviderTest.php @@ -21,13 +21,11 @@ use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; -use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Tests\Fixtures\TestBundle\Document\ProviderEntity; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use Doctrine\ODM\MongoDB\Aggregation\Builder; use Doctrine\ODM\MongoDB\Aggregation\Stage\MatchStage as AggregationMatch; use Doctrine\ODM\MongoDB\DocumentManager; @@ -71,11 +69,16 @@ public function testGetItemSingleIdentifier() $extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class); $extensionProphecy->applyToItem($aggregationBuilder, ProviderEntity::class, ['id' => 1], 'foo', $context)->shouldBeCalled(); - $resourceMetadataFactory->create(ProviderEntity::class)->willReturn(new ResourceMetadataCollection(ProviderEntity::class, [(new ApiResource())->withOperations(new Operations(['foo' => (new Get())->withUriVariables([(new Link())->withFromClass(ProviderEntity::class)->withIdentifiers(['id'])])]))])); + $operation = (new Get()) + ->withUriVariables([(new Link())->withFromClass(ProviderEntity::class)->withIdentifiers(['id'])]) + ->withClass(ProviderEntity::class) + ->withName('foo'); + $resourceMetadataCollection = new ResourceMetadataCollection(ProviderEntity::class, [(new ApiResource())->withOperations(new Operations(['foo' => $operation]))]); + $resourceMetadataFactory->create(ProviderEntity::class)->willReturn($resourceMetadataCollection); $dataProvider = new ItemProvider($resourceMetadataFactory->reveal(), $managerRegistry, [$extensionProphecy->reveal()]); - $this->assertEquals($result, $dataProvider->provide(ProviderEntity::class, ['id' => 1], 'foo', $context)); + $this->assertEquals($result, $dataProvider->provide($operation, ['id' => 1], $context)); } public function testGetItemWithExecuteOptions() @@ -99,16 +102,20 @@ public function testGetItemWithExecuteOptions() $managerRegistry = $this->getManagerRegistry(ProviderEntity::class, $aggregationBuilder); $resourceMetadataFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - $resourceMetadataFactory->create(ProviderEntity::class)->willReturn(new ResourceMetadataCollection(ProviderEntity::class, [ - (new ApiResource())->withOperations(new Operations(['foo' => (new Get())->withUriVariables([(new Link())->withFromClass(ProviderEntity::class)->withIdentifiers(['id'])])->withExtraProperties(['doctrine_mongodb' => ['execute_options' => ['allowDiskUse' => true]]])])), - ])); + $operation = (new Get()) + ->withUriVariables([(new Link())->withFromClass(ProviderEntity::class)->withIdentifiers(['id'])]) + ->withClass(ProviderEntity::class) + ->withName('foo') + ->withExtraProperties(['doctrine_mongodb' => ['execute_options' => ['allowDiskUse' => true]]]); + $resourceMetadataCollection = new ResourceMetadataCollection(ProviderEntity::class, [(new ApiResource())->withOperations(new Operations(['foo' => $operation]))]); + $resourceMetadataFactory->create(ProviderEntity::class)->willReturn($resourceMetadataCollection); $extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class); $extensionProphecy->applyToItem($aggregationBuilder, ProviderEntity::class, ['id' => 1], 'foo', $context)->shouldBeCalled(); $dataProvider = new ItemProvider($resourceMetadataFactory->reveal(), $managerRegistry, [$extensionProphecy->reveal()]); - $this->assertEquals($result, $dataProvider->provide(ProviderEntity::class, ['id' => 1], 'foo', $context)); + $this->assertEquals($result, $dataProvider->provide($operation, ['id' => 1], $context)); } public function testGetItemDoubleIdentifier() @@ -137,11 +144,16 @@ public function testGetItemDoubleIdentifier() $extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class); $extensionProphecy->applyToItem($aggregationBuilder, ProviderEntity::class, ['ida' => 1, 'idb' => 2], 'foo', $context)->shouldBeCalled(); - $resourceMetadataFactory->create(ProviderEntity::class)->willReturn(new ResourceMetadataCollection(ProviderEntity::class, [(new ApiResource())->withOperations(new Operations(['foo' => (new Get())->withUriVariables([(new Link())->withFromClass(ProviderEntity::class)->withIdentifiers(['ida', 'idb'])])]))])); + $operation = (new Get()) + ->withUriVariables([(new Link())->withFromClass(ProviderEntity::class)->withIdentifiers(['ida', 'idb'])]) + ->withClass(ProviderEntity::class) + ->withName('foo'); + $resourceMetadataCollection = new ResourceMetadataCollection(ProviderEntity::class, [(new ApiResource())->withOperations(new Operations(['foo' => $operation]))]); + $resourceMetadataFactory->create(ProviderEntity::class)->willReturn($resourceMetadataCollection); $dataProvider = new ItemProvider($resourceMetadataFactory->reveal(), $managerRegistry, [$extensionProphecy->reveal()]); - $this->assertEquals($result, $dataProvider->provide(ProviderEntity::class, ['ida' => 1, 'idb' => 2], 'foo', $context)); + $this->assertEquals($result, $dataProvider->provide($operation, ['ida' => 1, 'idb' => 2], $context)); } public function testAggregationResultExtension() @@ -163,61 +175,16 @@ public function testAggregationResultExtension() $extensionProphecy->getResult($aggregationBuilder, ProviderEntity::class, 'foo', $context)->willReturn([])->shouldBeCalled(); $resourceMetadataFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - $resourceMetadataFactory->create(ProviderEntity::class)->willReturn(new ResourceMetadataCollection(ProviderEntity::class, [(new ApiResource())->withOperations(new Operations(['foo' => (new Get())->withUriVariables([(new Link())->withFromClass(ProviderEntity::class)->withIdentifiers(['id'])])]))])); + $operation = (new Get()) + ->withUriVariables([(new Link())->withFromClass(ProviderEntity::class)->withIdentifiers(['id'])]) + ->withClass(ProviderEntity::class) + ->withName('foo'); + $resourceMetadataCollection = new ResourceMetadataCollection(ProviderEntity::class, [(new ApiResource())->withOperations(new Operations(['foo' => $operation]))]); + $resourceMetadataFactory->create(ProviderEntity::class)->willReturn($resourceMetadataCollection); $dataProvider = new ItemProvider($resourceMetadataFactory->reveal(), $managerRegistry, [$extensionProphecy->reveal()]); - $this->assertEquals([], $dataProvider->provide(ProviderEntity::class, ['id' => 1], 'foo', $context)); - } - - public function testSupportedClass() - { - $resourceMetadataFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - - $documentManagerProphecy = $this->prophesize(DocumentManager::class); - - $resourceMetadataFactory->create(ProviderEntity::class)->willReturn(new ResourceMetadataCollection(ProviderEntity::class, [(new ApiResource())->withOperations(new Operations(['foo' => new Get()]))])); - - $managerProphecy = $this->prophesize(ManagerRegistry::class); - - $managerProphecy->getManagerForClass(ProviderEntity::class)->willReturn($documentManagerProphecy->reveal())->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class); - - $dataProvider = new ItemProvider($resourceMetadataFactory->reveal(), $managerProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertTrue($dataProvider->supports(ProviderEntity::class, [], 'foo')); - } - - public function testNotItemOperation() - { - $resourceMetadataFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - - $documentManagerProphecy = $this->prophesize(DocumentManager::class); - - $resourceMetadataFactory->create(ProviderEntity::class)->willReturn(new ResourceMetadataCollection(ProviderEntity::class, [(new ApiResource())->withOperations(new Operations(['foo' => new GetCollection()]))])); - - $managerProphecy = $this->prophesize(ManagerRegistry::class); - - $managerProphecy->getManagerForClass(ProviderEntity::class)->willReturn($documentManagerProphecy->reveal())->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class); - - $dataProvider = new ItemProvider($resourceMetadataFactory->reveal(), $managerProphecy->reveal(), [$extensionProphecy->reveal()]); - - $this->assertFalse($dataProvider->supports(ProviderEntity::class, [], 'foo')); - } - - public function testUnsupportedClass() - { - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class); - - $resourceMetadataFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(); - - $dataProvider = new ItemProvider($resourceMetadataFactory, $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertFalse($dataProvider->supports(Dummy::class, [], 'foo')); + $this->assertEquals([], $dataProvider->provide($operation, ['id' => 1], $context)); } public function testCannotCreateAggregationBuilder() @@ -237,7 +204,7 @@ public function testCannotCreateAggregationBuilder() $resourceMetadataFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(); - (new ItemProvider($resourceMetadataFactory, $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]))->provide(ProviderEntity::class, [], 'foo', [IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]); + (new ItemProvider($resourceMetadataFactory, $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]))->provide((new Get())->withClass(ProviderEntity::class), [], [IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]); } /** diff --git a/tests/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php b/tests/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php new file mode 100644 index 00000000000..7bce670e5cc --- /dev/null +++ b/tests/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Doctrine\Orm\Metadata\Resource; + +use ApiPlatform\Core\Tests\ProphecyTrait; +use ApiPlatform\Doctrine\Common\State\PersistProcessor; +use ApiPlatform\Doctrine\Common\State\RemoveProcessor; +use ApiPlatform\Doctrine\Orm\Metadata\Resource\DoctrineOrmResourceCollectionMetadataFactory; +use ApiPlatform\Doctrine\Orm\State\CollectionProvider; +use ApiPlatform\Doctrine\Orm\State\ItemProvider; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Operations; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ManagerRegistry; +use PHPUnit\Framework\TestCase; + +class DoctrineOrmResourceCollectionMetadataFactoryTest extends TestCase +{ + use ProphecyTrait; + + private function getResourceMetadataCollectionFactory(Operation $operation) + { + $resourceMetadataCollectionFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); + $resourceMetadataCollectionFactory->create($operation->getClass())->willReturn(new ResourceMetadataCollection($operation->getClass(), [ + (new ApiResource()) + ->withOperations( + new Operations([$operation->getName() => $operation]) + )->withGraphQlOperations([ + 'graphql_'.$operation->getName() => $operation->withName('graphql_'.$operation->getName()), + ]), + ])); + + return $resourceMetadataCollectionFactory->reveal(); + } + + public function testWithoutManager() + { + $operation = (new Get())->withClass(Dummy::class)->withName('get'); + $managerRegistry = $this->prophesize(ManagerRegistry::class); + $managerRegistry->getManagerForClass(Dummy::class)->willReturn(null); + + $resourceMetadataCollectionFactory = new DoctrineOrmResourceCollectionMetadataFactory($managerRegistry->reveal(), $this->getResourceMetadataCollectionFactory($operation)); + $resourceMetadataCollection = $resourceMetadataCollectionFactory->create(Dummy::class); + + $this->assertNull($resourceMetadataCollection->getOperation('get')->getProvider()); + $this->assertNull($resourceMetadataCollection->getOperation('graphql_get')->getProvider()); + } + + /** + * @dataProvider operationProvider + */ + public function testWithProvider(Operation $operation, string $expectedProvider = null, string $expectedProcessor = null) + { + $objectManager = $this->prophesize(EntityManagerInterface::class); + $managerRegistry = $this->prophesize(ManagerRegistry::class); + $managerRegistry->getManagerForClass($operation->getClass())->willReturn($objectManager->reveal()); + $resourceMetadataCollectionFactory = new DoctrineOrmResourceCollectionMetadataFactory($managerRegistry->reveal(), $this->getResourceMetadataCollectionFactory($operation)); + $resourceMetadataCollection = $resourceMetadataCollectionFactory->create($operation->getClass()); + $this->assertEquals($expectedProvider, $resourceMetadataCollection->getOperation($operation->getName())->getProvider()); + $this->assertEquals($expectedProvider, $resourceMetadataCollection->getOperation('graphql_'.$operation->getName())->getProvider()); + $this->assertEquals($expectedProcessor, $resourceMetadataCollection->getOperation($operation->getName())->getProcessor()); + $this->assertEquals($expectedProcessor, $resourceMetadataCollection->getOperation('graphql_'.$operation->getName())->getProcessor()); + } + + public function operationProvider(): iterable + { + $default = (new Get())->withName('get')->withClass(Dummy::class); + + yield [(new Get())->withProvider('has a provider')->withProcessor('and a processor')->withOperation($default), 'has a provider', 'and a processor']; + yield [(new Get())->withOperation($default), ItemProvider::class, PersistProcessor::class]; + yield [(new GetCollection())->withOperation($default), CollectionProvider::class, PersistProcessor::class]; + yield [(new Delete())->withOperation($default), ItemProvider::class, RemoveProcessor::class]; + } +} diff --git a/tests/Doctrine/Orm/State/CollectionProviderTest.php b/tests/Doctrine/Orm/State/CollectionProviderTest.php index 6a9d0af244f..01e97627bfb 100644 --- a/tests/Doctrine/Orm/State/CollectionProviderTest.php +++ b/tests/Doctrine/Orm/State/CollectionProviderTest.php @@ -20,15 +20,12 @@ use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\OperationResource; use Doctrine\ORM\AbstractQuery; -use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\QueryBuilder; @@ -72,8 +69,9 @@ public function testGetCollection() $extensionProphecy = $this->prophesize(QueryCollectionExtensionInterface::class); $extensionProphecy->applyToCollection($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), OperationResource::class, 'getCollection', [])->shouldBeCalled(); + $operation = (new GetCollection())->withClass(OperationResource::class)->withName('getCollection'); $dataProvider = new CollectionProvider($resourceMetadataFactoryProphecy->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals([], $dataProvider->provide(OperationResource::class, [], 'getCollection')); + $this->assertEquals([], $dataProvider->provide($operation)); } /** @@ -105,7 +103,8 @@ public function testQueryResultExtension() $resourceMetadataFactoryProphecy->create(OperationResource::class)->willReturn(new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations(['' => new GetCollection()]))])); $dataProvider = new CollectionProvider($resourceMetadataFactoryProphecy->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals([], $dataProvider->provide(OperationResource::class)); + $operation = (new GetCollection())->withClass(OperationResource::class); + $this->assertEquals([], $dataProvider->provide($operation)); } /** @@ -127,60 +126,7 @@ public function testCannotCreateQueryBuilder() $managerRegistryProphecy->getManagerForClass(OperationResource::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); $dataProvider = new CollectionProvider($resourceMetadataFactoryProphecy->reveal(), $managerRegistryProphecy->reveal()); - $this->assertEquals([], $dataProvider->provide(OperationResource::class)); - } - - /** - * @requires PHP 8.0 - */ - public function testSupportedClass() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $entityManagerProphecy = $this->prophesize(EntityManagerInterface::class); - - $resourceMetadataFactoryProphecy->create(OperationResource::class)->willReturn(new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations(['getCollection' => new GetCollection()]))])); - $managerRegistryProphecy->getManagerForClass(OperationResource::class)->willReturn($entityManagerProphecy->reveal())->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(QueryResultCollectionExtensionInterface::class); - - $dataProvider = new CollectionProvider($resourceMetadataFactoryProphecy->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertTrue($dataProvider->supports(OperationResource::class, [], 'getCollection')); - } - - /** - * @requires PHP 8.0 - */ - public function testUnsupportedClass() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(QueryResultCollectionExtensionInterface::class); - - $dataProvider = new CollectionProvider($resourceMetadataFactoryProphecy->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertFalse($dataProvider->supports(Dummy::class)); - } - - /** - * @requires PHP 8.0 - */ - public function testNotCollectionOperation() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $entityManagerProphecy = $this->prophesize(EntityManagerInterface::class); - - $resourceMetadataFactoryProphecy->create(OperationResource::class)->willReturn(new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations(['get' => new Get()]))])); - $managerRegistryProphecy->getManagerForClass(OperationResource::class)->willReturn($entityManagerProphecy->reveal())->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(QueryResultCollectionExtensionInterface::class); - - $dataProvider = new CollectionProvider($resourceMetadataFactoryProphecy->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertFalse($dataProvider->supports(OperationResource::class, [], 'get')); + $operation = (new GetCollection())->withClass(OperationResource::class)->withName('getCollection'); + $this->assertEquals([], $dataProvider->provide($operation)); } } diff --git a/tests/Doctrine/Orm/State/ItemProviderTest.php b/tests/Doctrine/Orm/State/ItemProviderTest.php index 049d57b77a0..d0e6554ef6a 100644 --- a/tests/Doctrine/Orm/State/ItemProviderTest.php +++ b/tests/Doctrine/Orm/State/ItemProviderTest.php @@ -22,13 +22,12 @@ use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; -use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Company; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Employee; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\OperationResource; use Doctrine\DBAL\Connection; @@ -75,19 +74,22 @@ public function testGetItemSingleIdentifier() ], ], $queryBuilder); - $resourceMetadataFactoryProphecy->create(OperationResource::class)->willReturn(new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations(['get' => (new Get())->withUriVariables([ + /** @var HttpOperation */ + $operation = (new Get())->withUriVariables([ 'identifier' => (new Link())->withFromClass("ApiPlatform\Tests\Fixtures\TestBundle\Entity\OperationResource") ->withIdentifiers([ 0 => 'identifier', ]), - ])]))])); + ])->withClass(OperationResource::class)->withName('get'); + + $resourceMetadataFactoryProphecy->create(OperationResource::class)->willReturn(new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations(['get' => $operation]))])); $extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class); $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), OperationResource::class, ['identifier' => 1], 'get', $context)->shouldBeCalled(); $dataProvider = new ItemProvider($resourceMetadataFactoryProphecy->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals([], $dataProvider->provide(OperationResource::class, ['identifier' => 1], 'get', $context)); + $this->assertEquals([], $dataProvider->provide($operation, ['identifier' => 1], $context)); } /** @@ -110,7 +112,8 @@ public function testGetItemDoubleIdentifier() $queryBuilderProphecy->andWhere('o.ida = :ida_p2')->shouldBeCalled(); $queryBuilderProphecy->getRootAliases()->shouldBeCalled()->willReturn(['o']); - $resourceMetadataFactoryProphecy->create(OperationResource::class)->willReturn(new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations(['get' => (new Get())->withUriVariables([ + /** @var HttpOperation */ + $operation = (new Get())->withUriVariables([ 'ida' => (new Link())->withFromClass('ApiPlatform\Tests\Fixtures\TestBundle\Entity\OperationResource') ->withIdentifiers([ 0 => 'ida', @@ -119,7 +122,9 @@ public function testGetItemDoubleIdentifier() ->withIdentifiers([ 0 => 'idb', ]), - ])]))])); + ])->withName('get')->withClass(OperationResource::class); + + $resourceMetadataFactoryProphecy->create(OperationResource::class)->willReturn(new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations(['get' => $operation]))])); $queryBuilderProphecy->setParameter('idb_p1', 2, Types::INTEGER)->shouldBeCalled(); $queryBuilderProphecy->setParameter('ida_p2', 1, Types::INTEGER)->shouldBeCalled(); @@ -141,7 +146,7 @@ public function testGetItemDoubleIdentifier() $dataProvider = new ItemProvider($resourceMetadataFactoryProphecy->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals([], $dataProvider->provide(OperationResource::class, ['ida' => 1, 'idb' => 2], 'get', $context)); + $this->assertEquals([], $dataProvider->provide($operation, ['ida' => 1, 'idb' => 2], $context)); } /** @@ -170,69 +175,17 @@ public function testQueryResultExtension() $extensionProphecy->supportsResult(OperationResource::class, 'get', $context)->willReturn(true)->shouldBeCalled(); $extensionProphecy->getResult($queryBuilder, OperationResource::class, 'get', $context)->willReturn([])->shouldBeCalled(); - $resourceMetadataFactoryProphecy->create(OperationResource::class)->willReturn(new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations(['get' => (new Get())->withUriVariables([ + /** @var HttpOperation */ + $operation = (new Get())->withUriVariables([ 'identifier' => (new Link())->withFromClass("ApiPlatform\Tests\Fixtures\TestBundle\Entity\OperationResource")->withIdentifiers([ 0 => 'identifier', ]), - ])]))])); - - $dataProvider = new ItemProvider($resourceMetadataFactoryProphecy->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - - $this->assertEquals([], $dataProvider->provide(OperationResource::class, ['identifier' => 1], 'get', $context)); - } - - /** - * @requires PHP 8.0 - */ - public function testSupportedClass() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $entityManagerProphecy = $this->prophesize(EntityManagerInterface::class); - - $resourceMetadataFactoryProphecy->create(OperationResource::class)->willReturn(new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations(['get' => new Get()]))])); - $managerRegistryProphecy->getManagerForClass(OperationResource::class)->willReturn($entityManagerProphecy->reveal())->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(QueryResultItemExtensionInterface::class); - - $dataProvider = new ItemProvider($resourceMetadataFactoryProphecy->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertTrue($dataProvider->supports(OperationResource::class, [], 'get')); - } - - /** - * @requires PHP 8.0 - */ - public function testNotItemOperation() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $entityManagerProphecy = $this->prophesize(EntityManagerInterface::class); - - $resourceMetadataFactoryProphecy->create(OperationResource::class)->willReturn(new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations(['getCollection' => new GetCollection()]))])); - $managerRegistryProphecy->getManagerForClass(OperationResource::class)->willReturn($entityManagerProphecy->reveal())->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(QueryResultItemExtensionInterface::class); + ])->withClass(OperationResource::class)->withName('get'); + $resourceMetadataFactoryProphecy->create(OperationResource::class)->willReturn(new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations(['get' => $operation]))])); $dataProvider = new ItemProvider($resourceMetadataFactoryProphecy->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertFalse($dataProvider->supports(OperationResource::class, [], 'getCollection')); - } - - /** - * @requires PHP 8.0 - */ - public function testUnsupportedClass() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class); - - $dataProvider = new ItemProvider($resourceMetadataFactoryProphecy->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertFalse($dataProvider->supports(Dummy::class)); + $this->assertEquals([], $dataProvider->provide($operation, ['identifier' => 1], $context)); } /** @@ -267,8 +220,9 @@ public function testCannotCreateQueryBuilder() $extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class); + $operation = (new Get())->withClass(OperationResource::class); $itemProvider = new ItemProvider($resourceMetadataFactoryProphecy->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $itemProvider->provide(OperationResource::class, ['id' => 1234], null, [IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]); + $itemProvider->provide($operation, ['id' => 1234], [IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]); } /** @@ -342,18 +296,21 @@ public function testGetSubresourceFromProperty() Employee::class => $employeeClassMetadataProphecy->reveal(), ]); - $resourceMetadataFactoryProphecy->create(Company::class)->willReturn(new ResourceMetadataCollection(Company::class, [(new ApiResource())->withOperations(new Operations(['getCompany' => (new Get())->withUriVariables([ + /** @var HttpOperation */ + $operation = (new Get())->withUriVariables([ 'employeeId' => (new Link())->withFromClass(Employee::class) ->withIdentifiers([ 0 => 'id', ])->withFromProperty('company'), - ])]))])); + ])->withName('getCompany')->withClass(Company::class); + + $resourceMetadataFactoryProphecy->create(Company::class)->willReturn(new ResourceMetadataCollection(Company::class, [(new ApiResource())->withOperations(new Operations(['getCompany' => $operation]))])); $extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class); $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Company::class, ['employeeId' => 1], 'getCompany', [])->shouldBeCalled(); $dataProvider = new ItemProvider($resourceMetadataFactoryProphecy->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals([], $dataProvider->provide(Company::class, ['employeeId' => 1], 'getCompany')); + $this->assertEquals([], $dataProvider->provide($operation, ['employeeId' => 1])); } } diff --git a/tests/Elasticsearch/State/CollectionProviderTest.php b/tests/Elasticsearch/State/CollectionProviderTest.php index 9e16d2fe29f..7cf88adcc98 100644 --- a/tests/Elasticsearch/State/CollectionProviderTest.php +++ b/tests/Elasticsearch/State/CollectionProviderTest.php @@ -14,21 +14,15 @@ namespace ApiPlatform\Core\Tests\Elasticsearch\State; use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Elasticsearch\Exception\IndexNotFoundException; use ApiPlatform\Elasticsearch\Extension\RequestBodySearchCollectionExtensionInterface; use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; use ApiPlatform\Elasticsearch\Metadata\Document\Factory\DocumentMetadataFactoryInterface; use ApiPlatform\Elasticsearch\Paginator; use ApiPlatform\Elasticsearch\State\CollectionProvider; -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Operation; -use ApiPlatform\Metadata\Operations; +use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\State\Pagination\Pagination; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\CompositeRelation; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Foo; use Elasticsearch\Client; use PHPUnit\Framework\TestCase; @@ -53,59 +47,11 @@ public function testConstruct() $this->prophesize(Client::class)->reveal(), $this->prophesize(DocumentMetadataFactoryInterface::class)->reveal(), $this->prophesize(DenormalizerInterface::class)->reveal(), - new Pagination($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal()), - $this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal() + new Pagination($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal()) ) ); } - public function testSupports() - { - $documentMetadataFactoryProphecy = $this->prophesize(DocumentMetadataFactoryInterface::class); - $documentMetadataFactoryProphecy->create(Foo::class)->willReturn(new DocumentMetadata('foo'))->shouldBeCalled(); - $documentMetadataFactoryProphecy->create(Dummy::class)->willThrow(new IndexNotFoundException())->shouldBeCalled(); - $documentMetadataFactoryProphecy->create(CompositeRelation::class)->willReturn(new DocumentMetadata('composite_relation'))->shouldNotBeCalled(); - - $resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - - $fooResourceMetadataCollection = new ResourceMetadataCollection(Foo::class); - $fooResourceMetadataCollection[] = (new ApiResource())->withOperations(new Operations([ - 'api_foo_get_collection' => (new Operation())->withElasticsearch(true)->withCollection(true), - ])); - $resourceMetadataCollectionFactoryProphecy->create(Foo::class)->shouldBeCalled()->willReturn($fooResourceMetadataCollection); - - $dummyCarResourceMetadataCollection = new ResourceMetadataCollection(DummyCar::class); - $dummyCarResourceMetadataCollection[] = (new ApiResource())->withOperations(new Operations([ - 'api_dummy_car_get_collection' => (new Operation())->withElasticsearch(false)->withCollection(true), - ])); - $resourceMetadataCollectionFactoryProphecy->create(DummyCar::class)->shouldBeCalled()->willReturn($dummyCarResourceMetadataCollection); - - $dummyResourceMetadataCollection = new ResourceMetadataCollection(Dummy::class); - $dummyResourceMetadataCollection[] = (new ApiResource())->withOperations(new Operations([ - 'api_dummy_get_collection' => (new Operation())->withElasticsearch(true)->withCollection(true), - ])); - $resourceMetadataCollectionFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyResourceMetadataCollection); - - $compositeRelationResourceMetadataCollection = new ResourceMetadataCollection(CompositeRelation::class); - $compositeRelationResourceMetadataCollection[] = (new ApiResource())->withOperations(new Operations([ - 'api_composite_relation_get' => (new Operation())->withElasticsearch(true)->withCollection(false), - ])); - $resourceMetadataCollectionFactoryProphecy->create(CompositeRelation::class)->shouldBeCalled()->willReturn($compositeRelationResourceMetadataCollection); - - $provider = new CollectionProvider( - $this->prophesize(Client::class)->reveal(), - $documentMetadataFactoryProphecy->reveal(), - $this->prophesize(DenormalizerInterface::class)->reveal(), - new Pagination($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal()), - $resourceMetadataCollectionFactoryProphecy->reveal() - ); - - self::assertTrue($provider->supports(Foo::class, [], 'api_foo_get_collection')); - self::assertFalse($provider->supports(DummyCar::class, [], 'api_dummy_car_get_collection')); - self::assertFalse($provider->supports(Dummy::class, [], 'api_dummy_get_collection')); - self::assertFalse($provider->supports(CompositeRelation::class, [], 'api_composite_relation_get')); - } - public function testGetCollection() { $context = [ @@ -186,13 +132,12 @@ public function testGetCollection() $documentMetadataFactoryProphecy->reveal(), $denormalizer = $this->prophesize(DenormalizerInterface::class)->reveal(), new Pagination($resourceMetadataCollectionFactoryProphecy->reveal(), ['items_per_page' => 2]), - $resourceMetadataCollectionFactoryProphecy->reveal(), [$requestBodySearchCollectionExtensionProphecy->reveal()] ); self::assertEquals( new Paginator($denormalizer, $documents, Foo::class, 2, 0, $context), - $provider->provide(Foo::class, [], 'get', $context) + $provider->provide((new Get())->withName('get')->withClass(Foo::class), [], $context) ); } } diff --git a/tests/Elasticsearch/State/ItemProviderTest.php b/tests/Elasticsearch/State/ItemProviderTest.php index 41555ff51c0..7d97cc11caa 100644 --- a/tests/Elasticsearch/State/ItemProviderTest.php +++ b/tests/Elasticsearch/State/ItemProviderTest.php @@ -14,19 +14,12 @@ namespace ApiPlatform\Core\Tests\Elasticsearch\State; use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Elasticsearch\Exception\IndexNotFoundException; use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; use ApiPlatform\Elasticsearch\Metadata\Document\Factory\DocumentMetadataFactoryInterface; use ApiPlatform\Elasticsearch\Serializer\DocumentNormalizer; use ApiPlatform\Elasticsearch\State\ItemProvider; -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Operation; -use ApiPlatform\Metadata\Operations; +use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\CompositeRelation; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Foo; use Elasticsearch\Client; use Elasticsearch\Common\Exceptions\Missing404Exception; @@ -57,52 +50,6 @@ public function testConstruct() ); } - public function testSupports() - { - $documentMetadataFactoryProphecy = $this->prophesize(DocumentMetadataFactoryInterface::class); - $documentMetadataFactoryProphecy->create(Foo::class)->willReturn(new DocumentMetadata('foo'))->shouldBeCalled(); - $documentMetadataFactoryProphecy->create(Dummy::class)->willThrow(new IndexNotFoundException())->shouldBeCalled(); - $documentMetadataFactoryProphecy->create(CompositeRelation::class)->willReturn(new DocumentMetadata('composite_relation'))->shouldNotBeCalled(); - - $resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - - $fooResourceMetadataCollection = new ResourceMetadataCollection(Foo::class); - $fooResourceMetadataCollection[] = (new ApiResource())->withOperations(new Operations([ - 'api_foo_get' => (new Operation())->withElasticsearch(true)->withCollection(false), - ])); - $resourceMetadataCollectionFactoryProphecy->create(Foo::class)->shouldBeCalled()->willReturn($fooResourceMetadataCollection); - - $dummyCarResourceMetadataCollection = new ResourceMetadataCollection(DummyCar::class); - $dummyCarResourceMetadataCollection[] = (new ApiResource())->withOperations(new Operations([ - 'api_dummy_car_get' => (new Operation())->withElasticsearch(false)->withCollection(false), - ])); - $resourceMetadataCollectionFactoryProphecy->create(DummyCar::class)->shouldBeCalled()->willReturn($dummyCarResourceMetadataCollection); - - $dummyResourceMetadataCollection = new ResourceMetadataCollection(Dummy::class); - $dummyResourceMetadataCollection[] = (new ApiResource())->withOperations(new Operations([ - 'api_dummy_get' => (new Operation())->withElasticsearch(true)->withCollection(false), - ])); - $resourceMetadataCollectionFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyResourceMetadataCollection); - - $compositeRelationResourceMetadataCollection = new ResourceMetadataCollection(CompositeRelation::class); - $compositeRelationResourceMetadataCollection[] = (new ApiResource())->withOperations(new Operations([ - 'api_composite_relation_get_collection' => (new Operation())->withElasticsearch(true)->withCollection(true), - ])); - $resourceMetadataCollectionFactoryProphecy->create(CompositeRelation::class)->shouldBeCalled()->willReturn($compositeRelationResourceMetadataCollection); - - $provider = new ItemProvider( - $this->prophesize(Client::class)->reveal(), - $documentMetadataFactoryProphecy->reveal(), - $this->prophesize(DenormalizerInterface::class)->reveal(), - $resourceMetadataCollectionFactoryProphecy->reveal() - ); - - self::assertTrue($provider->supports(Foo::class, [], 'api_foo_get')); - self::assertFalse($provider->supports(DummyCar::class, [], 'api_dummy_car_get')); - self::assertFalse($provider->supports(Dummy::class, [], 'api_dummy_get')); - self::assertFalse($provider->supports(CompositeRelation::class, [], 'api_composite_relation_get_collection')); - } - public function testGetItem() { $documentMetadataFactoryProphecy = $this->prophesize(DocumentMetadataFactoryInterface::class); @@ -131,11 +78,9 @@ public function testGetItem() $denormalizerProphecy = $this->prophesize(DenormalizerInterface::class); $denormalizerProphecy->denormalize($document, Foo::class, DocumentNormalizer::FORMAT, [AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => true])->willReturn($foo)->shouldBeCalled(); - $resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - - $itemDataProvider = new ItemProvider($clientProphecy->reveal(), $documentMetadataFactoryProphecy->reveal(), $denormalizerProphecy->reveal(), $resourceMetadataCollectionFactoryProphecy->reveal()); + $itemDataProvider = new ItemProvider($clientProphecy->reveal(), $documentMetadataFactoryProphecy->reveal(), $denormalizerProphecy->reveal()); - self::assertSame($foo, $itemDataProvider->provide(Foo::class, ['id' => 1])); + self::assertSame($foo, $itemDataProvider->provide((new Get())->withClass(Foo::class), ['id' => 1])); } public function testGetItemWithMissing404Exception() @@ -148,8 +93,8 @@ public function testGetItemWithMissing404Exception() $resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - $itemDataProvider = new ItemProvider($clientProphecy->reveal(), $documentMetadataFactoryProphecy->reveal(), $this->prophesize(DenormalizerInterface::class)->reveal(), $resourceMetadataCollectionFactoryProphecy->reveal()); + $itemDataProvider = new ItemProvider($clientProphecy->reveal(), $documentMetadataFactoryProphecy->reveal(), $this->prophesize(DenormalizerInterface::class)->reveal()); - self::assertNull($itemDataProvider->provide(Foo::class, ['id' => 404])); + self::assertNull($itemDataProvider->provide((new Get())->withClass(Foo::class), ['id' => 404])); } } diff --git a/tests/Fixtures/TestBundle/Entity/AttributeResource.php b/tests/Fixtures/TestBundle/Entity/AttributeResource.php index f46e4b55f49..ca4f003a1cd 100644 --- a/tests/Fixtures/TestBundle/Entity/AttributeResource.php +++ b/tests/Fixtures/TestBundle/Entity/AttributeResource.php @@ -20,12 +20,23 @@ use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Put; +use ApiPlatform\Tests\Fixtures\TestBundle\State\AttributeResourceProcessor; +use ApiPlatform\Tests\Fixtures\TestBundle\State\AttributeResourceProvider; -#[ApiResource(normalizationContext: ['skip_null_values' => true])] +#[ApiResource( + normalizationContext: ['skip_null_values' => true], + provider: AttributeResourceProvider::class +)] #[Get] #[Put] #[Delete] -#[ApiResource('/dummy/{dummyId}/attribute_resources/{identifier}.{_format}', inputFormats: ['json' => ['application/merge-patch+json']], status: 301)] +#[ApiResource( + '/dummy/{dummyId}/attribute_resources/{identifier}.{_format}', + inputFormats: ['json' => ['application/merge-patch+json']], + status: 301, + provider: AttributeResourceProvider::class, + processor: [AttributeResourceProcessor::class, 'process'] +)] #[Get] #[Patch] final class AttributeResource diff --git a/tests/Fixtures/TestBundle/Entity/AttributeResources.php b/tests/Fixtures/TestBundle/Entity/AttributeResources.php index 97bd7119513..235a99dc7bf 100644 --- a/tests/Fixtures/TestBundle/Entity/AttributeResources.php +++ b/tests/Fixtures/TestBundle/Entity/AttributeResources.php @@ -16,11 +16,16 @@ use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Post; +use ApiPlatform\Tests\Fixtures\TestBundle\State\AttributeResourceProvider; use ArrayIterator; use IteratorAggregate; use Traversable; -#[ApiResource('/attribute_resources.{_format}', normalizationContext: ['skip_null_values' => true])] +#[ApiResource( + '/attribute_resources.{_format}', + normalizationContext: ['skip_null_values' => true], + provider: AttributeResourceProvider::class +)] #[GetCollection] #[Post] final class AttributeResources implements IteratorAggregate diff --git a/tests/Fixtures/TestBundle/Entity/OperationResource.php b/tests/Fixtures/TestBundle/Entity/OperationResource.php index 56eade5409d..5fe30a480c9 100644 --- a/tests/Fixtures/TestBundle/Entity/OperationResource.php +++ b/tests/Fixtures/TestBundle/Entity/OperationResource.php @@ -20,12 +20,13 @@ use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; +use ApiPlatform\Tests\Fixtures\TestBundle\State\OperationResourceProcessor; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity */ -#[ApiResource(normalizationContext: ['skip_null_values' => true])] +#[ApiResource(normalizationContext: ['skip_null_values' => true], processor: OperationResourceProcessor::class)] #[Get] #[Patch(inputFormats: ['json' => ['application/merge-patch+json']])] #[Post] diff --git a/tests/Fixtures/TestBundle/Metadata/ProviderResourceMetadatatCollectionFactory.php b/tests/Fixtures/TestBundle/Metadata/ProviderResourceMetadatatCollectionFactory.php new file mode 100644 index 00000000000..c415d3ec1f8 --- /dev/null +++ b/tests/Fixtures/TestBundle/Metadata/ProviderResourceMetadatatCollectionFactory.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\Metadata; + +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use ApiPlatform\Tests\Fixtures\TestBundle\Document\ContainNonResource as ContainNonResourceDocument; +use ApiPlatform\Tests\Fixtures\TestBundle\Document\Taxon as TaxonDocument; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ContainNonResource; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ResourceInterface; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Taxon; +use ApiPlatform\Tests\Fixtures\TestBundle\Model\ResourceInterface as ResourceInterfaceDocument; +use ApiPlatform\Tests\Fixtures\TestBundle\Model\SerializableResource; +use ApiPlatform\Tests\Fixtures\TestBundle\Model\TaxonInterface; +use ApiPlatform\Tests\Fixtures\TestBundle\State\ContainNonResourceProvider; +use ApiPlatform\Tests\Fixtures\TestBundle\State\ResourceInterfaceImplementationProvider; +use ApiPlatform\Tests\Fixtures\TestBundle\State\SerializableProvider; +use ApiPlatform\Tests\Fixtures\TestBundle\State\TaxonItemProvider; + +class ProviderResourceMetadatatCollectionFactory implements ResourceMetadataCollectionFactoryInterface +{ + /** + * @var ResourceMetadataCollectionFactoryInterface + */ + private $decorated; + + public function __construct(ResourceMetadataCollectionFactoryInterface $decorated) + { + $this->decorated = $decorated; + } + + /** + * {@inheritDoc} + */ + public function create(string $resourceClass): ResourceMetadataCollection + { + $resourceMetadataCollection = $this->decorated->create($resourceClass); + + if (ResourceInterface::class === $resourceClass || ResourceInterfaceDocument::class === $resourceClass) { + return $this->setProvider($resourceMetadataCollection, ResourceInterfaceImplementationProvider::class); + } + + if (ContainNonResource::class === $resourceClass || ContainNonResourceDocument::class === $resourceClass) { + return $this->setProvider($resourceMetadataCollection, ContainNonResourceProvider::class); + } + + if (SerializableResource::class === $resourceClass) { + return $this->setProvider($resourceMetadataCollection, SerializableProvider::class); + } + + if (Taxon::class === $resourceClass || TaxonDocument::class === $resourceClass || TaxonInterface::class === $resourceClass) { + return $this->setProvider($resourceMetadataCollection, TaxonItemProvider::class); + } + + return $resourceMetadataCollection; + } + + private function setProvider(ResourceMetadataCollection $resourceMetadataCollection, string $provider) + { + foreach ($resourceMetadataCollection as $i => $resourceMetadata) { + $operations = $resourceMetadata->getOperations(); + + if ($operations) { + foreach ($resourceMetadata->getOperations() as $operationName => $operation) { + $operations->add($operationName, $operation->withProvider($provider)); + } + + $resourceMetadata = $resourceMetadata->withOperations($operations); + } + + $graphQlOperations = $resourceMetadata->getGraphQlOperations(); + + if ($graphQlOperations) { + foreach ($graphQlOperations as $operationName => $graphQlOperation) { + $graphQlOperations[$operationName] = $graphQlOperation->withProvider($provider); + } + + $resourceMetadata = $resourceMetadata->withGraphQlOperations($graphQlOperations); + } + + $resourceMetadataCollection[$i] = $resourceMetadata; + } + + return $resourceMetadataCollection; + } +} diff --git a/tests/Fixtures/TestBundle/Model/ProductInterface.php b/tests/Fixtures/TestBundle/Model/ProductInterface.php index 439780e1ff0..a2780efbcd8 100644 --- a/tests/Fixtures/TestBundle/Model/ProductInterface.php +++ b/tests/Fixtures/TestBundle/Model/ProductInterface.php @@ -15,13 +15,14 @@ use ApiPlatform\Core\Annotation\ApiProperty; use ApiPlatform\Core\Annotation\ApiResource; +use ApiPlatform\Tests\Fixtures\TestBundle\State\ProductProvider; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; /** * @ApiResource( * shortName="Product", - * attributes={"identifiers"="code"}, + * attributes={"identifiers"="code", "provider"=ProductProvider::class}, * normalizationContext={ * "groups"={"product_read"}, * }, diff --git a/tests/Fixtures/TestBundle/State/AttributeResourceProcessor.php b/tests/Fixtures/TestBundle/State/AttributeResourceProcessor.php new file mode 100644 index 00000000000..cab13b2b8fc --- /dev/null +++ b/tests/Fixtures/TestBundle/State/AttributeResourceProcessor.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Fixtures\TestBundle\State; + +use ApiPlatform\Metadata\Operation; + +class AttributeResourceProcessor +{ + public static function process($data, Operation $operation, array $uriVariables = [], array $context = []) + { + } +} diff --git a/tests/Fixtures/TestBundle/State/AttributeResourceProvider.php b/tests/Fixtures/TestBundle/State/AttributeResourceProvider.php index 06808176569..875a64faad6 100644 --- a/tests/Fixtures/TestBundle/State/AttributeResourceProvider.php +++ b/tests/Fixtures/TestBundle/State/AttributeResourceProvider.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\State; +use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\AttributeResource; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\AttributeResources; @@ -20,14 +21,14 @@ class AttributeResourceProvider implements ProviderInterface { - public function provide(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []) { - if (isset($identifiers['identifier'])) { - $resource = new AttributeResource($identifiers['identifier'], 'Foo'); + if (isset($uriVariables['identifier'])) { + $resource = new AttributeResource($uriVariables['identifier'], 'Foo'); - if ($identifiers['dummyId'] ?? false) { + if ($uriVariables['dummyId'] ?? false) { $resource->dummy = new Dummy(); - $resource->dummy->setId($identifiers['dummyId']); + $resource->dummy->setId($uriVariables['dummyId']); } return $resource; @@ -35,9 +36,4 @@ public function provide(string $resourceClass, array $identifiers = [], ?string return new AttributeResources(new AttributeResource(1, 'Foo'), new AttributeResource(2, 'Bar')); } - - public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool - { - return AttributeResource::class === $resourceClass || AttributeResources::class === $resourceClass; - } } diff --git a/tests/Fixtures/TestBundle/State/ContainNonResourceProvider.php b/tests/Fixtures/TestBundle/State/ContainNonResourceProvider.php index c8177112b95..319545a7316 100644 --- a/tests/Fixtures/TestBundle/State/ContainNonResourceProvider.php +++ b/tests/Fixtures/TestBundle/State/ContainNonResourceProvider.php @@ -13,10 +13,9 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\State; +use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use ApiPlatform\Tests\Fixtures\NotAResource; -use ApiPlatform\Tests\Fixtures\TestBundle\Document\ContainNonResource as ContainNonResourceDocument; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ContainNonResource; /** * @author Kévin Dunglas @@ -26,13 +25,14 @@ class ContainNonResourceProvider implements ProviderInterface /** * {@inheritDoc} */ - public function provide(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []) { - $id = $identifiers['id'] ?? null; + $id = $uriVariables['id'] ?? null; if (!\is_scalar($id)) { throw new \InvalidArgumentException('The id must be a scalar.'); } + $resourceClass = $operation->getClass(); // Retrieve the blog post item from somewhere $cnr = new $resourceClass(); $cnr->id = $id; @@ -43,12 +43,4 @@ public function provide(string $resourceClass, array $identifiers = [], ?string return $cnr; } - - /** - * {@inheritDoc} - */ - public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool - { - return \in_array($resourceClass, [ContainNonResource::class, ContainNonResourceDocument::class], true); - } } diff --git a/tests/Fixtures/TestBundle/State/DummyDtoNoOutputProcessor.php b/tests/Fixtures/TestBundle/State/DummyDtoNoOutputProcessor.php index 6f09f968b67..8491a2f0ab4 100644 --- a/tests/Fixtures/TestBundle/State/DummyDtoNoOutputProcessor.php +++ b/tests/Fixtures/TestBundle/State/DummyDtoNoOutputProcessor.php @@ -32,7 +32,7 @@ public function __construct(ManagerRegistry $registry) /** * {@inheritDoc} */ - public function process($data, array $identifiers = [], ?string $operationName = null, array $context = []) + public function process($data, array $uriVariables = [], ?string $operationName = null, array $context = []) { $isOrm = true; $em = $this->registry->getManagerForClass(DummyDtoNoOutput::class); diff --git a/tests/Fixtures/TestBundle/State/OperationResourceProcessor.php b/tests/Fixtures/TestBundle/State/OperationResourceProcessor.php index 6397994f25a..26739e258d4 100644 --- a/tests/Fixtures/TestBundle/State/OperationResourceProcessor.php +++ b/tests/Fixtures/TestBundle/State/OperationResourceProcessor.php @@ -13,9 +13,9 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\State; +use ApiPlatform\Metadata\DeleteOperationInterface; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProcessorInterface; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\OperationResource; use ApiPlatform\Util\ClassInfoTrait; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; @@ -33,11 +33,6 @@ public function __construct(ManagerRegistry $managerRegistry) $this->managerRegistry = $managerRegistry; } - public function supports($data, array $identifiers = [], ?string $operationName = null, array $context = []): bool - { - return $data instanceof OperationResource; - } - private function persist($data, array $context = []) { if (!$manager = $this->getManager($data)) { @@ -64,9 +59,9 @@ private function remove($data, array $context = []) $manager->flush(); } - public function process($data, array $identifiers = [], ?string $operationName = null, array $context = []) + public function process($data, Operation $operation, array $uriVariables = [], array $context = []) { - if (\array_key_exists('operation', $context) && Operation::METHOD_DELETE === ($context['operation']->getMethod() ?? null)) { + if ($operation instanceof DeleteOperationInterface) { return $this->remove($data); } diff --git a/tests/Fixtures/TestBundle/State/ProductItemProvider.php b/tests/Fixtures/TestBundle/State/ProductProvider.php similarity index 58% rename from tests/Fixtures/TestBundle/State/ProductItemProvider.php rename to tests/Fixtures/TestBundle/State/ProductProvider.php index e27b9c9bd22..9f02ffa7a67 100644 --- a/tests/Fixtures/TestBundle/State/ProductItemProvider.php +++ b/tests/Fixtures/TestBundle/State/ProductProvider.php @@ -13,15 +13,14 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\State; -use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use ApiPlatform\Tests\Fixtures\TestBundle\Document\Product as ProductDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Product; -use ApiPlatform\Tests\Fixtures\TestBundle\Model\ProductInterface; use Doctrine\Persistence\ManagerRegistry; -class ProductItemProvider implements ProviderInterface +class ProductProvider implements ProviderInterface { private $managerRegistry; private $orm; @@ -35,21 +34,14 @@ public function __construct(ManagerRegistry $managerRegistry, bool $orm = true) /** * {@inheritDoc} */ - public function provide(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []) { + if ($operation instanceof CollectionOperationInterface) { + dd('todo'); + } + return $this->managerRegistry->getRepository($this->orm ? Product::class : ProductDocument::class)->findOneBy([ - 'code' => $identifiers['code'], + 'code' => $uriVariables['code'], ]); } - - /** - * {@inheritDoc} - */ - public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool - { - /** @var Operation */ - $operation = $context['operation'] ?? new Get(); - - return is_a($resourceClass, ProductInterface::class, true) && !$operation->isCollection(); - } } diff --git a/tests/Fixtures/TestBundle/State/RelatedQuestionsProvider.php b/tests/Fixtures/TestBundle/State/RelatedQuestionsProvider.php index 5b3ef160afa..b21ad5ce54c 100644 --- a/tests/Fixtures/TestBundle/State/RelatedQuestionsProvider.php +++ b/tests/Fixtures/TestBundle/State/RelatedQuestionsProvider.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\State; +use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use ApiPlatform\Tests\Fixtures\TestBundle\Document\Question as QuestionDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Question; @@ -27,18 +28,18 @@ public function __construct(ManagerRegistry $registry) $this->registry = $registry; } - public function provide(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []) { - $manager = $this->registry->getManagerForClass($resourceClass); - $repository = $manager->getRepository($resourceClass); + $manager = $this->registry->getManagerForClass($operation->getClass()); + $repository = $manager->getRepository($operation->getClass()); /** @var Question|QuestionDocument */ $question = $repository->findOneBy(['id' => 1]); return $question->getAnswer()->getRelatedQuestions(); } - public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool - { - return (QuestionDocument::class === $resourceClass || Question::class === $resourceClass) && '_api_/questions/{id}/answer/related_questions_get_collection' === $operationName; - } + // public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool + // { + // return (QuestionDocument::class === $resourceClass || Question::class === $resourceClass) && '_api_/questions/{id}/answer/related_questions_get_collection' === $operationName; + // } } diff --git a/tests/Fixtures/TestBundle/State/ResourceInterfaceImplementationProvider.php b/tests/Fixtures/TestBundle/State/ResourceInterfaceImplementationProvider.php index a3080d964cf..86bc528c7ce 100644 --- a/tests/Fixtures/TestBundle/State/ResourceInterfaceImplementationProvider.php +++ b/tests/Fixtures/TestBundle/State/ResourceInterfaceImplementationProvider.php @@ -13,9 +13,9 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\State; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; -use ApiPlatform\Tests\Fixtures\TestBundle\Model\ResourceInterface; use ApiPlatform\Tests\Fixtures\TestBundle\Model\ResourceInterfaceImplementation; final class ResourceInterfaceImplementationProvider implements ProviderInterface @@ -23,25 +23,15 @@ final class ResourceInterfaceImplementationProvider implements ProviderInterface /** * {@inheritDoc} */ - public function provide(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []) { - /** @var Operation */ - $operation = $context['operation']; - if ($operation->isCollection()) { + if ($operation instanceof CollectionOperationInterface) { return (function () { yield (new ResourceInterfaceImplementation())->setFoo('item1'); yield (new ResourceInterfaceImplementation())->setFoo('item2'); })(); } - return 'some-id' === $identifiers['foo'] ? (new ResourceInterfaceImplementation())->setFoo('single item') : null; - } - - /** - * {@inheritDoc} - */ - public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool - { - return ResourceInterface::class === $resourceClass; + return 'some-id' === $uriVariables['foo'] ? (new ResourceInterfaceImplementation())->setFoo('single item') : null; } } diff --git a/tests/Fixtures/TestBundle/State/SerializableProvider.php b/tests/Fixtures/TestBundle/State/SerializableProvider.php index e98de37e704..270dc585443 100644 --- a/tests/Fixtures/TestBundle/State/SerializableProvider.php +++ b/tests/Fixtures/TestBundle/State/SerializableProvider.php @@ -13,10 +13,10 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\State; +use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use ApiPlatform\State\SerializerAwareProviderInterface; use ApiPlatform\State\SerializerAwareProviderTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Model\SerializableResource; /** * @author Vincent Chalamon @@ -28,7 +28,7 @@ class SerializableProvider implements ProviderInterface, SerializerAwareProvider /** * {@inheritDoc} */ - public function provide(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []) { return $this->getSerializer()->deserialize(<<<'JSON' { @@ -37,14 +37,6 @@ public function provide(string $resourceClass, array $identifiers = [], ?string "bar": "Ipsum" } JSON - , $resourceClass, 'json'); - } - - /** - * {@inheritDoc} - */ - public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool - { - return SerializableResource::class === $resourceClass; + , $operation->getClass(), 'json'); } } diff --git a/tests/Fixtures/TestBundle/State/TaxonItemProvider.php b/tests/Fixtures/TestBundle/State/TaxonItemProvider.php index 0624c8a42df..29b34f0cd63 100644 --- a/tests/Fixtures/TestBundle/State/TaxonItemProvider.php +++ b/tests/Fixtures/TestBundle/State/TaxonItemProvider.php @@ -13,12 +13,10 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\State; -use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use ApiPlatform\Tests\Fixtures\TestBundle\Document\Taxon as TaxonDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Taxon; -use ApiPlatform\Tests\Fixtures\TestBundle\Model\TaxonInterface; use Doctrine\Persistence\ManagerRegistry; class TaxonItemProvider implements ProviderInterface @@ -35,21 +33,10 @@ public function __construct(ManagerRegistry $managerRegistry, bool $orm = true) /** * {@inheritDoc} */ - public function provide(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []) + public function provide(Operation $operation, array $uriVariables = [], array $context = []) { return $this->managerRegistry->getRepository($this->orm ? Taxon::class : TaxonDocument::class)->findOneBy([ - 'code' => $identifiers['code'], + 'code' => $uriVariables['code'], ]); } - - /** - * {@inheritDoc} - */ - public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool - { - /** @var Operation */ - $operation = $context['operation'] ?? new Get(); - - return is_a($resourceClass, TaxonInterface::class, true) && !$operation->isCollection(); - } } diff --git a/tests/Fixtures/app/AppKernel.php b/tests/Fixtures/app/AppKernel.php index 24038e8fcfe..1348cfc0c0d 100644 --- a/tests/Fixtures/app/AppKernel.php +++ b/tests/Fixtures/app/AppKernel.php @@ -271,6 +271,7 @@ class_exists(NativePasswordHasher::class) ? 'password_hashers' : 'encoders' => [ return; } + $loader->load(__DIR__.'/config/config_v3.yml'); $c->prependExtensionConfig('api_platform', [ 'mapping' => [ 'paths' => ['%kernel.project_dir%/../TestBundle/Resources/config/api_resources_v3'], diff --git a/tests/Fixtures/app/config/config_common.yml b/tests/Fixtures/app/config/config_common.yml index 8adb6baa164..83f2ec184e2 100644 --- a/tests/Fixtures/app/config/config_common.yml +++ b/tests/Fixtures/app/config/config_common.yml @@ -96,39 +96,41 @@ services: autowire: true autoconfigure: true - attribute_resources.state_provider: + ApiPlatform\Tests\Fixtures\TestBundle\State\AttributeResourceProvider: class: 'ApiPlatform\Tests\Fixtures\TestBundle\State\AttributeResourceProvider' public: false tags: - { name: 'api_platform.state_provider' } - related_questions.state_provider: - class: 'ApiPlatform\Tests\Fixtures\TestBundle\State\RelatedQuestionsProvider' - public: false - arguments: ['@doctrine'] - tags: - - { name: 'api_platform.state_provider' } + # related_questions.state_provider: + # class: 'ApiPlatform\Tests\Fixtures\TestBundle\State\RelatedQuestionsProvider' + # public: false + # arguments: ['@doctrine'] + # tags: + # - { name: 'api_platform.state_provider' } - operation_resources.state_processor: + ApiPlatform\Tests\Fixtures\TestBundle\State\OperationResourceProcessor: class: 'ApiPlatform\Tests\Fixtures\TestBundle\State\OperationResourceProcessor' public: false arguments: [ '@doctrine' ] tags: - { name: 'api_platform.state_processor' } - contain_non_resource.state_provider: + + ApiPlatform\Tests\Fixtures\TestBundle\State\ContainNonResourceProvider: class: 'ApiPlatform\Tests\Fixtures\TestBundle\State\ContainNonResourceProvider' public: false tags: - { name: 'api_platform.state_provider' } - serializable.state_provider: + ApiPlatform\Tests\Fixtures\TestBundle\State\SerializableProvider: class: 'ApiPlatform\Tests\Fixtures\TestBundle\State\SerializableProvider' public: false tags: - { name: 'api_platform.state_provider' } - resource_interface.state_provider: + + ApiPlatform\Tests\Fixtures\TestBundle\State\ResourceInterfaceImplementationProvider: class: 'ApiPlatform\Tests\Fixtures\TestBundle\State\ResourceInterfaceImplementationProvider' public: false tags: diff --git a/tests/Fixtures/app/config/config_mongodb.yml b/tests/Fixtures/app/config/config_mongodb.yml index 4640bdd1081..86283b057d2 100644 --- a/tests/Fixtures/app/config/config_mongodb.yml +++ b/tests/Fixtures/app/config/config_mongodb.yml @@ -80,8 +80,8 @@ services: tags: - name: 'api_platform.item_data_provider' - ApiPlatform\Tests\Fixtures\TestBundle\State\ProductItemProvider: - class: 'ApiPlatform\Tests\Fixtures\TestBundle\State\ProductItemProvider' + ApiPlatform\Tests\Fixtures\TestBundle\State\ProductProvider: + class: 'ApiPlatform\Tests\Fixtures\TestBundle\State\ProductProvider' public: false arguments: $managerRegistry: '@doctrine_mongodb' diff --git a/tests/Fixtures/app/config/config_test.yml b/tests/Fixtures/app/config/config_test.yml index 019bbd7a2a5..6ae4f627efa 100644 --- a/tests/Fixtures/app/config/config_test.yml +++ b/tests/Fixtures/app/config/config_test.yml @@ -88,8 +88,8 @@ services: tags: - name: 'api_platform.item_data_provider' - ApiPlatform\Tests\Fixtures\TestBundle\State\ProductItemProvider: - class: 'ApiPlatform\Tests\Fixtures\TestBundle\State\ProductItemProvider' + ApiPlatform\Tests\Fixtures\TestBundle\State\ProductProvider: + class: 'ApiPlatform\Tests\Fixtures\TestBundle\State\ProductProvider' public: false arguments: $managerRegistry: '@doctrine' diff --git a/tests/Fixtures/app/config/config_v3.yml b/tests/Fixtures/app/config/config_v3.yml new file mode 100644 index 00000000000..48b5660d438 --- /dev/null +++ b/tests/Fixtures/app/config/config_v3.yml @@ -0,0 +1,6 @@ +services: + ApiPlatform\Tests\Fixtures\TestBundle\Metadata\ProviderResourceMetadatatCollectionFactory: + class: 'ApiPlatform\Tests\Fixtures\TestBundle\Metadata\ProviderResourceMetadatatCollectionFactory' + decorates: api_platform.metadata.resource.metadata_collection_factory + arguments: ['@ApiPlatform\Tests\Fixtures\TestBundle\Metadata\ProviderResourceMetadatatCollectionFactory.inner'] + diff --git a/tests/GraphQl/Resolver/Factory/CollectionResolverFactoryTest.php b/tests/GraphQl/Resolver/Factory/CollectionResolverFactoryTest.php index a40e2e222bf..d0e81a6143b 100644 --- a/tests/GraphQl/Resolver/Factory/CollectionResolverFactoryTest.php +++ b/tests/GraphQl/Resolver/Factory/CollectionResolverFactoryTest.php @@ -19,10 +19,7 @@ use ApiPlatform\GraphQl\Resolver\Stage\SecurityPostDenormalizeStageInterface; use ApiPlatform\GraphQl\Resolver\Stage\SecurityStageInterface; use ApiPlatform\GraphQl\Resolver\Stage\SerializeStageInterface; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GraphQl\QueryCollection; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use GraphQL\Type\Definition\ResolveInfo; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; @@ -44,7 +41,6 @@ class CollectionResolverFactoryTest extends TestCase private $securityPostDenormalizeStageProphecy; private $serializeStageProphecy; private $queryResolverLocatorProphecy; - private $resourceMetadataCollectionFactoryProphecy; private $requestStackProphecy; /** @@ -57,7 +53,6 @@ protected function setUp(): void $this->securityPostDenormalizeStageProphecy = $this->prophesize(SecurityPostDenormalizeStageInterface::class); $this->serializeStageProphecy = $this->prophesize(SerializeStageInterface::class); $this->queryResolverLocatorProphecy = $this->prophesize(ContainerInterface::class); - $this->resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); $this->requestStackProphecy = $this->prophesize(RequestStack::class); $this->collectionResolverFactory = new CollectionResolverFactory( @@ -66,7 +61,6 @@ protected function setUp(): void $this->securityPostDenormalizeStageProphecy->reveal(), $this->serializeStageProphecy->reveal(), $this->queryResolverLocatorProphecy->reveal(), - $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->requestStackProphecy->reveal() ); } @@ -76,6 +70,7 @@ public function testResolve(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'collection_query'; + $operation = (new QueryCollection())->withName($operationName); $source = ['testField' => 0]; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); @@ -90,16 +85,14 @@ public function testResolve(): void $this->requestStackProphecy->getCurrentRequest()->willReturn($request); $readStageCollection = [new \stdClass()]; - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageCollection); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageCollection); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => new QueryCollection()])])); - - $this->securityStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $readStageCollection, ], ])->shouldNotBeCalled(); - $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $readStageCollection, 'previous_object' => $readStageCollection, @@ -107,9 +100,9 @@ public function testResolve(): void ])->shouldNotBeCalled(); $serializeStageData = ['serialized']; - $this->serializeStageProphecy->__invoke($readStageCollection, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); + $this->serializeStageProphecy->__invoke($readStageCollection, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); - $this->assertSame($serializeStageData, ($this->collectionResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertSame($serializeStageData, ($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function testResolveFieldNotInSource(): void @@ -117,6 +110,7 @@ public function testResolveFieldNotInSource(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'collection_query'; + $operation = (new QueryCollection())->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); @@ -124,14 +118,14 @@ public function testResolveFieldNotInSource(): void $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false]; $readStageCollection = [new \stdClass()]; - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldNotBeCalled(); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldNotBeCalled(); - $this->securityStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $readStageCollection, ], ])->shouldNotBeCalled(); - $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $readStageCollection, 'previous_object' => $readStageCollection, @@ -139,7 +133,7 @@ public function testResolveFieldNotInSource(): void ])->shouldNotBeCalled(); // Null should be returned if the field isn't in the source - as its lack of presence will be due to @ApiProperty security stripping unauthorized fields - $this->assertNull(($this->collectionResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertNull(($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function testResolveNullSource(): void @@ -147,6 +141,7 @@ public function testResolveNullSource(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'collection_query'; + $operation = (new QueryCollection())->withName($operationName); $source = null; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); @@ -160,16 +155,14 @@ public function testResolveNullSource(): void $this->requestStackProphecy->getCurrentRequest()->willReturn($request); $readStageCollection = [new \stdClass()]; - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageCollection); - - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => new QueryCollection()])])); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageCollection); - $this->securityStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $readStageCollection, ], ])->shouldBeCalled(); - $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $readStageCollection, 'previous_object' => $readStageCollection, @@ -177,9 +170,9 @@ public function testResolveNullSource(): void ])->shouldBeCalled(); $serializeStageData = ['serialized']; - $this->serializeStageProphecy->__invoke($readStageCollection, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); + $this->serializeStageProphecy->__invoke($readStageCollection, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); - $this->assertSame($serializeStageData, ($this->collectionResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertSame($serializeStageData, ($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function testResolveNullResourceClass(): void @@ -187,11 +180,12 @@ public function testResolveNullResourceClass(): void $resourceClass = null; $rootClass = 'rootClass'; $operationName = 'collection_query'; + $operation = (new QueryCollection())->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); - $this->assertNull(($this->collectionResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertNull(($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function testResolveNullRootClass(): void @@ -199,11 +193,12 @@ public function testResolveNullRootClass(): void $resourceClass = 'stdClass'; $rootClass = null; $operationName = 'collection_query'; + $operation = (new QueryCollection())->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); - $this->assertNull(($this->collectionResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertNull(($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function testResolveBadReadStageCollection(): void @@ -211,18 +206,19 @@ public function testResolveBadReadStageCollection(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'collection_query'; + $operation = (new QueryCollection())->withName($operationName); $source = null; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false]; $readStageCollection = new \stdClass(); - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageCollection); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageCollection); $this->expectException(\LogicException::class); $this->expectExceptionMessage('Collection from read stage should be iterable.'); - ($this->collectionResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info); + ($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info); } public function testResolveCustom(): void @@ -230,15 +226,14 @@ public function testResolveCustom(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'collection_query'; + $operation = (new QueryCollection())->withName($operationName)->withResolver('query_resolver_id'); $source = null; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false]; $readStageCollection = [new \stdClass()]; - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageCollection); - - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new QueryCollection())->withResolver('query_resolver_id')])])); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageCollection); $customCollection = [new \stdClass()]; $customCollection[0]->field = 'foo'; @@ -246,12 +241,12 @@ public function testResolveCustom(): void return $customCollection; }); - $this->securityStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $customCollection, ], ])->shouldBeCalled(); - $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $customCollection, 'previous_object' => $customCollection, @@ -259,8 +254,8 @@ public function testResolveCustom(): void ])->shouldBeCalled(); $serializeStageData = ['serialized']; - $this->serializeStageProphecy->__invoke($customCollection, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); + $this->serializeStageProphecy->__invoke($customCollection, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); - $this->assertSame($serializeStageData, ($this->collectionResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertSame($serializeStageData, ($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } } diff --git a/tests/GraphQl/Resolver/Factory/ItemMutationResolverFactoryTest.php b/tests/GraphQl/Resolver/Factory/ItemMutationResolverFactoryTest.php index 678727274b5..e7f8293d0b4 100644 --- a/tests/GraphQl/Resolver/Factory/ItemMutationResolverFactoryTest.php +++ b/tests/GraphQl/Resolver/Factory/ItemMutationResolverFactoryTest.php @@ -23,10 +23,7 @@ use ApiPlatform\GraphQl\Resolver\Stage\SerializeStageInterface; use ApiPlatform\GraphQl\Resolver\Stage\ValidateStageInterface; use ApiPlatform\GraphQl\Resolver\Stage\WriteStageInterface; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GraphQl\Mutation; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use GraphQL\Type\Definition\ResolveInfo; use PHPUnit\Framework\TestCase; @@ -49,7 +46,6 @@ class ItemMutationResolverFactoryTest extends TestCase private $writeStageProphecy; private $validateStageProphecy; private $mutationResolverLocatorProphecy; - private $resourceMetadataCollectionFactoryProphecy; private $securityPostValidationStageProphecy; /** @@ -65,7 +61,6 @@ protected function setUp(): void $this->writeStageProphecy = $this->prophesize(WriteStageInterface::class); $this->validateStageProphecy = $this->prophesize(ValidateStageInterface::class); $this->mutationResolverLocatorProphecy = $this->prophesize(ContainerInterface::class); - $this->resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); $this->securityPostValidationStageProphecy = $this->prophesize(SecurityPostValidationStageInterface::class); $this->itemMutationResolverFactory = new ItemMutationResolverFactory( @@ -77,7 +72,6 @@ protected function setUp(): void $this->writeStageProphecy->reveal(), $this->validateStageProphecy->reveal(), $this->mutationResolverLocatorProphecy->reveal(), - $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->securityPostValidationStageProphecy->reveal() ); } @@ -87,6 +81,7 @@ public function testResolve(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'create'; + $operation = (new Mutation())->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); @@ -94,36 +89,34 @@ public function testResolve(): void $readStageItem = new \stdClass(); $readStageItem->field = 'read'; - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); $deserializeStageItem = new \stdClass(); $deserializeStageItem->field = 'deserialize'; - $this->deserializeStageProphecy->__invoke($readStageItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($deserializeStageItem); + $this->deserializeStageProphecy->__invoke($readStageItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($deserializeStageItem); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => new Mutation()])])); - - $this->securityStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $readStageItem, ], ])->shouldBeCalled(); - $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $deserializeStageItem, 'previous_object' => $readStageItem, ], ])->shouldBeCalled(); - $this->validateStageProphecy->__invoke($deserializeStageItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled(); + $this->validateStageProphecy->__invoke($deserializeStageItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled(); $writeStageItem = new \stdClass(); $writeStageItem->field = 'write'; - $this->writeStageProphecy->__invoke($deserializeStageItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($writeStageItem); + $this->writeStageProphecy->__invoke($deserializeStageItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($writeStageItem); $serializeStageData = ['serialized']; - $this->serializeStageProphecy->__invoke($writeStageItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); + $this->serializeStageProphecy->__invoke($writeStageItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); - $this->assertSame($serializeStageData, ($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertSame($serializeStageData, ($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function testResolveNullResourceClass(): void @@ -131,11 +124,12 @@ public function testResolveNullResourceClass(): void $resourceClass = null; $rootClass = 'rootClass'; $operationName = 'create'; + $operation = (new Mutation())->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); - $this->assertNull(($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertNull(($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function testResolveNullOperationName(): void @@ -147,7 +141,7 @@ public function testResolveNullOperationName(): void $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); - $this->assertNull(($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertNull(($this->itemMutationResolverFactory)($resourceClass, $rootClass, null)($source, $args, null, $info)); } public function testResolveBadReadStageItem(): void @@ -155,18 +149,19 @@ public function testResolveBadReadStageItem(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'create'; + $operation = (new Mutation())->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => true, 'is_subscription' => false]; $readStageItem = []; - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); $this->expectException(\LogicException::class); $this->expectExceptionMessage('Item from read stage should be a nullable object.'); - ($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info); + ($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info); } public function testResolveNullDeserializeStageItem(): void @@ -174,6 +169,7 @@ public function testResolveNullDeserializeStageItem(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'create'; + $operation = (new Mutation())->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); @@ -181,19 +177,17 @@ public function testResolveNullDeserializeStageItem(): void $readStageItem = new \stdClass(); $readStageItem->field = 'read'; - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); $deserializeStageItem = null; - $this->deserializeStageProphecy->__invoke($readStageItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($deserializeStageItem); - - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => new Mutation()])])); + $this->deserializeStageProphecy->__invoke($readStageItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($deserializeStageItem); - $this->securityStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $readStageItem, ], ])->shouldBeCalled(); - $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $deserializeStageItem, 'previous_object' => $readStageItem, @@ -205,9 +199,9 @@ public function testResolveNullDeserializeStageItem(): void $this->writeStageProphecy->__invoke(Argument::cetera())->shouldNotBeCalled(); $serializeStageData = null; - $this->serializeStageProphecy->__invoke($deserializeStageItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); + $this->serializeStageProphecy->__invoke($deserializeStageItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); - $this->assertNull(($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertNull(($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function testResolveDelete(): void @@ -215,6 +209,7 @@ public function testResolveDelete(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'delete'; + $operation = (new Mutation())->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); @@ -222,16 +217,16 @@ public function testResolveDelete(): void $readStageItem = new \stdClass(); $readStageItem->field = 'read'; - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); $this->deserializeStageProphecy->__invoke(Argument::cetera())->shouldNotBeCalled(); - $this->securityStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $readStageItem, ], ])->shouldBeCalled(); - $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $readStageItem, 'previous_object' => $readStageItem, @@ -242,12 +237,12 @@ public function testResolveDelete(): void $writeStageItem = new \stdClass(); $writeStageItem->field = 'write'; - $this->writeStageProphecy->__invoke($readStageItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($writeStageItem); + $this->writeStageProphecy->__invoke($readStageItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($writeStageItem); $serializeStageData = ['serialized']; - $this->serializeStageProphecy->__invoke($writeStageItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); + $this->serializeStageProphecy->__invoke($writeStageItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); - $this->assertSame($serializeStageData, ($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertSame($serializeStageData, ($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function testResolveCustom(): void @@ -255,6 +250,7 @@ public function testResolveCustom(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'create'; + $operation = (new Mutation())->withName($operationName)->withResolver('query_resolver_id'); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); @@ -262,13 +258,11 @@ public function testResolveCustom(): void $readStageItem = new \stdClass(); $readStageItem->field = 'read'; - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); $deserializeStageItem = new \stdClass(); $deserializeStageItem->field = 'deserialize'; - $this->deserializeStageProphecy->__invoke($readStageItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($deserializeStageItem); - - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Mutation())->withResolver('query_resolver_id')])])); + $this->deserializeStageProphecy->__invoke($readStageItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($deserializeStageItem); $customItem = new \stdClass(); $customItem->field = 'foo'; @@ -276,28 +270,28 @@ public function testResolveCustom(): void return $customItem; }); - $this->securityStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $readStageItem, ], ])->shouldBeCalled(); - $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $customItem, 'previous_object' => $readStageItem, ], ])->shouldBeCalled(); - $this->validateStageProphecy->__invoke($customItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled(); + $this->validateStageProphecy->__invoke($customItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled(); $writeStageItem = new \stdClass(); $writeStageItem->field = 'write'; - $this->writeStageProphecy->__invoke($customItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($writeStageItem); + $this->writeStageProphecy->__invoke($customItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($writeStageItem); $serializeStageData = ['serialized']; - $this->serializeStageProphecy->__invoke($writeStageItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); + $this->serializeStageProphecy->__invoke($writeStageItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); - $this->assertSame($serializeStageData, ($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertSame($serializeStageData, ($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function testResolveCustomBadItem(): void @@ -305,6 +299,7 @@ public function testResolveCustomBadItem(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'create'; + $operation = (new Mutation())->withName($operationName)->withShortName('shortName')->withResolver('query_resolver_id'); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); @@ -312,13 +307,11 @@ public function testResolveCustomBadItem(): void $readStageItem = new \stdClass(); $readStageItem->field = 'read'; - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); $deserializeStageItem = new \stdClass(); $deserializeStageItem->field = 'deserialize'; - $this->deserializeStageProphecy->__invoke($readStageItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($deserializeStageItem); - - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Mutation())->withShortName('shortName')->withResolver('query_resolver_id')])])); + $this->deserializeStageProphecy->__invoke($readStageItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($deserializeStageItem); $customItem = new Dummy(); $this->mutationResolverLocatorProphecy->get('query_resolver_id')->shouldBeCalled()->willReturn(function () use ($customItem) { @@ -328,6 +321,6 @@ public function testResolveCustomBadItem(): void $this->expectException(\LogicException::class); $this->expectExceptionMessage('Custom mutation resolver "query_resolver_id" has to return an item of class shortName but returned an item of class Dummy.'); - ($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info); + ($this->itemMutationResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info); } } diff --git a/tests/GraphQl/Resolver/Factory/ItemResolverFactoryTest.php b/tests/GraphQl/Resolver/Factory/ItemResolverFactoryTest.php index 4fb2d32dc3b..def5ba3881e 100644 --- a/tests/GraphQl/Resolver/Factory/ItemResolverFactoryTest.php +++ b/tests/GraphQl/Resolver/Factory/ItemResolverFactoryTest.php @@ -19,10 +19,8 @@ use ApiPlatform\GraphQl\Resolver\Stage\SecurityPostDenormalizeStageInterface; use ApiPlatform\GraphQl\Resolver\Stage\SecurityStageInterface; use ApiPlatform\GraphQl\Resolver\Stage\SerializeStageInterface; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GraphQl\Query; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use GraphQL\Type\Definition\ResolveInfo; use PHPUnit\Framework\TestCase; @@ -75,21 +73,20 @@ public function testResolve(?string $resourceClass, string $determinedResourceCl { $rootClass = 'rootClass'; $operationName = 'item_query'; + $operation = (new Query())->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => false]; - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); - $this->resourceMetadataCollectionFactoryProphecy->create($determinedResourceClass)->willReturn(new ResourceMetadataCollection($determinedResourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => new Query()])])); - - $this->securityStageProphecy->__invoke($determinedResourceClass, $operationName, $resolverContext + [ + $this->securityStageProphecy->__invoke($determinedResourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $readStageItem, ], ])->shouldBeCalled(); - $this->securityPostDenormalizeStageProphecy->__invoke($determinedResourceClass, $operationName, $resolverContext + [ + $this->securityPostDenormalizeStageProphecy->__invoke($determinedResourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $readStageItem, 'previous_object' => $readStageItem, @@ -97,9 +94,9 @@ public function testResolve(?string $resourceClass, string $determinedResourceCl ])->shouldBeCalled(); $serializeStageData = ['serialized']; - $this->serializeStageProphecy->__invoke($readStageItem, $determinedResourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); + $this->serializeStageProphecy->__invoke($readStageItem, $determinedResourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); - $this->assertSame($serializeStageData, ($this->itemResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertSame($serializeStageData, ($this->itemResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function itemResourceProvider(): array @@ -134,18 +131,19 @@ public function testResolveBadReadStageItem(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'item_query'; + $operation = (new Query())->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => false]; $readStageItem = []; - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); $this->expectException(\LogicException::class); $this->expectExceptionMessage('Item from read stage should be a nullable object.'); - ($this->itemResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info); + ($this->itemResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info); } public function testResolveNoResourceNoItem(): void @@ -153,18 +151,19 @@ public function testResolveNoResourceNoItem(): void $resourceClass = null; $rootClass = 'rootClass'; $operationName = 'item_query'; + $operation = (new Query())->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => false]; $readStageItem = null; - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessage('Resource class cannot be determined.'); - ($this->itemResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info); + ($this->itemResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info); } public function testResolveBadItem(): void @@ -172,18 +171,19 @@ public function testResolveBadItem(): void $resourceClass = Dummy::class; $rootClass = 'rootClass'; $operationName = 'item_query'; + $operation = (new Query())->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => false]; $readStageItem = new \stdClass(); - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessage('Resolver only handles items of class Dummy but retrieved item is of class stdClass.'); - ($this->itemResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info); + ($this->itemResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info); } public function testResolveCustom(): void @@ -191,15 +191,14 @@ public function testResolveCustom(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'custom_query'; + $operation = (new Query())->withName($operationName)->withResolver('query_resolver_id'); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => false]; $readStageItem = new \stdClass(); - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); - - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withResolver('query_resolver_id')])])); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); $customItem = new \stdClass(); $customItem->field = 'foo'; @@ -207,12 +206,12 @@ public function testResolveCustom(): void return $customItem; }); - $this->securityStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $customItem, ], ])->shouldBeCalled(); - $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $customItem, 'previous_object' => $customItem, @@ -220,9 +219,9 @@ public function testResolveCustom(): void ])->shouldBeCalled(); $serializeStageData = ['serialized']; - $this->serializeStageProphecy->__invoke($customItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); + $this->serializeStageProphecy->__invoke($customItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); - $this->assertSame($serializeStageData, ($this->itemResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertSame($serializeStageData, ($this->itemResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function testResolveCustomBadItem(): void @@ -230,15 +229,14 @@ public function testResolveCustomBadItem(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'custom_query'; + $operation = (new Query())->withName($operationName)->withResolver('query_resolver_id'); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => false]; $readStageItem = new \stdClass(); - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); - - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withResolver('query_resolver_id')])])); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); $customItem = new Dummy(); $this->queryResolverLocatorProphecy->get('query_resolver_id')->shouldBeCalled()->willReturn(function () use ($customItem) { @@ -248,6 +246,6 @@ public function testResolveCustomBadItem(): void $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessage('Custom query resolver "query_resolver_id" has to return an item of class stdClass but returned an item of class Dummy.'); - ($this->itemResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info); + ($this->itemResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info); } } diff --git a/tests/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactoryTest.php b/tests/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactoryTest.php index e811f26dac1..37812230af7 100644 --- a/tests/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactoryTest.php +++ b/tests/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactoryTest.php @@ -20,10 +20,7 @@ use ApiPlatform\GraphQl\Resolver\Stage\SerializeStageInterface; use ApiPlatform\GraphQl\Subscription\MercureSubscriptionIriGeneratorInterface; use ApiPlatform\GraphQl\Subscription\SubscriptionManagerInterface; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GraphQl\Subscription; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use GraphQL\Type\Definition\ResolveInfo; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -51,7 +48,6 @@ protected function setUp(): void $this->readStageProphecy = $this->prophesize(ReadStageInterface::class); $this->securityStageProphecy = $this->prophesize(SecurityStageInterface::class); $this->serializeStageProphecy = $this->prophesize(SerializeStageInterface::class); - $this->resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); $this->subscriptionManagerProphecy = $this->prophesize(SubscriptionManagerInterface::class); $this->mercureSubscriptionIriGeneratorProphecy = $this->prophesize(MercureSubscriptionIriGeneratorInterface::class); @@ -59,7 +55,6 @@ protected function setUp(): void $this->readStageProphecy->reveal(), $this->securityStageProphecy->reveal(), $this->serializeStageProphecy->reveal(), - $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->subscriptionManagerProphecy->reveal(), $this->mercureSubscriptionIriGeneratorProphecy->reveal() ); @@ -70,32 +65,30 @@ public function testResolve(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'update'; + $operation = (new Subscription())->withMercure(true)->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => true]; - $readStageItem = new \stdClass(); - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); - $this->securityStageProphecy->__invoke($resourceClass, $operationName, $resolverContext + [ + $this->securityStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [ 'extra_variables' => [ 'object' => $readStageItem, ], ])->shouldBeCalled(); $serializeStageData = ['serialized']; - $this->serializeStageProphecy->__invoke($readStageItem, $resourceClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); + $this->serializeStageProphecy->__invoke($readStageItem, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData); $subscriptionId = 'subscriptionId'; $this->subscriptionManagerProphecy->retrieveSubscriptionId($resolverContext, $serializeStageData)->shouldBeCalled()->willReturn($subscriptionId); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Subscription())->withMercure(true)])])); - $mercureUrl = 'mercure-url'; $this->mercureSubscriptionIriGeneratorProphecy->generateMercureUrl($subscriptionId, null)->shouldBeCalled()->willReturn($mercureUrl); - $this->assertSame($serializeStageData + ['mercureUrl' => $mercureUrl], ($this->itemSubscriptionResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertSame($serializeStageData + ['mercureUrl' => $mercureUrl], ($this->itemSubscriptionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function testResolveNullResourceClass(): void @@ -103,23 +96,23 @@ public function testResolveNullResourceClass(): void $resourceClass = null; $rootClass = 'rootClass'; $operationName = 'update'; + $operation = (new Subscription())->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); - $this->assertNull(($this->itemSubscriptionResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertNull(($this->itemSubscriptionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function testResolveNullOperationName(): void { $resourceClass = 'stdClass'; $rootClass = 'rootClass'; - $operationName = null; $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); - $this->assertNull(($this->itemSubscriptionResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertNull(($this->itemSubscriptionResolverFactory)($resourceClass, $rootClass, null)($source, $args, null, $info)); } public function testResolveBadReadStageItem(): void @@ -127,18 +120,19 @@ public function testResolveBadReadStageItem(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'update'; + $operation = (new Subscription())->withName($operationName); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => true]; $readStageItem = []; - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageItem); $this->expectException(\LogicException::class); $this->expectExceptionMessage('Item from read stage should be a nullable object.'); - ($this->itemSubscriptionResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info); + ($this->itemSubscriptionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info); } public function testResolveNoSubscriptionId(): void @@ -146,24 +140,23 @@ public function testResolveNoSubscriptionId(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'update'; + $operation = (new Subscription())->withName($operationName)->withMercure(true); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => true]; $readStageItem = new \stdClass(); - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->willReturn($readStageItem); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->willReturn($readStageItem); $serializeStageData = ['serialized']; - $this->serializeStageProphecy->__invoke($readStageItem, $resourceClass, $operationName, $resolverContext)->willReturn($serializeStageData); + $this->serializeStageProphecy->__invoke($readStageItem, $resourceClass, $operation, $resolverContext)->willReturn($serializeStageData); $this->subscriptionManagerProphecy->retrieveSubscriptionId($resolverContext, $serializeStageData)->willReturn(null); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Subscription())->withMercure(true)])])); - $this->mercureSubscriptionIriGeneratorProphecy->generateMercureUrl(Argument::any())->shouldNotBeCalled(); - $this->assertSame($serializeStageData, ($this->itemSubscriptionResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info)); + $this->assertSame($serializeStageData, ($this->itemSubscriptionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info)); } public function testResolveNoMercureSubscriptionIriGenerator(): void @@ -171,22 +164,21 @@ public function testResolveNoMercureSubscriptionIriGenerator(): void $resourceClass = 'stdClass'; $rootClass = 'rootClass'; $operationName = 'update'; + $operation = (new Subscription())->withName($operationName)->withMercure(true); $source = ['source']; $args = ['args']; $info = $this->prophesize(ResolveInfo::class)->reveal(); $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => true]; $readStageItem = new \stdClass(); - $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operationName, $resolverContext)->willReturn($readStageItem); + $this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->willReturn($readStageItem); $serializeStageData = ['serialized']; - $this->serializeStageProphecy->__invoke($readStageItem, $resourceClass, $operationName, $resolverContext)->willReturn($serializeStageData); + $this->serializeStageProphecy->__invoke($readStageItem, $resourceClass, $operation, $resolverContext)->willReturn($serializeStageData); $subscriptionId = 'subscriptionId'; $this->subscriptionManagerProphecy->retrieveSubscriptionId($resolverContext, $serializeStageData)->willReturn($subscriptionId); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Subscription())->withMercure(true)])])); - $this->expectException(\LogicException::class); $this->expectExceptionMessage('Cannot use Mercure for subscriptions when MercureBundle is not installed. Try running "composer require mercure".'); @@ -194,11 +186,10 @@ public function testResolveNoMercureSubscriptionIriGenerator(): void $this->readStageProphecy->reveal(), $this->securityStageProphecy->reveal(), $this->serializeStageProphecy->reveal(), - $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->subscriptionManagerProphecy->reveal(), null ); - ($itemSubscriptionResolverFactory)($resourceClass, $rootClass, $operationName)($source, $args, null, $info); + ($itemSubscriptionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info); } } diff --git a/tests/GraphQl/Resolver/Stage/DeserializeStageTest.php b/tests/GraphQl/Resolver/Stage/DeserializeStageTest.php index de2f7be66c2..b4075839661 100644 --- a/tests/GraphQl/Resolver/Stage/DeserializeStageTest.php +++ b/tests/GraphQl/Resolver/Stage/DeserializeStageTest.php @@ -17,10 +17,7 @@ use ApiPlatform\GraphQl\Resolver\Stage\DeserializeStage; use ApiPlatform\GraphQl\Serializer\ItemNormalizer; use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GraphQl\Query; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -33,7 +30,6 @@ class DeserializeStageTest extends TestCase /** @var DeserializeStage */ private $deserializeStage; - private $resourceMetadataCollectionFactoryProphecy; private $denormalizerProphecy; private $serializerContextBuilderProphecy; @@ -42,12 +38,10 @@ class DeserializeStageTest extends TestCase */ protected function setUp(): void { - $this->resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); $this->denormalizerProphecy = $this->prophesize(DenormalizerInterface::class); $this->serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $this->deserializeStage = new DeserializeStage( - $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->denormalizerProphecy->reveal(), $this->serializerContextBuilderProphecy->reveal() ); @@ -60,12 +54,9 @@ protected function setUp(): void */ public function testApplyDisabled($objectToPopulate): void { - $operationName = 'item_query'; $resourceClass = 'myResource'; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withDeserialize(false)])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); - - $result = ($this->deserializeStage)($objectToPopulate, $resourceClass, $operationName, []); + $operation = (new Query())->withName('item_query')->withClass($resourceClass)->withDeserialize(false); + $result = ($this->deserializeStage)($objectToPopulate, $resourceClass, $operation, []); $this->assertSame($objectToPopulate, $result); } @@ -79,16 +70,15 @@ public function testApply($objectToPopulate, array $denormalizationContext): voi { $operationName = 'item_query'; $resourceClass = 'myResource'; + $operation = (new Query())->withName($operationName)->withClass($resourceClass); $context = ['args' => ['input' => 'myInput']]; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => new Query()])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); $this->serializerContextBuilderProphecy->create($resourceClass, $operationName, $context, false)->shouldBeCalled()->willReturn($denormalizationContext); $denormalizedData = new \stdClass(); $this->denormalizerProphecy->denormalize($context['args']['input'], $resourceClass, ItemNormalizer::FORMAT, $denormalizationContext)->shouldBeCalled()->willReturn($denormalizedData); - $result = ($this->deserializeStage)($objectToPopulate, $resourceClass, $operationName, $context); + $result = ($this->deserializeStage)($objectToPopulate, $resourceClass, $operation, $context); $this->assertSame($denormalizedData, $result); } diff --git a/tests/GraphQl/Resolver/Stage/ReadStageTest.php b/tests/GraphQl/Resolver/Stage/ReadStageTest.php index edaeb3eca71..8b48d91ae10 100644 --- a/tests/GraphQl/Resolver/Stage/ReadStageTest.php +++ b/tests/GraphQl/Resolver/Stage/ReadStageTest.php @@ -19,13 +19,10 @@ use ApiPlatform\GraphQl\Resolver\Stage\ReadStage; use ApiPlatform\GraphQl\Serializer\ItemNormalizer; use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\GraphQl\Mutation; use ApiPlatform\Metadata\GraphQl\Query; use ApiPlatform\Metadata\GraphQl\QueryCollection; -use ApiPlatform\Metadata\Operations; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\State\ProviderInterface; use GraphQL\Type\Definition\ResolveInfo; use PHPUnit\Framework\TestCase; @@ -43,7 +40,6 @@ class ReadStageTest extends TestCase /** @var ReadStage */ private $readStage; - private $resourceMetadataCollectionFactoryProphecy; private $iriConverterProphecy; private $providerProphecy; private $serializerContextBuilderProphecy; @@ -53,13 +49,11 @@ class ReadStageTest extends TestCase */ protected function setUp(): void { - $this->resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); $this->iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $this->providerProphecy = $this->prophesize(ProviderInterface::class); $this->serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $this->readStage = new ReadStage( - $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->iriConverterProphecy->reveal(), $this->providerProphecy->reveal(), $this->serializerContextBuilderProphecy->reveal(), @@ -74,14 +68,10 @@ protected function setUp(): void */ public function testApplyDisabled(array $context, $expectedResult): void { - $operationName = 'item_query'; $resourceClass = 'myResource'; - $resourceMetadata = (new ApiResource())->withGraphQlOperations([ - $operationName => (new Query())->withRead(false), - ]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [$resourceMetadata])); + $operation = (new Query())->withRead(false)->withName('item_query')->withClass($resourceClass); - $result = ($this->readStage)($resourceClass, null, $operationName, $context); + $result = ($this->readStage)($resourceClass, null, $operation, $context); $this->assertSame($expectedResult, $result); } @@ -112,7 +102,6 @@ public function testApplyItem(?string $identifier, $item, bool $throwNotFound, $ 'args' => ['id' => $identifier], 'info' => $info, ]; - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [new ApiResource()])); $normalizationContext = ['normalization' => true]; $this->serializerContextBuilderProphecy->create($resourceClass, $operationName, $context, true)->shouldBeCalled()->willReturn($normalizationContext); @@ -123,7 +112,7 @@ public function testApplyItem(?string $identifier, $item, bool $throwNotFound, $ $this->iriConverterProphecy->getItemFromIri($identifier, $normalizationContext)->willReturn($item); } - $result = ($this->readStage)($resourceClass, null, $operationName, $context); + $result = ($this->readStage)($resourceClass, null, (new Query())->withName($operationName), $context); $this->assertSame($expectedResult, $result); } @@ -156,7 +145,6 @@ public function testApplyMutationOrSubscription(bool $isMutation, bool $isSubscr 'args' => ['input' => ['id' => $identifier]], 'info' => $info, ]; - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withShortName('shortName')])])); $normalizationContext = ['normalization' => true]; $this->serializerContextBuilderProphecy->create($resourceClass, $operationName, $context, true)->shouldBeCalled()->willReturn($normalizationContext); @@ -172,7 +160,7 @@ public function testApplyMutationOrSubscription(bool $isMutation, bool $isSubscr $this->expectExceptionMessage($expectedExceptionMessage); } - $result = ($this->readStage)($resourceClass, null, $operationName, $context); + $result = ($this->readStage)($resourceClass, null, (new Mutation())->withName($operationName)->withShortName('shortName'), $context); $this->assertSame($expectedResult, $result); } @@ -211,15 +199,15 @@ public function testApplyCollection(array $args, ?string $rootClass, ?array $sou ]; $collectionOperation = (new GetCollection())->withFilters($expectedFilters); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withOperations(new Operations([$collectionOperation]))])); - $normalizationContext = ['normalization' => true, 'operation' => (new QueryCollection())->withOperation($collectionOperation)]; + $operation = (new QueryCollection())->withName($operationName); + $normalizationContext = ['normalization' => true, 'operation' => $operation]; $this->serializerContextBuilderProphecy->create($resourceClass, $operationName, $context, true)->shouldBeCalled()->willReturn($normalizationContext); - $this->providerProphecy->provide($resourceClass, [], $operationName, $normalizationContext + ['filters' => $expectedFilters])->willReturn([]); - $this->providerProphecy->provide($resourceClass, ['id' => 3], $operationName, $normalizationContext + ['filters' => $expectedFilters, 'linkClass' => 'myResource'])->willReturn(['subresource']); + $this->providerProphecy->provide($operation, [], $normalizationContext + ['filters' => $expectedFilters])->willReturn([]); + $this->providerProphecy->provide($operation, ['id' => 3], $normalizationContext + ['filters' => $expectedFilters, 'linkClass' => 'myResource'])->willReturn(['subresource']); - $result = ($this->readStage)($resourceClass, $rootClass, $operationName, $context); + $result = ($this->readStage)($resourceClass, $rootClass, $operation, $context); $this->assertSame($expectedResult, $result); } @@ -245,14 +233,14 @@ public function testPreserveOrderOfOrderFiltersIfNested(): void 'source' => null, ]; $collectionOperation = (new GetCollection()); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withOperations(new Operations([$collectionOperation]))])); + $operation = (new QueryCollection())->withName($operationName); $normalizationContext = ['normalization' => true]; $this->serializerContextBuilderProphecy->create($resourceClass, $operationName, $context, true)->shouldBeCalled()->willReturn($normalizationContext); - ($this->readStage)($resourceClass, $resourceClass, $operationName, $context); + ($this->readStage)($resourceClass, $resourceClass, $operation, $context); - $this->providerProphecy->provide($resourceClass, [], $operationName, Argument::that(function ($args) { + $this->providerProphecy->provide($operation, [], Argument::that(function ($args) { // Prophecy does not check the order of items in associative arrays. Checking if some.field comes first manually return array_search('some.field', array_keys($args['filters']['order']), true) < @@ -307,17 +295,16 @@ public function testApplyCollectionWithDeprecatedFilterSyntax(): void 'source' => null, ]; $filters = ['filter' => ['filterArg1' => 'filterValue1', 'filterArg2' => 'filterValue2']]; - $operation = (new GetCollection())->withFilters($filters); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withOperations(new Operations([$operation]))])); + $operation = (new QueryCollection())->withName($operationName)->withClass($resourceClass)->withFilters($filters); - $normalizationContext = ['normalization' => true, 'operation' => (new QueryCollection())->withOperation($operation)]; + $normalizationContext = ['normalization' => true, 'operation' => $operation]; $this->serializerContextBuilderProphecy->create($resourceClass, $operationName, $context, true)->shouldBeCalled()->willReturn($normalizationContext); - $this->providerProphecy->provide($resourceClass, [], $operationName, $normalizationContext + ['filters' => $filters])->willReturn([]); + $this->providerProphecy->provide($operation, [], $normalizationContext + ['filters' => $filters])->willReturn([]); $this->expectDeprecation('The filter syntax "filter: {filterArg1: "filterValue1", filterArg2: "filterValue2"}" is deprecated since API Platform 2.6, use the following syntax instead: "filter: [{filterArg1: "filterValue1"}, {filterArg2: "filterValue2"}]".'); - $result = ($this->readStage)($resourceClass, 'myResource', $operationName, $context); + $result = ($this->readStage)($resourceClass, 'myResource', $operation, $context); $this->assertSame([], $result); } diff --git a/tests/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageTest.php b/tests/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageTest.php index 416be61e524..9372d477d7b 100644 --- a/tests/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageTest.php +++ b/tests/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageTest.php @@ -15,10 +15,7 @@ use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\GraphQl\Resolver\Stage\SecurityPostDenormalizeStage; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GraphQl\Query; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface; use GraphQL\Type\Definition\ResolveInfo; use PHPUnit\Framework\TestCase; @@ -35,7 +32,6 @@ class SecurityPostDenormalizeStageTest extends TestCase /** @var SecurityPostDenormalizeStage */ private $securityPostDenormalizeStage; - private $resourceMetadataCollectionFactoryProphecy; private $resourceAccessCheckerProphecy; /** @@ -43,11 +39,9 @@ class SecurityPostDenormalizeStageTest extends TestCase */ protected function setUp(): void { - $this->resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); $this->resourceAccessCheckerProphecy = $this->prophesize(ResourceAccessCheckerInterface::class); $this->securityPostDenormalizeStage = new SecurityPostDenormalizeStage( - $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->resourceAccessCheckerProphecy->reveal() ); } @@ -56,12 +50,11 @@ public function testNoSecurity(): void { $operationName = 'item_query'; $resourceClass = 'myResource'; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => new Query()])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = new Query(); $this->resourceAccessCheckerProphecy->isGranted(Argument::cetera())->shouldNotBeCalled(); - ($this->securityPostDenormalizeStage)($resourceClass, $operationName, []); + ($this->securityPostDenormalizeStage)($resourceClass, $operation, []); } public function testGranted(): void @@ -70,12 +63,11 @@ public function testGranted(): void $resourceClass = 'myResource'; $isGranted = 'not_granted'; $extraVariables = ['extra' => false]; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withSecurityPostDenormalize($isGranted)])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withSecurityPostDenormalize($isGranted)->withName($operationName); $this->resourceAccessCheckerProphecy->isGranted($resourceClass, $isGranted, $extraVariables)->shouldBeCalled()->willReturn(true); - ($this->securityPostDenormalizeStage)($resourceClass, $operationName, ['extra_variables' => $extraVariables]); + ($this->securityPostDenormalizeStage)($resourceClass, $operation, ['extra_variables' => $extraVariables]); } /** @@ -88,8 +80,7 @@ public function testGrantedLegacy(): void $resourceClass = 'myResource'; $isGranted = 'not_granted'; $extraVariables = ['extra' => false]; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withSecurityPostDenormalize($isGranted)])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withSecurityPostDenormalize($isGranted)->withName($operationName); $this->resourceAccessCheckerProphecy->isGranted($resourceClass, $isGranted, $extraVariables)->shouldBeCalled()->willReturn(true); @@ -102,8 +93,7 @@ public function testNotGranted(): void $resourceClass = 'myResource'; $isGranted = 'not_granted'; $extraVariables = ['extra' => false]; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withSecurityPostDenormalize($isGranted)])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withSecurityPostDenormalize($isGranted)->withName($operationName); $this->resourceAccessCheckerProphecy->isGranted($resourceClass, $isGranted, $extraVariables)->shouldBeCalled()->willReturn(false); @@ -112,7 +102,7 @@ public function testNotGranted(): void $this->expectException(AccessDeniedHttpException::class); $this->expectExceptionMessage('Access Denied.'); - ($this->securityPostDenormalizeStage)($resourceClass, $operationName, [ + ($this->securityPostDenormalizeStage)($resourceClass, $operation, [ 'info' => $info, 'extra_variables' => $extraVariables, ]); @@ -120,30 +110,28 @@ public function testNotGranted(): void public function testNoSecurityBundleInstalled(): void { - $this->securityPostDenormalizeStage = new SecurityPostDenormalizeStage($this->resourceMetadataCollectionFactoryProphecy->reveal(), null); + $this->securityPostDenormalizeStage = new SecurityPostDenormalizeStage(null); $operationName = 'item_query'; $resourceClass = 'myResource'; $isGranted = 'not_granted'; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withSecurityPostDenormalize($isGranted)])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withSecurityPostDenormalize($isGranted)->withName($operationName); $this->expectException(\LogicException::class); - ($this->securityPostDenormalizeStage)($resourceClass, $operationName, []); + ($this->securityPostDenormalizeStage)($resourceClass, $operation, []); } public function testNoSecurityBundleInstalledNoExpression(): void { - $this->securityPostDenormalizeStage = new SecurityPostDenormalizeStage($this->resourceMetadataCollectionFactoryProphecy->reveal(), null); + $this->securityPostDenormalizeStage = new SecurityPostDenormalizeStage(null); $operationName = 'item_query'; $resourceClass = 'myResource'; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => new Query()])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withName($operationName); $this->resourceAccessCheckerProphecy->isGranted(Argument::any())->shouldNotBeCalled(); - ($this->securityPostDenormalizeStage)($resourceClass, $operationName, []); + ($this->securityPostDenormalizeStage)($resourceClass, $operation, []); } } diff --git a/tests/GraphQl/Resolver/Stage/SecurityPostValidationStageTest.php b/tests/GraphQl/Resolver/Stage/SecurityPostValidationStageTest.php index 1ba36149220..ad4d2572307 100644 --- a/tests/GraphQl/Resolver/Stage/SecurityPostValidationStageTest.php +++ b/tests/GraphQl/Resolver/Stage/SecurityPostValidationStageTest.php @@ -15,10 +15,7 @@ use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\GraphQl\Resolver\Stage\SecurityPostValidationStage; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GraphQl\Query; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface; use GraphQL\Type\Definition\ResolveInfo; use PHPUnit\Framework\TestCase; @@ -43,11 +40,9 @@ class SecurityPostValidationStageTest extends TestCase */ protected function setUp(): void { - $this->resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); $this->resourceAccessCheckerProphecy = $this->prophesize(ResourceAccessCheckerInterface::class); $this->securityPostValidationStage = new SecurityPostValidationStage( - $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->resourceAccessCheckerProphecy->reveal() ); } @@ -56,12 +51,11 @@ public function testNoSecurity(): void { $operationName = 'item_query'; $resourceClass = 'myResource'; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => new Query()])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withName($operationName)->withClass($resourceClass); $this->resourceAccessCheckerProphecy->isGranted(Argument::cetera())->shouldNotBeCalled(); - ($this->securityPostValidationStage)($resourceClass, $operationName, []); + ($this->securityPostValidationStage)($resourceClass, $operation, []); } public function testGranted(): void @@ -70,12 +64,12 @@ public function testGranted(): void $resourceClass = 'myResource'; $isGranted = 'not_granted'; $extraVariables = ['extra' => false]; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withSecurityPostValidation($isGranted)])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + + $operation = (new Query())->withName($operationName)->withClass($resourceClass)->withSecurityPostValidation($isGranted); $this->resourceAccessCheckerProphecy->isGranted($resourceClass, $isGranted, $extraVariables)->shouldBeCalled()->willReturn(true); - ($this->securityPostValidationStage)($resourceClass, $operationName, ['extra_variables' => $extraVariables]); + ($this->securityPostValidationStage)($resourceClass, $operation, ['extra_variables' => $extraVariables]); } public function testNotGranted(): void @@ -84,8 +78,7 @@ public function testNotGranted(): void $resourceClass = 'myResource'; $isGranted = 'not_granted'; $extraVariables = ['extra' => false]; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withSecurityPostValidation($isGranted)])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withName($operationName)->withClass($resourceClass)->withSecurityPostValidation($isGranted); $this->resourceAccessCheckerProphecy->isGranted($resourceClass, $isGranted, $extraVariables)->shouldBeCalled()->willReturn(false); @@ -94,7 +87,7 @@ public function testNotGranted(): void $this->expectException(AccessDeniedHttpException::class); $this->expectExceptionMessage('Access Denied.'); - ($this->securityPostValidationStage)($resourceClass, $operationName, [ + ($this->securityPostValidationStage)($resourceClass, $operation, [ 'info' => $info, 'extra_variables' => $extraVariables, ]); @@ -102,30 +95,28 @@ public function testNotGranted(): void public function testNoSecurityBundleInstalled(): void { - $this->securityPostValidationStage = new SecurityPostValidationStage($this->resourceMetadataCollectionFactoryProphecy->reveal(), null); + $this->securityPostValidationStage = new SecurityPostValidationStage(null); $operationName = 'item_query'; $resourceClass = 'myResource'; $isGranted = 'not_granted'; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withSecurityPostValidation($isGranted)])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withName($operationName)->withClass($resourceClass)->withSecurityPostValidation($isGranted); $this->expectException(\LogicException::class); - ($this->securityPostValidationStage)($resourceClass, $operationName, []); + ($this->securityPostValidationStage)($resourceClass, $operation, []); } public function testNoSecurityBundleInstalledNoExpression(): void { - $this->securityPostValidationStage = new SecurityPostValidationStage($this->resourceMetadataCollectionFactoryProphecy->reveal(), null); + $this->securityPostValidationStage = new SecurityPostValidationStage(null); $operationName = 'item_query'; $resourceClass = 'myResource'; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => new Query()])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withName($operationName)->withClass($resourceClass); $this->resourceAccessCheckerProphecy->isGranted(Argument::any())->shouldNotBeCalled(); - ($this->securityPostValidationStage)($resourceClass, $operationName, []); + ($this->securityPostValidationStage)($resourceClass, $operation, []); } } diff --git a/tests/GraphQl/Resolver/Stage/SecurityStageTest.php b/tests/GraphQl/Resolver/Stage/SecurityStageTest.php index 671e7964caf..ad6f710656a 100644 --- a/tests/GraphQl/Resolver/Stage/SecurityStageTest.php +++ b/tests/GraphQl/Resolver/Stage/SecurityStageTest.php @@ -15,10 +15,7 @@ use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\GraphQl\Resolver\Stage\SecurityStage; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GraphQl\Query; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface; use GraphQL\Type\Definition\ResolveInfo; use PHPUnit\Framework\TestCase; @@ -43,11 +40,9 @@ class SecurityStageTest extends TestCase */ protected function setUp(): void { - $this->resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); $this->resourceAccessCheckerProphecy = $this->prophesize(ResourceAccessCheckerInterface::class); $this->securityStage = new SecurityStage( - $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->resourceAccessCheckerProphecy->reveal() ); } @@ -56,12 +51,11 @@ public function testNoSecurity(): void { $operationName = 'item_query'; $resourceClass = 'myResource'; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => new Query()])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withName($operationName)->withClass($resourceClass); $this->resourceAccessCheckerProphecy->isGranted(Argument::cetera())->shouldNotBeCalled(); - ($this->securityStage)($resourceClass, $operationName, []); + ($this->securityStage)($resourceClass, $operation, []); } public function testGranted(): void @@ -70,12 +64,11 @@ public function testGranted(): void $resourceClass = 'myResource'; $isGranted = 'not_granted'; $extraVariables = ['extra' => false]; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withSecurity($isGranted)])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withName($operationName)->withClass($resourceClass)->withSecurity($isGranted); $this->resourceAccessCheckerProphecy->isGranted($resourceClass, $isGranted, $extraVariables)->shouldBeCalled()->willReturn(true); - ($this->securityStage)($resourceClass, 'item_query', ['extra_variables' => $extraVariables]); + ($this->securityStage)($resourceClass, $operation, ['extra_variables' => $extraVariables]); } public function testNotGranted(): void @@ -84,8 +77,7 @@ public function testNotGranted(): void $resourceClass = 'myResource'; $isGranted = 'not_granted'; $extraVariables = ['extra' => false]; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withSecurity($isGranted)])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withName($operationName)->withClass($resourceClass)->withSecurity($isGranted); $this->resourceAccessCheckerProphecy->isGranted($resourceClass, $isGranted, $extraVariables)->shouldBeCalled()->willReturn(false); @@ -94,7 +86,7 @@ public function testNotGranted(): void $this->expectException(AccessDeniedHttpException::class); $this->expectExceptionMessage('Access Denied.'); - ($this->securityStage)($resourceClass, 'item_query', [ + ($this->securityStage)($resourceClass, $operation, [ 'info' => $info, 'extra_variables' => $extraVariables, ]); @@ -102,30 +94,25 @@ public function testNotGranted(): void public function testNoSecurityBundleInstalled(): void { - $this->securityStage = new SecurityStage($this->resourceMetadataCollectionFactoryProphecy->reveal(), null); + $this->securityStage = new SecurityStage(null); $operationName = 'item_query'; $resourceClass = 'myResource'; $isGranted = 'not_granted'; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withSecurity($isGranted)])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withName($operationName)->withClass($resourceClass)->withSecurity($isGranted); $this->expectException(\LogicException::class); - ($this->securityStage)($resourceClass, 'item_query', []); + ($this->securityStage)($resourceClass, $operation, []); } public function testNoSecurityBundleInstalledNoExpression(): void { - $this->securityStage = new SecurityStage($this->resourceMetadataCollectionFactoryProphecy->reveal(), null); + $this->securityStage = new SecurityStage(null); - $operationName = 'item_query'; $resourceClass = 'myResource'; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => new Query()])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); - $this->resourceAccessCheckerProphecy->isGranted(Argument::any())->shouldNotBeCalled(); - ($this->securityStage)($resourceClass, $operationName, []); + ($this->securityStage)($resourceClass, new Query(), []); } } diff --git a/tests/GraphQl/Resolver/Stage/SerializeStageTest.php b/tests/GraphQl/Resolver/Stage/SerializeStageTest.php index a32709e75bc..81b80231fea 100644 --- a/tests/GraphQl/Resolver/Stage/SerializeStageTest.php +++ b/tests/GraphQl/Resolver/Stage/SerializeStageTest.php @@ -17,7 +17,6 @@ use ApiPlatform\GraphQl\Resolver\Stage\SerializeStage; use ApiPlatform\GraphQl\Serializer\ItemNormalizer; use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GraphQl\Mutation; use ApiPlatform\Metadata\GraphQl\Query; use ApiPlatform\Metadata\GraphQl\QueryCollection; @@ -39,7 +38,6 @@ class SerializeStageTest extends TestCase { use ProphecyTrait; - private $resourceMetadataCollectionFactoryProphecy; private $normalizerProphecy; private $serializerContextBuilderProphecy; @@ -48,7 +46,6 @@ class SerializeStageTest extends TestCase */ protected function setUp(): void { - $this->resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); $this->normalizerProphecy = $this->prophesize(NormalizerInterface::class); $this->serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); } @@ -60,10 +57,9 @@ public function testApplyDisabled(array $context, bool $paginationEnabled, ?arra { $operationName = 'item_query'; $resourceClass = 'myResource'; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withSerialize(false)])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withSerialize(false); - $result = ($this->createSerializeStage($paginationEnabled))(null, $resourceClass, $operationName, $context); + $result = ($this->createSerializeStage($paginationEnabled))(null, $resourceClass, $operation, $context); $this->assertSame($expectedResult, $result); } @@ -92,15 +88,18 @@ public function testApply($itemOrCollection, string $operationName, array $conte $operation = new Subscription(); } - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => $operation->withShortName('shortName')->withCollection($context['is_collection'] ?? false)])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + if ($context['is_collection'] ?? false) { + $operation = new QueryCollection(); + } + + $operation = $operation->withShortName('shortName')->withName($operationName)->withClass($resourceClass); $normalizationContext = ['normalization' => true]; $this->serializerContextBuilderProphecy->create($resourceClass, $operationName, $context, true)->shouldBeCalled()->willReturn($normalizationContext); $this->normalizerProphecy->normalize(Argument::type('stdClass'), ItemNormalizer::FORMAT, $normalizationContext)->willReturn(['normalized_item']); - $result = ($this->createSerializeStage($paginationEnabled))($itemOrCollection, $resourceClass, $operationName, $context); + $result = ($this->createSerializeStage($paginationEnabled))($itemOrCollection, $resourceClass, $operation, $context); $this->assertSame($expectedResult, $result); } @@ -135,8 +134,8 @@ public function testApplyCollectionWithPagination(iterable $collection, array $a 'args' => $args, 'info' => $this->prophesize(ResolveInfo::class)->reveal(), ]; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new QueryCollection())->withShortName('shortName')])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + + $operation = (new QueryCollection())->withShortName('shortName')->withName($operationName); $normalizationContext = ['normalization' => true]; $this->serializerContextBuilderProphecy->create($resourceClass, $operationName, $context, true)->shouldBeCalled()->willReturn($normalizationContext); @@ -148,7 +147,7 @@ public function testApplyCollectionWithPagination(iterable $collection, array $a $this->expectExceptionMessage($expectedExceptionMessage); } - $result = ($this->createSerializeStage(true))($collection, $resourceClass, $operationName, $context); + $result = ($this->createSerializeStage(true))($collection, $resourceClass, $operation, $context); $this->assertSame($expectedResult, $result); } @@ -180,8 +179,7 @@ public function testApplyBadNormalizedData(): void $operationName = 'item_query'; $resourceClass = 'myResource'; $context = ['is_collection' => false, 'is_mutation' => false, 'is_subscription' => false, 'args' => [], 'info' => $this->prophesize(ResolveInfo::class)->reveal()]; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => new Query()])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withName($operationName); $normalizationContext = ['normalization' => true]; $this->serializerContextBuilderProphecy->create($resourceClass, $operationName, $context, true)->shouldBeCalled()->willReturn($normalizationContext); @@ -191,15 +189,16 @@ public function testApplyBadNormalizedData(): void $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessage('Expected serialized data to be a nullable array.'); - ($this->createSerializeStage(false))(new \stdClass(), $resourceClass, $operationName, $context); + ($this->createSerializeStage(false))(new \stdClass(), $resourceClass, $operation, $context); } private function createSerializeStage(bool $paginationEnabled): SerializeStage { - $pagination = new Pagination($this->resourceMetadataCollectionFactoryProphecy->reveal(), [], ['enabled' => $paginationEnabled]); + $resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); + $resourceMetadataCollectionFactoryProphecy->create(Argument::type('string'))->willReturn(new ResourceMetadataCollection('')); + $pagination = new Pagination($resourceMetadataCollectionFactoryProphecy->reveal(), [], ['enabled' => $paginationEnabled]); return new SerializeStage( - $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->normalizerProphecy->reveal(), $this->serializerContextBuilderProphecy->reveal(), $pagination diff --git a/tests/GraphQl/Resolver/Stage/ValidateStageTest.php b/tests/GraphQl/Resolver/Stage/ValidateStageTest.php index d96ed29cac0..6d33ef01ccf 100644 --- a/tests/GraphQl/Resolver/Stage/ValidateStageTest.php +++ b/tests/GraphQl/Resolver/Stage/ValidateStageTest.php @@ -15,10 +15,7 @@ use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\GraphQl\Resolver\Stage\ValidateStage; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GraphQl\Query; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Validator\Exception\ValidationException; use ApiPlatform\Validator\ValidatorInterface; use GraphQL\Type\Definition\ResolveInfo; @@ -42,56 +39,48 @@ class ValidateStageTest extends TestCase */ protected function setUp(): void { - $this->resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); $this->validatorProphecy = $this->prophesize(ValidatorInterface::class); $this->validateStage = new ValidateStage( - $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->validatorProphecy->reveal() ); } public function testApplyDisabled(): void { - $operationName = 'item_query'; $resourceClass = 'myResource'; - $resourceMetadata = (new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withValidate(false)])])); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withValidate(false)->withName('item_query'); $this->validatorProphecy->validate(Argument::cetera())->shouldNotBeCalled(); - ($this->validateStage)(new \stdClass(), $resourceClass, $operationName, []); + ($this->validateStage)(new \stdClass(), $resourceClass, $operation, []); } public function testApply(): void { - $operationName = 'item_query'; $resourceClass = 'myResource'; $validationGroups = ['group']; - $resourceMetadata = (new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withValidationContext(['groups' => $validationGroups])])])); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withName('item_query')->withValidationContext(['groups' => $validationGroups]); $object = new \stdClass(); $this->validatorProphecy->validate($object, ['groups' => $validationGroups])->shouldBeCalled(); - ($this->validateStage)($object, $resourceClass, $operationName, []); + ($this->validateStage)($object, $resourceClass, $operation, []); } public function testApplyNotValidated(): void { - $operationName = 'item_query'; $resourceClass = 'myResource'; $validationGroups = ['group']; - $resourceMetadata = (new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operationName => (new Query())->withValidationContext(['groups' => $validationGroups])])])); + $operation = (new Query())->withValidationContext(['groups' => $validationGroups])->withName('item_query'); $info = $this->prophesize(ResolveInfo::class)->reveal(); $context = ['info' => $info]; - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); $object = new \stdClass(); $this->validatorProphecy->validate($object, ['groups' => $validationGroups])->shouldBeCalled()->willThrow(new ValidationException()); $this->expectException(ValidationException::class); - ($this->validateStage)($object, $resourceClass, $operationName, $context); + ($this->validateStage)($object, $resourceClass, $operation, $context); } } diff --git a/tests/GraphQl/Resolver/Stage/WriteStageTest.php b/tests/GraphQl/Resolver/Stage/WriteStageTest.php index 31a913c9cbd..c6cf489b2a5 100644 --- a/tests/GraphQl/Resolver/Stage/WriteStageTest.php +++ b/tests/GraphQl/Resolver/Stage/WriteStageTest.php @@ -16,11 +16,8 @@ use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\GraphQl\Resolver\Stage\WriteStage; use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GraphQl\Mutation; use ApiPlatform\Metadata\GraphQl\Query; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\State\ProcessorInterface; use PHPUnit\Framework\TestCase; @@ -42,12 +39,10 @@ class WriteStageTest extends TestCase */ protected function setUp(): void { - $this->resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); $this->processorProphecy = $this->prophesize(ProcessorInterface::class); $this->serializerContextBuilderProphecy = $this->prophesize(SerializerContextBuilderInterface::class); $this->writeStage = new WriteStage( - $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->processorProphecy->reveal(), $this->serializerContextBuilderProphecy->reveal() ); @@ -57,12 +52,9 @@ public function testNoData(): void { $resourceClass = 'myResource'; $operationName = 'item_query'; - $resourceMetadata = (new ApiResource())->withGraphQlOperations([ - $operationName => (new Query()), - ]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [$resourceMetadata])); + $operation = (new Query())->withName('item_query'); - $result = ($this->writeStage)(null, $resourceClass, $operationName, []); + $result = ($this->writeStage)(null, $resourceClass, $operation, []); $this->assertNull($result); } @@ -71,13 +63,10 @@ public function testApplyDisabled(): void { $operationName = 'item_query'; $resourceClass = 'myResource'; - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([ - $operationName => (new Query())->withWrite(false), - ])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); + $operation = (new Query())->withName('item_query')->withWrite(false); $data = new \stdClass(); - $result = ($this->writeStage)($data, $resourceClass, $operationName, []); + $result = ($this->writeStage)($data, $resourceClass, $operation, []); $this->assertSame($data, $result); } @@ -88,19 +77,15 @@ public function testApply(): void $resourceClass = 'myResource'; $context = []; $operation = (new Mutation())->withName($operationName); - $resourceMetadata = new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([ - $operationName => $operation, - ])]); - $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn($resourceMetadata); $denormalizationContext = ['denormalization' => true]; $this->serializerContextBuilderProphecy->create($resourceClass, $operationName, $context, false)->willReturn($denormalizationContext); $data = new \stdClass(); $processedData = new \stdClass(); - $this->processorProphecy->process($data, [], $operationName, ['operation' => $operation] + $denormalizationContext)->shouldBeCalled()->willReturn($processedData); + $this->processorProphecy->process($data, $operation, [], ['operation' => $operation] + $denormalizationContext)->shouldBeCalled()->willReturn($processedData); - $result = ($this->writeStage)($data, $resourceClass, $operationName, $context); + $result = ($this->writeStage)($data, $resourceClass, $operation, $context); $this->assertSame($processedData, $result); } diff --git a/tests/GraphQl/Subscription/SubscriptionManagerTest.php b/tests/GraphQl/Subscription/SubscriptionManagerTest.php index 89157bd9fbe..73c74a35f64 100644 --- a/tests/GraphQl/Subscription/SubscriptionManagerTest.php +++ b/tests/GraphQl/Subscription/SubscriptionManagerTest.php @@ -18,6 +18,12 @@ use ApiPlatform\GraphQl\Resolver\Stage\SerializeStageInterface; use ApiPlatform\GraphQl\Subscription\SubscriptionIdentifierGeneratorInterface; use ApiPlatform\GraphQl\Subscription\SubscriptionManager; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GraphQl\Subscription; +use ApiPlatform\Metadata\Operations; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use GraphQL\Type\Definition\ResolveInfo; use PHPUnit\Framework\TestCase; @@ -46,7 +52,8 @@ protected function setUp(): void $this->subscriptionIdentifierGeneratorProphecy = $this->prophesize(SubscriptionIdentifierGeneratorInterface::class); $this->serializeStageProphecy = $this->prophesize(SerializeStageInterface::class); $this->iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $this->subscriptionManager = new SubscriptionManager($this->subscriptionsCacheProphecy->reveal(), $this->subscriptionIdentifierGeneratorProphecy->reveal(), $this->serializeStageProphecy->reveal(), $this->iriConverterProphecy->reveal()); + $this->resourceMetadataCollectionFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); + $this->subscriptionManager = new SubscriptionManager($this->subscriptionsCacheProphecy->reveal(), $this->subscriptionIdentifierGeneratorProphecy->reveal(), $this->serializeStageProphecy->reveal(), $this->iriConverterProphecy->reveal(), $this->resourceMetadataCollectionFactory->reveal()); } public function testRetrieveSubscriptionIdNoIdentifier(): void @@ -166,6 +173,10 @@ public function testGetPushPayloadsNoHit(): void { $object = new Dummy(); + $this->resourceMetadataCollectionFactory->create(Dummy::class)->willReturn(new ResourceMetadataCollection(Dummy::class, [ + (new ApiResource())->withOperations(new Operations([(new Get())->withShortName('Dummy')])), + ])); + $this->iriConverterProphecy->getIriFromItem($object)->willReturn('/dummies/2'); $cacheItemProphecy = $this->prophesize(CacheItemInterface::class); @@ -179,6 +190,10 @@ public function testGetPushPayloadsHit(): void { $object = new Dummy(); + $this->resourceMetadataCollectionFactory->create(Dummy::class)->willReturn(new ResourceMetadataCollection(Dummy::class, [ + (new ApiResource())->withOperations(new Operations([(new Get())->withShortName('Dummy')])), + ])); + $this->iriConverterProphecy->getIriFromItem($object)->willReturn('/dummies/2'); $cacheItemProphecy = $this->prophesize(CacheItemInterface::class); @@ -189,8 +204,8 @@ public function testGetPushPayloadsHit(): void ]); $this->subscriptionsCacheProphecy->getItem('_dummies_2')->willReturn($cacheItemProphecy->reveal()); - $this->serializeStageProphecy->__invoke($object, Dummy::class, 'update_subscription', ['fields' => ['fieldsFoo'], 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => true])->willReturn(['newResultFoo', 'clientSubscriptionId' => 'client-subscription-id']); - $this->serializeStageProphecy->__invoke($object, Dummy::class, 'update_subscription', ['fields' => ['fieldsBar'], 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => true])->willReturn(['resultBar', 'clientSubscriptionId' => 'client-subscription-id']); + $this->serializeStageProphecy->__invoke($object, Dummy::class, (new Subscription())->withName('update_subscription')->withShortName('Dummy'), ['fields' => ['fieldsFoo'], 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => true])->willReturn(['newResultFoo', 'clientSubscriptionId' => 'client-subscription-id']); + $this->serializeStageProphecy->__invoke($object, Dummy::class, (new Subscription())->withName('update_subscription')->withShortName('Dummy'), ['fields' => ['fieldsBar'], 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => true])->willReturn(['resultBar', 'clientSubscriptionId' => 'client-subscription-id']); $this->assertSame([['subscriptionIdFoo', ['newResultFoo']]], $this->subscriptionManager->getPushPayloads($object)); } diff --git a/tests/GraphQl/Type/FieldsBuilderTest.php b/tests/GraphQl/Type/FieldsBuilderTest.php index c7da6625c3e..f906de8bdba 100644 --- a/tests/GraphQl/Type/FieldsBuilderTest.php +++ b/tests/GraphQl/Type/FieldsBuilderTest.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Core\Tests\GraphQl\Type; use ApiPlatform\Api\FilterInterface; +use ApiPlatform\Api\ResourceClassResolverInterface; use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\GraphQl\Resolver\Factory\ResolverFactoryInterface; use ApiPlatform\GraphQl\Type\FieldsBuilder; @@ -87,6 +88,9 @@ class FieldsBuilderTest extends TestCase /** @var ObjectProphecy */ private $filterLocatorProphecy; + /** @var ObjectProphecy */ + private $resourceClassResolverProphecy; + /** @var FieldsBuilder */ private $fieldsBuilder; @@ -106,12 +110,13 @@ protected function setUp(): void $this->itemMutationResolverFactoryProphecy = $this->prophesize(ResolverFactoryInterface::class); $this->itemSubscriptionResolverFactoryProphecy = $this->prophesize(ResolverFactoryInterface::class); $this->filterLocatorProphecy = $this->prophesize(ContainerInterface::class); + $this->resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $this->fieldsBuilder = $this->buildFieldsBuilder(); } private function buildFieldsBuilder(?AdvancedNameConverterInterface $advancedNameConverter = null): FieldsBuilder { - return new FieldsBuilder($this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->typesContainerProphecy->reveal(), $this->typeBuilderProphecy->reveal(), $this->typeConverterProphecy->reveal(), $this->itemResolverFactoryProphecy->reveal(), $this->collectionResolverFactoryProphecy->reveal(), $this->itemMutationResolverFactoryProphecy->reveal(), $this->itemSubscriptionResolverFactoryProphecy->reveal(), $this->filterLocatorProphecy->reveal(), new Pagination($this->resourceMetadataCollectionFactoryProphecy->reveal()), $advancedNameConverter ?? new CustomConverter(), '__'); + return new FieldsBuilder($this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->resourceClassResolverProphecy->reveal(), $this->typesContainerProphecy->reveal(), $this->typeBuilderProphecy->reveal(), $this->typeConverterProphecy->reveal(), $this->itemResolverFactoryProphecy->reveal(), $this->collectionResolverFactoryProphecy->reveal(), $this->itemMutationResolverFactoryProphecy->reveal(), $this->itemSubscriptionResolverFactoryProphecy->reveal(), $this->filterLocatorProphecy->reveal(), new Pagination($this->resourceMetadataCollectionFactoryProphecy->reveal()), $advancedNameConverter ?? new CustomConverter(), '__'); } public function testGetNodeQueryFields(): void @@ -143,13 +148,14 @@ public function testGetNodeQueryFields(): void */ public function testGetItemQueryFields(string $resourceClass, Operation $operation, array $configuration, ?GraphQLType $graphqlType, ?callable $resolver, array $expectedQueryFields): void { + $this->resourceClassResolverProphecy->isResourceClass($resourceClass)->willReturn(true); $this->typeConverterProphecy->convertType(Argument::type(Type::class), false, Argument::that(static function (Operation $arg) use ($operation): bool { return $arg->getName() === $operation->getName(); }), $resourceClass, $resourceClass, null, 0)->willReturn($graphqlType); $this->typeConverterProphecy->resolveType(Argument::type('string'))->willReturn(GraphQLType::string()); $this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(false); $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operation->getName() => $operation])])); - $this->itemResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation->getName())->willReturn($resolver); + $this->itemResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($resolver); $queryFields = $this->fieldsBuilder->getItemQueryFields($resourceClass, $operation, $configuration); @@ -224,6 +230,7 @@ public function itemQueryFieldsProvider(): array */ public function testGetCollectionQueryFields(string $resourceClass, Operation $operation, array $configuration, ?GraphQLType $graphqlType, ?callable $resolver, array $expectedQueryFields): void { + $this->resourceClassResolverProphecy->isResourceClass($resourceClass)->willReturn(true); $this->typeConverterProphecy->convertType(Argument::type(Type::class), false, Argument::that(static function (Operation $arg) use ($operation): bool { return $arg->getName() === $operation->getName(); }), $resourceClass, $resourceClass, null, 0)->willReturn($graphqlType); @@ -231,7 +238,7 @@ public function testGetCollectionQueryFields(string $resourceClass, Operation $o $this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(true); $this->typeBuilderProphecy->getResourcePaginatedCollectionType($graphqlType, $resourceClass, $operation->getName())->willReturn($graphqlType); $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operation->getName() => $operation])])); - $this->collectionResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation->getName())->willReturn($resolver); + $this->collectionResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($resolver); $this->filterLocatorProphecy->has('my_filter')->willReturn(true); $filterProphecy = $this->prophesize(FilterInterface::class); $filterProphecy->getDescription($resourceClass)->willReturn([ @@ -377,6 +384,7 @@ public function collectionQueryFieldsProvider(): array */ public function testGetMutationFields(string $resourceClass, Operation $operation, GraphQLType $graphqlType, GraphQLType $inputGraphqlType, ?callable $mutationResolver, array $expectedMutationFields): void { + $this->resourceClassResolverProphecy->isResourceClass($resourceClass)->willReturn(true); $this->typeConverterProphecy->convertType(Argument::type(Type::class), false, Argument::that(static function (Operation $arg) use ($operation): bool { return $arg->getName() === $operation->getName(); }), $resourceClass, $resourceClass, null, 0)->willReturn($graphqlType); @@ -386,7 +394,7 @@ public function testGetMutationFields(string $resourceClass, Operation $operatio $this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(false); $this->typeBuilderProphecy->getResourcePaginatedCollectionType($graphqlType, $resourceClass, $operation->getName())->willReturn($graphqlType); $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operation->getName() => $operation])])); - $this->itemMutationResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation->getName())->willReturn($mutationResolver); + $this->itemMutationResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($mutationResolver); $mutationFields = $this->fieldsBuilder->getMutationFields($resourceClass, $operation); @@ -444,6 +452,7 @@ public function mutationFieldsProvider(): array */ public function testGetSubscriptionFields(string $resourceClass, Operation $operation, GraphQLType $graphqlType, GraphQLType $inputGraphqlType, ?callable $subscriptionResolver, array $expectedSubscriptionFields): void { + $this->resourceClassResolverProphecy->isResourceClass($resourceClass)->willReturn(true); $this->typeConverterProphecy->convertType(Argument::type(Type::class), false, Argument::that(static function (Operation $arg) use ($operation): bool { return $arg->getName() === $operation->getName(); }), $resourceClass, $resourceClass, null, 0)->willReturn($graphqlType); @@ -453,7 +462,7 @@ public function testGetSubscriptionFields(string $resourceClass, Operation $oper $this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(false); $this->typeBuilderProphecy->getResourcePaginatedCollectionType($graphqlType, $resourceClass, $operation->getName())->willReturn($graphqlType); $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operation->getName() => $operation])])); - $this->itemSubscriptionResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation->getName())->willReturn($subscriptionResolver); + $this->itemSubscriptionResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($subscriptionResolver); $subscriptionFields = $this->fieldsBuilder->getSubscriptionFields($resourceClass, $operation); @@ -513,6 +522,8 @@ public function subscriptionFieldsProvider(): array */ public function testGetResourceObjectTypeFields(string $resourceClass, Operation $operation, array $properties, bool $input, int $depth, ?array $ioMetadata, array $expectedResourceObjectTypeFields, ?AdvancedNameConverterInterface $advancedNameConverter = null): void { + $this->resourceClassResolverProphecy->isResourceClass($resourceClass)->willReturn(true); + $this->resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(false); $this->propertyNameCollectionFactoryProphecy->create($resourceClass)->willReturn(new PropertyNameCollection(array_keys($properties))); foreach ($properties as $propertyName => $propertyMetadata) { $this->propertyMetadataFactoryProphecy->create($resourceClass, $propertyName, ['normalization_groups' => null, 'denormalization_groups' => null])->willReturn($propertyMetadata); @@ -530,7 +541,7 @@ public function testGetResourceObjectTypeFields(string $resourceClass, Operation return $arg->getName() === $operation->getName(); }), 'objectClass', $resourceClass, $propertyName, $depth + 1)->willReturn(new ObjectType(['name' => 'objectType'])); $this->resourceMetadataCollectionFactoryProphecy->create('objectClass')->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations(['item_query' => new Query()])])); - $this->itemResolverFactoryProphecy->__invoke('objectClass', $resourceClass, $operation->getName())->willReturn(static function () { + $this->itemResolverFactoryProphecy->__invoke('objectClass', $resourceClass, $operation)->willReturn(static function () { }); } $this->typeConverterProphecy->convertType(Argument::type(Type::class), true, Argument::that(static function (Operation $arg) use ($operation): bool { diff --git a/tests/Hydra/Serializer/EntrypointNormalizerTest.php b/tests/Hydra/Serializer/EntrypointNormalizerTest.php index 1c53b4414f7..5d52dc8cd21 100644 --- a/tests/Hydra/Serializer/EntrypointNormalizerTest.php +++ b/tests/Hydra/Serializer/EntrypointNormalizerTest.php @@ -21,7 +21,7 @@ use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\Hydra\Serializer\EntrypointNormalizer; use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; @@ -92,13 +92,13 @@ public function testNormalizeWithResourceCollection() $factoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); $factoryProphecy->create(Dummy::class)->willReturn( new ResourceMetadataCollection(Dummy::class, [ - (new ApiResource())->withUriTemplate('Dummy')->withShortName('dummy')->withOperations(new Operations(['get' => (new Get())->withCollection(true)])), + (new ApiResource())->withUriTemplate('Dummy')->withShortName('dummy')->withOperations(new Operations(['get' => (new GetCollection())])), ]) )->shouldBeCalled(); $factoryProphecy->create(FooDummy::class)->willReturn( new ResourceMetadataCollection(FooDummy::class, [ - (new ApiResource())->withUriTemplate('FooDummy')->withShortName('fooDummy')->withOperations(new Operations(['get' => (new Get())->withCollection(true)])), + (new ApiResource())->withUriTemplate('FooDummy')->withShortName('fooDummy')->withOperations(new Operations(['get' => (new GetCollection())])), ]) )->shouldBeCalled(); diff --git a/tests/Metadata/Extractor/Adapter/XmlResourceAdapter.php b/tests/Metadata/Extractor/Adapter/XmlResourceAdapter.php index e274103484b..89e3529cbde 100644 --- a/tests/Metadata/Extractor/Adapter/XmlResourceAdapter.php +++ b/tests/Metadata/Extractor/Adapter/XmlResourceAdapter.php @@ -30,7 +30,6 @@ final class XmlResourceAdapter implements ResourceAdapterInterface 'stateless', 'sunset', 'class', - 'collection', 'acceptPatch', 'status', 'host', @@ -54,13 +53,14 @@ final class XmlResourceAdapter implements ResourceAdapterInterface 'paginationMaximumItemsPerPage', 'paginationPartial', 'paginationType', + 'processor', + 'provider', 'security', 'securityMessage', 'securityPostDenormalize', 'securityPostDenormalizeMessage', 'securityPostValidation', 'securityPostValidationMessage', - 'compositeIdentifier', 'queryParameterValidationEnabled', ]; @@ -92,6 +92,10 @@ public function __invoke(string $resourceClass, array $parameters, array $fixtur $parameterName = $parameter->getName(); $value = \array_key_exists($parameterName, $fixture) ? $fixture[$parameterName] : null; + if ('compositeIdentifier' === $parameterName || 'provider' === $parameterName || 'processor' === $parameterName) { + continue; + } + if (method_exists($this, 'build'.ucfirst($parameterName))) { $this->{'build'.ucfirst($parameterName)}($resource, $value); continue; diff --git a/tests/Metadata/Extractor/ResourceMetadataCompatibilityTest.php b/tests/Metadata/Extractor/ResourceMetadataCompatibilityTest.php index 241248a9555..f8fe305f995 100644 --- a/tests/Metadata/Extractor/ResourceMetadataCompatibilityTest.php +++ b/tests/Metadata/Extractor/ResourceMetadataCompatibilityTest.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Tests\Metadata\Extractor; use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Extractor\XmlResourceExtractor; use ApiPlatform\Metadata\Extractor\YamlResourceExtractor; @@ -22,7 +23,7 @@ use ApiPlatform\Metadata\GraphQl\Mutation; use ApiPlatform\Metadata\GraphQl\Query; use ApiPlatform\Metadata\GraphQl\Subscription; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; @@ -87,7 +88,6 @@ final class ResourceMetadataCompatibilityTest extends TestCase 'securityPostDenormalizeMessage' => 'Sorry, you must an admin to access this resource.', 'securityPostValidation' => 'is_granted(\'ROLE_OWNER\')', 'securityPostValidationMessage' => 'Sorry, you must the owner of this resource to access it.', - 'compositeIdentifier' => true, 'queryParameterValidationEnabled' => true, 'types' => ['someirischema', 'anotheririschema'], 'formats' => [ @@ -166,7 +166,6 @@ final class ResourceMetadataCompatibilityTest extends TestCase 'description' => 'A list of Comments', 'class' => GetCollection::class, 'urlGenerationStrategy' => 0, - 'collection' => true, 'deprecationReason' => 'I don\'t know', 'normalizationContext' => [ 'groups' => 'comment:read_collection', @@ -270,7 +269,6 @@ final class ResourceMetadataCompatibilityTest extends TestCase 'controller' => 'App\Controller\CustomController', 'class' => GetCollection::class, 'urlGenerationStrategy' => 0, - 'collection' => true, 'deprecationReason' => 'I don\'t know', 'cacheHeaders' => [ 'max_age' => 60, @@ -320,7 +318,6 @@ final class ResourceMetadataCompatibilityTest extends TestCase 'securityMessage' => 'Sorry, you can\'t access this collection.', 'securityPostDenormalize' => 'is_granted(\'ROLE_CUSTOM_ADMIN\')', 'securityPostDenormalizeMessage' => 'Sorry, you must an admin to access this collection.', - 'compositeIdentifier' => false, 'exceptionToStatus' => [ 'Symfony\Component\Serializer\Exception\ExceptionInterface' => 404, ], @@ -330,7 +327,6 @@ final class ResourceMetadataCompatibilityTest extends TestCase 'validate' => false, 'write' => false, 'serialize' => true, - 'queryParameterValidate' => true, 'priority' => 200, 'extraProperties' => [ 'foo' => 'bar', @@ -373,6 +369,8 @@ final class ResourceMetadataCompatibilityTest extends TestCase 'paginationMaximumItemsPerPage', 'paginationPartial', 'paginationType', + 'processor', + 'provider', 'security', 'securityMessage', 'securityPostDenormalize', @@ -396,7 +394,6 @@ final class ResourceMetadataCompatibilityTest extends TestCase 'host', 'condition', 'controller', - 'compositeIdentifier', 'queryParameterValidationEnabled', 'exceptionToStatus', 'types', @@ -455,7 +452,7 @@ private function buildApiResources(): array // Build default operations $operations = []; foreach ([new Get(), new GetCollection(), new Post(), new Put(), new Patch(), new Delete()] as $operation) { - $operationName = sprintf('_api_%s_%s%s', $resource->getShortName(), strtolower($operation->getMethod()), $operation->isCollection() ? '_collection' : ''); + $operationName = sprintf('_api_%s_%s%s', $resource->getShortName(), strtolower($operation->getMethod()), $operation instanceof CollectionOperationInterface ? '_collection' : ''); $operations[$operationName] = $this->getOperationWithDefaults($resource, $operation)->withName($operationName); } @@ -519,7 +516,7 @@ private function withOperations(array $values, ?array $fixtures): Operations { $operations = []; foreach ($values as $value) { - $class = $value['class'] ?? Operation::class; + $class = $value['class'] ?? HttpOperation::class; unset($value['class']); $operation = (new $class())->withClass(self::RESOURCE_CLASS); @@ -543,7 +540,7 @@ private function withOperations(array $values, ?array $fixtures): Operations } if (null === $operation->getName()) { - $operation = $operation->withName(sprintf('_api_%s_%s%s', $operation->getUriTemplate() ?: $operation->getShortName(), strtolower($operation->getMethod()), $operation->isCollection() ? '_collection' : '')); + $operation = $operation->withName(sprintf('_api_%s_%s%s', $operation->getUriTemplate() ?: $operation->getShortName(), strtolower($operation->getMethod()), $operation instanceof CollectionOperationInterface ? '_collection' : '')); } $operations[$operation->getName()] = $operation; } @@ -597,7 +594,7 @@ private function withGraphQlOperations(array $values, ?array $fixtures): array return $operations; } - private function getOperationWithDefaults(ApiResource $resource, Operation $operation): Operation + private function getOperationWithDefaults(ApiResource $resource, HttpOperation $operation): HttpOperation { foreach (get_class_methods($resource) as $methodName) { if (0 !== strpos($methodName, 'get')) { diff --git a/tests/Metadata/Extractor/XmlExtractorTest.php b/tests/Metadata/Extractor/XmlExtractorTest.php index b15dd5510b3..853eb9b9b2b 100644 --- a/tests/Metadata/Extractor/XmlExtractorTest.php +++ b/tests/Metadata/Extractor/XmlExtractorTest.php @@ -64,7 +64,6 @@ public function testValidXML(): void 'securityPostDenormalizeMessage' => null, 'securityPostValidation' => null, 'securityPostValidationMessage' => null, - 'compositeIdentifier' => null, 'queryParameterValidationEnabled' => null, 'input' => null, 'output' => null, @@ -127,7 +126,6 @@ public function testValidXML(): void 'securityPostDenormalizeMessage' => null, 'securityPostValidation' => null, 'securityPostValidationMessage' => null, - 'compositeIdentifier' => null, 'queryParameterValidationEnabled' => null, 'input' => null, 'output' => null, @@ -205,7 +203,6 @@ public function testValidXML(): void 'securityPostDenormalizeMessage' => null, 'securityPostValidation' => null, 'securityPostValidationMessage' => null, - 'compositeIdentifier' => null, 'queryParameterValidationEnabled' => null, 'input' => null, 'output' => null, @@ -292,7 +289,6 @@ public function testValidXML(): void 'securityPostDenormalizeMessage' => null, 'securityPostValidation' => null, 'securityPostValidationMessage' => null, - 'compositeIdentifier' => null, 'queryParameterValidationEnabled' => null, 'input' => null, 'output' => null, diff --git a/tests/Metadata/Extractor/YamlExtractorTest.php b/tests/Metadata/Extractor/YamlExtractorTest.php index 29c7a9e71f6..1a71979c4ba 100644 --- a/tests/Metadata/Extractor/YamlExtractorTest.php +++ b/tests/Metadata/Extractor/YamlExtractorTest.php @@ -65,7 +65,6 @@ public function testValidYaml(): void 'securityPostDenormalizeMessage' => null, 'securityPostValidation' => null, 'securityPostValidationMessage' => null, - 'compositeIdentifier' => null, 'queryParameterValidationEnabled' => null, 'input' => null, 'output' => null, @@ -129,7 +128,6 @@ public function testValidYaml(): void 'securityPostDenormalizeMessage' => null, 'securityPostValidation' => null, 'securityPostValidationMessage' => null, - 'compositeIdentifier' => null, 'queryParameterValidationEnabled' => null, 'input' => null, 'output' => null, @@ -191,7 +189,6 @@ public function testValidYaml(): void 'securityPostDenormalizeMessage' => null, 'securityPostValidation' => null, 'securityPostValidationMessage' => null, - 'compositeIdentifier' => null, 'queryParameterValidationEnabled' => null, 'input' => null, 'output' => null, @@ -253,7 +250,6 @@ public function testValidYaml(): void 'securityPostDenormalizeMessage' => null, 'securityPostValidation' => null, 'securityPostValidationMessage' => null, - 'compositeIdentifier' => null, 'queryParameterValidationEnabled' => null, 'input' => null, 'output' => null, @@ -322,7 +318,6 @@ public function testValidYaml(): void 'securityPostDenormalizeMessage' => null, 'securityPostValidation' => null, 'securityPostValidationMessage' => null, - 'compositeIdentifier' => null, 'queryParameterValidationEnabled' => null, 'input' => null, 'output' => null, @@ -397,7 +392,6 @@ public function testValidYaml(): void 'securityPostDenormalizeMessage' => null, 'securityPostValidation' => null, 'securityPostValidationMessage' => null, - 'compositeIdentifier' => null, 'queryParameterValidationEnabled' => null, 'input' => null, 'output' => null, diff --git a/tests/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php b/tests/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php index 209eeeda677..f2b3d82e310 100644 --- a/tests/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php +++ b/tests/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php @@ -21,7 +21,7 @@ use ApiPlatform\Metadata\GraphQl\Mutation; use ApiPlatform\Metadata\GraphQl\Query; use ApiPlatform\Metadata\GraphQl\QueryCollection; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; @@ -128,7 +128,7 @@ public function testCreateWithDefaults(): void { $attributeResourceMetadataCollectionFactory = new AttributesResourceMetadataCollectionFactory(null, null, ['attributes' => ['cache_headers' => ['max_age' => 60], 'non_existing_attribute' => 'foo']]); - $operation = new Operation(shortName: 'AttributeDefaultOperations', class: AttributeDefaultOperations::class, collection: false, cacheHeaders: ['max_age' => 60], paginationItemsPerPage: 10); + $operation = new HttpOperation(shortName: 'AttributeDefaultOperations', class: AttributeDefaultOperations::class, collection: false, cacheHeaders: ['max_age' => 60], paginationItemsPerPage: 10); $this->assertEquals(new ResourceMetadataCollection(AttributeDefaultOperations::class, [ new ApiResource( shortName: 'AttributeDefaultOperations', @@ -158,7 +158,7 @@ public function testCreateShouldNotOverrideWithDefault(): void ] ); - $operation = new Operation(shortName: 'AttributeDefaultOperations', class: AttributeDefaultOperations::class, paginationItemsPerPage: 10); + $operation = new HttpOperation(shortName: 'AttributeDefaultOperations', class: AttributeDefaultOperations::class, paginationItemsPerPage: 10); $this->assertEquals(new ResourceMetadataCollection(AttributeDefaultOperations::class, [ new ApiResource( shortName: 'AttributeDefaultOperations', diff --git a/tests/Metadata/Resource/OperationTest.php b/tests/Metadata/Resource/OperationTest.php index 8275071552a..4125c144541 100644 --- a/tests/Metadata/Resource/OperationTest.php +++ b/tests/Metadata/Resource/OperationTest.php @@ -13,17 +13,19 @@ namespace ApiPlatform\Tests\Metadata\Resource; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\CollectionOperationInterface; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\HttpOperation; use PHPUnit\Framework\TestCase; final class OperationTest extends TestCase { public function testWithResourceTrait() { - $operation = (new Operation())->withOperation((new Operation())->withShortName('test')->withRead(false)->withCollection(true)); + $operation = (new GetCollection())->withOperation((new HttpOperation())->withShortName('test')->withRead(false)); $this->assertEquals($operation->getShortName(), 'test'); $this->assertEquals($operation->canRead(), false); - $this->assertEquals($operation->isCollection(), true); + $this->assertEquals($operation instanceof CollectionOperationInterface, true); } } diff --git a/tests/OpenApi/Factory/OpenApiFactoryTest.php b/tests/OpenApi/Factory/OpenApiFactoryTest.php index c2095e3bb8b..5b686cfd6e4 100644 --- a/tests/OpenApi/Factory/OpenApiFactoryTest.php +++ b/tests/OpenApi/Factory/OpenApiFactoryTest.php @@ -23,8 +23,8 @@ use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Link; -use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; @@ -65,14 +65,14 @@ class OpenApiFactoryTest extends TestCase public function testInvoke(): void { - $baseOperation = (new Operation())->withShortName('Dummy')->withDescription('This is a dummy')->withTypes(['http://schema.example.com/Dummy'])->withClass(Dummy::class)->withInputFormats(self::OPERATION_FORMATS['input_formats'])->withOutputFormats(self::OPERATION_FORMATS['output_formats'])->withOutput([ + $baseOperation = (new HttpOperation())->withShortName('Dummy')->withDescription('This is a dummy')->withTypes(['http://schema.example.com/Dummy'])->withClass(Dummy::class)->withInputFormats(self::OPERATION_FORMATS['input_formats'])->withOutputFormats(self::OPERATION_FORMATS['output_formats'])->withOutput([ 'class' => OutputDto::class, ])->withPaginationClientItemsPerPage(true); $dummyResource = (new ApiResource())->withOperations(new Operations([ 'getDummyItem' => (new Get())->withUriTemplate('/dummies/{id}')->withOperation($baseOperation)->withUriVariables(['id' => (new Link())->withFromClass(Dummy::class)->withIdentifiers(['id'])]), 'putDummyItem' => (new Put())->withUriTemplate('/dummies/{id}')->withOperation($baseOperation)->withUriVariables(['id' => (new Link())->withFromClass(Dummy::class)->withIdentifiers(['id'])]), 'deleteDummyItem' => (new Delete())->withUriTemplate('/dummies/{id}')->withOperation($baseOperation)->withUriVariables(['id' => (new Link())->withFromClass(Dummy::class)->withIdentifiers(['id'])]), - 'customDummyItem' => (new Operation())->withMethod(Operation::METHOD_HEAD)->withUriTemplate('/foo/{id}')->withOperation($baseOperation)->withUriVariables(['id' => (new Link())->withFromClass(Dummy::class)->withIdentifiers(['id'])])->withOpenapiContext([ + 'customDummyItem' => (new HttpOperation())->withMethod(HttpOperation::METHOD_HEAD)->withUriTemplate('/foo/{id}')->withOperation($baseOperation)->withUriVariables(['id' => (new Link())->withFromClass(Dummy::class)->withIdentifiers(['id'])])->withOpenapiContext([ 'x-visibility' => 'hide', 'description' => 'Custom description', 'parameters' => [ @@ -117,7 +117,7 @@ public function testInvoke(): void 'externalDocs' => ['url' => 'http://schema.example.com/Dummy', 'description' => 'See also'], ] ), - 'custom-http-verb' => (new Operation())->withMethod('TEST')->withOperation($baseOperation), + 'custom-http-verb' => (new HttpOperation())->withMethod('TEST')->withOperation($baseOperation), 'withRoutePrefix' => (new GetCollection())->withUriTemplate('/dummies')->withRoutePrefix('/prefix')->withOperation($baseOperation), 'formatsDummyItem' => (new Put())->withOperation($baseOperation)->withUriTemplate('/formatted/{id}')->withUriVariables(['id' => (new Link())->withFromClass(Dummy::class)->withIdentifiers(['id'])])->withInputFormats(['json' => ['application/json'], 'csv' => ['text/csv']])->withOutputFormats(['json' => ['application/json'], 'csv' => ['text/csv']]), 'getDummyCollection' => (new GetCollection())->withUriTemplate('/dummies')->withOpenApiContext([ @@ -127,10 +127,9 @@ public function testInvoke(): void ])->withOperation($baseOperation), 'postDummyCollection' => (new Post())->withUriTemplate('/dummies')->withOperation($baseOperation), // Filtered - 'filteredDummyCollection' => (new Get())->withUriTemplate('/filtered')->withCollection(true)->withFilters(['f1', 'f2', 'f3', 'f4', 'f5'])->withOperation($baseOperation), + 'filteredDummyCollection' => (new GetCollection())->withUriTemplate('/filtered')->withFilters(['f1', 'f2', 'f3', 'f4', 'f5'])->withOperation($baseOperation), // Paginated - 'paginatedDummyCollection' => (new Get())->withUriTemplate('/paginated') - ->withCollection(true) + 'paginatedDummyCollection' => (new GetCollection())->withUriTemplate('/paginated') ->withPaginationClientEnabled(true) ->withPaginationClientItemsPerPage(true) ->withPaginationItemsPerPage(20) diff --git a/tests/OpenApi/Serializer/OpenApiNormalizerTest.php b/tests/OpenApi/Serializer/OpenApiNormalizerTest.php index c2d6269ab2e..781487591a6 100644 --- a/tests/OpenApi/Serializer/OpenApiNormalizerTest.php +++ b/tests/OpenApi/Serializer/OpenApiNormalizerTest.php @@ -30,7 +30,7 @@ use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; -use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; @@ -256,7 +256,7 @@ public function testNormalize() $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name', 'description', 'dummyDate'])); $propertyNameCollectionFactoryProphecy->create('Zorro', Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['id'])); - $baseOperation = (new Operation())->withClass(Dummy::class)->withShortName('Dummy')->withDescription('This is a dummy.')->withTypes(['http://schema.example.com/Dummy']) + $baseOperation = (new HttpOperation())->withClass(Dummy::class)->withShortName('Dummy')->withDescription('This is a dummy.')->withTypes(['http://schema.example.com/Dummy']) ->withInputFormats(self::OPERATION_FORMATS['input_formats'])->withOutputFormats(self::OPERATION_FORMATS['output_formats']); $dummyMetadata = new ResourceMetadataCollection(Dummy::class, [ @@ -271,7 +271,7 @@ public function testNormalize() )), ]); - $zorroBaseOperation = (new Operation())->withClass('Zorro')->withShortName('Zorro')->withDescription('This is zorro.')->withTypes(['http://schema.example.com/Zorro']) + $zorroBaseOperation = (new HttpOperation())->withClass('Zorro')->withShortName('Zorro')->withDescription('This is zorro.')->withTypes(['http://schema.example.com/Zorro']) ->withInputFormats(self::OPERATION_FORMATS['input_formats'])->withOutputFormats(self::OPERATION_FORMATS['output_formats']); $zorroMetadata = new ResourceMetadataCollection(Dummy::class, [ diff --git a/tests/State/ChainProviderTest.php b/tests/State/ChainProviderTest.php deleted file mode 100644 index ea92c8ed9fb..00000000000 --- a/tests/State/ChainProviderTest.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Tests\State; - -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\State\ChainProvider; -use ApiPlatform\State\ProviderInterface; -use PHPUnit\Framework\TestCase; - -class ChainProviderTest extends TestCase -{ - use ProphecyTrait; - - /** - * @requires PHP 8.0 - */ - public function testChainProvider() - { - $a = $this->prophesize(ProviderInterface::class); - $a->supports('class', [], 'operationName', [])->willReturn(false); - $a->provide('class', [], 'operationName', [])->shouldNotBeCalled(); - - $b = $this->prophesize(ProviderInterface::class); - $b->supports('class', [], 'operationName', [])->willReturn(true); - $b->provide('class', [], 'operationName', [])->willReturn('value'); - - $chainProvider = new ChainProvider([ - $a->reveal(), - $b->reveal(), - ]); - - $this->assertEquals('value', $chainProvider->provide('class', [], 'operationName', [])); - $this->assertTrue($chainProvider->supports('class', [], 'operationName', [])); - } - - /** - * @requires PHP 8.0 - */ - public function testReturnValueWhenNoProvider() - { - $a = $this->prophesize(ProviderInterface::class); - $a->supports('class', ['id' => 1], 'operationName', [])->willReturn(false); - $a->supports('class', [], 'operationName', [])->willReturn(false); - $a->provide('class', ['id' => 1], 'operationName', [])->shouldNotBeCalled(); - $a->provide('class', [], 'operationName', [])->shouldNotBeCalled(); - - $chainProvider = new ChainProvider([ - $a->reveal(), - ]); - - $this->assertNull($chainProvider->provide('class', ['id' => 1], 'operationName', [])); - $this->assertEquals([], $chainProvider->provide('class', [], 'operationName', [])); - $this->assertFalse($chainProvider->supports('class', [], 'operationName', [])); - } -} diff --git a/tests/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index cd4d1c907d2..87277852c83 100644 --- a/tests/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -22,19 +22,26 @@ use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\DataTransformer\DataTransformerInitializerInterface; use ApiPlatform\DataTransformer\DataTransformerInterface; +use ApiPlatform\Doctrine\Common\State\PersistProcessor; +use ApiPlatform\Doctrine\Common\State\RemoveProcessor; use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface; +use ApiPlatform\Doctrine\Odm\State\CollectionProvider as MongoDbCollectionProvider; +use ApiPlatform\Doctrine\Odm\State\ItemProvider as MongoDbItemProvider; use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface as DoctrineQueryCollectionExtensionInterface; use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface; +use ApiPlatform\Doctrine\Orm\State\CollectionProvider; +use ApiPlatform\Doctrine\Orm\State\ItemProvider; use ApiPlatform\Elasticsearch\Extension\RequestBodySearchCollectionExtensionInterface; +use ApiPlatform\Elasticsearch\State\CollectionProvider as ElasticsearchCollectionProvider; +use ApiPlatform\Elasticsearch\State\ItemProvider as ElasticsearchItemProvider; use ApiPlatform\GraphQl\Error\ErrorHandlerInterface; use ApiPlatform\GraphQl\Resolver\MutationResolverInterface; use ApiPlatform\GraphQl\Resolver\QueryCollectionResolverInterface; use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface; use ApiPlatform\GraphQl\Type\Definition\TypeInterface as GraphQlTypeInterface; -use ApiPlatform\State\ProcessorInterface; -use ApiPlatform\State\ProviderInterface; use ApiPlatform\Symfony\Bundle\DependencyInjection\ApiPlatformExtension; +use ApiPlatform\Symfony\Messenger\Processor as MessengerProcessor; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRestrictionMetadataInterface; use ApiPlatform\Symfony\Validator\ValidationGroupsGeneratorInterface; use Doctrine\Common\Annotations\Annotation; @@ -160,18 +167,28 @@ protected function setUp(): void $this->container = new ContainerBuilder($containerParameterBag); } - private function assertContainerHas(array $services, array $aliases = [], array $tags = []) + private function assertContainerHas(array $services, array $aliases = []) { foreach ($services as $service) { - $this->assertTrue($this->container->hasDefinition($service)); + $this->assertTrue($this->container->hasDefinition($service), sprintf('Definition "%s" not found.', $service)); } foreach ($aliases as $alias) { - $this->assertTrue($this->container->hasAlias($alias)); + $this->assertContainerHasAlias($alias); } + } + + private function assertContainerHasAlias(string $alias) + { + $this->assertTrue($this->container->hasAlias($alias), sprintf('Alias "%s" not found.', $alias)); + } - foreach ($tags as $service => $tag) { - $this->assertArrayHasKey($tag, $this->container->getDefinition($service)->getTags()); + private function assertServiceHasTags($service, $tags = []) + { + $serviceTags = $this->container->getDefinition($service)->getTags(); + + foreach ($tags as $tag) { + $this->assertArrayHasKey($tag, $serviceTags, sprintf('Tag "%s" not found on the service "%s".', $tag, $service)); } } @@ -251,19 +268,17 @@ public function testCommonConfiguration(): void 'api_platform.path_segment_name_generator', ]; - $tags = [ - 'api_platform.cache.route_name_resolver' => 'cache.pool', - 'api_platform.serializer.normalizer.item' => 'serializer.normalizer', - 'api_platform.serializer_locator' => 'container.service_locator', - 'api_platform.filter_locator' => 'container.service_locator', + $this->assertContainerHas($services, $aliases); - // ramsey_uuid.xml - 'api_platform.identifier.uuid_normalizer' => 'api_platform.identifier.denormalizer', - 'api_platform.serializer.uuid_denormalizer' => 'serializer.normalizer', - 'api_platform.ramsey_uuid.uri_variables.transformer.uuid' => 'api_platform.uri_variables.transformer', - ]; + $this->assertServiceHasTags('api_platform.cache.route_name_resolver', ['cache.pool']); + $this->assertServiceHasTags('api_platform.serializer.normalizer.item', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.serializer_locator', ['container.service_locator']); + $this->assertServiceHasTags('api_platform.filter_locator', ['container.service_locator']); - $this->assertContainerHas($services, $aliases, $tags); + // ramsey_uuid.xml + $this->assertServiceHasTags('api_platform.identifier.uuid_normalizer', ['api_platform.identifier.denormalizer']); + $this->assertServiceHasTags('api_platform.serializer.uuid_denormalizer', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.ramsey_uuid.uri_variables.transformer.uuid', ['api_platform.uri_variables.transformer']); } public function testCommonConfigurationAbstractUid(): void @@ -282,14 +297,12 @@ public function testCommonConfigurationAbstractUid(): void 'api_platform.symfony.uri_variables.transformer.uuid', ]; - $tags = [ - 'api_platform.identifier.symfony_ulid_normalizer' => 'api_platform.identifier.denormalizer', - 'api_platform.identifier.symfony_uuid_normalizer' => 'api_platform.identifier.denormalizer', - 'api_platform.symfony.uri_variables.transformer.ulid' => 'api_platform.uri_variables.transformer', - 'api_platform.symfony.uri_variables.transformer.uuid' => 'api_platform.uri_variables.transformer', - ]; + $this->assertContainerHas($services, []); - $this->assertContainerHas($services, [], $tags); + $this->assertServiceHasTags('api_platform.identifier.symfony_ulid_normalizer', ['api_platform.identifier.denormalizer']); + $this->assertServiceHasTags('api_platform.identifier.symfony_uuid_normalizer', ['api_platform.identifier.denormalizer']); + $this->assertServiceHasTags('api_platform.symfony.uri_variables.transformer.ulid', ['api_platform.uri_variables.transformer']); + $this->assertServiceHasTags('api_platform.symfony.uri_variables.transformer.uuid', ['api_platform.uri_variables.transformer']); } public function dataProviderCommonConfigurationAliasNameConverter() @@ -374,23 +387,21 @@ public function testCommonConfigurationWithMetadataBackwardCompatibilityLayer() 'api_platform.cache.metadata.property', 'api_platform.openapi.factory', ]; - $tags = [ - // legacy/api.xml - 'api_platform.route_loader.legacy' => 'routing.loader', - 'api_platform.listener.request.add_format' => 'kernel.event_listener', - 'api_platform.listener.request.deserialize' => 'kernel.event_listener', - 'api_platform.listener.view.serialize' => 'kernel.event_listener', - 'api_platform.listener.view.respond' => 'kernel.event_listener', - 'api_platform.listener.exception.validation' => 'kernel.event_listener', - 'api_platform.listener.exception' => 'kernel.event_listener', - 'api_platform.listener.exception' => 'monolog.logger', - 'api_platform.identifier.integer' => 'api_platform.identifier.denormalizer', - 'api_platform.identifier.date_normalizer' => 'api_platform.identifier.denormalizer', - 'api_platform.listener.view.write.legacy' => 'kernel.event_listener', - 'api_platform.listener.request.read.legacy' => 'kernel.event_listener', - ]; - $this->assertContainerHas($services, $aliases, $tags); + $this->assertContainerHas($services, $aliases); + + // legacy/api.xml + $this->assertServiceHasTags('api_platform.route_loader.legacy', ['routing.loader']); + $this->assertServiceHasTags('api_platform.listener.request.add_format', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.request.deserialize', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.view.serialize', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.view.respond', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.exception.validation', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.exception', ['kernel.event_listener', 'monolog.logger']); + $this->assertServiceHasTags('api_platform.identifier.integer', ['api_platform.identifier.denormalizer']); + $this->assertServiceHasTags('api_platform.identifier.date_normalizer', ['api_platform.identifier.denormalizer']); + $this->assertServiceHasTags('api_platform.listener.view.write.legacy', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.request.read.legacy', ['kernel.event_listener']); } public function testCommonConfigurationWithoutMetadataBackwardCompatibilityLayer() @@ -415,13 +426,10 @@ public function testCommonConfigurationWithoutMetadataBackwardCompatibilityLayer // v3/state.xml 'api_platform.pagination.next', - 'api_platform.state_provider', - 'api_platform.state_processor', // v3/backward_compatibility.xml 'api_platform.metadata.resource.metadata_collection_factory.legacy_resource_metadata', 'api_platform.metadata.resource.metadata_collection_factory.legacy_subresource_metadata', - 'api_platform.legacy_data_provider_state', 'api_platform.listener.view.write.legacy', 'api_platform.listener.request.read.legacy', ]; @@ -442,23 +450,18 @@ public function testCommonConfigurationWithoutMetadataBackwardCompatibilityLayer // v3/state.xml 'ApiPlatform\State\Pagination\Pagination', 'api_platform.pagination', - 'ApiPlatform\State\ProviderInterface', - 'ApiPlatform\State\ProcessorInterface', ]; - $tags = [ - // v3/api.xml - 'api_platform.route_loader' => 'routing.loader', - 'api_platform.uri_variables.transformer.integer' => 'api_platform.uri_variables.transformer', - 'api_platform.uri_variables.transformer.date_time' => 'api_platform.uri_variables.transformer', + $this->assertContainerHas($services, $aliases); - // v3/backward_compatibility.xml - 'api_platform.legacy_data_provider_state' => 'api_platform.state_provider', - 'api_platform.listener.view.write.legacy' => 'kernel.event_listener', - 'api_platform.listener.request.read.legacy' => 'kernel.event_listener', - ]; + // v3/api.xml + $this->assertServiceHasTags('api_platform.route_loader', ['routing.loader']); + $this->assertServiceHasTags('api_platform.uri_variables.transformer.integer', ['api_platform.uri_variables.transformer']); + $this->assertServiceHasTags('api_platform.uri_variables.transformer.date_time', ['api_platform.uri_variables.transformer']); - $this->assertContainerHas($services, $aliases, $tags); + // v3/backward_compatibility.xml + $this->assertServiceHasTags('api_platform.listener.view.write.legacy', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.request.read.legacy', ['kernel.event_listener']); } public function testMetadataConfiguration(): void @@ -579,23 +582,21 @@ public function testMetadataConfiguration(): void 'api_platform.metadata.property.metadata_factory', ]; - $tags = [ - // legacy/metadata.xml - 'api_platform.cache.metadata.resource.legacy' => 'cache.pool', - 'api_platform.cache.metadata.property.legacy' => 'cache.pool', - 'api_platform.cache.subresource_operation_factory' => 'cache.pool', + $this->assertContainerHas($services, $aliases); - // metadata/property.xml - 'api_platform.cache.metadata.property' => 'cache.pool', + // legacy/metadata.xml + $this->assertServiceHasTags('api_platform.cache.metadata.resource.legacy', ['cache.pool']); + $this->assertServiceHasTags('api_platform.cache.metadata.property.legacy', ['cache.pool']); + $this->assertServiceHasTags('api_platform.cache.subresource_operation_factory', ['cache.pool']); - // metadata/resource.xml - 'api_platform.cache.metadata.resource_collection' => 'cache.pool', + // metadata/property.xml + $this->assertServiceHasTags('api_platform.cache.metadata.property', ['cache.pool']); - // metadata/resource_name.xml - 'api_platform.cache.metadata.resource' => 'cache.pool', - ]; + // metadata/resource.xml + $this->assertServiceHasTags('api_platform.cache.metadata.resource_collection', ['cache.pool']); - $this->assertContainerHas($services, $aliases, $tags); + // metadata/resource_name.xml + $this->assertServiceHasTags('api_platform.cache.metadata.resource', ['cache.pool']); } public function testMetadataConfigurationAnnotation() @@ -706,20 +707,18 @@ public function testSwaggerConfiguration() 'api_platform.openapi.factory', ]; - $tags = [ - // json_schema.xml - 'api_platform.json_schema.json_schema_generate_command' => 'console.command', + $this->assertContainerHas($services, $aliases); - // openapi.xml - 'api_platform.openapi.normalizer' => 'serializer.normalizer', - 'api_platform.openapi.command' => 'console.command', - 'api_platform.openapi.normalizer.api_gateway' => 'serializer.normalizer', + // json_schema.xml + $this->assertServiceHasTags('api_platform.json_schema.json_schema_generate_command', ['console.command']); - // swagger_ui.xml - 'api_platform.swagger.listener.ui' => 'kernel.event_listener', - ]; + // openapi.xml + $this->assertServiceHasTags('api_platform.openapi.normalizer', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.openapi.command', ['console.command']); + $this->assertServiceHasTags('api_platform.openapi.normalizer.api_gateway', ['serializer.normalizer']); - $this->assertContainerHas($services, $aliases, $tags); + // swagger_ui.xml + $this->assertServiceHasTags('api_platform.swagger.listener.ui', ['kernel.event_listener']); } public function testSwaggerConfigurationMetadataBackwardCompatibilityLayer() @@ -744,14 +743,12 @@ public function testSwaggerConfigurationMetadataBackwardCompatibilityLayer() 'api_platform.swagger_ui.action', ]; - $tags = [ - // legacy/swagger.xml - 'api_platform.swagger.normalizer.documentation' => 'serializer.normalizer', - 'api_platform.swagger.normalizer.api_gateway' => 'serializer.normalizer', - 'api_platform.swagger.command.swagger_command' => 'console.command', - ]; + $this->assertContainerHas($services, []); - $this->assertContainerHas($services, [], $tags); + // legacy/swagger.xml + $this->assertServiceHasTags('api_platform.swagger.normalizer.documentation', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.swagger.normalizer.api_gateway', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.swagger.command.swagger_command', ['console.command']); } public function testJsonApiConfiguration(): void @@ -779,22 +776,20 @@ public function testJsonApiConfiguration(): void 'api_platform.jsonapi.listener.request.transform_filtering_parameters', ]; - $tags = [ - // jsonapi.xml - 'api_platform.jsonapi.encoder' => 'serializer.encoder', - 'api_platform.jsonapi.normalizer.entrypoint' => 'serializer.normalizer', - 'api_platform.jsonapi.normalizer.collection' => 'serializer.normalizer', - 'api_platform.jsonapi.normalizer.item' => 'serializer.normalizer', - 'api_platform.jsonapi.normalizer.object' => 'serializer.normalizer', - 'api_platform.jsonapi.normalizer.constraint_violation_list' => 'serializer.normalizer', - 'api_platform.jsonapi.normalizer.error' => 'serializer.normalizer', - 'api_platform.jsonapi.listener.request.transform_pagination_parameters' => 'kernel.event_listener', - 'api_platform.jsonapi.listener.request.transform_sorting_parameters' => 'kernel.event_listener', - 'api_platform.jsonapi.listener.request.transform_fieldsets_parameters' => 'kernel.event_listener', - 'api_platform.jsonapi.listener.request.transform_filtering_parameters' => 'kernel.event_listener', - ]; - - $this->assertContainerHas($services, [], $tags); + $this->assertContainerHas($services, []); + + // jsonapi.xml + $this->assertServiceHasTags('api_platform.jsonapi.encoder', ['serializer.encoder']); + $this->assertServiceHasTags('api_platform.jsonapi.normalizer.entrypoint', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.jsonapi.normalizer.collection', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.jsonapi.normalizer.item', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.jsonapi.normalizer.object', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.jsonapi.normalizer.constraint_violation_list', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.jsonapi.normalizer.error', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.jsonapi.listener.request.transform_pagination_parameters', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.jsonapi.listener.request.transform_sorting_parameters', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.jsonapi.listener.request.transform_fieldsets_parameters', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.jsonapi.listener.request.transform_filtering_parameters', ['kernel.event_listener']); } public function testJsonLdHydraConfiguration(): void @@ -822,22 +817,20 @@ public function testJsonLdHydraConfiguration(): void 'api_platform.hydra.json_schema.schema_factory', ]; - $tags = [ - // jsonld.xml - 'api_platform.jsonld.normalizer.item' => 'serializer.normalizer', - 'api_platform.jsonld.normalizer.object' => 'serializer.normalizer', - 'api_platform.jsonld.encoder' => 'serializer.encoder', + $this->assertContainerHas($services, []); - // hydra.xml - 'api_platform.hydra.normalizer.documentation' => 'serializer.normalizer', - 'api_platform.hydra.listener.response.add_link_header' => 'kernel.event_listener', - 'api_platform.hydra.normalizer.constraint_violation_list' => 'serializer.normalizer', - 'api_platform.hydra.normalizer.entrypoint' => 'serializer.normalizer', - 'api_platform.hydra.normalizer.error' => 'serializer.normalizer', - 'api_platform.hydra.normalizer.collection' => 'serializer.normalizer', - ]; + // jsonld.xml + $this->assertServiceHasTags('api_platform.jsonld.normalizer.item', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.jsonld.normalizer.object', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.jsonld.encoder', ['serializer.encoder']); - $this->assertContainerHas($services, [], $tags); + // hydra.xml + $this->assertServiceHasTags('api_platform.hydra.normalizer.documentation', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.hydra.listener.response.add_link_header', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.hydra.normalizer.constraint_violation_list', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.hydra.normalizer.entrypoint', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.hydra.normalizer.error', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.hydra.normalizer.collection', ['serializer.normalizer']); } public function testJsonHalConfiguration(): void @@ -855,16 +848,14 @@ public function testJsonHalConfiguration(): void 'api_platform.hal.json_schema.schema_factory', ]; - $tags = [ - // hal.xml - 'api_platform.hal.encoder' => 'serializer.encoder', - 'api_platform.hal.normalizer.entrypoint' => 'serializer.normalizer', - 'api_platform.hal.normalizer.collection' => 'serializer.normalizer', - 'api_platform.hal.normalizer.item' => 'serializer.normalizer', - 'api_platform.hal.normalizer.object' => 'serializer.normalizer', - ]; + $this->assertContainerHas($services, []); - $this->assertContainerHas($services, [], $tags); + // hal.xml + $this->assertServiceHasTags('api_platform.hal.encoder', ['serializer.encoder']); + $this->assertServiceHasTags('api_platform.hal.normalizer.entrypoint', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.hal.normalizer.collection', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.hal.normalizer.item', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.hal.normalizer.object', ['serializer.normalizer']); } public function testJsonProblemConfiguration(): void @@ -879,14 +870,12 @@ public function testJsonProblemConfiguration(): void 'api_platform.problem.normalizer.error', ]; - $tags = [ - // problem.xml - 'api_platform.problem.encoder' => 'serializer.encoder', - 'api_platform.problem.normalizer.constraint_violation_list' => 'serializer.normalizer', - 'api_platform.problem.normalizer.error' => 'serializer.normalizer', - ]; + $this->assertContainerHas($services, []); - $this->assertContainerHas($services, [], $tags); + // problem.xml + $this->assertServiceHasTags('api_platform.problem.encoder', ['serializer.encoder']); + $this->assertServiceHasTags('api_platform.problem.normalizer.constraint_violation_list', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.problem.normalizer.error', ['serializer.normalizer']); } public function testGraphQlConfiguration(): void @@ -947,27 +936,25 @@ public function testGraphQlConfiguration(): void 'ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface', ]; - $tags = [ - // graphql.xml - 'api_platform.graphql.query_resolver_locator' => 'container.service_locator', - 'api_platform.graphql.mutation_resolver_locator' => 'container.service_locator', - 'api_platform.graphql.iterable_type' => 'api_platform.graphql.type', - 'api_platform.graphql.upload_type' => 'api_platform.graphql.type', - 'api_platform.graphql.type_locator' => 'container.service_locator', - 'api_platform.graphql.fields_builder_locator' => 'container.service_locator', - 'api_platform.graphql.cache.subscription' => 'cache.pool', - 'api_platform.graphql.command.export_command' => 'console.command', - - // v3/graphql.xml - 'api_platform.graphql.normalizer.item' => 'serializer.normalizer', - 'api_platform.graphql.normalizer.object' => 'serializer.normalizer', - 'api_platform.graphql.normalizer.error' => 'serializer.normalizer', - 'api_platform.graphql.normalizer.validation_exception' => 'serializer.normalizer', - 'api_platform.graphql.normalizer.http_exception' => 'serializer.normalizer', - 'api_platform.graphql.normalizer.runtime_exception' => 'serializer.normalizer', - ]; - - $this->assertContainerHas($services, $aliases, $tags); + $this->assertContainerHas($services, $aliases); + + // graphql.xml + $this->assertServiceHasTags('api_platform.graphql.query_resolver_locator', ['container.service_locator']); + $this->assertServiceHasTags('api_platform.graphql.mutation_resolver_locator', ['container.service_locator']); + $this->assertServiceHasTags('api_platform.graphql.iterable_type', ['api_platform.graphql.type']); + $this->assertServiceHasTags('api_platform.graphql.upload_type', ['api_platform.graphql.type']); + $this->assertServiceHasTags('api_platform.graphql.type_locator', ['container.service_locator']); + $this->assertServiceHasTags('api_platform.graphql.fields_builder_locator', ['container.service_locator']); + $this->assertServiceHasTags('api_platform.graphql.cache.subscription', ['cache.pool']); + $this->assertServiceHasTags('api_platform.graphql.command.export_command', ['console.command']); + + // v3/graphql.xml + $this->assertServiceHasTags('api_platform.graphql.normalizer.item', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.graphql.normalizer.object', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.graphql.normalizer.error', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.graphql.normalizer.validation_exception', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.graphql.normalizer.http_exception', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.graphql.normalizer.runtime_exception', ['serializer.normalizer']); } public function testGraphQlConfigurationMetadataBackwardCompatibilityLayer(): void @@ -1019,25 +1006,23 @@ public function testGraphQlConfigurationMetadataBackwardCompatibilityLayer(): vo 'api_platform.graphql.command.export_command', ]; - $tags = [ - // legacy/graphql.xml - 'api_platform.graphql.query_resolver_locator' => 'container.service_locator', - 'api_platform.graphql.mutation_resolver_locator' => 'container.service_locator', - 'api_platform.graphql.iterable_type' => 'api_platform.graphql.type', - 'api_platform.graphql.upload_type' => 'api_platform.graphql.type', - 'api_platform.graphql.type_locator' => 'container.service_locator', - 'api_platform.graphql.fields_builder_locator' => 'container.service_locator', - 'api_platform.graphql.normalizer.item' => 'serializer.normalizer', - 'api_platform.graphql.normalizer.object' => 'serializer.normalizer', - 'api_platform.graphql.normalizer.error' => 'serializer.normalizer', - 'api_platform.graphql.normalizer.validation_exception' => 'serializer.normalizer', - 'api_platform.graphql.normalizer.http_exception' => 'serializer.normalizer', - 'api_platform.graphql.normalizer.runtime_exception' => 'serializer.normalizer', - 'api_platform.graphql.cache.subscription' => 'cache.pool', - 'api_platform.graphql.command.export_command' => 'console.command', - ]; - - $this->assertContainerHas($services, [], $tags); + $this->assertContainerHas($services, []); + + // legacy/graphql.xml + $this->assertServiceHasTags('api_platform.graphql.query_resolver_locator', ['container.service_locator']); + $this->assertServiceHasTags('api_platform.graphql.mutation_resolver_locator', ['container.service_locator']); + $this->assertServiceHasTags('api_platform.graphql.iterable_type', ['api_platform.graphql.type']); + $this->assertServiceHasTags('api_platform.graphql.upload_type', ['api_platform.graphql.type']); + $this->assertServiceHasTags('api_platform.graphql.type_locator', ['container.service_locator']); + $this->assertServiceHasTags('api_platform.graphql.fields_builder_locator', ['container.service_locator']); + $this->assertServiceHasTags('api_platform.graphql.normalizer.item', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.graphql.normalizer.object', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.graphql.normalizer.error', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.graphql.normalizer.validation_exception', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.graphql.normalizer.http_exception', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.graphql.normalizer.runtime_exception', ['serializer.normalizer']); + $this->assertServiceHasTags('api_platform.graphql.cache.subscription', ['cache.pool']); + $this->assertServiceHasTags('api_platform.graphql.command.export_command', ['console.command']); } public function testLegacyBundlesConfigurationFosUserBundle(): void @@ -1054,12 +1039,10 @@ public function testLegacyBundlesConfigurationFosUserBundle(): void 'api_platform.fos_user.event_listener', ]; - $tags = [ - // fos_user.xml - 'api_platform.fos_user.event_listener' => 'kernel.event_listener', - ]; + $this->assertContainerHas($services, []); - $this->assertContainerHas($services, [], $tags); + // fos_user.xml + $this->assertServiceHasTags('api_platform.fos_user.event_listener', ['kernel.event_listener']); } public function testLegacyBundlesConfigurationNelmioApiDocBundle(): void @@ -1078,13 +1061,11 @@ public function testLegacyBundlesConfigurationNelmioApiDocBundle(): void 'api_platform.nelmio_api_doc.parser', ]; - $tags = [ - // nelmio_api_doc.xml - 'api_platform.nelmio_api_doc.annotations_provider' => 'nelmio_api_doc.extractor.annotations_provider', - 'api_platform.nelmio_api_doc.parser' => 'nelmio_api_doc.extractor.parser', - ]; + $this->assertContainerHas($services, []); - $this->assertContainerHas($services, [], $tags); + // nelmio_api_doc.xml + $this->assertServiceHasTags('api_platform.nelmio_api_doc.annotations_provider', ['nelmio_api_doc.extractor.annotations_provider']); + $this->assertServiceHasTags('api_platform.nelmio_api_doc.parser', ['nelmio_api_doc.extractor.parser']); } public function testDoctrineOrmConfiguration(): void @@ -1096,9 +1077,10 @@ public function testDoctrineOrmConfiguration(): void $services = [ // doctrine_orm.xml 'api_platform.doctrine.metadata_factory', - 'api_platform.doctrine.orm.state.processor', - 'api_platform.doctrine.orm.state.collection_provider', - 'api_platform.doctrine.orm.state.item_provider', + RemoveProcessor::class, + PersistProcessor::class, + CollectionProvider::class, + ItemProvider::class, 'api_platform.doctrine.orm.search_filter', 'api_platform.doctrine.orm.order_filter', 'api_platform.doctrine.orm.range_filter', @@ -1134,26 +1116,24 @@ public function testDoctrineOrmConfiguration(): void 'ApiPlatform\Doctrine\Orm\Extension\OrderExtension', ]; - $tags = [ - // doctrine_orm.xml - 'api_platform.doctrine.orm.state.processor' => 'api_platform.state_processor', - 'api_platform.doctrine.orm.state.collection_provider' => 'api_platform.state_provider', - 'api_platform.doctrine.orm.state.item_provider' => 'api_platform.state_provider', - 'api_platform.doctrine.orm.query_extension.eager_loading' => 'api_platform.doctrine.orm.query_extension.item', - 'api_platform.doctrine.orm.query_extension.eager_loading' => 'api_platform.doctrine.orm.query_extension.collection', - 'api_platform.doctrine.orm.query_extension.filter' => 'api_platform.doctrine.orm.query_extension.collection', - 'api_platform.doctrine.orm.query_extension.filter_eager_loading' => 'api_platform.doctrine.orm.query_extension.collection', - 'api_platform.doctrine.orm.query_extension.pagination' => 'api_platform.doctrine.orm.query_extension.collection', - 'api_platform.doctrine.orm.query_extension.order' => 'api_platform.doctrine.orm.query_extension.collection', - - // legacy/doctrine_orm.xml - 'api_platform.doctrine.orm.data_persister' => 'api_platform.data_persister', - 'api_platform.doctrine.orm.default.collection_data_provider' => 'api_platform.collection_data_provider', - 'api_platform.doctrine.orm.default.item_data_provider' => 'api_platform.item_data_provider', - 'api_platform.doctrine.orm.default.subresource_data_provider' => 'api_platform.subresource_data_provider', - ]; - - $this->assertContainerHas($services, $aliases, $tags); + $this->assertContainerHas($services, $aliases); + + // doctrine_orm.xml + $this->assertServiceHasTags(RemoveProcessor::class, ['api_platform.state_processor']); + $this->assertServiceHasTags(PersistProcessor::class, ['api_platform.state_processor']); + $this->assertServiceHasTags(CollectionProvider::class, ['api_platform.state_provider']); + $this->assertServiceHasTags(ItemProvider::class, ['api_platform.state_provider']); + $this->assertServiceHasTags('api_platform.doctrine.orm.query_extension.eager_loading', ['api_platform.doctrine.orm.query_extension.item', 'api_platform.doctrine.orm.query_extension.collection']); + $this->assertServiceHasTags('api_platform.doctrine.orm.query_extension.filter', ['api_platform.doctrine.orm.query_extension.collection']); + $this->assertServiceHasTags('api_platform.doctrine.orm.query_extension.filter_eager_loading', ['api_platform.doctrine.orm.query_extension.collection']); + $this->assertServiceHasTags('api_platform.doctrine.orm.query_extension.pagination', ['api_platform.doctrine.orm.query_extension.collection']); + $this->assertServiceHasTags('api_platform.doctrine.orm.query_extension.order', ['api_platform.doctrine.orm.query_extension.collection']); + + // legacy/doctrine_orm.xml + $this->assertServiceHasTags('api_platform.doctrine.orm.data_persister', ['api_platform.data_persister']); + $this->assertServiceHasTags('api_platform.doctrine.orm.default.collection_data_provider', ['api_platform.collection_data_provider']); + $this->assertServiceHasTags('api_platform.doctrine.orm.default.item_data_provider', ['api_platform.item_data_provider']); + $this->assertServiceHasTags('api_platform.doctrine.orm.default.subresource_data_provider', ['api_platform.subresource_data_provider']); } public function testDoctrineMongoDbOdmConfiguration(): void @@ -1166,9 +1146,10 @@ public function testDoctrineMongoDbOdmConfiguration(): void // doctrine_mongo_odm.xml 'api_platform.doctrine_mongodb.odm.default_document_manager.property_info_extractor', 'api_platform.doctrine.metadata_factory', - 'api_platform.doctrine_mongodb.odm.state.processor', - 'api_platform.doctrine.odm.state.collection_provider', - 'api_platform.doctrine.odm.state.item_provider', + RemoveProcessor::class, + PersistProcessor::class, + MongoDbCollectionProvider::class, + MongoDbItemProvider::class, 'api_platform.doctrine_mongodb.odm.search_filter', 'api_platform.doctrine_mongodb.odm.boolean_filter', 'api_platform.doctrine_mongodb.odm.date_filter', @@ -1206,25 +1187,23 @@ public function testDoctrineMongoDbOdmConfiguration(): void 'ApiPlatform\Doctrine\Odm\Extension\OrderExtension', ]; - $tags = [ - // doctrine_mongo_odm.xml - 'api_platform.doctrine_mongodb.odm.default_document_manager.property_info_extractor' => 'property_info.list_extractor', - 'api_platform.doctrine_mongodb.odm.default_document_manager.property_info_extractor' => 'property_info.type_extractor', - 'api_platform.doctrine_mongodb.odm.state.processor' => 'api_platform.state_processor', - 'api_platform.doctrine.odm.state.collection_provider' => 'api_platform.state_provider', - 'api_platform.doctrine.odm.state.item_provider' => 'api_platform.state_provider', - 'api_platform.doctrine_mongodb.odm.aggregation_extension.filter' => 'api_platform.doctrine_mongodb.odm.aggregation_extension.collection', - 'api_platform.doctrine_mongodb.odm.aggregation_extension.pagination' => 'api_platform.doctrine_mongodb.odm.aggregation_extension.collection', - 'api_platform.doctrine_mongodb.odm.aggregation_extension.order' => 'api_platform.doctrine_mongodb.odm.aggregation_extension.collection', + $this->assertContainerHas($services, $aliases); - // legacy/doctrine_odm.xml - 'api_platform.doctrine_mongodb.odm.data_persister' => 'api_platform.data_persister', - 'api_platform.doctrine_mongodb.odm.default.collection_data_provider' => 'api_platform.collection_data_provider', - 'api_platform.doctrine_mongodb.odm.default.item_data_provider' => 'api_platform.item_data_provider', - 'api_platform.doctrine_mongodb.odm.default.subresource_data_provider' => 'api_platform.subresource_data_provider', - ]; + $this->assertServiceHasTags(RemoveProcessor::class, ['api_platform.state_processor']); + $this->assertServiceHasTags(PersistProcessor::class, ['api_platform.state_processor']); + $this->assertServiceHasTags(MongoDbCollectionProvider::class, ['api_platform.state_provider']); + $this->assertServiceHasTags(MongoDbItemProvider::class, ['api_platform.state_provider']); + // doctrine_mongo_odm.xml + $this->assertServiceHasTags('api_platform.doctrine_mongodb.odm.default_document_manager.property_info_extractor', ['property_info.list_extractor', 'property_info.type_extractor']); + $this->assertServiceHasTags('api_platform.doctrine_mongodb.odm.aggregation_extension.filter', ['api_platform.doctrine_mongodb.odm.aggregation_extension.collection']); + $this->assertServiceHasTags('api_platform.doctrine_mongodb.odm.aggregation_extension.pagination', ['api_platform.doctrine_mongodb.odm.aggregation_extension.collection']); + $this->assertServiceHasTags('api_platform.doctrine_mongodb.odm.aggregation_extension.order', ['api_platform.doctrine_mongodb.odm.aggregation_extension.collection']); - $this->assertContainerHas($services, $aliases, $tags); + // legacy/doctrine_odm.xml + $this->assertServiceHasTags('api_platform.doctrine_mongodb.odm.data_persister', ['api_platform.data_persister']); + $this->assertServiceHasTags('api_platform.doctrine_mongodb.odm.default.collection_data_provider', ['api_platform.collection_data_provider']); + $this->assertServiceHasTags('api_platform.doctrine_mongodb.odm.default.item_data_provider', ['api_platform.item_data_provider']); + $this->assertServiceHasTags('api_platform.doctrine_mongodb.odm.default.subresource_data_provider', ['api_platform.subresource_data_provider']); } public function testHttpCacheConfiguration(): void @@ -1247,18 +1226,18 @@ public function testHttpCacheConfiguration(): void 'api_platform.http_cache.listener.response.add_tags', ]; - $tags = [ - // http_cache.xml - 'api_platform.http_cache.listener.response.configure' => 'kernel.event_listener', + $this->assertContainerHas($services); - // doctrine_orm_http_cache_purger.xml - 'api_platform.doctrine.listener.http_cache.purge' => 'doctrine.event_listener', + // http_cache.xml + $this->assertServiceHasTags('api_platform.http_cache.listener.response.configure', ['kernel.event_listener']); - // http_cache_tags.xml - 'api_platform.http_cache.listener.response.add_tags' => 'kernel.event_listener', - ]; + // doctrine_orm_http_cache_purger.xml + $this->assertServiceHasTags('api_platform.doctrine.listener.http_cache.purge', ['doctrine.event_listener']); + + // http_cache_tags.xml + $this->assertServiceHasTags('api_platform.http_cache.listener.response.add_tags', ['kernel.event_listener']); - $this->assertTrue($this->container->hasAlias('api_platform.http_cache.purger.varnish')); + $this->assertContainerHasAlias('api_platform.http_cache.purger.varnish'); $this->assertSame([ ['event' => 'preUpdate'], @@ -1305,28 +1284,26 @@ public function testValidatorConfiguration(): void 'ApiPlatform\Validator\ValidatorInterface', ]; - $tags = [ - // metadata/validator.xml - 'api_platform.metadata.property_schema.choice_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.collection_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.count_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.greater_than_or_equal_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.greater_than_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.length_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.less_than_or_equal_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.less_than_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.one_of_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.range_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.regex_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.format_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.unique_restriction' => 'api_platform.metadata.property_schema_restriction', - - // symfony/validator.xml - 'api_platform.listener.view.validate' => 'kernel.event_listener', - 'api_platform.listener.view.validate_query_parameters' => 'kernel.event_listener', - ]; - - $this->assertContainerHas($services, $aliases, $tags); + $this->assertContainerHas($services, $aliases); + + // metadata/validator.xml + $this->assertServiceHasTags('api_platform.metadata.property_schema.choice_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.collection_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.count_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.greater_than_or_equal_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.greater_than_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.length_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.less_than_or_equal_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.less_than_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.one_of_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.range_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.regex_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.format_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.unique_restriction', ['api_platform.metadata.property_schema_restriction']); + + // symfony/validator.xml + $this->assertServiceHasTags('api_platform.listener.view.validate', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.view.validate_query_parameters', ['kernel.event_listener']); } public function testValidatorConfigurationMetadataBackwardCompatibilityLayer(): void @@ -1367,26 +1344,24 @@ public function testValidatorConfigurationMetadataBackwardCompatibilityLayer(): 'ApiPlatform\Core\Validator\ValidatorInterface', ]; - $tags = [ - // legacy/validator.xml - 'api_platform.metadata.property_schema.choice_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.collection_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.count_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.greater_than_or_equal_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.greater_than_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.length_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.less_than_or_equal_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.less_than_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.one_of_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.range_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.regex_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.format_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.metadata.property_schema.unique_restriction' => 'api_platform.metadata.property_schema_restriction', - 'api_platform.listener.view.validate' => 'kernel.event_listener', - 'api_platform.listener.view.validate_query_parameters' => 'kernel.event_listener', - ]; - - $this->assertContainerHas($services, $aliases, $tags); + $this->assertContainerHas($services, $aliases); + + // legacy/validator.xml + $this->assertServiceHasTags('api_platform.metadata.property_schema.choice_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.collection_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.count_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.greater_than_or_equal_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.greater_than_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.length_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.less_than_or_equal_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.less_than_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.one_of_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.range_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.regex_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.format_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.metadata.property_schema.unique_restriction', ['api_platform.metadata.property_schema_restriction']); + $this->assertServiceHasTags('api_platform.listener.view.validate', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.view.validate_query_parameters', ['kernel.event_listener']); } public function testDataCollectorConfiguration(): void @@ -1406,18 +1381,15 @@ public function testDataCollectorConfiguration(): void // v3/debug.xml 'debug.api_platform.debug_resource.command', - 'debug.api_platform.processor', ]; - $tags = [ - // v3/data_collector.xml - 'api_platform.data_collector.request' => 'data_collector', + $this->assertContainerHas($services, []); - // v3/debug.xml - 'debug.api_platform.debug_resource.command' => 'console.command', - ]; + // v3/data_collector.xml + $this->assertServiceHasTags('api_platform.data_collector.request', ['data_collector']); - $this->assertContainerHas($services, [], $tags); + // v3/debug.xml + $this->assertServiceHasTags('debug.api_platform.debug_resource.command', ['console.command']); } public function testDataCollectorConfigurationMetadataBackwardCompatibilityLayer(): void @@ -1439,12 +1411,10 @@ public function testDataCollectorConfigurationMetadataBackwardCompatibilityLayer 'debug.api_platform.data_persister', ]; - $tags = [ - // legacy/data_collector.xml - 'api_platform.data_collector.request' => 'data_collector', - ]; + $this->assertContainerHas($services, []); - $this->assertContainerHas($services, [], $tags); + // legacy/data_collector.xml + $this->assertServiceHasTags('api_platform.data_collector.request', ['data_collector']); } public function testMercureConfiguration(): void @@ -1470,18 +1440,16 @@ public function testMercureConfiguration(): void 'api_platform.graphql.subscription.mercure_iri_generator', ]; - $tags = [ - // mercure.xml - 'api_platform.mercure.listener.response.add_link_header' => 'kernel.event_listener', + $this->assertContainerHas($services, []); - // v3/doctrine_orm_mercure_publisher - 'api_platform.doctrine.orm.listener.mercure.publish' => 'doctrine.event_listener', + // mercure.xml + $this->assertServiceHasTags('api_platform.mercure.listener.response.add_link_header', ['kernel.event_listener']); - // v3/doctrine_odm_mercure_publisher.xml - 'api_platform.doctrine_mongodb.odm.listener.mercure.publish' => 'doctrine_mongodb.odm.event_listener', - ]; + // v3/doctrine_orm_mercure_publisher + $this->assertServiceHasTags('api_platform.doctrine.orm.listener.mercure.publish', ['doctrine.event_listener']); - $this->assertContainerHas($services, [], $tags); + // v3/doctrine_odm_mercure_publisher.xml + $this->assertServiceHasTags('api_platform.doctrine_mongodb.odm.listener.mercure.publish', ['doctrine_mongodb.odm.event_listener']); $this->assertSame([ ['event' => 'onFlush'], @@ -1514,14 +1482,13 @@ public function testMercureConfigurationMetadataBackwardCompatibilityLayer(): vo 'api_platform.graphql.subscription.mercure_iri_generator', ]; - $tags = [ - // legacy/doctrine_orm_mercure_publisher.xml - 'api_platform.doctrine.orm.listener.mercure.publish' => 'doctrine.event_listener', + $this->assertContainerHas($services, []); - // legacy/doctrine_odm_mercure_publisher.xml - 'api_platform.doctrine_mongodb.odm.listener.mercure.publish' => 'doctrine_mongodb.odm.event_listener', - ]; - $this->assertContainerHas($services, [], $tags); + // legacy/doctrine_orm_mercure_publisher.xml + $this->assertServiceHasTags('api_platform.doctrine.orm.listener.mercure.publish', ['doctrine.event_listener']); + + // legacy/doctrine_odm_mercure_publisher.xml + $this->assertServiceHasTags('api_platform.doctrine_mongodb.odm.listener.mercure.publish', ['doctrine_mongodb.odm.event_listener']); $this->assertSame([ ['event' => 'onFlush'], @@ -1542,8 +1509,8 @@ public function testMessengerConfiguration(): void $services = [ // messenger.xml 'api_platform.messenger.data_persister', - 'api_platform.messenger.processor', 'api_platform.messenger.data_transformer', + MessengerProcessor::class, ]; $aliases = [ @@ -1551,14 +1518,12 @@ public function testMessengerConfiguration(): void 'api_platform.message_bus', ]; - $tags = [ - // messenger.xml - 'api_platform.messenger.data_persister' => 'api_platform.data_persister', - 'api_platform.messenger.processor' => 'api_platform.state_processor', - 'api_platform.messenger.data_transformer' => 'api_platform.data_transformer', - ]; + $this->assertContainerHas($services, $aliases); - $this->assertContainerHas($services, $aliases, $tags); + // messenger.xml + $this->assertServiceHasTags('api_platform.messenger.data_persister', ['api_platform.data_persister']); + $this->assertServiceHasTags('api_platform.messenger.data_transformer', ['api_platform.data_transformer']); + $this->assertServiceHasTags(MessengerProcessor::class, ['api_platform.state_processor']); } public function testElasticsearchConfiguration(): void @@ -1569,6 +1534,8 @@ public function testElasticsearchConfiguration(): void $services = [ // elasticsearch.xml + ElasticsearchItemProvider::class, + ElasticsearchCollectionProvider::class, 'api_platform.elasticsearch.client', 'api_platform.elasticsearch.metadata.resource.metadata_factory.operation', 'api_platform.elasticsearch.cache.metadata.document', @@ -1580,9 +1547,7 @@ public function testElasticsearchConfiguration(): void 'api_platform.elasticsearch.name_converter.inner_fields', 'api_platform.elasticsearch.normalizer.item', 'api_platform.elasticsearch.normalizer.document', - 'api_platform.elasticsearch.state.item_provider', 'api_platform.elasticsearch.item_data_provider', - 'api_platform.elasticsearch.state.collection_provider', 'api_platform.elasticsearch.collection_data_provider', 'api_platform.elasticsearch.request_body_search_extension.filter', 'api_platform.elasticsearch.request_body_search_extension.constant_score_filter', @@ -1604,20 +1569,18 @@ public function testElasticsearchConfiguration(): void 'ApiPlatform\Elasticsearch\Filter\OrderFilter', ]; - $tags = [ - // elasticsearch.xml - 'api_platform.elasticsearch.cache.metadata.document' => 'cache.pool', - 'api_platform.elasticsearch.normalizer.document' => 'serializer.normalizer', - 'api_platform.elasticsearch.state.item_provider' => 'api_platform.state_provider', - 'api_platform.elasticsearch.item_data_provider' => 'api_platform.item_data_provider', - 'api_platform.elasticsearch.state.collection_provider' => 'api_platform.state_provider', - 'api_platform.elasticsearch.collection_data_provider' => 'api_platform.collection_data_provider', - 'api_platform.elasticsearch.request_body_search_extension.constant_score_filter' => 'api_platform.elasticsearch.request_body_search_extension.collection', - 'api_platform.elasticsearch.request_body_search_extension.sort_filter' => 'api_platform.elasticsearch.request_body_search_extension.collection', - 'api_platform.elasticsearch.request_body_search_extension.sort' => 'api_platform.elasticsearch.request_body_search_extension.collection', - ]; + $this->assertContainerHas($services, $aliases); - $this->assertContainerHas($services, $aliases, $tags); + // elasticsearch.xml + $this->assertServiceHasTags('api_platform.elasticsearch.cache.metadata.document', ['cache.pool']); + $this->assertServiceHasTags('api_platform.elasticsearch.normalizer.document', ['serializer.normalizer']); + $this->assertServiceHasTags(ElasticsearchItemProvider::class, ['api_platform.state_provider']); + $this->assertServiceHasTags(ElasticsearchCollectionProvider::class, ['api_platform.state_provider']); + $this->assertServiceHasTags('api_platform.elasticsearch.item_data_provider', ['api_platform.item_data_provider']); + $this->assertServiceHasTags('api_platform.elasticsearch.collection_data_provider', ['api_platform.collection_data_provider']); + $this->assertServiceHasTags('api_platform.elasticsearch.request_body_search_extension.constant_score_filter', ['api_platform.elasticsearch.request_body_search_extension.collection']); + $this->assertServiceHasTags('api_platform.elasticsearch.request_body_search_extension.sort_filter', ['api_platform.elasticsearch.request_body_search_extension.collection']); + $this->assertServiceHasTags('api_platform.elasticsearch.request_body_search_extension.sort', ['api_platform.elasticsearch.request_body_search_extension.collection']); $autoconfiguredInstances = $this->container->getAutoconfiguredInstanceof(); $this->assertArrayHasKey(RequestBodySearchCollectionExtensionInterface::class, $autoconfiguredInstances); @@ -1642,13 +1605,11 @@ public function testSecurityConfiguration(): void 'ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface', ]; - $tags = [ - // security.xml - 'api_platform.security.listener.request.deny_access' => 'kernel.event_listener', - 'api_platform.security.expression_language_provider' => 'security.expression_language_provider', - ]; + $this->assertContainerHas($services, $aliases); - $this->assertContainerHas($services, $aliases, $tags); + // security.xml + $this->assertServiceHasTags('api_platform.security.listener.request.deny_access', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.security.expression_language_provider', ['security.expression_language_provider']); $this->assertSame([ ['event' => 'kernel.request', 'method' => 'onSecurity', 'priority' => 3], @@ -1669,13 +1630,11 @@ public function testMakerConfiguration(): void 'api_platform.maker.command.data_persister', ]; - $tags = [ - // maker.xml - 'api_platform.maker.command.data_provider' => 'maker.command', - 'api_platform.maker.command.data_persister' => 'maker.command', - ]; + $this->assertContainerHas($services, []); - $this->assertContainerHas($services, [], $tags); + // maker.xml + $this->assertServiceHasTags('api_platform.maker.command.data_provider', ['maker.command']); + $this->assertServiceHasTags('api_platform.maker.command.data_persister', ['maker.command']); } public function testArgumentResolverConfiguration(): void @@ -1688,12 +1647,10 @@ public function testArgumentResolverConfiguration(): void 'api_platform.argument_resolver.payload', ]; - $tags = [ - // argument_resolver.xml - 'api_platform.argument_resolver.payload' => 'controller.argument_value_resolver', - ]; + $this->assertContainerHas($services, []); - $this->assertContainerHas($services, [], $tags); + // argument_resolver.xml + $this->assertServiceHasTags('api_platform.argument_resolver.payload', ['controller.argument_value_resolver']); } public function testLegacyServices(): void @@ -1725,24 +1682,22 @@ public function testLegacyServices(): void 'ApiPlatform\Core\Api\IdentifiersExtractorInterface', ]; - $tags = [ - // legacy/identifiers.xml - 'api_platform.cache.identifiers_extractor' => 'cache.pool', + $this->assertContainerHas($services, $aliases); - // symfony.xml - 'api_platform.listener.request.add_format' => 'kernel.event_listener', - 'api_platform.listener.request.read' => 'kernel.event_listener', - 'api_platform.listener.view.write' => 'kernel.event_listener', - 'api_platform.listener.request.deserialize' => 'kernel.event_listener', - 'api_platform.listener.view.serialize' => 'kernel.event_listener', - 'api_platform.listener.view.respond' => 'kernel.event_listener', - 'api_platform.listener.exception.validation' => 'kernel.event_listener', - 'api_platform.listener.exception' => 'kernel.event_listener', - 'api_platform.listener.exception' => 'monolog.logger', - 'api_platform.cache_warmer.cache_pool_clearer' => 'kernel.cache_warmer', - ]; + // legacy/identifiers.xml + $this->assertServiceHasTags('api_platform.cache.identifiers_extractor', ['cache.pool']); - $this->assertContainerHas($services, $aliases, $tags); + // symfony.xml + $this->assertServiceHasTags('api_platform.listener.request.add_format', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.request.read', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.view.write', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.request.deserialize', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.view.serialize', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.view.respond', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.exception.validation', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.exception', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.exception', ['monolog.logger']); + $this->assertServiceHasTags('api_platform.cache_warmer.cache_pool_clearer', ['kernel.cache_warmer']); } public function testLegacyServicesMetadataBackwardCompatibilityLayer(): void @@ -1782,23 +1737,21 @@ public function testLegacyServicesMetadataBackwardCompatibilityLayer(): void 'api_platform.operation_path_resolver.legacy', ]; - $tags = [ - // legacy/api.xml - 'api_platform.route_loader.legacy' => 'routing.loader', - 'api_platform.listener.request.add_format' => 'kernel.event_listener', - 'api_platform.listener.request.deserialize' => 'kernel.event_listener', - 'api_platform.listener.view.serialize' => 'kernel.event_listener', - 'api_platform.listener.view.respond' => 'kernel.event_listener', - 'api_platform.listener.exception.validation' => 'kernel.event_listener', - 'api_platform.listener.exception' => 'kernel.event_listener', - 'api_platform.listener.exception' => 'monolog.logger', - 'api_platform.identifier.integer' => 'api_platform.identifier.denormalizer', - 'api_platform.identifier.date_normalizer' => 'api_platform.identifier.denormalizer', - 'api_platform.listener.view.write.legacy' => 'kernel.event_listener', - 'api_platform.listener.request.read.legacy' => 'kernel.event_listener', - ]; - - $this->assertContainerHas($services, $aliases, $tags); + $this->assertContainerHas($services, $aliases); + + // legacy/api.xml + $this->assertServiceHasTags('api_platform.route_loader.legacy', ['routing.loader']); + $this->assertServiceHasTags('api_platform.listener.request.add_format', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.request.deserialize', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.view.serialize', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.view.respond', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.exception.validation', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.exception', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.exception', ['monolog.logger']); + $this->assertServiceHasTags('api_platform.identifier.integer', ['api_platform.identifier.denormalizer']); + $this->assertServiceHasTags('api_platform.identifier.date_normalizer', ['api_platform.identifier.denormalizer']); + $this->assertServiceHasTags('api_platform.listener.view.write.legacy', ['kernel.event_listener']); + $this->assertServiceHasTags('api_platform.listener.request.read.legacy', ['kernel.event_listener']); } public function testRectorConfiguration(): void @@ -1813,11 +1766,8 @@ public function testRectorConfiguration(): void 'api_platform.upgrade_resource.command', ]; - $tags = [ - 'api_platform.upgrade_resource.command' => 'console.command', - ]; - - $this->assertContainerHas($services, [], $tags); + $this->assertContainerHas($services, []); + $this->assertServiceHasTags('api_platform.upgrade_resource.command', ['console.command']); } public function testAutoConfigurableInterfaces(): void @@ -1827,8 +1777,6 @@ public function testAutoConfigurableInterfaces(): void $interfaces = [ FilterInterface::class => 'api_platform.filter', - ProviderInterface::class => 'api_platform.state_provider', - ProcessorInterface::class => 'api_platform.state_processor', DataPersisterInterface::class => 'api_platform.data_persister', ItemDataProviderInterface::class => 'api_platform.item_data_provider', CollectionDataProviderInterface::class => 'api_platform.collection_data_provider', diff --git a/tests/Symfony/Bundle/Twig/ApiPlatformProfilerPanelTest.php b/tests/Symfony/Bundle/Twig/ApiPlatformProfilerPanelTest.php index 63cca792ad1..0481327fbe2 100644 --- a/tests/Symfony/Bundle/Twig/ApiPlatformProfilerPanelTest.php +++ b/tests/Symfony/Bundle/Twig/ApiPlatformProfilerPanelTest.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Tests\Symfony\Bundle\Twig; -use ApiPlatform\Doctrine\Common\State\Processor; use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy as DocumentDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use Doctrine\ORM\EntityManagerInterface; @@ -162,7 +161,7 @@ public function testProfilerGeneralLayout() $this->assertCount(1, $metrics->filter('.metric'), 'The should be one metric displayed (resource class).'); $this->assertSame('mongodb' === $this->env ? DocumentDummy::class : Dummy::class, $metrics->filter('span.value')->html()); - $this->assertCount(8, $crawler->filter('.sf-tabs .tab-content'), 'Tabs must be presents on the panel.'); + $this->assertCount(6, $crawler->filter('.sf-tabs .tab-content'), 'Tabs must be presents on the panel.'); // Metadata tab $this->assertSame('Metadata', $crawler->filter('.tab:nth-of-type(1) .tab-title')->html()); @@ -181,70 +180,5 @@ public function testProfilerGeneralLayout() // Data persisters tab $this->assertSame('Data Persisters', $crawler->filter('.data-persister-tab-title')->html()); $this->assertNotEmpty($crawler->filter('.data-persister-tab-content')); - - // Providers tab - $this->assertSame('Providers', $crawler->filter('.provider-tab-title')->html()); - $this->assertNotEmpty($crawler->filter('.provider-tab-content .empty')); - - // Processors tab - $this->assertSame('Processors', $crawler->filter('.processor-tab-title')->html()); - $this->assertNotEmpty($crawler->filter('.processor-tab-content .empty')); - } - - /** - * @requires PHP 8.0 - */ - public function testPostCollectionProcessorProfiler() - { - if ($this->legacy) { - $this->markTestSkipped('Legacy test.'); - - return; - } - - $client = static::createClient(); - $client->enableProfiler(); - $client->request('POST', '/processor_entities', [], [], ['HTTP_ACCEPT' => 'application/ld+json', 'CONTENT_TYPE' => 'application/ld+json'], '{"foo": "bar"}'); - $this->assertEquals(201, $client->getResponse()->getStatusCode()); - $crawler = $client->request('get', '/_profiler/latest?panel=api_platform.data_collector.request'); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - - // Metadata tab - $tabContent = $crawler->filter('.metadata-tab-content'); - $this->assertSame('_api_/processor_entities.{_format}_post', $tabContent->filter('th.status-success')->html(), 'The actual operation should be highlighted.'); - - // Data provider tab - $tabContent = $crawler->filter('.data-provider-tab-content'); - $this->assertStringContainsString('No calls to collection data provider have been recorded.', $tabContent->html()); - $this->assertStringContainsString('No calls to item data provider have been recorded.', $tabContent->html()); - $this->assertStringContainsString('No calls to subresource data provider have been recorded.', $tabContent->html()); - - // Processors tab - $tabContent = $crawler->filter('.processor-tab-content'); - $this->assertSame('TRUE', $tabContent->filter('table tbody .status-success')->html()); - $this->assertStringContainsString(Processor::class, $tabContent->html()); - } - - /** - * @requires PHP 8.0 - */ - public function testStateProvidersProfiler() - { - if ($this->legacy) { - $this->markTestSkipped('Legacy test.'); - - return; - } - - $client = static::createClient(); - $client->enableProfiler(); - // request to resource that uses state provider - $client->request('GET', '/attribute_resources/2', [], [], ['HTTP_ACCEPT' => 'application/ld+json']); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - $crawler = $client->request('GET', '/_profiler/latest?panel=api_platform.data_collector.request', [], [], []); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - - // Providers tab - $this->assertNotEmpty($crawler->filter('.provider-tab-content table')); } } diff --git a/tests/Symfony/EventListener/WriteListenerTest.php b/tests/Symfony/EventListener/WriteListenerTest.php index e57dda3b89e..d3054253736 100644 --- a/tests/Symfony/EventListener/WriteListenerTest.php +++ b/tests/Symfony/EventListener/WriteListenerTest.php @@ -20,6 +20,7 @@ use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; @@ -47,6 +48,11 @@ class WriteListenerTest extends TestCase private $resourceMetadataCollectionFactory; private $resourceClassResolver; + public static function noopProcessor($data) + { + return $data; + } + protected function setUp(): void { parent::setUp(); @@ -64,14 +70,12 @@ public function testOnKernelViewWithControllerResultAndPersist() { $operationResource = new OperationResource(1, 'foo'); - $this->processorProphecy->supports($operationResource, [], Argument::type('string'), Argument::type('array'))->willReturn(true); - $this->processorProphecy->process($operationResource, Argument::type('array'), Argument::type('string'), Argument::type('array'))->willReturn($operationResource)->shouldBeCalled(); - $this->iriConverterProphecy->getIriFromItem($operationResource)->willReturn('/operation_resources/1')->shouldBeCalled(); $this->resourceClassResolver->isResourceClass(Argument::type('string'))->willReturn(true); + $this->processorProphecy->process($operationResource, Argument::type(Operation::class), [], Argument::type('array'))->willReturn($operationResource)->shouldBeCalled(); $operationResourceMetadata = new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations([ - '_api_OperationResource_patch' => (new Patch())->withName('_api_OperationResource_patch'), + '_api_OperationResource_patch' => (new Patch())->withName('_api_OperationResource_patch')->withProcessor('processor'), '_api_OperationResource_put' => (new Put())->withName('_api_OperationResource_put'), '_api_OperationResource_post_collection' => (new Post())->withName('_api_OperationResource_post_collection'), ]))]); @@ -104,14 +108,13 @@ public function testOnKernelViewDoNotCallIriConverterWhenOutputClassDisabled() { $operationResource = new OperationResource(1, 'foo'); - $this->processorProphecy->supports($operationResource, [], 'create_no_output', Argument::type('array'))->willReturn(true); - $this->processorProphecy->process($operationResource, Argument::type('array'), Argument::type('string'), Argument::type('array'))->willReturn($operationResource)->shouldBeCalled(); + $this->processorProphecy->process($operationResource, Argument::type(Operation::class), [], Argument::type('array'))->willReturn($operationResource)->shouldBeCalled(); $this->iriConverterProphecy->getIriFromItem($operationResource)->shouldNotBeCalled(); $this->resourceClassResolver->isResourceClass(Argument::type('string'))->willReturn(true); $operationResourceMetadata = new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations([ - 'create_no_output' => (new Post())->withOutput(false)->withName('create_no_output'), + 'create_no_output' => (new Post())->withOutput(false)->withName('create_no_output')->withProcessor('processor'), ]))]); $this->resourceMetadataCollectionFactory->create(OperationResource::class)->willReturn($operationResourceMetadata); @@ -136,12 +139,11 @@ public function testOnKernelViewWithControllerResultAndRemove() { $operationResource = new OperationResource(1, 'foo'); - $this->processorProphecy->supports($operationResource, ['identifier' => 1], '_api_OperationResource_delete', Argument::type('array'))->willReturn(true); - $this->processorProphecy->process($operationResource, Argument::type('array'), Argument::type('string'), Argument::type('array'))->willReturn($operationResource)->shouldBeCalled(); + $this->processorProphecy->process($operationResource, Argument::type(Operation::class), ['identifier' => 1], Argument::type('array'))->willReturn($operationResource)->shouldBeCalled(); $this->iriConverterProphecy->getIriFromItem($operationResource)->shouldNotBeCalled(); $operationResourceMetadata = new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations([ - '_api_OperationResource_delete' => (new Delete())->withName('_api_OperationResource_delete')->withUriVariables(['identifier' => (new Link())->withFromClass(OperationResource::class)->withIdentifiers(['identifier'])]), + '_api_OperationResource_delete' => (new Delete())->withName('_api_OperationResource_delete')->withUriVariables(['identifier' => (new Link())->withFromClass(OperationResource::class)->withIdentifiers(['identifier'])])->withProcessor('processor'), ]))]); $this->resourceMetadataCollectionFactory->create(OperationResource::class)->willReturn($operationResourceMetadata); @@ -166,8 +168,7 @@ public function testOnKernelViewWithSafeMethod() { $operationResource = new OperationResource(1, 'foo'); - $this->processorProphecy->supports($operationResource, [], '_api_OperationResource_get', Argument::type('array'))->shouldNotBeCalled(); - $this->processorProphecy->process($operationResource, Argument::type('array'))->shouldNotBeCalled(); + $this->processorProphecy->process($operationResource, Argument::type(Operation::class), [], Argument::type('array'))->willReturn($operationResource)->shouldNotBeCalled(); $this->iriConverterProphecy->getIriFromItem($operationResource)->shouldNotBeCalled(); @@ -195,7 +196,6 @@ public function testOnKernelViewWithSafeMethod() */ public function testDoNotWriteWhenControllerResultIsResponse() { - $this->processorProphecy->supports(Argument::cetera())->shouldNotBeCalled(); $this->processorProphecy->process(Argument::cetera())->shouldNotBeCalled(); $request = new Request(); @@ -219,7 +219,6 @@ public function testDoNotWriteWhenCant() { $operationResource = new OperationResource(1, 'foo'); - $this->processorProphecy->supports(Argument::cetera())->shouldNotBeCalled(); $this->processorProphecy->process(Argument::cetera())->shouldNotBeCalled(); $operationResourceMetadata = new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations([ @@ -248,8 +247,7 @@ public function testOnKernelViewWithNoResourceClass() { $operationResource = new OperationResource(1, 'foo'); - $this->processorProphecy->supports($operationResource, Argument::type('array'))->shouldNotBeCalled(); - $this->processorProphecy->process($operationResource, Argument::type('array'))->shouldNotBeCalled(); + $this->processorProphecy->process(Argument::cetera())->shouldNotBeCalled(); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getIriFromItem($operationResource)->shouldNotBeCalled(); @@ -267,37 +265,6 @@ public function testOnKernelViewWithNoResourceClass() (new WriteListener($this->processorProphecy->reveal(), $this->iriConverterProphecy->reveal(), $this->resourceMetadataCollectionFactory->reveal(), $this->resourceClassResolver->reveal()))->onKernelView($event); } - /** - * @requires PHP 8.0 - */ - public function testOnKernelViewWithNoProcessorSupport() - { - $attributeResource = new AttributeResource(1, 'name'); - - $this->processorProphecy->supports($attributeResource, [], 'post', Argument::type('array'))->willReturn(false)->shouldBeCalled(); - $this->processorProphecy->process($attributeResource, Argument::type('array'), Argument::type('string'), Argument::type('array'))->shouldNotBeCalled(); - - $this->iriConverterProphecy->getIriFromItem($attributeResource)->shouldNotBeCalled(); - $this->resourceClassResolver->isResourceClass(Argument::type('string'))->shouldNotBeCalled(); - - $attributeResourceMetadata = new ResourceMetadataCollection(AttributeResource::class, [(new ApiResource())->withOperations(new Operations([ - 'post' => (new Post())->withName('post'), - ]))]); - $this->resourceMetadataCollectionFactory->create(AttributeResource::class)->willReturn($attributeResourceMetadata); - - $request = new Request([], [], ['_api_resource_class' => AttributeResource::class, '_api_operation_name' => 'post']); - $request->setMethod('POST'); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - HttpKernelInterface::MASTER_REQUEST, - $attributeResource - ); - - (new WriteListener($this->processorProphecy->reveal(), $this->iriConverterProphecy->reveal(), $this->resourceMetadataCollectionFactory->reveal(), $this->resourceClassResolver->reveal()))->onKernelView($event); - } - /** * @requires PHP 8.0 */ @@ -308,14 +275,13 @@ public function testOnKernelViewInvalidIdentifiers() $this->expectException(NotFoundHttpException::class); $this->expectExceptionMessage('Invalid identifier value or configuration.'); - $this->processorProphecy->supports($attributeResource, ['slug' => 'test'], '_api_OperationResource_delete', Argument::type('array'))->shouldNotBeCalled(); - $this->processorProphecy->process($attributeResource, Argument::type('array'), Argument::type('string'), Argument::type('array'))->shouldNotBeCalled(); + $this->processorProphecy->process($attributeResource, Argument::type(Operation::class), ['slug' => 'test'], Argument::type('array'))->shouldNotBeCalled(); $this->iriConverterProphecy->getIriFromItem($attributeResource)->shouldNotBeCalled(); $this->resourceClassResolver->isResourceClass(Argument::type('string'))->shouldNotBeCalled(); $operationResourceMetadata = new ResourceMetadataCollection(OperationResource::class, [(new ApiResource())->withOperations(new Operations([ - '_api_OperationResource_delete' => (new Delete())->withName('_api_OperationResource_delete')->withUriVariables(['identifier' => (new Link())->withFromClass(OperationResource::class)->withIdentifiers(['identifier'])]), + '_api_OperationResource_delete' => (new Delete())->withName('_api_OperationResource_delete')->withUriVariables(['identifier' => (new Link())->withFromClass(OperationResource::class)->withIdentifiers(['identifier'])])->withProcessor('processor'), ]))]); $this->resourceMetadataCollectionFactory->create(OperationResource::class)->willReturn($operationResourceMetadata); diff --git a/tests/Symfony/Messenger/ProcessorTest.php b/tests/Symfony/Messenger/ProcessorTest.php index f8597883a5f..fa04a2c4365 100644 --- a/tests/Symfony/Messenger/ProcessorTest.php +++ b/tests/Symfony/Messenger/ProcessorTest.php @@ -14,20 +14,13 @@ namespace ApiPlatform\Tests\Symfony\Messenger; use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\OperationNotFoundException; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; -use ApiPlatform\Metadata\GraphQl\Mutation; -use ApiPlatform\Metadata\Operations; -use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Symfony\Messenger\ContextStamp; use ApiPlatform\Symfony\Messenger\Processor; use ApiPlatform\Symfony\Messenger\RemoveStamp; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Symfony\Component\Messenger\Envelope; @@ -38,32 +31,6 @@ class ProcessorTest extends TestCase { use ProphecyTrait; - public function testSupport() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection(Dummy::class, [(new ApiResource())->withOperations(new Operations([ - 'get' => (new Get())->withMessenger(true), - 'create' => (new Post())->withMessenger(true), - ]))])); - - $processor = new Processor($resourceMetadataFactoryProphecy->reveal(), $this->prophesize(MessageBusInterface::class)->reveal()); - $this->assertTrue($processor->supports(new Dummy(), [], 'get')); - $this->assertTrue($processor->supports(new Dummy(), [], 'create')); - } - - public function testSupportWithContext() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection(Dummy::class, [(new ApiResource())->withOperations(new Operations([ - 'get' => (new Get())->withMessenger(true), - ]))])); - $resourceMetadataFactoryProphecy->create(DummyCar::class)->shouldBeCalled()->willThrow(new OperationNotFoundException()); - - $processor = new Processor($resourceMetadataFactoryProphecy->reveal(), $this->prophesize(MessageBusInterface::class)->reveal()); - $this->assertTrue($processor->supports(new DummyCar(), [], null, ['resource_class' => Dummy::class])); - $this->assertFalse($processor->supports(new DummyCar())); - } - public function testPersist() { $dummy = new Dummy(); @@ -74,7 +41,7 @@ public function testPersist() }))->willReturn(new Envelope($dummy))->shouldBeCalled(); $processor = new Processor($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $messageBus->reveal()); - $this->assertSame($dummy, $processor->process($dummy)); + $this->assertSame($dummy, $processor->process($dummy, new Get())); } public function testRemove() @@ -88,7 +55,7 @@ public function testRemove() }))->willReturn(new Envelope($dummy))->shouldBeCalled(); $processor = new Processor($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $messageBus->reveal()); - $processor->process($dummy, [], null, ['operation' => new Delete()]); + $processor->process($dummy, new Delete()); } public function testHandle() @@ -101,15 +68,6 @@ public function testHandle() }))->willReturn((new Envelope($dummy))->with(new HandledStamp($dummy, 'DummyHandler::__invoke')))->shouldBeCalled(); $processor = new Processor($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $messageBus->reveal()); - $this->assertSame($dummy, $processor->process($dummy)); - } - - public function testSupportWithGraphqlContext() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection('Dummy', [(new ApiResource())->withGraphQlOperations(['create' => (new Mutation())->withMessenger(true)])])); - - $processor = new Processor($resourceMetadataFactoryProphecy->reveal(), $this->prophesize(MessageBusInterface::class)->reveal()); - $this->assertTrue($processor->supports(new DummyCar(), [], 'create', ['resource_class' => Dummy::class, 'graphql_operation_name' => 'create'])); + $this->assertSame($dummy, $processor->process($dummy, new Get())); } } diff --git a/tests/Symfony/Routing/ApiLoaderTest.php b/tests/Symfony/Routing/ApiLoaderTest.php index 369b85b0393..9f004247084 100644 --- a/tests/Symfony/Routing/ApiLoaderTest.php +++ b/tests/Symfony/Routing/ApiLoaderTest.php @@ -23,9 +23,9 @@ use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Operations; -use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; @@ -65,12 +65,12 @@ public function testApiLoader() 'api_dummies_put_item' => (new Put())->withUriTemplate($path), 'api_dummies_delete_item' => (new Delete())->withUriTemplate($path), // Custom operations - 'api_dummies_my_op_collection' => (new Get())->withUriTemplate('/dummies.{_format}')->withDefaults(['my_default' => 'default_value', '_format' => 'a valid format'])->withRequirements(['_format' => 'a valid format'])->withCondition("request.headers.get('User-Agent') matches '/firefox/i'")->withController('some.service.name')->withCollection(true), - 'api_dummies_my_second_op_collection' => (new Post())->withUriTemplate('/dummies.{_format}')->withOptions(['option' => 'option_value'])->withHost('{subdomain}.api-platform.com')->withSchemes(['https'])->withCollection(true), + 'api_dummies_my_op_collection' => (new GetCollection())->withUriTemplate('/dummies.{_format}')->withDefaults(['my_default' => 'default_value', '_format' => 'a valid format'])->withRequirements(['_format' => 'a valid format'])->withCondition("request.headers.get('User-Agent') matches '/firefox/i'")->withController('some.service.name'), + 'api_dummies_my_second_op_collection' => (new GetCollection())->withMethod('POST')->withUriTemplate('/dummies.{_format}')->withOptions(['option' => 'option_value'])->withHost('{subdomain}.api-platform.com')->withSchemes(['https']), // without controller, takes the default one - 'api_dummies_my_path_op_collection' => (new Get())->withUriTemplate('some/custom/path')->withCollection(true), + 'api_dummies_my_path_op_collection' => (new GetCollection())->withUriTemplate('some/custom/path'), // Custom path - 'api_dummies_my_stateless_op_collection' => (new Get())->withUriTemplate('/dummies.{_format}')->withStateless(true)->withCollection(true), + 'api_dummies_my_stateless_op_collection' => (new GetCollection())->withUriTemplate('/dummies.{_format}')->withStateless(true), ])), ]); diff --git a/tests/Symfony/Routing/IriConverterTest.php b/tests/Symfony/Routing/IriConverterTest.php index 167c2479a55..958139d8aad 100644 --- a/tests/Symfony/Routing/IriConverterTest.php +++ b/tests/Symfony/Routing/IriConverterTest.php @@ -204,7 +204,7 @@ public function testGetItemFromIri() ])); $stateProviderProphecy = $this->prophesize(ProviderInterface::class); - $stateProviderProphecy->provide(Dummy::class, ['id' => 1], $operationName, Argument::type('array'))->willReturn($item); + $stateProviderProphecy->provide($operation, ['id' => 1], Argument::type('array'))->willReturn($item); $iriConverter = $this->getIriConverter($stateProviderProphecy, $routerProphecy, null, $resourceMetadataCollectionFactoryProphecy); $this->assertEquals($item, $iriConverter->getItemFromIri('/dummies/1')); } @@ -226,7 +226,7 @@ public function testGetNoItemFromIri() ])); $stateProviderProphecy = $this->prophesize(ProviderInterface::class); - $stateProviderProphecy->provide(Dummy::class, ['id' => 1], $operationName, Argument::type('array'))->willReturn(null); + $stateProviderProphecy->provide($operation, ['id' => 1], Argument::type('array'))->willReturn(null); $iriConverter = $this->getIriConverter($stateProviderProphecy, $routerProphecy, null, $resourceMetadataCollectionFactoryProphecy); $iriConverter->getItemFromIri('/dummies/1'); }