Skip to content

Commit ee0fabb

Browse files
authored
fix(state): operation argument in provide/process (#4712)
* fix(state): operation argument in provide/process * Abstract Operation, rename Operation to HttpOperation * use operation in graphql * Fixes * changelog * useless comments
1 parent 22054b2 commit ee0fabb

File tree

196 files changed

+3827
-4298
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

196 files changed

+3827
-4298
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# Changelog
22

3-
## 2.7.0
3+
## 2.7.0-alpha.2
4+
5+
* Review interfaces (ProcessorInterface, ProviderInterface, TypeConverterInterface, ResolverFactoryInterface etc.) to use `ApiPlatform\Metadata\Operation` instead of `operationName` (#4712)
6+
* Introduce `CollectionOperationInterface` instead of the `collection` flag (#4712)
7+
* Introduce `DeleteOperationInterface` instead of the `delete` flag (#4712)
8+
* The `compositeIdentifier` flag only lives under the `uriVariables` property (#4712)
9+
* The `provider` or `processor` property is specified within the `Operation` and we removed the chain pattern (#4712)
10+
11+
## 2.7.0-alpha.1
412

513
* Swagger UI: Add `usePkceWithAuthorizationCodeGrant` to Swagger UI initOAuth (#4649)
614
* **BC**: `mapping.paths` in configuration should override bundles configuration (#4465)

src/Api/IdentifiersExtractor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public function getIdentifiersFromItem($item, string $operationName = null, arra
5454
{
5555
$identifiers = [];
5656
$resourceClass = $this->getResourceClass($item, true);
57-
$operation = $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation($operationName);
57+
$operation = $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation($operationName, false, true);
5858

5959
$links = $operation instanceof GraphQlOperation ? $operation->getLinks() : $operation->getUriVariables();
6060
foreach ($links ?? [] as $link) {

src/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommand.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Core\Bridge\Symfony\Bundle\Command;
1515

1616
use ApiPlatform\Core\Annotation\ApiResource;
17+
use ApiPlatform\Core\Api\IdentifiersExtractorInterface;
1718
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
1819
use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface;
1920
use ApiPlatform\Core\Upgrade\ColorConsoleDiffFormatter;
@@ -42,15 +43,17 @@ final class UpgradeApiResourceCommand extends Command
4243
private $subresourceOperationFactory;
4344
private $subresourceTransformer;
4445
private $reader;
46+
private $identifiersExtractor;
4547
private $localCache = [];
4648

47-
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, SubresourceOperationFactoryInterface $subresourceOperationFactory, SubresourceTransformer $subresourceTransformer, AnnotationReader $reader)
49+
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, SubresourceOperationFactoryInterface $subresourceOperationFactory, SubresourceTransformer $subresourceTransformer, AnnotationReader $reader, IdentifiersExtractorInterface $identifiersExtractor)
4850
{
4951
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
5052
$this->resourceMetadataFactory = $resourceMetadataFactory;
5153
$this->subresourceOperationFactory = $subresourceOperationFactory;
5254
$this->subresourceTransformer = $subresourceTransformer;
5355
$this->reader = $reader;
56+
$this->identifiersExtractor = $identifiersExtractor;
5457

5558
parent::__construct();
5659
}
@@ -204,7 +207,7 @@ private function transformApiResource(InputInterface $input, OutputInterface $ou
204207
continue;
205208
}
206209

207-
$traverser->addVisitor(new UpgradeApiResourceVisitor($attribute, $isAnnotation));
210+
$traverser->addVisitor(new UpgradeApiResourceVisitor($attribute, $isAnnotation, $this->identifiersExtractor, $resourceClass));
208211

209212
$oldCode = file_get_contents($fileName);
210213
$oldStmts = $parser->parse($oldCode);

src/Core/EventListener/ReadListener.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@ public function onKernelRequest(RequestEvent $event): void
8080
if (
8181
!($attributes = RequestAttributesExtractor::extractAttributes($request))
8282
|| !$attributes['receive']
83-
|| $request->isMethod('POST') && isset($attributes['collection_operation_name'])
83+
|| ($request->isMethod('POST') && isset($attributes['collection_operation_name']))
8484
|| ($operation && !($operation->getExtraProperties()['is_legacy_resource_metadata'] ?? false) && !($operation->getExtraProperties()['is_legacy_subresource'] ?? false))
85+
|| ($operation && false === $operation->canRead())
8586
|| $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY)
8687
) {
8788
return;

src/Core/Metadata/Resource/ApiResourceToLegacyResourceMetadataTrait.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
namespace ApiPlatform\Core\Metadata\Resource;
1515

1616
use ApiPlatform\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\CollectionOperationInterface;
18+
use ApiPlatform\Metadata\HttpOperation;
1719
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
1820

1921
/**
@@ -37,12 +39,14 @@ private function transformResourceToResourceMetadata(ApiResource $resource): Res
3739
}
3840

3941
$arrayOperation['openapi_context']['operationId'] = $name;
42+
$arrayOperation['composite_identifier'] = $this->hasCompositeIdentifier($operation);
4043

41-
if ($operation->getExtraProperties()['is_alternate_resource_metadata'] ?? false) {
42-
$arrayOperation['composite_identifier'] = $operation->getCompositeIdentifier() ?? false;
44+
if (HttpOperation::METHOD_POST === $operation->getMethod() && !$operation->getUriVariables()) {
45+
$collectionOperations[$name] = $arrayOperation;
46+
continue;
4347
}
4448

45-
if ($operation->isCollection()) {
49+
if ($operation instanceof CollectionOperationInterface) {
4650
$collectionOperations[$name] = $arrayOperation;
4751
continue;
4852
}
@@ -108,4 +112,15 @@ private function transformUriVariablesToIdentifiers(array $arrayOperation): arra
108112

109113
return $arrayOperation;
110114
}
115+
116+
private function hasCompositeIdentifier(HttpOperation $operation): bool
117+
{
118+
foreach ($operation->getUriVariables() ?? [] as $parameterName => $uriVariable) {
119+
if ($uriVariable->getCompositeIdentifier()) {
120+
return true;
121+
}
122+
}
123+
124+
return false;
125+
}
111126
}

src/Core/Swagger/Serializer/DocumentationNormalizer.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ private function updatePostOperation(bool $v3, \ArrayObject $pathOperation, arra
583583
$identifiers = (array) $resourceMetadata
584584
->getTypedOperationAttribute($operationType, $operationName, 'identifiers', [], false);
585585

586-
$pathOperation = $this->addItemOperationParameters($v3, $pathOperation, $operationType, $operationName, $resourceMetadata, $resourceClass);
586+
$pathOperation = $this->addItemOperationParameters($v3, $pathOperation, $operationType, $operationName, $resourceMetadata, $resourceClass, OperationType::ITEM === $operationType ? false : true);
587587

588588
$successResponse = ['description' => sprintf('%s resource created', $resourceShortName)];
589589
[$successResponse, $defined] = $this->addSchemas($v3, $successResponse, $definitions, $resourceClass, $operationType, $operationName, $responseMimeTypes);
@@ -677,14 +677,14 @@ private function updateDeleteOperation(bool $v3, \ArrayObject $pathOperation, st
677677
return $this->addItemOperationParameters($v3, $pathOperation, $operationType, $operationName, $resourceMetadata, $resourceClass);
678678
}
679679

680-
private function addItemOperationParameters(bool $v3, \ArrayObject $pathOperation, string $operationType, string $operationName, ResourceMetadata $resourceMetadata, string $resourceClass): \ArrayObject
680+
private function addItemOperationParameters(bool $v3, \ArrayObject $pathOperation, string $operationType, string $operationName, ResourceMetadata $resourceMetadata, string $resourceClass, bool $isPost = false): \ArrayObject
681681
{
682682
$identifiers = (array) $resourceMetadata
683683
->getTypedOperationAttribute($operationType, $operationName, 'identifiers', [], false);
684684

685685
// Auto-generated routes in API Platform < 2.7 are considered as collection, hotfix this as the OpenApi Factory supports new operations anyways.
686686
// this also fixes a bug where we could not create POST item operations in API P 2.6
687-
if (OperationType::ITEM === $operationType && 'post' === substr($operationName, -4)) {
687+
if (OperationType::ITEM === $operationType && $isPost) {
688688
$operationType = OperationType::COLLECTION;
689689
}
690690

src/Core/Upgrade/UpgradeApiResourceVisitor.php

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
use ApiPlatform\Api\UrlGeneratorInterface;
1717
use ApiPlatform\Core\Annotation\ApiResource as LegacyApiResource;
1818
use ApiPlatform\Core\Annotation\ApiSubresource;
19+
use ApiPlatform\Core\Api\IdentifiersExtractorInterface;
1920
use ApiPlatform\Metadata\ApiResource;
2021
use ApiPlatform\Metadata\Delete;
2122
use ApiPlatform\Metadata\Get;
2223
use ApiPlatform\Metadata\GetCollection;
2324
use ApiPlatform\Metadata\GraphQl\Mutation;
2425
use ApiPlatform\Metadata\GraphQl\Query;
2526
use ApiPlatform\Metadata\GraphQl\QueryCollection;
27+
use ApiPlatform\Metadata\Link;
2628
use ApiPlatform\Metadata\Patch;
2729
use ApiPlatform\Metadata\Post;
2830
use ApiPlatform\Metadata\Put;
@@ -36,12 +38,16 @@ final class UpgradeApiResourceVisitor extends NodeVisitorAbstract
3638
use RemoveAnnotationTrait;
3739

3840
private LegacyApiResource $resourceAnnotation;
41+
private IdentifiersExtractorInterface $identifiersExtractor;
3942
private bool $isAnnotation = false;
43+
private string $resourceClass;
4044

41-
public function __construct(LegacyApiResource $resourceAnnotation, bool $isAnnotation = false)
45+
public function __construct(LegacyApiResource $resourceAnnotation, bool $isAnnotation, IdentifiersExtractorInterface $identifiersExtractor, string $resourceClass)
4246
{
4347
$this->resourceAnnotation = $resourceAnnotation;
4448
$this->isAnnotation = $isAnnotation;
49+
$this->identifiersExtractor = $identifiersExtractor;
50+
$this->resourceClass = $resourceClass;
4551
}
4652

4753
/**
@@ -80,6 +86,10 @@ public function enterNode(Node $node)
8086
$this->getGraphQlOperationsNamespaces($this->resourceAnnotation->graphql ?? [])
8187
));
8288

89+
if (true === !($this->resourceAnnotation->attributes['composite_identifier'] ?? true)) {
90+
$namespaces[] = Link::class;
91+
}
92+
8393
foreach ($node->stmts as $k => $stmt) {
8494
if (!$stmt instanceof Node\Stmt\Use_) {
8595
break;
@@ -202,6 +212,40 @@ public function enterNode(Node $node)
202212
continue;
203213
}
204214

215+
if ('compositeIdentifier' === $key) {
216+
if (false !== $value) {
217+
continue;
218+
}
219+
220+
$identifiers = $this->identifiersExtractor->getIdentifiersFromResourceClass($this->resourceClass);
221+
$identifierNodeItems = [];
222+
foreach ($identifiers as $identifier) {
223+
$identifierNodes = [
224+
'compositeIdentifier' => new Node\Expr\ConstFetch(new Node\Name('false')),
225+
'fromClass' => new Node\Expr\ClassConstFetch(
226+
new Node\Name(
227+
'self'
228+
),
229+
'class'
230+
),
231+
'identifiers' => new Node\Expr\Array_(
232+
[
233+
new Node\Expr\ArrayItem(new Node\Scalar\String_($identifier)),
234+
],
235+
['kind' => Node\Expr\Array_::KIND_SHORT]
236+
),
237+
];
238+
239+
$identifierNodeItems[] = new Node\Expr\ArrayItem(
240+
new Node\Expr\New_(new Node\Name('Link'), $this->arrayToArguments($identifierNodes)),
241+
new Node\Scalar\String_($identifier)
242+
);
243+
}
244+
245+
$arguments['uriVariables'] = new Node\Expr\Array_($identifierNodeItems, ['kind' => Node\Expr\Array_::KIND_SHORT]);
246+
continue;
247+
}
248+
205249
$arguments[$key] = $this->valueToNode($value);
206250
}
207251

src/Doctrine/Common/State/LinksHandlerTrait.php

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,22 @@
1313

1414
namespace ApiPlatform\Doctrine\Common\State;
1515

16+
use ApiPlatform\Exception\OperationNotFoundException;
1617
use ApiPlatform\Exception\RuntimeException;
1718
use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
19+
use ApiPlatform\Metadata\GraphQl\Query;
20+
use ApiPlatform\Metadata\HttpOperation;
1821
use ApiPlatform\Metadata\Link;
1922
use ApiPlatform\Metadata\Operation;
2023

2124
trait LinksHandlerTrait
2225
{
2326
/**
24-
* @param Operation|GraphQlOperation $operation
27+
* @param HttpOperation|GraphQlOperation $operation
2528
*
2629
* @return Link[]
2730
*/
28-
private function getLinks(string $resourceClass, $operation, array $context): array
31+
private function getLinks(string $resourceClass, Operation $operation, array $context): array
2932
{
3033
$links = ($operation instanceof GraphQlOperation ? $operation->getLinks() : $operation->getUriVariables()) ?? [];
3134

@@ -41,8 +44,26 @@ private function getLinks(string $resourceClass, $operation, array $context): ar
4144
}
4245
}
4346

44-
$operation = $this->resourceMetadataCollectionFactory->create($linkClass)->getOperation($operation->getName());
45-
foreach ($operation instanceof GraphQlOperation ? $operation->getLinks() : $operation->getUriVariables() as $link) {
47+
// Using graphql, it's possible that we won't find a graphql operation of the same type (eg it is disabled).
48+
try {
49+
$resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($linkClass);
50+
$linkedOperation = $resourceMetadataCollection->getOperation($operation->getName());
51+
} catch (OperationNotFoundException $e) {
52+
if (!$operation instanceof GraphQlOperation) {
53+
throw $e;
54+
}
55+
56+
// Instead we'll look for the first Query available
57+
foreach ($resourceMetadataCollection as $resourceMetadata) {
58+
foreach ($resourceMetadata->getGraphQlOperations() as $operation) {
59+
if ($operation instanceof Query) {
60+
$linkedOperation = $operation;
61+
}
62+
}
63+
}
64+
}
65+
66+
foreach ($linkedOperation instanceof GraphQlOperation ? $linkedOperation->getLinks() : $linkedOperation->getUriVariables() as $link) {
4667
if ($resourceClass === $link->getToClass()) {
4768
$newLinks[] = $link;
4869
}

src/Doctrine/Common/State/Processor.php renamed to src/Doctrine/Common/State/PersistProcessor.php

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@
1313

1414
namespace ApiPlatform\Doctrine\Common\State;
1515

16+
use ApiPlatform\Metadata\Operation;
1617
use ApiPlatform\State\ProcessorInterface;
1718
use ApiPlatform\Util\ClassInfoTrait;
1819
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
1920
use Doctrine\ORM\Mapping\ClassMetadataInfo;
2021
use Doctrine\Persistence\ManagerRegistry;
2122
use Doctrine\Persistence\ObjectManager as DoctrineObjectManager;
2223

23-
final class Processor implements ProcessorInterface
24+
final class PersistProcessor implements ProcessorInterface
2425
{
2526
use ClassInfoTrait;
2627

@@ -31,12 +32,7 @@ public function __construct(ManagerRegistry $managerRegistry)
3132
$this->managerRegistry = $managerRegistry;
3233
}
3334

34-
public function supports($data, array $uriVariables = [], ?string $operationName = null, array $context = []): bool
35-
{
36-
return null !== $this->getManager($data);
37-
}
38-
39-
private function persist($data, array $context = [])
35+
public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
4036
{
4137
if (!$manager = $this->getManager($data)) {
4238
return $data;
@@ -52,25 +48,6 @@ private function persist($data, array $context = [])
5248
return $data;
5349
}
5450

55-
private function remove($data, array $context = [])
56-
{
57-
if (!$manager = $this->getManager($data)) {
58-
return;
59-
}
60-
61-
$manager->remove($data);
62-
$manager->flush();
63-
}
64-
65-
public function process($data, array $uriVariables = [], ?string $operationName = null, array $context = [])
66-
{
67-
if (\array_key_exists('operation', $context) && $context['operation']->isDelete()) {
68-
return $this->remove($data);
69-
}
70-
71-
return $this->persist($data);
72-
}
73-
7451
/**
7552
* Gets the Doctrine object manager associated with given data.
7653
*
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Doctrine\Common\State;
15+
16+
use ApiPlatform\Metadata\Operation;
17+
use ApiPlatform\State\ProcessorInterface;
18+
use ApiPlatform\Util\ClassInfoTrait;
19+
use Doctrine\Persistence\ManagerRegistry;
20+
use Doctrine\Persistence\ObjectManager as DoctrineObjectManager;
21+
22+
final class RemoveProcessor implements ProcessorInterface
23+
{
24+
use ClassInfoTrait;
25+
26+
private $managerRegistry;
27+
28+
public function __construct(ManagerRegistry $managerRegistry)
29+
{
30+
$this->managerRegistry = $managerRegistry;
31+
}
32+
33+
public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
34+
{
35+
if (!$manager = $this->getManager($data)) {
36+
return;
37+
}
38+
39+
$manager->remove($data);
40+
$manager->flush();
41+
}
42+
43+
/**
44+
* Gets the Doctrine object manager associated with given data.
45+
*
46+
* @param mixed $data
47+
*/
48+
private function getManager($data): ?DoctrineObjectManager
49+
{
50+
return \is_object($data) ? $this->managerRegistry->getManagerForClass($this->getObjectClass($data)) : null;
51+
}
52+
}

0 commit comments

Comments
 (0)