Skip to content

Commit fb96f33

Browse files
authored
refactor(metadata): cascade resource to operation (#7246)
1 parent 4ecde01 commit fb96f33

20 files changed

+184
-61
lines changed

src/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactory.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use ApiPlatform\Doctrine\Odm\State\CollectionProvider;
1717
use ApiPlatform\Doctrine\Odm\State\ItemProvider;
1818
use ApiPlatform\Doctrine\Odm\State\Options;
19-
use ApiPlatform\Metadata\ApiResource;
2019
use ApiPlatform\Metadata\CollectionOperationInterface;
2120
use ApiPlatform\Metadata\DeleteOperationInterface;
2221
use ApiPlatform\Metadata\Operation;
@@ -41,12 +40,10 @@ public function create(string $resourceClass): ResourceMetadataCollection
4140
{
4241
$resourceMetadataCollection = $this->decorated->create($resourceClass);
4342

44-
/** @var ApiResource $resourceMetadata */
4543
foreach ($resourceMetadataCollection as $i => $resourceMetadata) {
4644
$operations = $resourceMetadata->getOperations();
4745

4846
if ($operations) {
49-
/** @var Operation $operation */
5047
foreach ($resourceMetadata->getOperations() as $operationName => $operation) {
5148
$documentClass = $this->getStateOptionsClass($operation, $operation->getClass(), Options::class);
5249
if (!$this->managerRegistry->getManagerForClass($documentClass) instanceof DocumentManager) {

src/Doctrine/Odm/Tests/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
use ApiPlatform\Metadata\Delete;
2222
use ApiPlatform\Metadata\Get;
2323
use ApiPlatform\Metadata\GetCollection;
24-
use ApiPlatform\Metadata\Operation;
24+
use ApiPlatform\Metadata\HttpOperation;
2525
use ApiPlatform\Metadata\Operations;
2626
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2727
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
@@ -34,7 +34,7 @@ final class DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest extends Test
3434
{
3535
use ProphecyTrait;
3636

37-
private function getResourceMetadataCollectionFactory(Operation $operation)
37+
private function getResourceMetadataCollectionFactory(HttpOperation $operation)
3838
{
3939
if (!class_exists(DocumentManager::class)) {
4040
$this->markTestSkipped('ODM not installed');
@@ -71,7 +71,7 @@ public function testWithoutManager(): void
7171
}
7272

7373
#[\PHPUnit\Framework\Attributes\DataProvider('operationProvider')]
74-
public function testWithProvider(Operation $operation, ?string $expectedProvider = null, ?string $expectedProcessor = null): void
74+
public function testWithProvider(HttpOperation $operation, ?string $expectedProvider = null, ?string $expectedProcessor = null): void
7575
{
7676
if (!class_exists(DocumentManager::class)) {
7777
$this->markTestSkipped('ODM not installed');

src/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use ApiPlatform\Doctrine\Orm\State\CollectionProvider;
1717
use ApiPlatform\Doctrine\Orm\State\ItemProvider;
1818
use ApiPlatform\Doctrine\Orm\State\Options;
19-
use ApiPlatform\Metadata\ApiResource;
2019
use ApiPlatform\Metadata\CollectionOperationInterface;
2120
use ApiPlatform\Metadata\DeleteOperationInterface;
2221
use ApiPlatform\Metadata\Operation;
@@ -41,12 +40,10 @@ public function create(string $resourceClass): ResourceMetadataCollection
4140
{
4241
$resourceMetadataCollection = $this->decorated->create($resourceClass);
4342

44-
/** @var ApiResource $resourceMetadata */
4543
foreach ($resourceMetadataCollection as $i => $resourceMetadata) {
4644
$operations = $resourceMetadata->getOperations();
4745

4846
if ($operations) {
49-
/** @var Operation $operation */
5047
foreach ($resourceMetadata->getOperations() as $operationName => $operation) {
5148
$entityClass = $this->getStateOptionsClass($operation, $operation->getClass(), Options::class);
5249

src/Doctrine/Orm/Tests/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
use ApiPlatform\Metadata\Delete;
2222
use ApiPlatform\Metadata\Get;
2323
use ApiPlatform\Metadata\GetCollection;
24-
use ApiPlatform\Metadata\Operation;
24+
use ApiPlatform\Metadata\HttpOperation;
2525
use ApiPlatform\Metadata\Operations;
2626
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2727
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
@@ -34,7 +34,7 @@ class DoctrineOrmResourceCollectionMetadataFactoryTest extends TestCase
3434
{
3535
use ProphecyTrait;
3636

37-
private function getResourceMetadataCollectionFactory(Operation $operation)
37+
private function getResourceMetadataCollectionFactory(HttpOperation $operation)
3838
{
3939
$resourceMetadataCollectionFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
4040
$resourceMetadataCollectionFactory->create($operation->getClass())->willReturn(new ResourceMetadataCollection($operation->getClass(), [
@@ -63,7 +63,7 @@ public function testWithoutManager(): void
6363
}
6464

6565
#[\PHPUnit\Framework\Attributes\DataProvider('operationProvider')]
66-
public function testWithProvider(Operation $operation, ?string $expectedProvider = null, ?string $expectedProcessor = null): void
66+
public function testWithProvider(HttpOperation $operation, ?string $expectedProvider = null, ?string $expectedProcessor = null): void
6767
{
6868
$objectManager = $this->prophesize(EntityManagerInterface::class);
6969
$managerRegistry = $this->prophesize(ManagerRegistry::class);

src/Hydra/Serializer/EntrypointNormalizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public function normalize(mixed $object, ?string $format = null, array $context
6161
}
6262

6363
try {
64-
$entrypoint[$key] = $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_PATH, $operation); // @phpstan-ignore-line phpstan issue as type is CollectionOperationInterface & Operation
64+
$entrypoint[$key] = $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_PATH, $operation);
6565
} catch (InvalidArgumentException|OperationNotFoundException) {
6666
// Ignore resources without GET operations
6767
}

src/JsonApi/Serializer/EntrypointNormalizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public function normalize(mixed $object, ?string $format = null, array $context
5353
}
5454

5555
try {
56-
$iri = $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_URL, $operation); // @phpstan-ignore-line phpstan issue as type is CollectionOperationInterface & Operation
56+
$iri = $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_URL, $operation);
5757
$entrypoint['links'][lcfirst($resource->getShortName())] = $iri;
5858
} catch (InvalidArgumentException) {
5959
// Ignore resources without GET operations

src/Metadata/ApiResource.php

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,24 @@
3131
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
3232
class ApiResource extends Metadata
3333
{
34+
use CascadeToOperationsTrait;
3435
use WithResourceTrait;
3536

37+
/**
38+
* @var Operations<HttpOperation>
39+
*/
3640
protected ?Operations $operations;
3741

3842
/**
39-
* @param array<int, HttpOperation>|array<string, HttpOperation>|Operations|null $operations Operations is a list of HttpOperation
40-
* @param array<string, Link>|array<string, mixed[]>|string[]|string|null $uriVariables
41-
* @param array<string, string> $headers
42-
* @param string|callable|null $provider
43-
* @param string|callable|null $processor
44-
* @param mixed|null $mercure
45-
* @param mixed|null $messenger
46-
* @param mixed|null $input
47-
* @param mixed|null $output
43+
* @param list<HttpOperation>|array<string, HttpOperation>|Operations<HttpOperation>|null $operations Operations is a list of HttpOperation
44+
* @param array<string, Link>|array<string, mixed[]>|string[]|string|null $uriVariables
45+
* @param array<string, string> $headers
46+
* @param string|callable|null $provider
47+
* @param string|callable|null $processor
48+
* @param mixed|null $mercure
49+
* @param mixed|null $messenger
50+
* @param mixed|null $input
51+
* @param mixed|null $output
4852
*/
4953
public function __construct(
5054
/**
@@ -1012,6 +1016,7 @@ class: $class,
10121016
extraProperties: $extraProperties
10131017
);
10141018

1019+
/* @var Operations<HttpOperation> $operations> */
10151020
$this->operations = null === $operations ? null : new Operations($operations);
10161021
$this->provider = $provider;
10171022
$this->processor = $processor;
@@ -1020,11 +1025,17 @@ class: $class,
10201025
}
10211026
}
10221027

1028+
/**
1029+
* @return Operations<HttpOperation>
1030+
*/
10231031
public function getOperations(): ?Operations
10241032
{
10251033
return $this->operations;
10261034
}
10271035

1036+
/**
1037+
* @param Operations<HttpOperation> $operations
1038+
*/
10281039
public function withOperations(Operations $operations): static
10291040
{
10301041
$self = clone $this;

src/Metadata/CascadeFromResource.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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\Metadata;
15+
16+
/**
17+
* @internal
18+
*
19+
* @phpstan-require-extends Operation
20+
*/
21+
trait CascadeFromResource
22+
{
23+
use WithResourceTrait;
24+
25+
public function cascadeFromResource(ApiResource $apiResource, array $ignoredOptions = []): static
26+
{
27+
return $this->copyFrom($apiResource, $ignoredOptions);
28+
}
29+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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\Metadata;
15+
16+
use ApiPlatform\Metadata\Exception\RuntimeException;
17+
use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
18+
19+
/**
20+
* @internal
21+
*
22+
* @phpstan-require-extends ApiResource
23+
*/
24+
trait CascadeToOperationsTrait
25+
{
26+
public function cascadeToOperations(): static
27+
{
28+
if (!$this instanceof ApiResource) {
29+
throw new RuntimeException('Not an API resource');
30+
}
31+
32+
if (!($operations = $this->getOperations() ?? [])) {
33+
return $this;
34+
}
35+
36+
return (clone $this)->withOperations(
37+
new Operations($this->getMutatedOperations($operations, $this)),
38+
);
39+
}
40+
41+
public function cascadeToGraphQlOperations(): static
42+
{
43+
if (!$this instanceof ApiResource) {
44+
throw new RuntimeException('Not an API resource');
45+
}
46+
47+
if (!($operations = $this->getGraphQlOperations() ?? [])) {
48+
return $this;
49+
}
50+
51+
return (clone $this)->withGraphQlOperations(
52+
$this->getMutatedOperations($operations, $this),
53+
);
54+
}
55+
56+
/**
57+
* @param Operations<HttpOperation>|list<GraphQlOperation> $operations
58+
*
59+
* @return array[string, HttpOperation]|list<GraphQlOperation>
60+
*/
61+
private function getMutatedOperations(iterable $operations, ApiResource $apiResource): iterable
62+
{
63+
$modifiedOperations = [];
64+
foreach ($operations as $key => $operation) {
65+
$modifiedOperations[$key] = $operation->cascadeFromResource($apiResource);
66+
}
67+
68+
return $modifiedOperations;
69+
}
70+
}

src/Metadata/Operation.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*/
2121
abstract class Operation extends Metadata
2222
{
23+
use CascadeFromResource;
2324
use WithResourceTrait;
2425

2526
/**
@@ -861,7 +862,7 @@ class: $class,
861862
);
862863
}
863864

864-
public function withOperation($operation)
865+
public function withOperation(self $operation): static
865866
{
866867
return $this->copyFrom($operation);
867868
}

0 commit comments

Comments
 (0)