Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Embedding Objects with Hal?

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

Check out the Hal example on their docs. There are actually three different sections of the json: the actual data - like currentlyProcessing, _links and _embedded.

Here's a cool idea: we know that it's nice to add links to a response. These are called relations: they point to related resources. But there's another way to add a relation to a response: embed the related resource right in the JSON.

Remember: the whole point of adding link relations is to make life easier for your API clients. If embedding the data is even easier than advertising a link, do it.

In fact, let's pretend that when we return a Battle resource, we still want to include a link to the related Programmer, but we also want to embed that Programmer entirely.

Adding an Embedded Relation

To do that, after href, add an embedded="expr()" with object.getProgrammer():

... lines 1 - 10
/**
... lines 12 - 14
* @Hateoas\Relation(
* "programmer",
* href=@Hateoas\Route(
* "api_programmers_show",
* parameters={"nickname"= "expr(object.getProgrammerNickname())"}
* ),
* embedded = "expr(object.getProgrammer())"
* )
*/
class Battle
... lines 25 - 142

Let's see what this looks like! Open BattleControllerTest and right at the bottom, add our handy $this->debugResponse($response):

... lines 1 - 6
class BattleControllerTest extends ApiTestCase
{
... lines 9 - 15
public function testPOSTCreateBattle()
{
... lines 18 - 40
$this->asserter()->assertResponsePropertyEquals(
$response,
'_links.programmer.href',
$this->adjustUri('/api/programmers/Fred')
);
$this->debugResponse($response);
... lines 47 - 49
}
... lines 51 - 75
}

Perfect! Copy that method name and run it:

./vendor/bin/phpunit --filter testPOSTCreateBattle

Oh, cool: we still have the relation in _links, but now we also have an entire programmer resource in _embedded. So when you setup these @Hateoas\Relation annotations:

... lines 1 - 10
/**
... lines 12 - 14
* @Hateoas\Relation(
* "programmer",
* href=@Hateoas\Route(
* "api_programmers_show",
* parameters={"nickname"= "expr(object.getProgrammerNickname())"}
* ),
* embedded = "expr(object.getProgrammer())"
* )
*/
class Battle
... lines 25 - 142

You can choose whether you want this to be a link or an embedded object.

And OK, we cheated on this test by looking at it first, but now I guess we should specifically have a test for it. Add: $this->asserter()->assertResponsePropertyEquals() with $response. Look for _embedded.programmer.nickname to be equal to our friend Fred:

... lines 1 - 6
class BattleControllerTest extends ApiTestCase
{
... lines 9 - 15
public function testPOSTCreateBattle()
{
... lines 18 - 40
$this->asserter()->assertResponsePropertyEquals(
$response,
'_links.programmer.href',
$this->adjustUri('/api/programmers/Fred')
);
$this->asserter()->assertResponsePropertyEquals(
$response,
'_embedded.programmer.nickname',
'Fred'
);
... lines 51 - 53
}
... lines 55 - 79
}

Run that!

./vendor/bin/phpunit --filter testPOSTCreateBattle

It passes! Now let's customize how these links render.

Leave a comment!

2
Login or Register to join the conversation
Chuck norris Avatar
Chuck norris Avatar Chuck norris | posted 5 years ago

Hi,

I have a small interrogation.

In a rest world, what is the best between embed the relation with Hatoeos or Expose the doctrine relation ?

Thanks in advance.

Reply

Yo Chuck!

Haha, I don't know :). I mean, the "best practice" is to choose a standard - whether it's HAL, JSON API, or whatever - and put make your responses look like they should. For HAL, you're supposed to put things under _embedded. For JSON API, it's different. So, I would say that *if* you find a standard that you really like and are comfortable following, then follow its rules completely. Otherwise, you're kind of "doing your own thing", which is "frowned upon", but happens all the time. And in that case, I like simple structures, so I just exposed the doctrine relation.

I hope that helps! There's still a lot in REST that is subjective. I think we *are* moving towards standards everywhere, but those standards (and the tools that make them easy to use) are still developing.

Cheers!

Reply
Cat in space

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

This tutorial uses an older version of Symfony. The concepts of Hypermedia & HATEOAS are still valid. But I recommend using API Platform in modern Symfony apps.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.0.*", // v3.0.3
        "doctrine/orm": "^2.5", // v2.5.4
        "doctrine/doctrine-bundle": "^1.6", // 1.6.2
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // v2.10.0
        "sensio/distribution-bundle": "^5.0", // v5.0.4
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.14
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.2
        "jms/serializer-bundle": "^1.1.0", // 1.1.0
        "white-october/pagerfanta-bundle": "^1.0", // v1.0.5
        "lexik/jwt-authentication-bundle": "^1.4", // v1.4.3
        "willdurand/hateoas-bundle": "^1.1" // 1.1.1
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.6
        "symfony/phpunit-bridge": "^3.0", // v3.0.3
        "behat/behat": "~3.1@dev", // dev-master
        "behat/mink-extension": "~2.2.0", // v2.2
        "behat/mink-goutte-driver": "~1.2.0", // v1.2.1
        "behat/mink-selenium2-driver": "~1.3.0", // v1.3.1
        "phpunit/phpunit": "~4.6.0", // 4.6.10
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}
userVoice