Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This course is archived!
This tutorial uses a deprecated micro-framework called Silex. The fundamentals of REST are still ?valid, but the code we use can't be used in a real application.

Battles and Programmer - Link them!

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 $12.00

Let's do something really interesting. First I want to see what the Battle response looks like - so I'll say "And print last response":

... lines 1 - 25
Scenario: GET one battle
... lines 27 - 29
When I request "GET /api/battles/%battles.last.id%"
... lines 31 - 33
And print last response

Now, run Behat.

php vendor/bin/behat features/api/battle.feature

At the bottom, you can see that response has programmer and project information right inside of it. That's because the Battle class has two properties that hold these objects. The serializer sees these objects, and serializes them recursively.

Controlling (Removing) Embedded Resources

In a couple of chapters, we're going to talk about embedding resources where we do this on purpose. But for now, I want to avoid it: if I'm getting a Battle, I only want to retrieve that Battle. So like before, we need to take control of how this is serialized.

I'll copy the Serializer use statement from Programmer into Battle. Next, let's also copy the ExclusionPolicy annotation into Battle, which tells the serializer to only serialize properties that we explicitly expose with the @Serializer\Expose annotation. Which properties you want to expose is totally up to you. I'll do it with the $id, of course $didProgrammerWin, $foughtAt and we'll also expose the $notes property:

... lines 1 - 4
use JMS\Serializer\Annotation as Serializer;
/**
* @Serializer\ExclusionPolicy("all")
*/
class Battle
{
/**
* @Serializer\Expose()
*/
public $id;
... lines 16 - 26
/**
* @Serializer\Expose()
*/
public $didProgrammerWin;
/**
* @Serializer\Expose()
*/
public $foughtAt;
/**
* @Serializer\Expose()
*/
public $notes;
}

You guys know the drill - let's run just line 26 to make sure things still pass:

php vendor/bin/behat features/api/battle.feature:26

We're still printing out the last response, but nothing is broken, so that's good. You can see that it's in fact not printing out the programmer or project anymore.

But now that we did that, if we think about somebody who is retrieving a single battle, they might want to know who the programmer was. I can hear our user now:

"ok, I see this battle, but what programmer fought in it and how can 
get more information about them?"

So what I'll do is add a line to the scenario and look for a new, invented field: And the "programmerUri" field should equal "/api/programmers/Fred":

... lines 1 - 25
Scenario: GET one battle
... lines 27 - 33
And the "programmerUri" property should equal "/api/programmers/Fred"

So we're saying that the response will have this extra field that's not really on the Battle class. What's cool about this is that as an API client, I'll see this and say:

"Oh, Fred was the programmer, and I can just go to that URL to get his details".

First, let's run this and watch it fail:

php vendor/bin/behat features/api/battle.feature:26

Using a VirtualProperty

So how can we add this? The problem is that there really is no programmerUri property on Battle. So one of the cool features from JMS serializer is the ability to have virtual properties.

Create a new function called getProgrammerUri - the name of the method is important - and for right now, I'm just going to hardcode in the URL instead of generating it from the name like we have been doing. I'll fix that later:

... lines 1 - 9
class Battle
{
... lines 12 - 44
public function getProgrammerUri()
{
return '/api/programmers/'.$this->programmer->nickname;
}
}

But just because you have this method does not means it's going to be served out to your API. You can use an annotation called @Serializer\VirtualProperty:

... lines 1 - 41
/**
* @Serializer\VirtualProperty()
*/
public function getProgrammerUri()
{
return '/api/programmers/'.$this->programmer->nickname;
}
... lines 49 - 50

And just like that, it's going to call getProgrammerUri, strip the get off of there, and look like a programmerUri field. And when I run my test, it does exactly that.

Congratulations! We just added our first link. And we're going to add a bunch more!

Leave a comment!

0
Login or Register to join the conversation
Cat in space

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

This tutorial uses a deprecated micro-framework called Silex. The fundamentals of REST are still ?valid, but the code we use can't be used in a real application.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "silex/silex": "~1.0", // v1.3.2
        "symfony/twig-bridge": "~2.1", // v2.7.3
        "symfony/security": "~2.4", // v2.7.3
        "doctrine/dbal": "^2.5.4", // v2.5.4
        "monolog/monolog": "~1.7.0", // 1.7.0
        "symfony/validator": "~2.4", // v2.7.3
        "symfony/expression-language": "~2.4", // v2.7.3
        "jms/serializer": "~0.16", // 0.16.0
        "willdurand/hateoas": "~2.3" // v2.3.0
    },
    "require-dev": {
        "behat/mink": "~1.5", // v1.5.0
        "behat/mink-goutte-driver": "~1.0.9", // v1.0.9
        "behat/mink-selenium2-driver": "~1.1.1", // v1.1.1
        "behat/behat": "~2.5", // v2.5.5
        "behat/mink-extension": "~1.2.0", // v1.2.0
        "phpunit/phpunit": "~5.7.0", // 5.7.27
        "guzzle/guzzle": "~3.7" // v3.9.3
    }
}
userVoice