Buy

Evaluating the Link Expression

Before we fix the expression stuff, remove the class option from getSubscribedEvents() because we want this to be called for all classes:

74 lines src/AppBundle/Serializer/LinkSerializationSubscriber.php
... 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:

74 lines src/AppBundle/Serializer/LinkSerializationSubscriber.php
... 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():

74 lines src/AppBundle/Serializer/LinkSerializationSubscriber.php
... 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:

74 lines src/AppBundle/Serializer/LinkSerializationSubscriber.php
... 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):

74 lines src/AppBundle/Serializer/LinkSerializationSubscriber.php
... 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!

74 lines src/AppBundle/Serializer/LinkSerializationSubscriber.php
... 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:

74 lines src/AppBundle/Serializer/LinkSerializationSubscriber.php
... 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?

Leave a comment!

  • 2016-04-05 Neandher Carlos

    Yeah!!!!! Awesome!!

  • 2016-04-05 weaverryan

    Awesome work :). I just finished coding up episode *5*, where we'll use the HATEOAS library (github.com/willdurand/hateoas) to do even a bit more with links. It doesn't offer any big advantages, but I think you'll find it interesting. It should come out next month.

    Keep up the good work!

  • 2016-04-02 Neandher Carlos

    I try implement this now and for this work i need change PaginatedCollection.php because on do serializer for this class, he add _links and LinkSerializationSubscriber.php add _links too. Soo i add @Serializer\ExclusionPolicy("all") and works fine.
    https://gist.github.com/neandh...

  • 2016-04-01 Neandher Carlos

    Wooow!!! I was much too close to your code! Thanks for clarifying my doubts!! Learning all this is awesome!!!!!
    SF + KNPU === \o/

  • 2016-04-01 weaverryan

    Hey!

    Here's the solution :). It indeed was a bit more complex than I originally thought - sorry about that! https://gist.github.com/weaver...

    Cheers!

  • 2016-03-30 Neandher Carlos

    "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????? :)