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.

Collections: The HAL Way

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

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.

Hal Collection Format

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

Collections are "Collection Resources"

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.

Structuring Our Collections

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 CollectionRepresentation

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.

Celebrate with Hal (Browser)

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.

Leave a comment!

11
Login or Register to join the conversation
GDIBass Avatar
GDIBass Avatar GDIBass | posted 5 years ago

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;

1 Reply

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!

1 Reply

Collections have been updated in Hateoas 3 and the current method of declaring the _embedded names does not work. Any solutions to this?

Reply

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!

Reply
Default user avatar

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.

Reply

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!

Reply
Default user avatar

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!

Reply
Agnes Avatar

I had the same problem but it works with the epic-rewrite branch from 2013.

Reply
Default user avatar

Thanks looking into that A Maria.

Reply
Thao L. Avatar
Thao L. Avatar Thao L. | posted 5 years ago

Whats with the flashing screen in this tutorial...

Reply

Hey Thao L.,

Could you please point me in which second is that flash ?
I couldn't find it :)

Have a nice day!

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