Skip to content

Commit 1b3ab0e

Browse files
committed
Add Operation mutator
1 parent 7f165b9 commit 1b3ab0e

15 files changed

+352
-55
lines changed

src/Metadata/AsOperationMutator.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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+
#[\Attribute(\Attribute::TARGET_CLASS)]
17+
final class AsOperationMutator
18+
{
19+
public function __construct(
20+
public readonly string $operationName,
21+
) {
22+
}
23+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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\Mutator;
15+
16+
use ApiPlatform\Metadata\OperationMutatorInterface;
17+
use Psr\Container\ContainerInterface;
18+
19+
final class OperationMutatorCollection implements ContainerInterface
20+
{
21+
private array $mutators;
22+
23+
public function addMutator(string $operationName, OperationMutatorInterface $mutator): void
24+
{
25+
$this->mutators[$operationName][] = $mutator;
26+
}
27+
28+
public function get(string $id): array
29+
{
30+
return $this->mutators[$id] ?? [];
31+
}
32+
33+
public function has(string $id): bool
34+
{
35+
return isset($this->mutators[$id]);
36+
}
37+
}

src/Metadata/Mutator/ResourceMutatorCollection.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313

1414
namespace ApiPlatform\Metadata\Mutator;
1515

16+
use ApiPlatform\Metadata\ResourceMutatorInterface;
1617
use Psr\Container\ContainerInterface;
1718

1819
final class ResourceMutatorCollection implements ContainerInterface
1920
{
2021
private array $mutators;
2122

22-
public function addMutator(string $resourceClass, object $mutator): void
23+
public function addMutator(string $resourceClass, ResourceMutatorInterface $mutator): void
2324
{
2425
$this->mutators[$resourceClass][] = $mutator;
2526
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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+
interface OperationMutatorInterface
17+
{
18+
public function __invoke(Operation $operation): Operation;
19+
}

src/Metadata/Resource/Factory/CustomResourceMetadataCollectionFactory.php

Lines changed: 0 additions & 46 deletions
This file was deleted.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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\Resource\Factory;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\Operation;
18+
use ApiPlatform\Metadata\OperationMutatorInterface;
19+
use ApiPlatform\Metadata\Operations;
20+
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
21+
use ApiPlatform\Metadata\ResourceMutatorInterface;
22+
use Psr\Container\ContainerInterface;
23+
24+
final class MutatorResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
25+
{
26+
/**
27+
* @param ContainerInterface<ResourceMutatorInterface[]> $resourceMutators
28+
* @param ContainerInterface<OperationMutatorInterface[]> $operationMutators
29+
*/
30+
public function __construct(
31+
private readonly ContainerInterface $resourceMutators,
32+
private readonly ContainerInterface $operationMutators,
33+
private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null,
34+
) {
35+
}
36+
37+
public function create(string $resourceClass): ResourceMetadataCollection
38+
{
39+
$resourceMetadataCollection = new ResourceMetadataCollection($resourceClass);
40+
if ($this->decorated) {
41+
$resourceMetadataCollection = $this->decorated->create($resourceClass);
42+
}
43+
44+
$newMetadataCollection = new ResourceMetadataCollection($resourceClass);
45+
46+
foreach ($resourceMetadataCollection as $resource) {
47+
$resource = $this->mutateResource($resource, $resourceClass);
48+
$operations = $this->mutateOperations($resource->getOperations() ?? new Operations());
49+
$resource = $resource->withOperations($operations);
50+
51+
$newMetadataCollection[] = $resource;
52+
}
53+
54+
return $newMetadataCollection;
55+
}
56+
57+
private function mutateResource(ApiResource $resource, string $resourceClass): ApiResource
58+
{
59+
foreach ($this->resourceMutators->get($resourceClass) as $mutator) {
60+
$resource = $mutator($resource);
61+
}
62+
63+
return $resource;
64+
}
65+
66+
private function mutateOperations(Operations $operations): Operations
67+
{
68+
$newOperations = new Operations();
69+
70+
/** @var Operation $operation */
71+
foreach ($operations as $key => $operation) {
72+
foreach ($this->operationMutators->get($key) as $mutator) {
73+
$operation = $mutator($operation);
74+
}
75+
76+
$newOperations->add($key, $operation);
77+
}
78+
79+
return $newOperations;
80+
}
81+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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+
interface ResourceMutatorInterface
17+
{
18+
public function __invoke(ApiResource $resource): ApiResource;
19+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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\Tests\Resource\Factory;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\HttpOperation;
18+
use ApiPlatform\Metadata\Mutator\OperationMutatorCollection;
19+
use ApiPlatform\Metadata\Mutator\ResourceMutatorCollection;
20+
use ApiPlatform\Metadata\Operation;
21+
use ApiPlatform\Metadata\OperationMutatorInterface;
22+
use ApiPlatform\Metadata\Operations;
23+
use ApiPlatform\Metadata\Resource\Factory\MutatorResourceMetadataCollectionFactory;
24+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
25+
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
26+
use ApiPlatform\Metadata\ResourceMutatorInterface;
27+
use PHPUnit\Framework\TestCase;
28+
29+
final class MutatorResourceMetadataCollectionFactoryTest extends TestCase
30+
{
31+
public function testMutateResource(): void
32+
{
33+
$decorated = $this->createMock(ResourceMetadataCollectionFactoryInterface::class);
34+
$resourceClass = \stdClass::class;
35+
$resourceMetadataCollection = new ResourceMetadataCollection($resourceClass);
36+
$resourceMetadataCollection[] = (new ApiResource())->withClass($resourceClass);
37+
38+
$resourceMutatorCollection = new ResourceMutatorCollection();
39+
$resourceMutatorCollection->addMutator($resourceClass, new DummyResourceMutator());
40+
41+
$customResourceMetadataCollectionFactory = new MutatorResourceMetadataCollectionFactory($resourceMutatorCollection, new OperationMutatorCollection(), $decorated);
42+
43+
$decorated->expects($this->once())->method('create')->with($resourceClass)->willReturn(
44+
$resourceMetadataCollection,
45+
);
46+
47+
$resourceMetadataCollection = $customResourceMetadataCollectionFactory->create($resourceClass);
48+
49+
$resource = $resourceMetadataCollection->getIterator()->current();
50+
$this->assertInstanceOf(ApiResource::class, $resource);
51+
$this->assertSame('dummy', $resource->getShortName());
52+
}
53+
54+
public function testMutateOperation(): void
55+
{
56+
$decorated = $this->createMock(ResourceMetadataCollectionFactoryInterface::class);
57+
$resourceClass = \stdClass::class;
58+
59+
$operations = new Operations();
60+
$operations->add('_api_Dummy_get', new HttpOperation());
61+
62+
$resourceMetadataCollection = new ResourceMetadataCollection($resourceClass);
63+
$resourceMetadataCollection[] = (new ApiResource())->withClass($resourceClass)->withOperations($operations);
64+
65+
$operationMutatorCollection = new OperationMutatorCollection();
66+
$operationMutatorCollection->addMutator('_api_Dummy_get', new DummyOperationMutator());
67+
68+
$customResourceMetadataCollectionFactory = new MutatorResourceMetadataCollectionFactory(new ResourceMutatorCollection(), $operationMutatorCollection, $decorated);
69+
70+
$decorated->expects($this->once())->method('create')->with($resourceClass)->willReturn(
71+
$resourceMetadataCollection,
72+
);
73+
74+
$resourceMetadataCollection = $customResourceMetadataCollectionFactory->create($resourceClass);
75+
76+
$resource = $resourceMetadataCollection->getIterator()->current();
77+
$this->assertInstanceOf(ApiResource::class, $resource);
78+
$this->assertEquals('custom_dummy', $resourceMetadataCollection->getOperation('_api_Dummy_get')->getShortName());
79+
}
80+
}
81+
82+
final class DummyResourceMutator implements ResourceMutatorInterface
83+
{
84+
public function __invoke(ApiResource $resource): ApiResource
85+
{
86+
return $resource->withShortName('dummy');
87+
}
88+
}
89+
90+
final class DummyOperationMutator implements OperationMutatorInterface
91+
{
92+
public function __invoke(Operation $operation): Operation
93+
{
94+
return $operation->withShortName('custom_dummy');
95+
}
96+
}

src/Symfony/Bundle/ApiPlatformBundle.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlResolverPass;
2525
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlTypePass;
2626
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\MetadataAwareNameConverterPass;
27-
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\MutatorPass;
27+
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\OperationMutatorPass;
28+
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\ResourceMutatorPass;
2829
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\SerializerMappingLoaderPass;
2930
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\TestClientPass;
3031
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\TestMercureHubPass;
@@ -63,6 +64,7 @@ public function build(ContainerBuilder $container): void
6364
$container->addCompilerPass(new TestMercureHubPass());
6465
$container->addCompilerPass(new AuthenticatorManagerPass());
6566
$container->addCompilerPass(new SerializerMappingLoaderPass());
66-
$container->addCompilerPass(new MutatorPass());
67+
$container->addCompilerPass(new ResourceMutatorPass());
68+
$container->addCompilerPass(new OperationMutatorPass());
6769
}
6870
}

0 commit comments

Comments
 (0)