Skip to content

feat(metadata) Customize Resource & operations #7213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

loic425
Copy link
Contributor

@loic425 loic425 commented Jun 13, 2025

Q A
Branch? main
Tickets
License MIT
Doc PR api-platform/docs#...

The idea of this feature is to allow customizing built-in endpoints from a third party API (ex Sylius E-commerce API).

<?php

namespace App\Mutator;

use ApiPlatform\Metadata\AsResourceMutator;
use ApiPlatform\Metadata\ApiResource;
use App\Entity\Speaker;

#[AsResourceMutator(resourceClass: Speaker::class)]
final class SpeakerResourceMutator
{
    public function __invoke(ApiResource $resource): ApiResource
    {
        $operations = $resource->getOperations();
        $operations->remove('_api_Speaker_get_collection');

        return $resource->withOperations($operations);
    }
}

@loic425 loic425 marked this pull request as draft June 13, 2025 14:18
@loic425 loic425 force-pushed the customize-resource-and-operations branch from d9fd4f2 to 15dcaa4 Compare June 13, 2025 14:21
@loic425 loic425 force-pushed the customize-resource-and-operations branch from 15dcaa4 to 7f165b9 Compare June 13, 2025 14:21
Copy link
Member

@soyuka soyuka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really cool, this is a follow up for php configured metadata as this allows to mutate vendor operations. I'll probably demonstrate this shortly.

I'd recommend to add a ResourceMutatorInterface. Obviously we should introduce an AsOperationMutator. I'd suggest that it takes:

public readonly string $operationName

URI Template would be nice as well but it will probably match several operations, therefore it's probably better to just use the $operationName.

use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
use Psr\Container\ContainerInterface;

final class CustomResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
final class CustomResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
final class MutatorResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface


foreach ($resourceMetadataCollection as $resource) {
foreach ($this->resourceMutators->get($resourceClass) as $mutators) {
$resource = $mutators($resource);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an interface would be able for typehint to know what the callable arguments are and make the code a little bit cleaner

@loic425 loic425 force-pushed the customize-resource-and-operations branch from cb16e36 to 10140bd Compare June 16, 2025 12:41
@loic425 loic425 force-pushed the customize-resource-and-operations branch from 10140bd to 1b3ab0e Compare June 16, 2025 13:03
namespace ApiPlatform\Metadata;

#[\Attribute(\Attribute::TARGET_CLASS)]
final class AsOperationMutator
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
final class AsOperationMutator
class AsOperationMutator

namespace ApiPlatform\Metadata;

#[\Attribute(\Attribute::TARGET_CLASS)]
final class AsResourceMutator
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
final class AsResourceMutator
class AsResourceMutator

use ApiPlatform\Metadata\OperationMutatorInterface;
use Psr\Container\ContainerInterface;

final class OperationMutatorCollection implements ContainerInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you comment on this class to explain what its used for? Also mark it @internal no?

{
private array $mutators;

public function addMutator(string $operationName, OperationMutatorInterface $mutator): void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function addMutator(string $operationName, OperationMutatorInterface $mutator): void
/**
* Adds a mutator to the container for a given operation name.
*/
public function add(string $operationName, OperationMutatorInterface $mutator): void


final class OperationMutatorCollection implements ContainerInterface
{
private array $mutators;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private array $mutators;
private array $mutators = [];

*/
public function __construct(
private readonly ContainerInterface $resourceMutators,
private readonly ContainerInterface $operationMutators,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure container interface has any generic template, you can't phpdoc like you did or we need a new interface that inherits the container interface (which is alright)

@@ -62,5 +64,7 @@ public function build(ContainerBuilder $container): void
$container->addCompilerPass(new TestMercureHubPass());
$container->addCompilerPass(new AuthenticatorManagerPass());
$container->addCompilerPass(new SerializerMappingLoaderPass());
$container->addCompilerPass(new ResourceMutatorPass());
$container->addCompilerPass(new OperationMutatorPass());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should create only one pass


foreach ($mutators as $id => $tags) {
foreach ($tags as $tag) {
$definition->addMethodCall('addMutator', [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$definition->addMethodCall('addMutator', [
$definition->addMethodCall('add', [


foreach ($mutators as $id => $tags) {
foreach ($tags as $tag) {
$definition->addMethodCall('addMutator', [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$definition->addMethodCall('addMutator', [
$definition->addMethodCall('add', [

}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge both compiler passes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants