Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This course is archived!
This tutorial uses a deprecated micro-framework called Silex. The fundamentals of REST are still ?valid, but the code we use can't be used in a real application.

HATEOAS Loves Routers

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

This is great, except that I don't like this hardcoded URL in the Relation:

... lines 1 - 7
use Hateoas\Configuration\Annotation as Hateoas;
... line 9
/**
* @Serializer\ExclusionPolicy("all")
* @Hateoas\Relation("self", href = "expr('/api/programmers/' ~ object.nickname)")
*/
class Programmer
{
... lines 16 - 52
}

What I'd rather do is generate that URL from the internal name api_programmers_show like we normally do.

Fortunately, the HATEOAS library allows us to do that, and this can be hooked up to work with any framework, since they all generate URLs. As part of that HateoasBuilder::create() step, you can set a URL generator that does whatever you need.

Hooking up the SymfonyUrlGenerator

If you're using Silex or Symfony, life is a little bit easier because there's a built-in class called SymfonyUrlGenerator:

// from the HATEOAS docs
use Hateoas\UrlGenerator\SymfonyUrlGenerator;

$hateoas = HateoasBuilder::create()
    ->setUrlGenerator(null, new SymfonyUrlGenerator($app['url_generator']))
    ->build()
;

I'll copy this line. Go back into Application, move build() onto the next line and paste this:

... lines 1 - 6
use Hateoas\UrlGenerator\SymfonyUrlGenerator;
... lines 8 - 150
private function configureServices()
{
$app = $this;
... lines 154 - 217
$this['serializer'] = $this->share(function() use ($app) {
... lines 219 - 224
// create the Hateoas serializer
return HateoasBuilder::create($jmsBuilder)
->setUrlGenerator(null, new SymfonyUrlGenerator($app['url_generator']))
->build();
});
... lines 230 - 233
}
... lines 235 - 352

And don't forget that we need a use statement for that SymfonyUrlGenerator, so I'll click "import" to have PhpStorm add this class to the top of the file for me. This class comes from the HATEOAS library, and we're just passing it the url_generator object, which in Silex, is the object responsible for generating URLs. In Symfony, it's called router.

Using the Router in Annotations

With this, we can go back into Programmer. First, I'm going to move things onto multiple lines for my sanity. Instead of setting the href to a URL, we'll say @HATEOAS\Route . I'll make sure I have all my parenthesis in the right place. In the Route, we'll have 2 arguments - the first is the name of the route. The second argument is whatever variables we need to pass into the route, in a parameters key. Because the route has the {nickname}, we're going to pass that here using the expression language again. This time, we'll say object - because that represents the Programmer - .nickname. That's fancy way of saying: "generate me a URL, and here's the nickname to use in that URL.":

... lines 1 - 9
/**
* @Serializer\ExclusionPolicy("all")
* @Hateoas\Relation(
* "self",
* href = @Hateoas\Route(
* "api_programmers_show"
* parameters = { "nickname" = expr("object.nickname") }
* )
* )
*/
class Programmer
... lines 21 - 60

Unless I've messed something up, the test should pass like before:

php vendor/bin/behat features/api/programmer.feature:66

Ah, and it doesn't! I messed up some syntax. Anytime you see the Doctrine\Common\Annotations T_CLOSE_PARENTHESIS type of thing, this is a syntax error in your annotation. I'm missing a comma between my arguments. Let's try that one more time. Ah, I messed up again! If you look back at the docs, which I've been ignoring, you can see that the quotes should be around the entire nickname value. I'll fix that, and learn that it's always good to follow the docs:

... lines 1 - 9
/**
* @Serializer\ExclusionPolicy("all")
* @Hateoas\Relation(
* "self",
* href = @Hateoas\Route(
* "api_programmers_show",
* parameters = { "nickname" = "expr(object.nickname)" }
* )
* )
*/
... lines 20 - 60

And this time it finally passes. So other than my syntax error, that was easy to fix up. And if this looks overwhelming to you, that's ok. From now on, we're just going to be copying and pasting this and customizing it for whatever links we need.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

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

This tutorial uses a deprecated micro-framework called Silex. The fundamentals of REST are still ?valid, but the code we use can't be used in a real application.

What PHP libraries does this tutorial use?

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