Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

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.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

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!

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?

Leave a comment!

6
Login or Register to join the conversation
Default user avatar
Default user avatar Neandher Carlos | posted 5 years ago

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

Reply

Hey!

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

Cheers!

Reply
Default user avatar
Default user avatar Neandher Carlos | weaverryan | posted 5 years ago

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/nea...

1 Reply

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!

Reply
Default user avatar
Default user avatar Neandher Carlos | weaverryan | posted 5 years ago

Yeah!!!!! Awesome!!

Reply
Default user avatar
Default user avatar Neandher Carlos | weaverryan | posted 5 years ago

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

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony. The concepts of REST and serialization are still valid, but I recommend using API Platform in new Symfony apps.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.3.3",
        "symfony/symfony": "2.6.*", // v2.6.11
        "doctrine/orm": "~2.2,>=2.2.3,<2.5", // v2.4.7
        "doctrine/dbal": "<2.5", // v2.4.4
        "doctrine/doctrine-bundle": "~1.2", // v1.4.0
        "twig/extensions": "~1.0", // v1.2.0
        "symfony/assetic-bundle": "~2.3", // v2.6.1
        "symfony/swiftmailer-bundle": "~2.3", // v2.3.8
        "symfony/monolog-bundle": "~2.4", // v2.7.1
        "sensio/distribution-bundle": "~3.0,>=3.0.12", // v3.0.21
        "sensio/framework-extra-bundle": "~3.0,>=3.0.2", // v3.0.7
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.0
        "hautelook/alice-bundle": "0.2.*", // 0.2
        "jms/serializer-bundle": "0.13.*", // 0.13.0
        "white-october/pagerfanta-bundle": "^1.0" // v1.2.4
    },
    "require-dev": {
        "sensio/generator-bundle": "~2.3", // v2.5.3
        "behat/behat": "~3.0", // v3.0.15
        "behat/mink-extension": "~2.0.1", // v2.0.1
        "behat/mink-goutte-driver": "~1.1.0", // v1.1.0
        "behat/mink-selenium2-driver": "~1.2.0", // v1.2.0
        "phpunit/phpunit": "~4.6.0" // 4.6.4
    }
}
userVoice