Buy Access to Course
09.

Evaluating the Link Expression

Share this awesome video!

|

Keep on Learning!

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?