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.
Adding Real Links with HATEOAS
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.
Let's add more links! Back in battle.feature
, we're returning a programmerUri
,
which was our way of creating a link before we knew there was a good standard
to follow:
// ... lines 1 - 25 | |
Scenario: GET one battle | |
// ... lines 27 - 33 | |
And the "programmerUri" property should equal "/api/programmers/Fred" |
So now we can say:
And the "_links.programmer.href" property should equal "/api/programmers/Fred"
:
// ... lines 1 - 25 | |
Scenario: GET one battle | |
// ... lines 27 - 33 | |
And the "_links.programmer.href" property should equal "/api/programmers/Fred" |
This time, instead of using self
, we're using programmer
. There are some
special names like self that mean something, but when you're linking from
a battle to a programmer, we'll just invent something new. We'll want to
use this consistently in our API: whenever we're linking to a programmer,
we'll use that same string programmer
so that our API clients learn that
whenever they see this link link they know what type of resource to expect
on the other side.
First, let's run our test - line 26 - and make sure that it fails:
php vendor/bin/behat features/api/battle.feature:26
Let's go in and add that relation. Open up Battle
and also open up Programmer
so we can steal the Relation
from there as promised. And don't forget,
every time you use an annotation for the first time in a class, you need
a use
statement for it.
And also, since we have this relationship now, I'm going to remove our VirtualProperty
down below. So this is really good - we're linking to a Programmer
like before.
So the route name is good and the nickname is good. The only thing that needs
to change is that in order to get the nickname of the programmer for this
Battle, we need to say object.programmer.nickname
so that it uses the
programmer
field below. Let's try our test. Ah, and it fails! I got caught
by copying and pasting - we do have a link, but its name is self
. Change
that to be programmer
:
// ... lines 1 - 6 | |
use Hateoas\Configuration\Annotation as Hateoas; | |
// ... line 8 | |
/** | |
* @Hateoas\Relation( | |
* "programmer", | |
* href = @Hateoas\Route( | |
* "api_programmers_show", | |
* parameters = { "nickname" = "expr(object.programmer.nickname)" } | |
* ) | |
* ) | |
* @Serializer\ExclusionPolicy("all") | |
*/ | |
class Battle | |
// ... lines 20 - 51 |
And now, we'll get that to pass. Awesome.
Consistency = New Behat Step
Because we're always putting links under a _links
key, I have a new
piece of language that we can use in Behat to check for links:
// ... lines 1 - 25 | |
Scenario: GET one battle | |
// ... lines 27 - 33 | |
And the link "programmer" should exist and its value should be "/api/programmers/Fred" |
Why would I do this? It's just proving how consistent we are. This new sentence
will look for the _links
property, so there's no reason to repeat it
in all of our scenarios. So let's try the test again - perfect.
php vendor/bin/behat features/api/battle.feature:26
We can repeat this same thing over in programmer.feature
when we're checking
the self
link. I'll comment out the old line for reference:
// ... lines 1 - 65 | |
Scenario: GET one programmer | |
// ... lines 67 - 80 | |
And the link "self" should exist and its value should be "/api/programmers/UnitTester" | |
// ... lines 82 - 131 |
If we run our entire test suite, things keep passing:
php vendor/bin/behat
I love to see all of that green!
How are many-to-many relationships represented in HAL?
For example, a programmer can have many projects and a project can have many programmers.
Programmer <--many to many--> Projects
My concern is with the association table (let's call it the middle table) in between the programmer and the projects..
Suppose I want to update the list of projects related to a programmer, should I consider the middle table as a resource and work on that resource.
{
name: "Foo",
"_embedded": {
"ProgrammerProjects": [
{
id: 1,
project_id: 100
},
{
id: 2,
project_id: 102
}
]
}
}
Or, is it possible to make the association an attribute of the programmer resource.
{
name: "Foo"
projects: [
100,
102
]
}
Sometimes, a resource can have an attribute that has multiple values. Thus, we use extra tables in the database to store them. Should we abstract that detail from the API. That is, just show it as an array of values and don't deal with them as separate resources?