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.

Hypermedia vs. Media (Buzzwords!)

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

It's time to talk about a big term in REST -- hypermedia. It's one of those terms that seems like it was invented to scare people, but it's really quite underwhelming. We all know that every response has a Content-Type, like text/html or application/json. So when you hear "media" or "media types" and "content types", they're referring to the same idea.

We have two things: media and we have hypermedia.

Media is...

Media is any format: text/html, text/plain, application/json. These all contain data, and it's as simple as that.

Some of these formats are also called hypermedia. What's the difference between media and hypermedia? Hypermedia is a format that has a place for links to live. And that's it. The classic case you'll hear people talk about for hypermedia is HTML. HTML is the o.g. (original) hypermedia format. It contains data - like we see here - but it also has a way to express links, via anchor tags and forms are also a type of link. So these are links here and these are links. So implicit in the HTML format is a way to separate links from the rest of your data.

JSON is not Hypermedia

JSON is not hypermedia. That may seem confusing, because you might be thinking:

"but didn't we just add links to our JSON - isn't that hypermedia?"

And that answer is no, because if you read the official JSON specification, all it will talk about is where your curly braces, quotes, colons and commas should go. JSON is about the structure of the data - it says nothing about what's actually inside of the data. So by itself, JSON is just a media type, because there's nothing in there that says where your links should live.

Hal+JSON IS Hypermedia

But what's cool is that we've adopted this HAL JSON. This is something that's built on top of JSON: it starts with the JSON structure and then adds extra rules about where links should live. So when you talk about JSON, that's a media format. But when you talk about HAL, that's a hypermedia format, because it's spec tells you that links live below _links.

So that's really it: hypermedia is just a way to say: I have a structure that returns links, and there are rules about where those links live.

Advertising your Hypermedia Type (Content-Type Header)

As soon as you adopt a hypermedia format, instead of returning a Content-Type of application/json, you can return a Content-Type of something different, like application/hal+json. At the bottom of this page, there's an example of what a response looks like:

HTTP/1.1 201 Created
Content-Type: application/hal+json
Location: http://example.org/api/user/matthew

{
    ...
}

And you can see that the response comes back with a Content-Type of application/hal+json. This is a signal to the client that the response has a JSON structure but has some additional semantic rules on top of it. What's awesome is that if we return this in our API and someone looks at that header, they're going to say:

"Oh, what's this application/hal+json format?".

If they haven't heard of it, they can Google it and read about the structure and say:

"oh, they're using a format where the links live in an `_links` key"

along with some other rules.

Globally Setting the Content-Type

Because we're already following HAL, returning this Content-Type header on all of our endpoints is an easy win. In battle.feature, let's add a new scenario line to test this. My editor isn't happy with my language here. If you can't remember your definitions, run behat with the -dl option:

php vendor/bin/behat -dl

I'll grep this for header because I know I have a definition. Ah, and my language is slightly off. And now PHPStorm is very happy:

... lines 1 - 25
Scenario: GET one battle
... lines 27 - 29
When I request "GET /api/battles/%battles.last.id%"
Then the response status code should be 200
... lines 32 - 34
And the "Content-Type" header should be "application/hal+json"

Oh, and we actually want to look for application/hal+json. I'll run the test first, and it's failing:

php vendor/bin/behat features/api/battle.feature:26

Remember, this is served from BattleController, so let's go back there. And all of our endpoints call this same createApiResponse method:

... lines 1 - 52
public function showAction($id)
{
$battle = $this->getBattleRepository()->find($id);
... lines 56 - 59
$response = $this->createApiResponse($battle, 200);
return $response;
}
... lines 64 - 65

If we click into this, it opens up the BaseController and this is a method we created earlier. It uses the serializer then creates a Response. So let's just update that Content-Type header:

... lines 1 - 236
protected function createApiResponse($data, $statusCode = 200)
{
$json = $this->serialize($data);
return new Response($json, $statusCode, array(
'Content-Type' => 'application/hal+json'
));
}
... lines 245 - 301

Run the test, and it passes perfectly:

php vendor/bin/behat features/api/battle.feature:26

Now, API clients can see this header and know that we're using some extra rules on top of the JSON structure.

Leave a comment!

2
Login or Register to join the conversation
Shaun T. Avatar
Shaun T. Avatar Shaun T. | posted 5 years ago

Hey Ryan,

This strange error has just popped up and I can't figure out where it's coming from, any idea what's happening here?!

POST: http://localhost:8000/api/tokens



Fatal error: Uncaught InvalidArgumentException: Identifier "api.response_factory" is not defined. in /Users/shaunthornburgh/Documents/Development/code-battle-3/vendor/pimple/pimple/lib/Pimple.php:78
Stack trace:
#0 /Users/shaunthornburgh/Documents/Development/code-battle-3/src/KnpU/CodeBattle/Application.php(286): Pimple->offsetGet('api.response_fa...')
#1 /Users/shaunthornburgh/Documents/Development/code-battle-3/vendor/pimple/pimple/lib/Pimple.php(126): KnpU\CodeBattle\Application->KnpU\CodeBattle\{closure}(Object(KnpU\CodeBattle\Application))
#2 /Users/shaunthornburgh/Documents/Development/code-battle-3/vendor/pimple/pimple/lib/Pimple.php(83): Pimple::{closure}(Object(KnpU\CodeBattle\Application))
#3 /Users/shaunthornburgh/Documents/Development/code-battle-3/vendor/silex/silex/src/Silex/Provider/SecurityServiceProvider.php(399): Pimple->offsetGet('security.entry_...')
#4 /Users/shaunthornburgh/Documents/Development/code-battle-3/vendor/pimple/pimple/lib/Pimple.php(126): Silex\Provider\SecurityServiceProvider->Silex\Pro in /Users/shaunthornburgh/Documents/Development/code-battle-3/vendor/pimple/pimple/lib/Pimple.php on line 78

Here is my repo... https://github.com/shauntho...

Reply

Hey Shaun T.!

Ah, I think it's an easy fix :). For some reason, I don't see your api.response_factory defined: we created it here: https://knpuniversity.com/screencast/rest-ep2/centralizing-error-response-creation#creating-an-api-response-factory-service. Did you maybe just miss that part somehow?

Cheers!

Reply
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