Evaluating the Link Expression
Keep on Learning!
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 SubscribeBefore we fix the expression stuff, remove the class
option from getSubscribedEvents()
because we want this to be called for all classes:
// ... lines 1 - 12 | |
class LinkSerializationSubscriber implements EventSubscriberInterface | |
{ | |
// ... lines 15 - 62 | |
public static function getSubscribedEvents() | |
{ | |
return array( | |
array( | |
'event' => 'serializer.post_serialize', | |
'method' => 'onPostSerialize', | |
'format' => 'json', | |
) | |
); | |
} | |
} |
Avoiding Duplicated _links
When you do that, things still work. But now: clear your cache in the terminal:
./app/console cache:clear
That's not normally something you need to do - but the JMSSerializerBundle doesn't properly update its cache when you change this option. Refresh again.
Ah, huge error! Apparently there is already data for _links
!? That's a bit weird.
Ah, but wait: one of the things we're serializing is the paginated collection itself, which already has a _links
property.
For better or worse, the JMS serializer library doesn't let you overwrite data on a field. To fix this, add an if
statement that only adds _links
if we found some on the object:
// ... lines 1 - 12 | |
class LinkSerializationSubscriber implements EventSubscriberInterface | |
{ | |
// ... lines 15 - 27 | |
public function onPostSerialize(ObjectEvent $event) | |
{ | |
// ... lines 30 - 47 | |
if ($links) { | |
$visitor->addData('_links', $links); | |
} | |
} | |
// ... lines 52 - 72 | |
} |
There's an even better fix - but I'll leave it to you. That would be to go into PaginatedCollection
and replace its links with the @Link
annotation. This would be a little bit of work, but I believe in you!
Evaluating the Expression
Refresh the browser again. Things look good! Time to evaluate the expression.
To use the expression language, we just need to create an ExpressionLanguage
object. We could register this as a service, but I'll take a shortcut and instantiate a new expression language right inside the constructor: $this->expressionEngine = new ExpressionLanguage()
:
// ... lines 1 - 12 | |
class LinkSerializationSubscriber implements EventSubscriberInterface | |
{ | |
// ... lines 15 - 18 | |
private $expressionLanguage; | |
// ... line 20 | |
public function __construct(RouterInterface $router, Reader $annotationReader) | |
{ | |
// ... lines 23 - 24 | |
$this->expressionLanguage = new ExpressionLanguage(); | |
} | |
// ... lines 27 - 72 | |
} |
That class lives in the ExpressionLanguage
component.
Rename that property to expressionLanguage
. Later, if I do want to register this as a service instead of creating it new right here, that'll be really easy.
Wrap $annotation->params
in a call to $this->resolveParams()
and pass it the params and the object, since we'll need to pass that into the expression itself:
// ... lines 1 - 12 | |
class LinkSerializationSubscriber implements EventSubscriberInterface | |
{ | |
// ... lines 15 - 27 | |
public function onPostSerialize(ObjectEvent $event) | |
{ | |
// ... lines 30 - 37 | |
foreach ($annotations as $annotation) { | |
if ($annotation instanceof Link) { | |
$uri = $this->router->generate( | |
$annotation->route, | |
$this->resolveParams($annotation->params, $object) | |
); | |
$links[$annotation->name] = $uri; | |
} | |
} | |
// ... lines 47 - 50 | |
} | |
// ... lines 52 - 72 | |
} |
Add the new private function resolveParams()
and then loop over $params
: foreach ($params as $key => $param)
:
// ... lines 1 - 12 | |
class LinkSerializationSubscriber implements EventSubscriberInterface | |
{ | |
// ... lines 15 - 52 | |
private function resolveParams(array $params, $object) | |
{ | |
foreach ($params as $key => $param) { | |
// ... lines 56 - 57 | |
} | |
// ... lines 59 - 60 | |
} | |
// ... lines 62 - 72 | |
} |
For each param, we'll replace it with $this->expressionLanguage->evaluate()
. Pass it $param
- that's the expression. Next, since the expressions are expecting a variable called object
, pass an array as the second argument with an object
key set to $object
. And let's not forget our $object
argument to this method!
// ... lines 1 - 12 | |
class LinkSerializationSubscriber implements EventSubscriberInterface | |
{ | |
// ... lines 15 - 52 | |
private function resolveParams(array $params, $object) | |
{ | |
foreach ($params as $key => $param) { | |
$params[$key] = $this->expressionLanguage | |
->evaluate($param, array('object' => $object)); | |
} | |
// ... lines 59 - 60 | |
} | |
// ... lines 62 - 72 | |
} |
Finally, wrap this up return $params;
. Now, each parameter is evaluated through the expression language, which is a lot like Twig:
// ... lines 1 - 12 | |
class LinkSerializationSubscriber implements EventSubscriberInterface | |
{ | |
// ... lines 15 - 52 | |
private function resolveParams(array $params, $object) | |
{ | |
// ... lines 55 - 59 | |
return $params; | |
} | |
// ... lines 62 - 72 | |
} |
Ok, back to the browser. There it is! How about our test?
./bin/phpunit -c app --filter testGETProgrammer
Hey, they're passing too! Amazing!
In just a few short minutes, we made an entirely reusable linking system. I will admit that this idea was stolen from a library called Hateoas. Now, don't you feel dangerous?
"That would be to go into PaginatedCollection and replace its links with the @Link annotation. This would be a little bit of work, but I believe in you!"
Any else tips for do this????? :)