Usage & Configuration

Usage & Configuration

Links are relations that are identified by a name (e.g. self) and that carry an href parameter, which is the actual URI pointing to the target resource. You configure links on a class by adding @Hateoas\Relation annotations to it.

The following example shows the most basic configuration for a User class, adding a self link that points to the user resource:

use JMS\Serializer\Annotation as Serializer;
use Hateoas\Configuration\Annotation as Hateoas;

/**
 * @Serializer\XmlRoot("user")
 *
 * @Hateoas\Relation("self", href = "expr('/api/users/' ~ object.getId())")
 */
class User
{
    /** @Serializer\XmlAttribute */
    private $id;
    private $firstName;
    private $lastName;

    public function getId() {}
}

The library supports several configuration formats: annotations, XML, and YAML. Annotations are recommended as they keep the configuration close to the code itself.

Now, use the HateoasBuilder to create a serializer that is capable of serializing HATEOAS representations:

use Hateoas\HateoasBuilder;

$hateoas = HateoasBuilder::create()->build();

$user = new User(42, 'Adrien', 'Brault');
$json = $hateoas->serialize($user, 'json');
$xml  = $hateoas->serialize($user, 'xml');

The $json variable will contain the following HAL compliant representation:

{
    "id": 42,
    "first_name": "Adrien",
    "last_name": "Brault",
    "_links": {
        "self": {
            "href": "/api/users/42"
        }
    }
}

The $xml variable will contain the following Atom Links representation:

<user id="42">
    <first_name><![CDATA[Adrien]]></first_name>
    <last_name><![CDATA[Brault]]></last_name>
    <link rel="self" href="/api/users/42"/>
</user>

Embedding Resources

Sometimes it is helpful to embed related resources rather than linking to them. The @Hateoas\Relation annotation supports an embedded parameter that allows you to inline a related resource directly within your representation.

The following example adds a manager relation that embeds the manager user object alongside its link:

use JMS\Serializer\Annotation as Serializer;
use Hateoas\Configuration\Annotation as Hateoas;

/**
 * @Serializer\XmlRoot("user")
 *
 * @Hateoas\Relation("self", href = "expr('/api/users/' ~ object.getId())")
 * @Hateoas\Relation(
 *     "manager",
 *     href     = "expr('/api/users/' ~ object.getManager().getId())",
 *     embedded = "expr(object.getManager())"
 * )
 */
class User
{
    /** @Serializer\XmlAttribute */
    private $id;
    private $firstName;
    private $lastName;

    /** @Serializer\Exclude */
    private $manager;
}

The JSON representation will include both links and embedded resources in HAL format:

{
    "id": 42,
    "first_name": "Adrien",
    "last_name": "Brault",
    "_links": {
        "self": {
            "href": "/api/users/42"
        },
        "manager": {
            "href": "/api/users/1"
        }
    },
    "_embedded": {
        "manager": {
            "id": 1,
            "first_name": "William",
            "last_name": "Durand",
            "_links": {
                "self": {
                    "href": "/api/users/1"
                }
            }
        }
    }
}

The XML representation will inline the embedded resource:

<user id="42">
    <first_name><![CDATA[Adrien]]></first_name>
    <last_name><![CDATA[Brault]]></last_name>
    <link rel="self" href="/api/users/42"/>
    <link rel="manager" href="/api/users/1"/>
    <user rel="manager" id="1">
        <first_name><![CDATA[William]]></first_name>
        <last_name><![CDATA[Durand]]></last_name>
        <link rel="self" href="/api/users/1"/>
    </user>
</user>

Note: The manager property is excluded from serialization using @Serializer\Exclude because it is already exposed through the embedded relation. The embedded XML element name is determined by the @XmlRoot annotation on the target class.

Dealing With Collections

Hateoas provides three representation classes for dealing with collections of resources: CollectionRepresentation, PaginatedRepresentation, and OffsetRepresentation. These classes add pagination metadata and navigation links to your collections automatically.

The following example creates a paginated collection of users:

use Hateoas\Representation\PaginatedRepresentation;
use Hateoas\Representation\CollectionRepresentation;

$paginatedCollection = new PaginatedRepresentation(
    new CollectionRepresentation($pager->getCurrentPageResults()),
    'user_list',            // route
    array(),                 // route parameters
    $pager->getCurrentPage(), // page number
    $pager->getMaxPerPage(),  // limit
    $pager->getNbPages(),     // total pages
    'page',                  // page route parameter name, optional
    'limit',                 // limit route parameter name, optional
    true,                    // generate absolute URIs, optional
    $pager->getNbResults()   // total collection size, optional
);

The JSON representation will contain the pagination metadata and navigation links:

{
    "page": 1,
    "limit": 10,
    "pages": 10,
    "total": 100,
    "_links": {
        "self": {
            "href": "/api/users?page=1&limit=10"
        },
        "first": {
            "href": "/api/users?page=1&limit=10"
        },
        "last": {
            "href": "/api/users?page=10&limit=10"
        },
        "next": {
            "href": "/api/users?page=2&limit=10"
        }
    },
    "_embedded": {
        "items": [
            { "id": 1 },
            { "id": 2 },
            { "id": 3 }
        ]
    }
}

The XML representation:

<collection page="1" limit="10" pages="10" total="100">
    <user id="1"/>
    <user id="2"/>
    <user id="3"/>
    <link rel="self" href="/api/users?page=1&limit=10"/>
    <link rel="first" href="/api/users?page=1&limit=10"/>
    <link rel="last" href="/api/users?page=10&limit=10"/>
    <link rel="next" href="/api/users?page=2&limit=10"/>
</collection>

If you are using Pagerfanta, you can use the PagerfantaFactory to create paginated representations more easily:

use Hateoas\Representation\Factory\PagerfantaFactory;

$pagerfantaFactory = new PagerfantaFactory();

$paginatedCollection = $pagerfantaFactory->createRepresentation(
    $pager,
    new Route('user_list', array())
);

You can customize the CollectionRepresentation and the XML root element name by passing additional arguments:

$paginatedCollection = $pagerfantaFactory->createRepresentation(
    $pager,
    new Route('user_list', array()),
    new CollectionRepresentation(
        $pager->getCurrentPageResults(),
        'users',  // embedded rel, optional
        'users'  // XML element name, optional
    )
);

Representations

VndErrorRepresentation

The VndErrorRepresentation allows you to describe error responses following the vnd.error specification. This is useful for returning standardized error responses from your API.

use Hateoas\Representation\VndErrorRepresentation;

$error = new VndErrorRepresentation(
    'Validation Failed',
    42,
    'The request could not be processed due to validation errors.',
    'http://example.com/help',
    'http://example.com/log/42'
);

The XML representation:

<resource logref="42">
    <message><![CDATA[Validation Failed]]></message>
    <link rel="help" href="http://example.com/help"/>
    <link rel="describes" href="http://example.com/log/42"/>
</resource>

The JSON representation:

{
    "message": "Validation Failed",
    "logref": 42,
    "_links": {
        "help": {
            "href": "http://example.com/help"
        },
        "describes": {
            "href": "http://example.com/log/42"
        }
    }
}