-
-
Notifications
You must be signed in to change notification settings - Fork 849
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
[GraphQL] Add custom mutations #2447
[GraphQL] Add custom mutations #2447
Conversation
if ('create' !== $operationName && 'update' !== $operationName) { | ||
$mutationId = $resourceMetadata->getGraphqlAttribute($operationName, 'mutation'); | ||
if (!$this->mutationLocator->has($mutationId)) { | ||
@trigger_error(sprintf('The mutation %s does not exist', $mutationId)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not throwing an exception instead?
} else { | ||
$data['id'] = null; | ||
} | ||
break; | ||
case 'create': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two following lines can be removed then, right?
* | ||
* @author Raoul Clais <raoul.clais@gmail.com> | ||
*/ | ||
final class MutationPass implements CompilerPassInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GraphQlMutationResolverPass
instead?
* | ||
* @author Raoul Clais <raoul.clais@gmail.com> | ||
*/ | ||
interface MutationInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MutationResolverInterface
instead?
@@ -111,6 +112,8 @@ public function load(array $configs, ContainerBuilder $container) | |||
->addTag('api_platform.subresource_data_provider'); | |||
$container->registerForAutoconfiguration(FilterInterface::class) | |||
->addTag('api_platform.filter'); | |||
$container->registerForAutoconfiguration(MutationInterface::class) | |||
->addTag('api_platform.graphql_mutation'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api_platform.graphql.mutation_resolver
instead?
use Symfony\Component\DependencyInjection\Reference; | ||
|
||
/** | ||
* Injects mutations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* Injects mutations. | |
* Injects GraphQL Mutation resolvers. |
|
||
<!-- Mutation --> | ||
|
||
<service id="api_platform.graphql.mutation_locator" class="Symfony\Component\DependencyInjection\ServiceLocator"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api_platform.graphql.mutation_resolver_locator
instead?
It should be great to have a custom input, but can be done later in another PR. |
f1b592a
to
aecdec8
Compare
use GraphQL\Type\Definition\ResolveInfo; | ||
|
||
/** | ||
* Interface MutationInterface. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be replaced by an explicit definition or removed
private $resourceAccessChecker; | ||
private $validator; | ||
|
||
public function __construct(IriConverterInterface $iriConverter, DataPersisterInterface $dataPersister, NormalizerInterface $normalizer, ResourceMetadataFactoryInterface $resourceMetadataFactory, ResourceAccessCheckerInterface $resourceAccessChecker = null, ValidatorInterface $validator = null) | ||
public function __construct(IriConverterInterface $iriConverter, DataPersisterInterface $dataPersister, NormalizerInterface $normalizer, ResourceMetadataFactoryInterface $resourceMetadataFactory, ContainerInterface $mutationLocator, ResourceAccessCheckerInterface $resourceAccessChecker = null, ValidatorInterface $validator = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a BC break. It's allowed for @experimental
classes but it would be better to avoid it. Can we also made this argument optional? It doesn't have to be mandatory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be made optional but do we really want to handle the "breaking" case (when it's null)?
*/ | ||
interface MutationResolverInterface | ||
{ | ||
public function __invoke($item, array $input, ResolveInfo $info); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't the mutation return something? How is it take into account by the Schema generator?
fde97ce
to
11f2e33
Compare
/** | ||
* @Given there is a custom mutation dummy object | ||
*/ | ||
public function thereIsACustomMutationDummyObjects() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public function thereIsACustomMutationDummyObjects() | |
public function thereIsACustomMutationDummyObject() |
} | ||
|
||
$mutationId = $resourceMetadata->getGraphqlAttribute($operationName, 'mutation'); | ||
if (!$this->mutationLocator->has($mutationId)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!$this->mutationLocator->has($mutationId)) { | |
if (!$this->mutationLocator || !$this->mutationLocator->has($mutationId)) { |
$context = null === $item ? ['resource_class' => $resourceClass] : ['resource_class' => $resourceClass, 'object_to_populate' => $item]; | ||
$context += $resourceMetadata->getGraphqlAttribute($operationName, 'denormalization_context', [], true); | ||
$item = $this->normalizer->denormalize($args['input'], $resourceClass, ItemNormalizer::FORMAT, $context); | ||
|
||
if ('create' !== $operationName && 'update' !== $operationName) { | ||
if (null === $this->mutationLocator) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really like to throw an exception here because the service is null. I have added my suggestion below.
/** | ||
* @return mixed The mutated item | ||
*/ | ||
public function __invoke($item, array $input, ResolveInfo $info); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public function __invoke($item, array $input, ResolveInfo $info); | |
public function __invoke($item, array $context); |
Maybe having a context is better to be independent of webonyx/graphql-php.
Then in context we have:
['input' => [], 'resolve_info' => $info]
* | ||
* @author Raoul Clais <raoul.clais@gmail.com> | ||
*/ | ||
class CustomMutationDummy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be added also as document for MongoDB.
/** | ||
* {@inheritdoc} | ||
* | ||
* @throws RuntimeException |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be removed.
effbb45
to
bcff6f6
Compare
bcff6f6
to
a106494
Compare
@SCIF, @lukasluecke I think this PR is good to go, could you take a look please? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me 👍
It looks pretty same as custom query PR's. And complains are generally the same:
I'm pretty sure, that getting rid of Regardless mentioned above, it looks good. |
This is not right. You can with both custom mutation and query resolvers. I've added a Behat test in the custom query resolver PR and I will document it. Maybe I should add one here too.
I'm not sure to follow you. The documentation about custom operations for REST is here: https://api-platform.com/docs/core/operations/#creating-custom-operations-and-controllers It says:
This is exactly what this PR does for GraphQL. |
I think it's pretty clear that the merged PR was "custom queries" and this one is "custom mutations", and that should and will be reflected in the docs. Anything beyond that can be implemented at any point in the future - if you have any suggestions on that, maybe you can start by creating the issue, and we can discuss there? I believe the id is not required, which should solve almost any concerns regarding it - yes, you can not remove the argument right now, but it does not really matter that much. |
4f1e61f
to
a106494
Compare
Thank you @raoulclais 🎉 |
Introduce a new
MutationResolverInterface
interface.Create a service which implements this interface and declare your custom mutation on your entity:
If
null
is returned from the resolver, the item will not be persisted.