If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
Let's go to one other endpoint - /api/programmers
:
Put this into the Hal Browser:
http://localhost:8000/api/programmers
Here, things don't quite work out as well. The response body on the right is the same as the properties on the left. That might not look wrong to you, but the format for our collection is not correct yet according to HAL.
If we look back at the HAL Primer doc, you'll see that this is what a collection resource should look like:
{
"_links": {
"self": {
"href": "http://example.org/api/user?page=3"
},
"first": {
"href": "http://example.org/api/user"
},
"prev": {
"href": "http://example.org/api/user?page=2"
},
"next": {
"href": "http://example.org/api/user?page=4"
},
"last": {
"href": "http://example.org/api/user?page=133"
}
}
"count": 3,
"total": 498,
"_embedded": {
"users": [
{
"_links": {
"self": {
"href": "http://example.org/api/user/mwop"
}
},
"id": "mwop",
"name": "Matthew Weier O'Phinney"
},
{
"_links": {
"self": {
"href": "http://example.org/api/user/mac_nibblet"
}
},
"id": "mac_nibblet",
"name": "Antoine Hedgecock"
}
]
}
}
By the way, we've talked about resources like Programmer and Battle, but with
the collection endpoints - like /api/programmers
- the collection itself
is known as a resource. So this is a collection resource. And in HAL, the
way this works is that all of the items in the collection are considered
to be embedded resources. If you imagine that this is /api/users
, all the
users live under the _embedded
key. The only true properties - if you have
any - relate to the collection itself: things like the total number of items
in the collection or what page you're on.
And above, you'll see that this collection has pagination and it's using the links to help the client know how to get to other pages. We're going to do this exact same thing later, which is awesome and it'll be really easy.
But for now, I want to get things into this structure. First, before we
implement it, you guys know what we're going to do, we're going to update
our test. In our programmer.feature
, the scenario for the collection was
looking for a programmers
property to be the array. But now, it's going
to be _embedded.programmers
. Let's go even one step further and say that
the first item in this should be the UnitTester:
... lines 1 - 82 | |
Scenario: GET a collection of programmers | |
Given the following programmers exist: | |
| nickname | avatarNumber | | |
| UnitTester | 3 | | |
| CowboyCoder | 5 | | |
When I request "GET /api/programmers" | |
Then the response status code should be 200 | |
And the "_embedded.programmers" property should be an array | |
And the "_embedded.programmers" property should contain 2 items | |
And the "_embedded.programmers.0.nickname" property should equal "UnitTester" | |
... lines 93 - 132 |
So its nickname should equal UnitTester. This is line 84, so let's run this test first to make sure it fails:
php vendor/bin/behat features/api/programmer.feature:84
And it does.
The HATEOAS library has its own way of dealing with collections. If you scroll to the top of its docs, you'll see a section all about this:
// From the HATEOAS documentation
use Hateoas\Representation\PaginatedRepresentation;
use Hateoas\Representation\CollectionRepresentation;
$paginatedCollection = new PaginatedRepresentation(
new CollectionRepresentation(
array($user1, $user2, ...),
'users', // embedded rel
'users' // xml element name
),
'user_list', // route
array(), // route parameters
1, // page
20, // limit
4, // total pages
'page', // page route parameter name, optional, defaults to 'page'
'limit', // limit route parameter name, optional, defaults to 'limit'
false // generate relative URIs
);
First of all, don't worry about this PaginatedRepresentation
thing, we're
going to talk about that later - it's not nearly as scary as it looks here.
If we have a collection that doesn't need pagination, all we need to do is
create this CollectionRepresentation
object.
Open ProgrammerController
and go down to listAction. Right now we're taking
the programmers, putting them onto a programmer key and serializing that.
Instead, create a new CollectionRepresentation
- I'll use my IDE to auto-complete
that, which automatically adds a use
statement at the top of the file.
The CollectionRepresentation
takes two arguments: the items that are
inside the collection and the key that these should live under. I could use
anything here, but I'll use programmers
:
... lines 1 - 72 | |
public function listAction() | |
{ | |
$programmers = $this->getProgrammerRepository()->findAll(); | |
$collection = new CollectionRepresentation( | |
$programmers, | |
'programmers', | |
'programmers' | |
); | |
$response = $this->createApiResponse($collection, 200, 'json'); | |
return $response; | |
} | |
... lines 87 - 154 |
Just choose something that makes sense, then be consistent so that whenever
your API clients see a programmers
key, they know what it contains.
Now, we'll just pass this new object directly to createApiResponse
. And
hey, that's it! Let's try the test:
php vendor/bin/behat features/api/programmer.feature:84
And this time it passes! So whenever you have a collection resource, use this object. If it's paginated, stay tuned.
And now that we're following the rules of HAL, if we go back to the HAL browser
and re-send the request, you get a completely different page. This time it says
we have no properties, since our collection has no keys outside of the embedded stuff,
and below it shows our embedded programmers and we can see the data for each. Each
programmer even has a self
link, which we can follow to move our client to that
new URL and see the data for that one programmer. If it has any other links, we could
keep going by following those. So hey, this is getting kind of fun! And as you'll see,
the HAL browser can help figure out what new links can be added to make life better.
Hey GDIBass!
Hmm, thanks for sharing! I'm not sure why you needed this and we didn't - that code isn't new in hal.js (I just checked). But, no worries :). We may have fixed this later in chapter 24 when we added the Api homepage. But either way, I'm glad you figured it out. And yea, we should have _links on each resource.
Cheers!
Collections have been updated in Hateoas 3 and the current method of declaring the _embedded names does not work. Any solutions to this?
Hey @Sym!
Sorry for the slow reply :). I believe the idea in Hateoas 3 is this: instead of using the built-in CollectionRepresentation from the library, you're supposed to create your *own* class. Basically, you're supposed to copy CollectionRepresentation (https://github.com/willdura..., put it into your code, and customize it. For example, you might have a ProgrammerCollectionRepresentation where you customize the annotation on top, if you want to take control of the _embedded names.
I hope this helps!
Cheers!
Hi there! I had problem when i tried viewing with HALbrowser - /api/progrmmers or any route that didn't have _links outside _embedded items. its caused by hal.js line 16. "HAL.._links..undefined" look at your inspector console. I just commented that line(probably i should do something more sophisticated) and everything seems to work fine, even when the are _links. I hope this helps someone.
Hey emm!
Thanks for sharing - in case it helps anyone else :). I personally couldn't repeat the error - I updated to the latest Hal Browser (and used the version in the screencast) - but when I go to /api/programmers (where I have no _links)... it works ok! I'm not sure why - but not big deal either way :).
Cheers!
I agree with you its not a big deal because behat tests are passing.
So your answer may help someone else by saying its not a big deal :).
cheers to y'all!
Hey Thao L.,
Could you please point me in which second is that flash ?
I couldn't find it :)
Have a nice day!
// 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
}
}
My hal.js file was blowing up. I had to change line 16 to, because it was expecting _links on the top level and we weren't providing any.
if ( HAL.currentDocument._links ) var curies = HAL.currentDocument._links.curies;