Skip to content

Commit 10140bd

Browse files
committed
Add Operation mutator
1 parent 7f165b9 commit 10140bd

14 files changed

+235
-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 <[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+
#[\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 <[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\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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace ApiPlatform\Metadata;
4+
5+
interface OperationMutatorInterface
6+
{
7+
public function __invoke(Operation $operation): Operation;
8+
}

src/Metadata/Resource/Factory/CustomResourceMetadataCollectionFactory.php

Lines changed: 0 additions & 46 deletions
This file was deleted.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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\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+
* @param ResourceMetadataCollectionFactoryInterface|null $decorated
30+
*/
31+
public function __construct(
32+
private readonly ContainerInterface $resourceMutators,
33+
private readonly ContainerInterface $operationMutators,
34+
private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null,
35+
) {
36+
}
37+
38+
public function create(string $resourceClass): ResourceMetadataCollection
39+
{
40+
$resourceMetadataCollection = new ResourceMetadataCollection($resourceClass);
41+
if ($this->decorated) {
42+
$resourceMetadataCollection = $this->decorated->create($resourceClass);
43+
}
44+
45+
$newMetadataCollection = new ResourceMetadataCollection($resourceClass);
46+
47+
foreach ($resourceMetadataCollection as $resource) {
48+
$resource = $this->mutateResource($resource, $resourceClass);
49+
$operations = $this->mutateOperations($resource->getOperations() ?? new Operations());
50+
$resource = $resource->withOperations($operations);
51+
52+
$newMetadataCollection[] = $resource;
53+
}
54+
55+
return $newMetadataCollection;
56+
}
57+
58+
private function mutateResource(ApiResource $resource, string $resourceClass): ApiResource
59+
{
60+
foreach ($this->resourceMutators->get($resourceClass) as $mutator) {
61+
$resource = $mutator($resource);
62+
}
63+
64+
return $resource;
65+
}
66+
67+
private function mutateOperations(Operations $operations): Operations
68+
{
69+
$newOperations = new Operations();
70+
71+
/** @var Operation $operation */
72+
foreach ($operations as $key => $operation) {
73+
foreach ($this->operationMutators->get($key) as $mutator) {
74+
$operation = $mutator($operation);
75+
}
76+
77+
$newOperations->add($key, $operation);
78+
}
79+
80+
return $newOperations;
81+
}
82+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace ApiPlatform\Metadata;
4+
5+
interface ResourceMutatorInterface
6+
{
7+
public function __invoke(ApiResource $resource): ApiResource;
8+
}

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
}

