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

The Great Hateoas PHP Library

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

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Google for "HATEOAS PHP" to find a fun library that a friend of mine made. This library has a bundle that integrates it into Symfony: so click to view the BazingaHateoasBundle and go straight to its docs. Before we talk about what it does: get it installed.

Installing BazingaHateoasBundle

Copy the composer require statement and then flip over to your terminal and paste that:

composer require willdurand/hateoas-bundle

This is a bundle, so grab the new bundle statement, open AppKernel and pop that at the bottom:

... lines 1 - 5
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
... lines 11 - 21
new Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle(),
);
... lines 24 - 33
}
... lines 35 - 52
}

Perfect. Currently, we have our own super sweet annotation system for adding links. In Battle, we use @Link to create a programmer link:

... lines 1 - 9
/**
... lines 11 - 13
* @Link(
* "programmer",
* route="api_programmers_show",
* params={"nickname": "object.getProgrammerNickname()"}
* )
*/
class Battle
... lines 21 - 138

Guess what! I completely stole that idea from this library. But now, to make our app a little simpler and to get some new features, let's replace our @Link code with this library.

Go back to the library itself, and scroll down to the first coding example. This uses an annotation system that looks pretty similar to ours. Copy the use statement on top, open Battle and paste that:

... lines 1 - 8
use Hateoas\Configuration\Annotation as Hateoas;
... lines 10 - 141

Next, change the annotation to @Hateoas\Relation:

... lines 1 - 10
/**
... lines 12 - 14
* @Hateoas\Relation(
... lines 16 - 20
* )
*/
class Battle
... lines 24 - 141

Keep programmer: that will still be the link's rel. But add href=@Hateoas\Route and pass that the name of the route: api_programmers_show:

... lines 1 - 10
/**
... lines 12 - 14
* @Hateoas\Relation(
* "programmer",
* href=@Hateoas\Route(
* "api_programmers_show",
... line 19
* )
* )
*/
class Battle
... lines 24 - 141

Update params to parameters, and inside, set nickname equal, and wrap the expression in expr():

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

That translates our annotation format to the one used by the bundle. And the result is almost the same. Open BattleControllerTest and copy the first method name:

... lines 1 - 6
class BattleControllerTest extends ApiTestCase
{
... lines 9 - 15
public function testPOSTCreateBattle()
... lines 17 - 74
}

we have a test for a link near the bottom of this:

... lines 1 - 6
class BattleControllerTest extends ApiTestCase
{
... lines 9 - 15
public function testPOSTCreateBattle()
{
... lines 18 - 40
$this->asserter()->assertResponsePropertyEquals(
$response,
'_links.programmer',
$this->adjustUri('/api/programmers/Fred')
);
... lines 46 - 48
}
... lines 50 - 74
}

Change over to the terminal and, as long as Composer is done, run:

vendor/bin/phpunit --filter testPOSTCreateBattle

Check it out! It fails - but barely. This library still puts links under an _links key, but instead of listing the URLs directly, it wraps each inside an object with an href key. That's causes the failure.

Ok, fair enough. Let's fix that by updating the test to look for _links.programmer.href:

... 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')
);
... lines 46 - 48
}
... lines 50 - 74
}

Run the test again:

vendor/bin/phpunit --filter testPOSTCreateBattle

And now we're green.

Holy Toledo Batman: This is HAL JSON!

But guess what? It's no accident that this library used this exact format: with an _links key and an href below that. This is a semi-official standard format called HAL JSON.

Leave a comment!

7
Login or Register to join the conversation
Default user avatar

Is there a similar bundle for Symfony 3?

5 Reply

Yo Chris!

The bundle should work just fine with Symfony 3 as well :). Their composer.json supports Symfony 2 and 3: https://github.com/willdura...

If you're having any issues, let us know!

Cheers!

Reply
Diaconescu Avatar
Diaconescu Avatar Diaconescu | posted 5 years ago

in composer I have:
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.0.*",
"doctrine/orm": "^2.5",
"doctrine/doctrine-bundle": "^1.6",
"doctrine/doctrine-cache-bundle": "^1.2",
"symfony/swiftmailer-bundle": "^2.3",
"symfony/monolog-bundle": "^2.8",
"sensio/distribution-bundle": "^5.0",
"sensio/framework-extra-bundle": "^3.0.2",
"incenteev/composer-parameter-handler": "~2.0",
"jms/serializer-bundle": "^1.1.0",
"white-october/pagerfanta-bundle": "^1.0",
"lexik/jwt-authentication-bundle": "^1.4",
"willdurand/hateoas": "^2.11"
},

In AppKernel.php:
$bundles = array(
new Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle()
)

Even I put in composer.json:
"autoload": {
"psr-4": {
"": "src/",
"App\\Behat\\": "features/",
"Bazinga\\Bundle\\HateoasBundle\\": ""
},
"classmap": [ "app/AppKernel.php", "app/AppCache.php" ]
},

The net result when I run testPostCreateBattle test is:
PHP Fatal error: Uncaught Error: Class 'Bazinga\Bundle\HateoasBundle\BazingaHateoasBundle' not found in restSymfony/app/AppKernel.php:22

Reply

Hey Diaconescu!

Hmmm. I see willdurand/hateoas in your composer.json, but you actually need willdurand/hateoas-bundle. Maybe that was just a little typo? Try running composer require willdurand/hateoas-bundle. I think (hope) it will fix the issue!

Cheers!

Reply
Diaconescu Avatar
Diaconescu Avatar Diaconescu | posted 5 years ago

Unfortunately Is not a typo. In the link you provided as response for months ago(https://github.com/willdura... is the same string in require section for hateoas. And doesn't work.

Reply

Hey Diaconescu!

Hmm. But in the tutorial, we say to install the bundle: https://knpuniversity.com/screencast/symfony-rest5/hateoas-php-library#installing-bazingahateoasbundle


composer require willdurand/hateoas-bundle

Is there somewhere else that we recommend installing the library, instead of the bundle? I think there could be a bug somewhere, but I don't see it yet :).

Cheers!

Reply
Diaconescu Avatar
Diaconescu Avatar Diaconescu | posted 5 years ago

Scuse me. I was stupid. It was a typo.

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