If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeI gotta admit... I kinda cheated with the last example. The logic in the listener was really easy because the only object that we needed to operate on was the currently-authenticated User
. That made it super easy to grab that one object and set some data on it.
But... what if the object we need to set the data on is not the current User
object... it's a CheeseListing
or something else. For example, if the user goes to /api/cheeses/1.jsonld
, how can we get access to the CheeseListing
object with id 1 so that we can set data on it? Or even more interesting, if the user goes to /api/cheeses.jsonld
, how could we get access to all of the CheeseListing
objects that are about to be displayed so that we can set custom data on all of them?
To answer that question, search for "API Platform events". We haven't talked much about the API Platform event system yet. But in reality, almost everything that API Platform does is actually done by a listener behind the scenes.
For example, this ReadListener
is what's responsible for calling the data providers, which fetch the objects from the database. Then, the DeserializeListener
is what deserializes the JSON data on POST
, PUT
and PATCH
requests to update or create an object. Later, ValidateListener
executes validation and WriteListener
calls the data persisters. There are a few other listeners, but these do the majority of the work.
Now check this out: both ReadListener
and DeserializeListener
listen to the kernel.request
event - the same event that we are listening to! And these have a priority of 4 and 2.
When you create a subscriber, you can specify a priority in the getSubscribedEvents()
method:
... lines 1 - 9 | |
class SetIsMeOnCurrentUserSubscriber implements EventSubscriberInterface | |
{ | |
... lines 12 - 33 | |
public static function getSubscribedEvents() | |
{ | |
return [ | |
RequestEvent::class => 'onRequestEvent', | |
]; | |
} | |
} |
Since we haven't, our priority is zero. That means that both ReadListener
and DeserializeListener
are called before us.
That's important because after calling the data providers, ReadListener
stores that information on a request attribute. And if DeserializeListener
creates a new object, it does the same thing!
Check this out, in our listener - as an experiment - add dd($event->getRequest()->attributes->get('data'))
:
... lines 1 - 9 | |
class SetIsMeOnCurrentUserSubscriber implements EventSubscriberInterface | |
{ | |
... lines 12 - 18 | |
public function onRequestEvent(RequestEvent $event) | |
{ | |
dd($event->getRequest()->attributes->get('data')); | |
... lines 22 - 32 | |
} | |
... lines 34 - 40 | |
} |
That's the special key where API platform puts the "data" for the current API request. When we spin over now and refresh the collections endpoint for users... awesome! It's our Paginator
object! We could loop over that to get access to every User
object that is about to be serialized.
And when we go to /api/users/1.jsonld
, this dumps the individual User
object.
So... this is awesome! At any point, we can grab the data
key off of the request attributes to get access to the item or items for the current API request. This is how you could set a custom field for any entity inside a listener.
Remove that dd()
:
... lines 1 - 9 | |
class SetIsMeOnCurrentUserSubscriber implements EventSubscriberInterface | |
{ | |
... lines 12 - 18 | |
public function onRequestEvent(RequestEvent $event) | |
{ | |
dd($event->getRequest()->attributes->get('data')); | |
... lines 22 - 32 | |
} | |
... lines 34 - 40 | |
} |
I really like the listener solution! Though, it does have two downsides, which may or may not be important. First, the event system isn't used in API Platform's GraphQL support... so this won't work if you're using GraphQL. And second, if you're writing some custom console commands, the isMe
field will never be set there... because there's no request and so no RequestEvent
!
For the isMe
field... that's probably fine... because nobody is the currently-authenticated user in a console command anyways. But if you did want a custom field to be populated everywhere - even in a console command - we have one more solution: a Doctrine postLoad
listener. Let's check that out next!
Hey @Yangzhi,
Try to play with event priorities
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['onKernelRequest', 50] // or -50
];
}
probably my example is not very accurate, so try to use:
bin/console debug:event-dispatcher kernel.request
to find best priority for your need!
Cheers!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"api-platform/core": "^2.1", // v2.5.10
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/annotations": "^1.0", // 1.12.1
"doctrine/doctrine-bundle": "^2.0", // 2.1.2
"doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.2
"doctrine/orm": "^2.4.5", // 2.8.2
"nelmio/cors-bundle": "^2.1", // 2.1.0
"nesbot/carbon": "^2.17", // 2.39.1
"phpdocumentor/reflection-docblock": "^3.0 || ^4.0 || ^5.0", // 5.2.2
"ramsey/uuid-doctrine": "^1.6", // 1.6.0
"symfony/asset": "5.1.*", // v5.1.5
"symfony/console": "5.1.*", // v5.1.5
"symfony/debug-bundle": "5.1.*", // v5.1.5
"symfony/dotenv": "5.1.*", // v5.1.5
"symfony/expression-language": "5.1.*", // v5.1.5
"symfony/flex": "^1.1", // v1.18.7
"symfony/framework-bundle": "5.1.*", // v5.1.5
"symfony/http-client": "5.1.*", // v5.1.5
"symfony/monolog-bundle": "^3.4", // v3.5.0
"symfony/security-bundle": "5.1.*", // v5.1.5
"symfony/twig-bundle": "5.1.*", // v5.1.5
"symfony/validator": "5.1.*", // v5.1.5
"symfony/webpack-encore-bundle": "^1.6", // v1.8.0
"symfony/yaml": "5.1.*" // v5.1.5
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3", // 3.3.2
"symfony/browser-kit": "5.1.*", // v5.1.5
"symfony/css-selector": "5.1.*", // v5.1.5
"symfony/maker-bundle": "^1.11", // v1.23.0
"symfony/phpunit-bridge": "5.1.*", // v5.1.5
"symfony/stopwatch": "5.1.*", // v5.1.5
"symfony/twig-bundle": "5.1.*", // v5.1.5
"symfony/web-profiler-bundle": "5.1.*", // v5.1.5
"zenstruck/foundry": "^1.1" // v1.8.0
}
}
hi,if I want to do something before every api request(post,get,patch,put,delete,getcollection.... all of it), which event should I write it on?
for now,i have TokenAuthorizationSubscriber, but when i use postman reuqest api like host/api/areas. the
dd()
not working,but if i request router on browser,thedd()
is working