-
-
Notifications
You must be signed in to change notification settings - Fork 918
WIP: Add events #2329
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
WIP: Add events #2329
Conversation
Thanks for working on this 🙂 |
f2c5977
to
3d505eb
Compare
@@ -95,20 +103,37 @@ public function __invoke(string $resourceClass = null, string $rootClass = null, | |||
$context += $resourceMetadata->getGraphqlAttribute($operationName, 'denormalization_context', [], true); | |||
$item = $this->normalizer->denormalize($args['input'], $resourceClass, ItemNormalizer::FORMAT, $context); | |||
$this->validate($item, $info, $resourceMetadata, $operationName); | |||
|
|||
if (null !== $this->dispatcher) { | |||
$this->dispatcher->dispatch(PreWriteEvent::NAME, new PreWriteEvent($operationName, $item)); |
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.
Guess there will be an issue here, since the $operationName
from GraphQL (create
, update
, delete
) is not the same as the HTTP methods used in REST
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.
Would it be useful too to have PreValidate and PostValidate events to keep the same logic than in REST ?
core/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php
Lines 130 to 134 in d678481
try { | |
$this->validator->validate($item, ['groups' => $validationGroups]); | |
} catch (ValidationException $e) { | |
throw Error::createLocatedError($e->getMessage(), $info->fieldNodes, $info->path); | |
} |
|
||
if (null !== $this->dispatcher) { | ||
$this->dispatcher->dispatch(PostReadEvent::NAME, new PostReadEvent($item)); | ||
} | ||
} catch (ItemNotFoundException $e) { | ||
return 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.
wouldn't it be better to change this block to
try {
if (null !== $this->dispatcher) {
$this->dispatcher->dispatch(PreReadEvent::NAME, new PreReadEvent(null));
}
$item = $this->iriConverter->getItemFromIri($args['id'], $baseNormalizationContext);
} catch (ItemNotFoundException $e) {
$item = null;
}
if (null !== $this->dispatcher) {
$this->dispatcher->dispatch(PostReadEvent::NAME, new PostReadEvent($item));
}
if (null === $item) {
return null;
}
to ensure that the PostReadEvent is always sent like in the ReadListener ?
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 think it should be dispatched when Read has failed.
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 think we can restrict the events to:
- read
- deserialize
- write
- validate
- serialize
Other ones are Symfony specific and should be handled using Symfony kernel events. WDYT?
@dunglas Sound good, but I think we should keep the pre- and post- events. |
b7cb661
to
ac09958
Compare
src/Event/Events.php
Outdated
const PRE_RESPOND = 'api_platform.pre_respond'; | ||
const POST_RESPOND = 'api_platform.post_respond'; | ||
|
||
const PRE_ADD_FORMAT = 'api_platform.pre_add_format'; |
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.
These events should be removed as they're not related to the resource.
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.
Please remove the "Add Format" events.
src/Event/PreWriteEvent.php
Outdated
|
||
final class PreWriteEvent extends Event | ||
{ | ||
const NAME = Events::PRE_WRITE; |
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 really weird thing to do. Did I miss the discussion on why this is done this way?
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.
You mean, defining a NAME
constant in the classes?
I did it mainly for comfort purpose, actually: it makes more sense to me to use a constant attached to the concerned class, so I kept it after @dunglas asked me to create the separate Events
class.
Do you want me to remove the NAME
constants?
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 see the value of these constants. Just use e.g. Events::PRE_WRITE
directly.
@@ -18,6 +18,7 @@ | |||
<argument type="service" id="api_platform.security.resource_access_checker" on-invalid="null" /> | |||
<argument type="service" id="request_stack" /> | |||
<argument>%api_platform.collection.pagination.enabled%</argument> | |||
<argument type="service" id="event_dispatcher" /> |
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.
You need to add the same on-invalid="null"
in this file too.
src/EventListener/ReadListener.php
Outdated
@@ -90,6 +95,10 @@ public function onKernelRequest(GetResponseEvent $event) | |||
try { | |||
$identifiers = $this->extractIdentifiers($request->attributes->all(), $attributes); | |||
|
|||
if (null !== $this->dispatcher) { | |||
$this->dispatcher->dispatch(PreReadEvent::NAME, new PreReadEvent($data)); |
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's really weird to have data in Pre Read. I don't think it makes sense...
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.
Perhaps what we could do is to keep a reference to the event object, then check if the data is already set on the event. If it is set, we should not override it.
Very glad this is being worked on. Is the idea with not attaching the Also, with the current events system they're executed in a very predictable linear order. Am I understanding that due to the way GraphQL resolvers work (one entity at a time), these events can be executed more like |
Hi, sorry for the late, I forgot to answer when I saw your comment ^^
Actually, these events are supposed to be higher levelled than Symfony's ones, so they're meant to contain only the details about the data themselves. If you want to get details about the request itself, you'll probably want to intercept Symfony's events instead.
That GraphQL part is actually quite cryptic to me, I need to study that more… if you have any tips to make my integration of the events better, feel free to share them 🙂 |
No worries!
I thought one of the motivations here was that Symfony events aren't getting fired during the lifecycle of GraphQL requests (see #2167 )? So do you mean intercepting a combination of the 2? I.e. in a single listener, listen to Symfony's request event to store the route/method, then combine that with the data retrieved from this event, to execute the side effect? I just think that individually, these events with "only the details about the data", and Symfony's events which don't have the controller result, don't contain enough information to add granular side effects in a lot of real world cases. Both are needed together most of the time. |
Yes, that's what's planned ideally: an event that's risen, whenever the action comes from REST or GraphQL :)
I think I see what you mean, problem is, I'm not sure I can get that Symfony event, for the same reason we can't do it currently like described in #2167… We'll need to think more about it, maybe it would be a good thing to have an object that contain the information that may be useful and are available for both REST and GraphQL? |
When I wrote my own quick workaround for #2167 (master...jamesisaac:graphql-events), I just injected the To me that solution added a lot of convenience, as the data alone doesn't tell you the context of how that data is being reached in the API (not even whether it's a query or a mutation). But I know you mentioned these events are meant to be higher level, so maybe there's a separation of concerns I'm missing out on. |
What about adding to all the events an object that would be common to both REST and GraphQL, and would contain the context of the events? This way, you would get as many information as possible, without having to take care about Symfony's events being actually risen or not. Currently, I can see at least two things this event could store:
WDYT? Do you see any other thing that could be interesting to add in such an object? @dunglas and @teohhanhui, I'd be interested by your opinion about that too :) |
That would be very helpful to me. In addition to your list, the things I make use of are:
|
9481ef2
to
03edea2
Compare
Closing this PR since a better implementation is proposed on #2506. |
Adding events to make API Platform’s actions tweaking easier.
Todos:
\ApiPlatform\Core\EventListener\*
listeners)