Before 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', | |
) | |
); | |
} | |
} |
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!
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?