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.
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!
Hi Jake!
Sorry for the late response due to the holidays :).
You bring up some good questions. First, you asked about being able to update the list or projects related to a programmer. One important thing to remember (and this fooled me for a long time) is that the format that the client sends to the server to do things like updates/creates, should NOT be in some fancy hypermedia format (like HAL). This means that you can choose whatever structure you want, then document it (and of course, try to be consistent). So, I might do something like this (imagine these are docs):
To update the projects related to a programmer, send a PUT request to /api/programmers/{nickname}/projects with a JSON body that looks like this:
{
"projects": [13, 14, 15]
}
One could argue that the JSON could just be the array of id's - I think it's not too important. In this case, I'm treating the projects as a subordinate resource to programmers and and updating it with a PUT.
Next, I would probably not normally expose the "join" table to the user, but it's up to you. After-all, it may be a join table today, but perhaps tomorrow you suddenly realize that you need to add a new column to that table called "programmerRatingOfProject". If you did this, it would probably be its own resource and you wouldn't be able to do something like what I did above anymore (assuming programmerRatingOfProject is required).
Finally, when an attribute has multiple values, which are stored in an extra table, your best bet depends on your situation. Abstracting it from your API (treating them as an array of values) is probably easier for your API client, but probably harder for you to code. So, weigh the trade-off and do what's best :).
Cheers and apologies again for the late reply!
// 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
}
}
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?