Une bibliothèque PHP pour prendre en charge l'implémentation de représentations pour les services Web HATEOAS REST.
La méthode recommandée pour installer Hateoas consiste à utiliser Composer. Exigez le package willdurand/hateoas
en exécutant la commande suivante :
composer require willdurand/hateoas
Cela résoudra la dernière version stable.
Sinon, installez la bibliothèque et configurez vous-même le chargeur automatique.
Si vous souhaitez utiliser des annotations pour la configuration, vous devez installer le package doctrine/annotations
:
composer require doctrine/annotations
Si votre application utilise PHP 8.1 ou supérieur, il est recommandé d'utiliser les attributs PHP natifs. Dans ce cas, vous n'avez pas besoin d'installer le package Doctrine.
Il y a un pack pour ça ! Installez le BazingaHateoasBundle et profitez-en !
Important:
Pour ceux qui utilisent la version
1.0
, vous pouvez accéder à cette page de documentation.Pour ceux qui utilisent la version
2.0
, vous pouvez accéder à cette page de documentation.La documentation suivante a été écrite pour Hateoas 3.0 et supérieur.
Hateoas exploite la bibliothèque Serializer pour fournir un moyen efficace de créer des services Web HATEOAS REST. HATEOAS signifie Hypermedia as the Engine of Application State et ajoute des liens hypermédia à vos représentations (c'est-à-dire vos réponses API). HATEOAS concerne la découvrabilité des actions sur une ressource.
Par exemple, disons que vous disposez d'une API utilisateur qui renvoie une représentation d'un seul utilisateur comme suit :
{
"user" : {
"id" : 123 ,
"first_name" : " John " ,
"last_name" : " Doe "
}
}
Afin d'indiquer à vos consommateurs d'API comment récupérer les données de cet utilisateur spécifique, vous devez ajouter votre tout premier lien vers cette représentation, appelons- self
car il s'agit de l'URI de cet utilisateur particulier :
{
"user" : {
"id" : 123 ,
"first_name" : " John " ,
"last_name" : " Doe " ,
"_links" : {
"self" : { "href" : " http://example.com/api/users/123 " }
}
}
}
Examinons maintenant Hateoas.
Dans la terminologie Hateoas, les liens sont considérés comme des relations ajoutées aux ressources. Il convient de mentionner que les relations font également référence aux ressources intégrées , mais ce sujet sera abordé dans la section Intégration des ressources.
Un lien est une relation qui est identifiée par un name
(par exemple self
) et qui possède un paramètre href
:
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 () {}
}
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 () {}
}
Dans l'exemple ci-dessus, nous configurons une relation self
qui est un lien grâce au paramètre href
. Sa valeur, qui peut paraître étrange à première vue, sera détaillée dans la section Le langage d'expression. Cette valeur spéciale est utilisée pour générer un URI.
Dans cette section, les annotations/attributs sont utilisés pour configurer Hateoas. Les formats XML et YAML sont également pris en charge. Si vous le souhaitez, vous pouvez également utiliser du PHP simple.
Important : vous devez configurer le sérialiseur et Hateoas de la même manière. Par exemple, si vous utilisez YAML pour configurer Serializer, utilisez YAML pour configurer Hateoas.
Le moyen le plus simple d’essayer HATEOAS est d’utiliser HateoasBuilder
. Le constructeur dispose de nombreuses méthodes pour configurer le sérialiseur Hateoas, mais nous ne les aborderons pas pour le moment (voir The HateoasBuilder). Tout fonctionne bien dès la sortie de la boîte :
use Hateoas HateoasBuilder ;
$ hateoas = HateoasBuilder:: create ()-> build ();
$ user = new User ( 42 , ' Adrien ' , ' Brault ' );
$ json = $ hateoas -> serialize ( $ user , ' json ' );
$ xml = $ hateoas -> serialize ( $ user , ' xml ' );
L'objet $hateoas
est une instance de JMSSerializerSerializerInterface
, provenant de la bibliothèque Serializer. Hateoas n'est pas livré avec son propre sérialiseur, il se connecte au sérialiseur JMS.
Par défaut, Hateoas utilise le langage d'application hypertexte (HAL) pour la sérialisation JSON. Ceci spécifie la structure de la réponse (par exemple, que les "liens" doivent résider sous une clé _links
) :
{
"id" : 42 ,
"first_name" : " Adrien " ,
"last_name" : " Brault " ,
"_links" : {
"self" : {
"href" : " /api/users/42 "
}
}
}
Pour XML, les liens Atom sont utilisés par défaut :
< user id = " 42 " >
< first_name > <![CDATA[ Adrien ]]> </ first_name >
< last_name > <![CDATA[ Brault ]]> </ last_name >
< link rel = " self " href = " /api/users/42 " />
</ user >
Il convient de mentionner que ces formats sont ceux par défaut et non les seuls disponibles. Vous pouvez utiliser différents formats via différents sérialiseurs et même ajouter les vôtres.
Maintenant que vous savez comment ajouter des liens , voyons comment ajouter des ressources intégrées .
Parfois, il est plus efficace d'intégrer des ressources associées plutôt que de créer un lien vers elles, car cela évite aux clients d'avoir à faire des requêtes supplémentaires pour récupérer ces ressources.
Une ressource incorporée est une relation nommée qui contient des données, représentées par le paramètre embedded
.
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 ;
}
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 ;
}
Remarque : Vous devrez exclure la propriété du gestionnaire de la sérialisation, sinon le sérialiseur et Hateoas la sérialiseront. Vous devrez également exclure la relation manager lorsque le manager est null
, car sinon une erreur se produira lors de la création du lien href
(appel de getId()
sur null
).
Astuce : Si la propriété manager est un objet qui possède déjà un lien _self
, vous pouvez réutiliser cette valeur pour le href
au lieu de la répéter ici. Voir 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 ' );
Pour json
, la représentation HAL place ces relations intégrées dans une clé _embedded
:
{
"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 "
}
}
}
}
}
En XML, la sérialisation des relations embedded
créera de nouveaux éléments :
< 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/23 " />
< manager rel = " manager " id = " 23 " >
< first_name > <![CDATA[ Will ]]> </ first_name >
< last_name > <![CDATA[ Durand ]]> </ last_name >
< link rel = " self " href = " /api/users/23 " />
</ manager >
</ user >
Le nom de balise d'une ressource intégrée est déduit de l'annotation @XmlRoot
( xml_root_name
en YAML, xml-root-name
en XML) provenant de la configuration du Serializer.
La bibliothèque fournit plusieurs classes dans l'espace de noms HateoasRepresentation*
pour vous aider dans les tâches courantes. Ce sont des classes simples configurées avec les annotations de la bibliothèque.
Les classes PaginatedRepresentation
, OffsetRepresentation
et CollectionRepresentation
sont probablement les plus intéressantes. Ceux-ci sont utiles lorsque votre ressource est en réalité une collection de ressources (par exemple, /users
est une collection d'utilisateurs). Ceux-ci vous aident à représenter la collection et à ajouter une pagination et des limites :
use Hateoas Representation PaginatedRepresentation ;
use Hateoas Representation CollectionRepresentation ;
$ paginatedCollection = new PaginatedRepresentation (
new CollectionRepresentation ( array ( $ user1 , $ user2 , ...)),
' user_list ' , // route
array (), // route parameters
1 , // page number
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 , optional , defaults to `false`
75 // total collection size , optional , defaults to `null`
);
$ json = $ hateoas -> serialize ( $ paginatedCollection , ' json ' );
$ xml = $ hateoas -> serialize ( $ paginatedCollection , ' xml ' );
CollectionRepresentation
offre une représentation de base d’une collection intégrée.
La PaginatedRepresentation
est conçue pour ajouter des liens self
, first
et, si possible, last
, next
et previous
.
OffsetRepresentation
fonctionne exactement comme PaginatedRepresentation
mais est utile lorsque la pagination est exprimée par offset
, limit
et total
.
La RouteAwareRepresentation
ajoute une relation self
basée sur un itinéraire donné.
Vous pouvez générer des URI absolus en définissant le paramètre absolute
sur true
dans PaginatedRepresentation
et RouteAwareRepresentation
.
La bibliothèque Hateoas fournit également une PagerfantaFactory
pour créer facilement PaginatedRepresentation
à partir d'une instance Pagerfanta. Si vous utilisez la bibliothèque Pagerfanta, il s'agit d'un moyen plus simple de créer les représentations de collection :
use Hateoas Configuration Route ;
use Hateoas Representation Factory PagerfantaFactory ;
$ pagerfantaFactory = new PagerfantaFactory (); // you can pass the page ,
// and limit parameters name
$ paginatedCollection = $ pagerfantaFactory -> createRepresentation (
$ pager ,
new Route ( ' user_list ' , array ())
);
$ json = $ hateoas -> serialize ( $ paginatedCollection , ' json ' );
$ xml = $ hateoas -> serialize ( $ paginatedCollection , ' xml ' );
Vous obtiendrez le contenu JSON suivant :
{
"page" : 1 ,
"limit" : 10 ,
"pages" : 1 ,
"_links" : {
"self" : {
"href" : " /api/users?page=1&limit=10 "
},
"first" : {
"href" : " /api/users?page=1&limit=10 "
},
"last" : {
"href" : " /api/users?page=1&limit=10 "
}
},
"_embedded" : {
"items" : [
{ "id" : 123 },
{ "id" : 456 }
]
}
}
Et le contenu XML suivant :
<? xml version = " 1.0 " encoding = " UTF-8 " ?>
< collection page = " 1 " limit = " 10 " pages = " 1 " >
< entry id = " 123 " ></ entry >
< entry id = " 456 " ></ entry >
< 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=1 & limit=10 " />
</ collection >
Si vous souhaitez personnaliser le CollectionRepresentation
inline, transmettez-en un comme troisième argument de la méthode createRepresentation()
:
use Hateoas Representation Factory PagerfantaFactory ;
$ pagerfantaFactory = new PagerfantaFactory (); // you can pass the page and limit parameters name
$ paginatedCollection = $ pagerfantaFactory -> createRepresentation (
$ pager ,
new Route ( ' user_list ' , array ()),
new CollectionRepresentation ( $ pager -> getCurrentPageResults ())
);
$ json = $ hateoas -> serialize ( $ paginatedCollection , ' json ' );
$ xml = $ hateoas -> serialize ( $ paginatedCollection , ' xml ' );
Si vous souhaitez modifier le nom racine XML de la collection, créez une nouvelle classe avec la racine XML configurée et utilisez le mécanisme en ligne :
use JMS Serializer Annotation as Serializer ;
/ * *
* @ Serializer XmlRoot ( "users" )
* /
class UsersRepresentation
{
/ * *
* @ Serializer Inline
* /
private $ inline ;
public function __construct ( $ inline )
{
$ this -> inline = $ inline ;
}
}
$ paginatedCollection = . . . ;
$ paginatedCollection = new UsersRepresentation ( $ paginatedCollection );
use JMS Serializer Annotation as Serializer ;
#[ Serializer XmlRoot( ' users ' )]
class UsersRepresentation
{
#[ Serializer Inline]
private $ inline ;
public function __construct ( $ inline )
{
$ this -> inline = $ inline ;
}
}
$ paginatedCollection = . . . ;
$ paginatedCollection = new UsersRepresentation ( $ paginatedCollection );
Comme mentionné dans la section précédente, les représentations sont des classes configurées avec les annotations de la bibliothèque afin de vous aider dans les tâches courantes. Les représentations de collection sont décrites dans Gérer la collection.
La VndErrorRepresentation
vous permet de décrire une réponse d'erreur suivant la spécification vnd.error
.
$ error = new VndErrorRepresentation (
' Validation failed ' ,
42 ,
' http://.../ ' ,
' http://.../ '
);
La sérialisation d'une telle représentation en XML et JSON vous donnerait les résultats suivants :
<? xml version = " 1.0 " encoding = " UTF-8 " ?>
< resource logref = " 42 " >
< message > <![CDATA[ Validation failed ]]> </ message >
< link rel = " help " href = " http://.../ " />
< link rel = " describes " href = " http://.../ " />
</ resource >
{
"message" : " Validation failed " ,
"logref" : 42 ,
"_links" : {
"help" : {
"href" : " http://.../ "
},
"describes" : {
"href" : " http://.../ "
}
}
}
Astuce : il est recommandé de créer vos propres classes d'erreur qui étendent la classe VndErrorRepresentation
.
Hateoas s'appuie sur le puissant composant Symfony ExpressionLanguage pour récupérer des valeurs telles que des liens, des identifiants ou des objets à intégrer.
Chaque fois que vous renseignez une valeur (par exemple une Relation href
dans des annotations ou YAML), vous pouvez soit transmettre une valeur codée en dur , soit une expression . Pour utiliser le langage d'expression, vous devez utiliser la notation expr()
:
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Hateoas Relation ( "self" , href = "expr('/api/users/' ~ object.getId())" )
* /
use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas Relation( ' self ' , href: " expr('/api/users/' ~ object.getId()) " )]
Vous pouvez en savoir plus sur la syntaxe d'expression en lisant la documentation officielle : La syntaxe d'expression.
Nativement, une variable spéciale nommée object
est disponible dans chaque expression, et représente l'objet courant :
expr(object.getId())
Nous appelons une telle variable une variable contextuelle .
Vous pouvez ajouter vos propres variables de contexte au contexte du langage d'expression en les ajoutant à l'évaluateur d'expression.
À l'aide de HateoasBuilder
, appelez la méthode setExpressionContextVariable()
pour ajouter de nouvelles variables de contexte :
use Hateoas HateoasBuilder ;
$ hateoas = HateoasBuilder:: create ()
-> setExpressionContextVariable ( ' foo ' , new Foo ())
-> build ();
La variable foo
est maintenant disponible :
expr(foo !== null)
Pour plus d'informations sur la façon d'ajouter des fonctions au langage d'expression, veuillez vous référer à https://symfony.com/doc/current/components/expression_langage/extending.html
Puisque vous pouvez utiliser le langage d'expression pour définir les liens de relations (touche href
), vous pouvez faire beaucoup de choses par défaut. Cependant, si vous utilisez un framework, il est probable que vous souhaitiez utiliser des routes pour créer des liens.
Vous devrez d'abord configurer un UrlGenerator
sur le builder. Vous pouvez soit implémenter HateoasUrlGeneratorUrlGeneratorInterface
, soit utiliser HateoasUrlGeneratorCallableUrlGenerator
:
use Hateoas UrlGenerator CallableUrlGenerator ;
$ hateoas = HateoasBuilder:: create ()
-> setUrlGenerator (
null , // By default all links uses the generator configured with the null name
new CallableUrlGenerator ( function ( $ route , array $ parameters , $ absolute ) use ( $ myFramework ) {
return $ myFramework -> generateTheUrl ( $ route , $ parameters , $ absolute );
})
)
-> build ()
;
Vous pourrez alors utiliser l'annotation @Route :
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Hateoas Relation (
* "self" ,
* href = @ Hateoas Route (
* "user_get" ,
* parameters = {
* "id" = "expr(object.getId())"
* }
* )
* )
* /
class User
use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas Relation(
' self ' ,
href: new Hateoas Route (
' user_get ' ,
parameters: [
' id ' => ' expr(object.getId()) ' ,
],
)
)]
class User
{
"id" : 42 ,
"first_name" : " Adrien " ,
"last_name" : " Brault " ,
"_links" : {
"self" : {
"href" : " /api/users/42 "
}
}
}
Notez que la bibliothèque est livrée avec un SymfonyUrlGenerator
. Par exemple, pour l'utiliser dans Silex :
use Hateoas UrlGenerator SymfonyUrlGenerator ;
$ hateoas = HateoasBuilder:: create ()
-> setUrlGenerator ( null , new SymfonyUrlGenerator ( $ app [ ' url_generator ' ]))
-> build ()
;
Hateoas fournit un ensemble d'aides pour faciliter le processus de création d'API.
La classe LinkHelper
fournit une méthode getLinkHref($object, $rel, $absolute = false)
qui vous permet d'obtenir la valeur href de n'importe quel objet, pour n'importe quel nom de relation donné. Il est capable de générer un URI (absolu ou relatif) à partir de n'importe quelle relation de lien :
$ user = new User ( 123 , ' William ' , ' Durand ' );
$ linkHelper -> getLinkHref ( $ user , ' self ' );
// / api / users / 123
$ linkHelper -> getLinkHref ( $ user , ' self ' , true );
// http : // example . com / api / users / 123
link
La fonctionnalité ci-dessus est également disponible dans vos expressions (cf. Le Langage d'Expression) via la fonction link(object, rel, absolute)
:
/ * *
* @ Hateoas Relation (
* "self" ,
* href = @ Hateoas Route ( "post_get" , parameters = { "id" = "expr(object.getId())" })
* )
* /
class Post {}
/ * *
* @ Hateoas Relation (
* "self" ,
* href = @ Hateoas Route ( "user_get" , parameters = { "id" = "expr(object.getId())" })
* )
* @ Hateoas Relation (
* "post" ,
* href = "expr(link(object.getPost(), 'self', true))"
* )
* @ Hateoas Relation (
* "relative" ,
* href = "expr(link(object.getRelativePost(), 'self'))"
* )
* /
class User
{
...
public function getPost ()
{
return new Post ( 456 );
}
public function getRelativePost ()
{
return new Post ( 789 );
}
}
#[ Hateoas Relation(
' self ' ,
href: new Hateoas Route (
' post_get ' ,
parameters: [
' id ' => ' expr(object.getId()) ' ,
],
),
)]
class Post {}
#[ Hateoas Relation(
' self ' ,
href: new Hateoas Route (
' user_get ' ,
parameters: [
' id ' => ' expr(object.getId()) ' ,
],
),
)]
#[ Hateoas Relation(
' post ' ,
href: " expr(link(object.getPost(), 'self', true)) " ,
)]
#[ Hateoas Relation(
' relative ' ,
href: " expr(link(object.getRelativePost(), 'self')) " ,
)]
class User
{
...
public function getPost ()
{
return new Post ( 456 );
}
public function getRelativePost ()
{
return new Post ( 789 );
}
}
Faites attention aux expressions href
pour les relations post
et relative
, ainsi qu'à leurs valeurs correspondantes dans le contenu JSON suivant :
{
"user" : {
"id" : 123 ,
"first_name" : " William " ,
"last_name" : " Durand " ,
"_links" : {
"self" : { "href" : " http://example.com/api/users/123 " },
"post" : { "href" : " http://example.com/api/posts/456 " },
"relative" : { "href" : " /api/posts/789 " }
}
}
}
Il convient de mentionner que vous pouvez forcer si vous souhaitez un URI absolu ou relatif en utilisant le troisième argument à la fois dans la méthode getLinkHref()
et dans la fonction link
.
Important : par défaut, tous les URI seront relatifs , même ceux qui sont définis comme absolus dans leur configuration.
$ linkHelper -> getLinkHref ( $ user , ' post ' );
// / api / posts / 456
$ linkHelper -> getLinkHref ( $ user , ' post ' , true );
// http : // example . com / api / posts / 456
$ linkHelper -> getLinkHref ( $ user , ' relative ' );
// / api / posts / 789
$ linkHelper -> getLinkHref ( $ user , ' relative ' , true );
// http : // example . com / api / posts / 789
Hateoas fournit également un ensemble d'extensions Twig.
La LinkExtension
vous permet d'utiliser le LinkHelper dans vos modèles Twig, afin que vous puissiez générer des liens dans vos modèles HTML par exemple.
Cette extension expose la méthode d'assistance getLinkHref()
via la fonction link_href
Twig :
{{ link_href(user, 'self') }}
{# will generate: /users/123 #}
{{ link_href(will, 'self', false) }}
{# will generate: /users/123 #}
{{ link_href(will, 'self', true) }}
{# will generate: http://example.com/users/123 #}
Hateoas fournit un ensemble de sérialiseurs . Chaque sérialiseur permet de générer du contenu XML ou JSON suivant un format spécifique, comme HAL, ou Atom Links par exemple.
Le JsonHalSerializer
vous permet de générer des relations conformes à HAL en JSON. Il s'agit du sérialiseur JSON par défaut dans Hateoas.
HAL fournit sa capacité de liaison avec une convention qui dit qu'un objet ressource possède une propriété réservée appelée _links
. Cette propriété est un objet qui contient des liens. Ces liens sont définis par leur relation de lien.
HAL décrit également une autre convention qui dit qu'une ressource peut avoir une autre propriété réservée nommée _embedded
. Cette propriété est similaire à _links
dans la mesure où les ressources intégrées sont classées par nom de relation. La principale différence est que plutôt que d’être des liens, les valeurs sont des objets ressources.
{
"message" : " Hello, World! " ,
"_links" : {
"self" : {
"href" : " /notes/0 "
}
},
"_embedded" : {
"associated_events" : [
{
"name" : " SymfonyCon " ,
"date" : " 2013-12-12T00:00:00+0100 "
}
]
}
}
Le XmlSerializer
vous permet de générer des liens Atom dans vos documents XML. Il s'agit du sérialiseur XML par défaut.
<? xml version = " 1.0 " encoding = " UTF-8 " ?>
< note >
< message > <![CDATA[ Hello, World! ]]> </ message >
< link rel = " self " href = " /notes/0 " />
< events rel = " associated_events " >
< event >
< name > <![CDATA[ SymfonyCon ]]> </ name >
< date > <![CDATA[ 2013-12-12T00:00:00+0100 ]]> </ date >
</ event >
</ events >
</ note >
Le XmlHalSerializer
vous permet de générer des relations conformes à HAL en XML.
HAL en XML est similaire à HAL en JSON dans le sens où il décrit les balises link
et les balises resource
.
Remarque : la relation self
deviendra en fait un attribut de la ressource principale au lieu d'être une balise link
. D'autres liens seront générés sous forme de balises link
.
<? xml version = " 1.0 " encoding = " UTF-8 " ?>
< note href = " /notes/0 " >
< message > <![CDATA[ Hello, World! ]]> </ message >
< resource rel = " associated_events " >
< name > <![CDATA[ SymfonyCon ]]> </ name >
< date > <![CDATA[ 2013-12-12T00:00:00+0100 ]]> </ date >
</ resource >
</ note >
Vous devez implémenter SerializerInterface
qui décrit deux méthodes pour sérialiser les liens et les relations intégrées .
La classe HateoasBuilder
permet de configurer facilement Hateoas grâce à une API puissante et fluide.
use Hateoas HateoasBuilder ;
$ hateoas = HateoasBuilder:: create ()
-> setCacheDir ( ' /path/to/cache/dir ' )
-> setDebug ( $ trueOrFalse )
-> setDefaultXmlSerializer ()
. . .
-> build ();
Toutes les méthodes ci-dessous renvoient le constructeur actuel, afin que vous puissiez les enchaîner.
setXmlSerializer(SerializerInterface $xmlSerializer)
: définit le sérialiseur XML à utiliser. La valeur par défaut est : XmlSerializer
;setDefaultXmlSerializer()
: définit le sérialiseur XML par défaut ( XmlSerializer
). setJsonSerializer(SerializerInterface $jsonSerializer)
: définit le sérialiseur JSON à utiliser. La valeur par défaut est : JsonHalSerializer
;setDefaultJsonSerializer()
: définit le sérialiseur JSON par défaut ( JsonHalSerializer
). setUrlGenerator($name = null, UrlGeneratorInterface $urlGenerator)
: ajoute un nouveau générateur d'URL nommé. Si $name
est null
, le générateur d'URL sera celui par défaut. setExpressionContextVariable($name, $value)
: ajoute une nouvelle variable de contexte d'expression ;setExpressionLanguage(ExpressionLanguage $expressionLanguage)
; includeInterfaceMetadata($include)
: s'il faut inclure les métadonnées des interfaces ;setMetadataDirs(array $namespacePrefixToDirMap)
: définit une carte des préfixes d'espace de noms vers les répertoires. Cette méthode remplace tous les répertoires précédemment définis ;addMetadataDir($dir, $namespacePrefix = '')
: ajoute un répertoire dans lequel le sérialiseur recherchera les métadonnées de la classe ;addMetadataDirs(array $namespacePrefixToDirMap)
: ajoute une carte des préfixes d'espace de noms aux répertoires ;replaceMetadataDir($dir, $namespacePrefix = '')
: similaire à addMetadataDir()
, mais remplace une entrée existante.Veuillez lire la documentation officielle du sérialiseur pour plus de détails.
setDebug($debug)
: active ou désactive le mode débogage ;setCacheDir($dir)
: définit le répertoire du cache.Le sérialiseur et les bibliothèques Hateoas collectent des métadonnées sur vos objets à partir de diverses sources telles que YML, XML ou des annotations. Afin de rendre ce processus aussi efficace que possible, il est recommandé d'autoriser la bibliothèque Hateoas à mettre en cache ces informations. Pour ce faire, configurez un répertoire de cache :
$ builder = Hateoas HateoasBuilder:: create ();
$ hateoas = $ builder
-> setCacheDir ( $ someWritableDir )
-> build ();
Hateoas prend en charge plusieurs sources de métadonnées. Par défaut, il utilise des annotations Doctrine (PHP < 8.1) ou des attributs PHP natifs (PHP >= 8.1), mais vous pouvez également stocker des métadonnées dans des fichiers XML ou YAML. Pour ce dernier, il est nécessaire de configurer un répertoire de métadonnées où se trouvent ces fichiers :
$ hateoas = Hateoas HateoasBuilder:: create ()
-> addMetadataDir ( $ someDir )
-> build ();
Hateoas s'attendrait à ce que les fichiers de métadonnées soient nommés comme les noms de classe complets où tous sont remplacés par
.
. Si votre classe s'appelle VendorPackageFoo
le fichier de métadonnées devra se trouver dans $someDir/Vendor.Package.Foo.(xml|yml)
.
Hateoas permet aux frameworks d'ajouter dynamiquement des relations aux classes en fournissant un point d'extension au niveau de la configuration. Cette fonctionnalité peut être utile pour ceux qui souhaitent créer une nouvelle couche au-dessus de Hateoas, ou ajouter des relations "globales" plutôt que de copier la même configuration sur chaque classe.
Afin de tirer parti de ce mécanisme, l'interface ConfigurationExtensionInterface
doit être implémentée :
use Hateoas Configuration Metadata ConfigurationExtensionInterface ;
use Hateoas Configuration Metadata ClassMetadataInterface ;
use Hateoas Configuration Relation ;
class AcmeFooConfigurationExtension implements ConfigurationExtensionInterface
{
/ * *
* {@ inheritDoc }
* /
public function decorate ( ClassMetadataInterface $ classMetadata ): void
{
if ( 0 === strpos ( ' AcmeFooModel ' , $ classMetadata -> getName ())) {
// Add a "root" relation to all classes in the `AcmeFooModel` namespace
$ classMetadata -> addRelation (
new Relation (
' root ' ,
' / '
)
);
}
}
}
Vous pouvez accéder aux relations existantes chargées depuis Annotations, XML ou YAML avec $classMetadata->getRelations()
.
Si $classMetadata
a des relations, ou si vous y ajoutez des relations, ses relations seront mises en cache. Ainsi, si vous lisez des fichiers de configuration (Annotations, XML ou YAML), assurez-vous de les référencer sur les métadonnées de la classe :
$ classMetadata -> fileResources [] = $ file ;
<? xml version = " 1.0 " encoding = " UTF-8 " ?>
< serializer >
< class name = " AcmeDemoRepresentationUser " h : providers = " Class::getRelations expr(sevice('foo').getMyAdditionalRelations()) " xmlns : h = " https://github.com/willdurand/Hateoas " >
< h : relation rel = " self " >
< h : href uri = " http://acme.com/foo/1 " />
</ h : relation >
< h : relation rel = " friends " >
< h : href route = " user_friends " generator = " my_custom_generator " >
< h : parameter name = " id " value = " expr(object.getId()) " />
< h : parameter name = " page " value = " 1 " />
</ h : ref >
< h : embedded xml-element-name = " users " >
< h : content >expr(object.getFriends())</ h : content >
< h : exclusion ... />
</ h : embedded >
< h : exclusion groups = " Default, user_full " since-version = " 1.0 " until-version = " 2.2 " exclude-if = " expr(object.getFriends() === null) " />
</ h : relation >
</ class >
</ serializer >
Voir le fichier hateoas.xsd
pour plus de détails.
AcmeDemoRepresentationUser :
relations :
-
rel : self
href : http://acme.com/foo/1
-
rel : friends
href :
route : user_friends
parameters :
id : expr(object.getId())
page : 1
generator : my_custom_generator
absolute : false
embedded :
content : expr(object.getFriends())
xmlElementName : users
exclusion : ...
exclusion :
groups : [Default, user_full]
since_version : 1.0
until_version : 2.2
exclude_if : expr(object.getFriends() === null)
relation_providers : [ "Class::getRelations", "expr(sevice('foo').getMyAdditionalRelations())" ]
Cette annotation peut être définie sur une classe.
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Hateoas Relation (
* name = "self" ,
* href = "http://hello" ,
* embedded = "expr(object.getHello())" ,
* attributes = { "foo" = "bar" },
* exclusion = ...,
* )
* /
use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas Relation(
name: ' self ' ,
href: ' http://hello ' ,
embedded: ' expr(object.getHello()) ' ,
attributes: [ ' foo ' => ' bar ' ],
exclusion: ' ... ' ,
)]
Propriété | Requis | Contenu | Langage d'expression |
---|---|---|---|
nom | Oui | chaîne | Non |
href | Si l'intégration n'est pas définie | chaîne / @Route | Oui |
intégré | Si href n'est pas défini | chaîne / @Embedded | Oui |
attributs | Non | tableau | Oui sur les valeurs |
exclusion | Non | @Exclusion | N / A |
Important : attributes
ne sont utilisés que sur les relations de lien (c'est-à-dire combinés avec la propriété href
, pas avec celle embedded
).
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Hateoas Relation (
* name = "self" ,
* href = @ Hateoas Route (
* "user_get" ,
* parameters = { "id" = "expr(object.getId())" },
* absolute = true ,
* generator = "my_custom_generator"
* )
* )
* /
use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas Relation(
name: ' self ' ,
href: new Hateoas Route (
' user_get ' ,
parameters: [ ' id ' = ' expr (object. getId ())'],
absolute: true ,
generator: ' my_custom_generator ' ,
),
)]
Cette annotation peut être définie dans la propriété href de l'annotation @Relation. Cela vous permet d'accéder à votre générateur d'URL, si vous en avez configuré un.
Propriété | Requis | Contenu | Langage d'expression |
---|---|---|---|
nom | Oui | chaîne | Non |
paramètres | La valeur par défaut est array() | tableau/chaîne | Oui (chaîne + valeurs du tableau) |
absolu | La valeur par défaut est false | booléen / chaîne | Oui |
générateur | Non | chaîne / nul | Non |
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Hateoas Relation (
* name = "friends" ,
* embedded = @ Hateoas Embedded (
* "expr(object.getFriends())" ,
* exclusion = ...,
* xmlElementName = "users"
* )
* )
* /
use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas Relation(
name: ' friends ' ,
embedded: new Hateoas Embedded (
' expr(object.getFriends()) ' ,
exclusion: ' ... ' ,
xmlElementName: ' users ' ,
),
)]
Cette annotation peut être définie dans la propriété incorporée de l'annotation @Relation. Ceci est utile si vous devez configurer les options exclusion
ou xmlElementName
pour la ressource intégrée.
Propriété | Requis | Contenu | Langage d'expression |
---|---|---|---|
contenu | Oui | chaîne/tableau | Oui (chaîne) |
exclusion | La valeur par défaut est array() | @Exclusion | N / A |
xmlElementName | La valeur par défaut est array() | chaîne | Non |
Cette annotation peut être définie dans la propriété d'exclusion des annotations @Relation et @Embedded.
Propriété | Requis | Contenu | Langage d'expression |
---|---|---|---|
groupes | Non | tableau | Non |
depuis la version | Non | chaîne | Non |
jusqu'à la version | Non | chaîne | Non |
profondeur max | Non | entier | Non |
exclureSi | Non | chaîne / booléen | Oui |
Toutes les valeurs, à l'exception de excludeIf
agissent de la même manière que lorsqu'elles sont utilisées directement sur les propriétés normales avec le sérialiseur.
excludeIf
attend un booléen et est utile lorsqu'une autre expression échouerait dans certaines circonstances. Dans cet exemple, si la méthode getManager
est null
, vous devez l'exclure pour éviter l'échec de la génération d'URL :
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Hateoas Relation (
* "manager" ,
* href = @ Hateoas Route (
* "user_get" ,
* parameters = { "id" = "expr(object.getManager().getId())" }
* ),
* exclusion = @ Hateoas Exclusion ( excludeIf = "expr(null === object.getManager())" )
* )
* /
class User
{
public function getId () {}
/ * *
* @ return User | null
* /
public function getManager () {}
}
use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas Relation(
name: ' manager ' ,
href: new Hateoas Route (
' user_get ' ,
parameters: [ ' id ' => ' expr(object.getManager().getId()) ' ],
),
exclusion: new Hateoas Exclusion (excludeIf: ' expr(null === object.getManager()) ' )
)]
class User
{
public function getId () {}
public function getManager (): ? User {}
}
Cette annotation peut être définie sur une classe. C'est utile si vous souhaitez sérialiser plusieurs relations (liens). A titre d'exemple :
{
"_links": {
"relation_name": [
{"href": "link1"},
{"href": "link2"},
{"href": "link3"}
]
}
}
Propriété | Requis | Contenu | Langage d'expression |
---|---|---|---|
nom | Oui | chaîne | Oui |
Il peut s'agir de "nom" :
my_func
MyClass::getExtraRelations
expr(service('user.rel_provider').getExtraRelations())
Ici et exemple utilisant le langage d'expression :
use Hateoas Configuration Annotation as Hateoas ;
/ * *
* @ Hateoas RelationProvider ( "expr(service('user.rel_provider').getExtraRelations())" )
* /
class User
{
...
}
use Hateoas Configuration Annotation as Hateoas ;
#[ Hateoas RelationProvider( " expr(service('user.rel_provider').getExtraRelations()) " )]
class User
{
...
}
Ici la classe UserRelPrvider
:
use Hateoas Configuration Relation ;
use Hateoas Configuration Route ;
class UserRelPrvider
{
private $ evaluator ;
public function __construct ( CompilableExpressionEvaluatorInterface $ evaluator )
{
$ this -> evaluator = $ evaluator ;
}
/ * *
* @ return Relation []
* /
public function getExtraRelations (): array
{
// You need to return the relations
return array (
new Relation (
' self ' ,
new Route (
' foo_get ' ,
[ ' id ' => $ this -> evaluator -> parse ( ' object.getId() ' , [ ' object ' ])]
)
)
);
}
}
$this->evaluator
implémentant CompilableExpressionEvaluatorInterface
est utilisé pour analyser le langage d'expression sous une forme qui peut être mise en cache et enregistrée pour une utilisation ultérieure. Si vous n'avez pas besoin du langage d'expression dans vos relations, alors ce service n'est pas nécessaire.
Le service user.rel_provider
est défini comme :
user.rel_provider :
class : UserRelPrvider
arguments :
- ' @jms_serializer.expression_evaluator '
Dans ce cas, jms_serializer.expression_evaluator
est un service implémentant CompilableExpressionEvaluatorInterface
.
Cette section fait référence aux éléments internes de Hateoas, fournissant de la documentation sur les parties cachées de cette bibliothèque. Ce n’est pas toujours pertinent pour les utilisateurs finaux, mais intéressant pour les développeurs ou les personnes intéressées à apprendre comment les choses fonctionnent sous le capot.
willdurand/hateoas
suit le versioning sémantique.
Depuis octobre 2013, les versions 1.x
et 0.x
ne sont officiellement plus prises en charge (notez que 1.x
n'a jamais été publiée).
La version 3.x
est la version stable majeure actuelle.
La version 2.x
est maintenue uniquement pour les corrections de bogues de sécurité et pour les problèmes majeurs pouvant survenir.
Voir dossier CONTRIBUANT.
Installez les dépendances dev
de Composer :
php composer.phar install --dev
Ensuite, exécutez la suite de tests en utilisant PHPUnit :
bin/phpunit
Hateoas est publié sous la licence MIT. Consultez le fichier LICENSE fourni pour plus de détails.