If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
When we made our Battle endpoint, we decided it might be convenient to have a link to Programmer. It's just a nice thing to help our API clients. But to get that programmer's information, they're going to need to make a second request out to that URI. If it's really common to need the programmer information when you GET a battle, you may choose to put the Programmer's data, right inside the Battle's response.
What's really nice here is that HAL already has rules about how this should work:
{
"_links": {
"self": {
"href": "http://example.org/api/user/matthew"
}
}
"id": "matthew",
"name": "Matthew Weier O'Phinney",
"_embedded": {
"contacts": [
{
"_links": {
"self": {
"href": "http://example.org/api/user/mac_nibblet"
}
},
"id": "mac_nibblet",
"name": "Antoine Hedgecock"
},
{
"_links": {
"self": {
"href": "http://example.org/api/user/spiffyjr"
}
},
"id": "spiffyjr",
"name": "Kyle Spraggs"
}
]
}
}
There's the _links
section, but there's also an _embedded
section.
And our HATEOAS library will help us put stuff there.
So let's try to embed our programmer into the battle. First, let's add a
line to the scenario that looks for this. Let's look for _embedded
, and
we know it's going to be called programmer
, and the data will live below
this and we know one of the fields on a programmer is nickname
. And we know
this should be equal to Fred
:
... lines 1 - 25 | |
Scenario: GET one battle | |
Given there is a project called "projectA" | |
And there is a programmer called "Fred" | |
And there has been a battle between "Fred" and "projectA" | |
When I request "GET /api/battles/%battles.last.id%" | |
... lines 31 - 34 | |
And the "_embedded.programmer.nickname" property should equal "Fred" | |
... lines 36 - 37 |
Let's make sure that fails first - and it does:
php vendor/bin/behat features/api/battle.feature:26
To make this work, we'll add more annotations to Battle
. When you think
of one resource relating to another - like how our Battle
relates to
the Programmer
resource - there are 2 ways to express that relation. You
can either link to it or you can embed it. Those are both just valid ways
to think about expressing a link between 2 resources.
This @HATEOAS\Relation
lets you do whichever you want. For a link, use
the href
. To embed something, use the embedded
key and set it to an expression
that points to which object you want to embed:
... lines 1 - 8 | |
/** | |
* @Hateoas\Relation( | |
* "programmer", | |
* href = @Hateoas\Route( | |
* "api_programmers_show", | |
* parameters = { "nickname" = "expr(object.programmer.nickname)" } | |
* ), | |
* embedded = "expr(object.programmer)" | |
* ) | |
... line 18 | |
*/ | |
class Battle | |
... lines 21 - 52 |
And actually, if you include both href
and embedded
, it'll create a link
and embed it.
Before we run the test, add a "And print last response", because I like to see how my endpoints look. Let's run it:
php vendor/bin/behat features/api/battle.feature:26
Awesome it passes! If you look - HATEOAS is doing all the work for us. We
still have _links
, but we also have _embedded
. What's cool is that it
goes out to the Programmer
resource and serializes it. You end up with
all the same properties as normal, and you even end up with its links. So
a lot of things are falling into place accidentally.
And just like with links, since embedded data always lives under _embedded
,
I have a built-in definition you can choose to use if you want to:
... lines 1 - 25 | |
Scenario: GET one battle | |
... lines 27 - 34 | |
And the embedded "programmer" should have a "nickname" property equal to "Fred" | |
... lines 36 - 37 |
Behind the scenes, this knows to look for all of this on the _embedded
property.
And the test still passes. Now I'll take out the print last response. When it comes to linking and embedding, I hope you're feeling dangerous!
Hi Juan!
Hmm, it sounds like you correctly understand that if you query with a join, then the serializer should *not* be making these queries. So, this *is* weird! 2 things to check for:
1) If you Simply say $a->getB() in your controller (don't use the serializer temporarily), can you verify that the extra query is NOT made?
2) On your join, do you have an addSelect like described here? http://knpuniversity.com/sc...
In short, the serializer is kind of "stupid" - it simply knows that it should access the "b" property on A and use it to serialize into JSON. It doesn't know that Doctrine exists, and so shouldn't be interfering with how the queries are made for stuff. Well, that's how it *should* be working at least :).
Cheers!
// 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
}
}
Hi Ryan,
I have a question about the number of queries to the database when using JMSSerializer and HateoasBundle.
Example:
An entity A is associated with OneToOne to entity B through property $b.
class A
{
...
/**
@ORM\OneToOne(targetEntity="B")
# If I put @Serializer\Expose() Internally it generates a query extra to get the data of the table B. It also happens if I use the embedded annotation.
*/
private $b
}
This would be the right thing, but in my controller I have a query with join that pulls me all the data from both tables.
How do I do this with a single query? It's possible?
I have also tried to use fetch EAGER of doctrine, but still performing 2 query..
Any ideas?
Thank you very much