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!

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
    }
}