src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@
3131
use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface;
3232
use ApiPlatform\GraphQl\Type\Definition\TypeInterface as GraphQlTypeInterface;
3333
use ApiPlatform\Metadata\ApiResource;
34+
use ApiPlatform\Metadata\AsOperationMutator;
3435
use ApiPlatform\Metadata\AsResourceMutator;
3536
use ApiPlatform\Metadata\FilterInterface;
37+
use ApiPlatform\Metadata\OperationMutatorInterface;
38+
use ApiPlatform\Metadata\ResourceMutatorInterface;
3639
use ApiPlatform\Metadata\UriVariableTransformerInterface;
3740
use ApiPlatform\Metadata\UrlGeneratorInterface;
3841
use ApiPlatform\OpenApi\Model\Tag;
@@ -186,13 +189,29 @@ public function load(array $configs, ContainerBuilder $container): void
186189
->addTag('container.excluded', ['source' => 'by #[ApiResource] attribute']);
187190
});
188191
$container->registerAttributeForAutoconfiguration(AsResourceMutator::class,
189-
static function (ChildDefinition $definition, AsResourceMutator $attribute): void {
192+
static function (ChildDefinition $definition, AsResourceMutator $attribute, \ReflectionClass $reflector): void {
193+
if (!is_a($reflector->name, ResourceMutatorInterface::class, true)) {
194+
throw new RuntimeException(sprintf('Resource mutator "%s" should implement %s', $reflector->name, ResourceMutatorInterface::class));
195+
}
196+
190197
$definition->addTag('api_platform.resource_mutator', [
191198
'resourceClass' => $attribute->resourceClass,
192199
]);
193200
},
194201
);
195202

203+
$container->registerAttributeForAutoconfiguration(AsOperationMutator::class,
204+
static function (ChildDefinition $definition, AsOperationMutator $attribute, \ReflectionClass $reflector): void {
205+
if (!is_a($reflector->name, OperationMutatorInterface::class, true)) {
206+
throw new RuntimeException(sprintf('Operation mutator "%s" should implement %s', $reflector->name, OperationMutatorInterface::class));
207+
}
208+
209+
$definition->addTag('api_platform.operation_mutator', [
210+
'operationName' => $attribute->operationName,
211+
]);
212+
},
213+
);
214+
196215
if (!$container->has('api_platform.state.item_provider')) {
197216
$container->setAlias('api_platform.state.item_provider', 'api_platform.state_provider.object');
198217
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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\Symfony\Bundle\DependencyInjection\Compiler;
15+
16+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\Reference;
19+
20+
class OperationMutatorPass implements CompilerPassInterface
21+
{
22+
public function process(ContainerBuilder $container): void
23+
{
24+
if (!$container->hasDefinition('api_platform.metadata.mutator_collection.operation')) {
25+
return;
26+
}
27+
28+
$definition = $container->getDefinition('api_platform.metadata.mutator_collection.operation');
29+
30+
$mutators = $container->findTaggedServiceIds('api_platform.operation_mutator');
31+
32+
foreach ($mutators as $id => $tags) {
33+
foreach ($tags as $tag) {
34+
$definition->addMethodCall('addMutator', [
35+
$tag['operationName'],
36+
new Reference($id),
37+
]);
38+
}
39+
}
40+
}
41+
}

src/Symfony/Bundle/DependencyInjection/Compiler/MutatorPass.php renamed to src/Symfony/Bundle/DependencyInjection/Compiler/ResourceMutatorPass.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use Symfony\Component\DependencyInjection\ContainerBuilder;
1818
use Symfony\Component\DependencyInjection\Reference;
1919

20-
class MutatorPass implements CompilerPassInterface
20+
class ResourceMutatorPass implements CompilerPassInterface
2121
{
2222
public function process(ContainerBuilder $container): void
2323
{

src/Symfony/Bundle/Resources/config/metadata/mutator.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@
66

77
<services>
88
<service id="api_platform.metadata.mutator_collection.resource" class="ApiPlatform\Metadata\Mutator\ResourceMutatorCollection" public="false" />
9+
10+
<service id="api_platform.metadata.mutator_collection.operation" class="ApiPlatform\Metadata\Mutator\OperationMutatorCollection" public="false" />
911
</services>
1012
</container>

src/Symfony/Bundle/Resources/config/metadata/resource.xml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@
3030
<argument type="service" id="service_container" on-invalid="null" />
3131
</service>
3232

33-
<service id="api_platform.metadata.resource.metadata_collection_factory.custom" class="ApiPlatform\Metadata\Resource\Factory\CustomResourceMetadataCollectionFactory" decorates="api_platform.metadata.resource.metadata_collection_factory" decoration-priority="800" public="false" >
33+
<service id="api_platform.metadata.resource.metadata_collection_factory.mutator" class="ApiPlatform\Metadata\Resource\Factory\MutatorResourceMetadataCollectionFactory" decorates="api_platform.metadata.resource.metadata_collection_factory" decoration-priority="800" public="false" >
3434
<argument type="service" id="api_platform.metadata.mutator_collection.resource" />
35-
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.custom.inner" />
35+
<argument type="service" id="api_platform.metadata.mutator_collection.operation" />
36+
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory.mutator.inner" />
3637
</service>
3738

3839
<service id="api_platform.metadata.resource.metadata_collection_factory.concerns" class="ApiPlatform\Metadata\Resource\Factory\ConcernsResourceMetadataCollectionFactory" decorates="api_platform.metadata.resource.metadata_collection_factory" decoration-priority="800" public="false">

tests/Symfony/Bundle/ApiPlatformBundleTest.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlResolverPass;
2626
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlTypePass;
2727
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\MetadataAwareNameConverterPass;
28-
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\MutatorPass;
28+
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\OperationMutatorPass;
29+
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\ResourceMutatorPass;
2930
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\SerializerMappingLoaderPass;
3031
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\TestClientPass;
3132
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\TestMercureHubPass;
@@ -59,7 +60,8 @@ public function testBuild(): void
5960
$containerProphecy->addCompilerPass(Argument::type(TestMercureHubPass::class))->willReturn($containerProphecy->reveal())->shouldBeCalled();
6061
$containerProphecy->addCompilerPass(Argument::type(AuthenticatorManagerPass::class))->willReturn($containerProphecy->reveal())->shouldBeCalled();
6162
$containerProphecy->addCompilerPass(Argument::type(SerializerMappingLoaderPass::class))->willReturn($containerProphecy->reveal())->shouldBeCalled();
62-
$containerProphecy->addCompilerPass(Argument::type(MutatorPass::class))->willReturn($containerProphecy->reveal())->shouldBeCalled();
63+
$containerProphecy->addCompilerPass(Argument::type(ResourceMutatorPass::class))->willReturn($containerProphecy->reveal())->shouldBeCalled();
64+
$containerProphecy->addCompilerPass(Argument::type(OperationMutatorPass::class))->willReturn($containerProphecy->reveal())->shouldBeCalled();
6365

6466
$bundle = new ApiPlatformBundle();
6567
$bundle->build($containerProphecy->reveal());

0 commit comments

Comments
 (0)