A PHP library to support implementing representations for HATEOAS REST web services.
A PHP library to support implementing representations for HATEOAS REST web services.
A PHP library to support implementing representations for HATEOAS REST web services.
The recommended way to install Hateoas is through
Composer. Require the willdurand/hateoas package
by running the following command:
composer require willdurand/hateoasThis will resolve the latest stable version.
Otherwise, install the library and setup the autoloader yourself.
If you want to use annotations for configuration you need
to install the doctrine/annotations package:
composer require doctrine/annotationsIf your app uses PHP 8.1 or higher it is recommended to use native PHP attributes. In this case you don't need to install the Doctrine package.
There is a bundle for that! Install the BazingaHateoasBundle, and enjoy!
Important:
For those who use the
1.0version, you can jump to this documentation page.For those who use the
2.0version, you can jump to this documentation page.The following documentation has been written for Hateoas 3.0 and above.
Hateoas leverages the Serializer library to provide a nice way to build HATEOAS REST web services. HATEOAS stands for Hypermedia as the Engine of Application State, and adds hypermedia links to your representations (i.e. your API responses). HATEOAS is about the discoverability of actions on a resource.
For instance, let's say you have a User API which returns a representation of a single user as follow:
{
"user": {
"id": 123,
"first_name": "John",
"last_name": "Doe"
}
}In order to tell your API consumers how to retrieve the data for this specific
user, you have to add your very first link to this representation, let's
call it self as it is the URI for this particular user:
{
"user": {
"id": 123,
"first_name": "John",
"last_name": "Doe",
"_links": {
"self": { "href": "http://example.com/api/users/123" }
}
}
}Let's dig into Hateoas now.
In Hateoas terminology, links are seen as relations added to resources. It is worth mentioning that relations also refer to embedded resources too, but this topic will be covered in the Embedding Resources section.
A link is a relation which is identified by a name (e.g. self) and that
has an href parameter:
Annotation (PHP
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() {}
}Attribute (PHP 8.1 and greater)
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() {}
}In the example above, we configure a self relation that is a link because of
the href parameter. Its value, which may look weird at first glance, will be
extensively covered in The Expression Language
section. This special value is used to generate a URI.
In this section, annotations/attributes are used to configure Hateoas. XML and YAML formats are also supported. If you wish, you can use plain PHP too.
Important: you must configure both the Serializer and Hateoas the same way. E.g. if you use YAML for configuring Serializer, use YAML for configuring Hateoas.
The easiest way to try HATEOAS is with the HateoasBuilder. The builder has
numerous methods to configure the Hateoas serializer, but we won't dig into
them right now (see The HateoasBuilder).
Everything works fine out of the box:
use Hateoas\HateoasBuilder;
$hateoas = HateoasBuilder::create()->build();
$user = new User(42, 'Adrien', 'Brault');
$json = $hateoas->serialize($user, 'json');
$xml = $hateoas->serialize($user, 'xml');The $hateoas object is an instance of JMS\Serializer\SerializerInterface,
coming from the Serializer library. Hateoas does not come with its own
serializer, it hooks into the JMS Serializer.
By default, Hateoas uses the Hypertext Application
Language (HAL) for JSON
serialization. This specifies the structure of the response (e.g. that
"links" should live under a _links key):
{
"id": 42,
"first_name": "Adrien",
"last_name": "Brault",
"_links": {
"self": {
"href": "/api/users/42"
}
}
}For XML, Atom Links are used by default:
It is worth mentioning that these formats are the default ones, not the only available ones. You can use different formats through different serializers, and even add your owns.
Now that you know how to add links, let's see how to add embedded resources.
Sometimes, it's more efficient to embed related resources rather than link to them, as it prevents clients from having to make extra requests to fetch those resources.
An embedded resource is a named relation that contains data, represented
by the embedded parameter.
Annotation (PHP
use JMS\Serializer\Annotation as Serializer;
use Hateoas\Configuration\Annotation as Hateoas;
/**
* ...
*
* @Hateoas\Relation(
* "manager",
* href = "expr('/api/users/' ~ object.getManager().getId())",
* embedded = "expr(object.getManager())",
* exclusion = @Hateoas\Exclusion(excludeIf = "expr(object.getManager() === null)")
* )
*/
class User
{
...
/** @Serializer\Exclude */
private $manager;
}Attribute (PHP 8.1 and greater)
use JMS\Serializer\Annotation as Serializer;
use Hateoas\Configuration\Annotation as Hateoas;
#[Hateoas\Relation(
'manager',
href: "expr('/api/users/' ~ object.getManager().getId())",
embedded: "expr(object.getManager())",
exclusion: new Hateoas\Exclusion(excludeif: "expr(object.getManager() === null)"),
)]
class User
{
...
#[Serializer\Exclude]
private $manager;
}Note: You will need to exclude the manager property from the serialization,
otherwise both the serializer and Hateoas will serialize it.
You will also have to exclude the manager relation when the manager is null,
because otherwise an error will occur when creating the href link (calling
getId() on null).
Tip: If the manager property is an object that already has a _self
link, you can re-use that value for the href instead of repeating it here.
See LinkHelper.
$hateoas = HateoasBuilder::create()->build();
$user = new User(42, 'Adrien', 'Brault', new User(23, 'Will', 'Durand'));
$json = $hateoas->serialize($user, 'json');
$xml = $hateoas->serialize($user, 'xml');For json, the HAL representation places these embedded relations inside
an _embedded key:
{
"id": 42,
"first_name": "Adrien",
"last_name": "Brault",
"_links": {
"self": {
"href": "/api/users/42"
},
"manager": {
"href": "/api/users/23"
}
},
"_embedded": {
"manager": {
"id": 23,
"first_name": "Will",
"last_name": "Durand",
"_links": {
"self": {
"href": "/api/users/23"
}
}
}
}
}In XML, serializing embedded relations will create new elements:
The tag name of an embedded resource is inferred from the
@XmlRoot
annotation (xml_root_name in YAML, xml-root-name in XML) coming from the
Serializer configuration.
The library provides several classes in the Hateoas\Representation\*
namespace to help you with common tasks. These are simple classes configured
with the library's annotations.
The PaginatedRepresentation, OffsetRepresentation and CollectionRepresentation classes are
probably the most interesting ones. These are helpful when your resource is
actually a collection of resources (e.g. /users is a collection of users).
These help you represent the collection and add pagination and limits:
…The CollectionRepresentation offers a basic representation of an embedded collection.
The PaginatedRepresentation is designed to add self, first, and when
possible last, next, and previous links.
The OffsetRepresentation works just like PaginatedRepresentation but is useful
when pagination is expressed by offset, limit and total.
The RouteAwareRepresentation adds a self relation based on a given route.
You can generate absolute URIs by setting the absolute parameter to true
in both the PaginatedRepresentation and the RouteAwareRepresentation.
The Hateoas library also provides a PagerfantaFactory to easily build
PaginatedRepresentation from a
Pagerfanta instance. If you use
the Pagerfanta library, this is an easier way to create the collection
represent
No open issues yet, or sync has not completed.