Eine PHP-Bibliothek zur Unterstützung der Implementierung von Darstellungen für HATEOAS REST-Webdienste.
Die empfohlene Methode zur Installation von Hateoas ist Composer. Fordern Sie das Paket willdurand/hateoas
an, indem Sie den folgenden Befehl ausführen:
composer require willdurand/hateoas
Dadurch wird die neueste stabile Version aufgelöst.
Andernfalls installieren Sie die Bibliothek und richten Sie den Autoloader selbst ein.
Wenn Sie Annotationen für die Konfiguration verwenden möchten, müssen Sie das Paket doctrine/annotations
installieren:
composer require doctrine/annotations
Wenn Ihre App PHP 8.1 oder höher verwendet, wird empfohlen, native PHP-Attribute zu verwenden. In diesem Fall müssen Sie das Doctrine-Paket nicht installieren.
Dafür gibt es ein Bundle! Installieren Sie das BazingaHateoasBundle und genießen Sie es!
Wichtig:
Für diejenigen, die die Version
1.0
verwenden, können Sie zu dieser Dokumentationsseite springen.Für diejenigen, die die Version
2.0
verwenden, können Sie zu dieser Dokumentationsseite springen.Die folgende Dokumentation wurde für Hateoas 3.0 und höher geschrieben.
Hateoas nutzt die Serializer-Bibliothek, um eine gute Möglichkeit zum Erstellen von HATEOAS REST-Webdiensten zu bieten. HATEOAS steht für Hypermedia as the Engine of Application State und fügt Hypermedia-Links zu Ihren Darstellungen (d. h. Ihren API-Antworten) hinzu. Bei HATEOAS geht es um die Auffindbarkeit von Aktionen auf einer Ressource.
Nehmen wir zum Beispiel an, Sie haben eine Benutzer-API, die eine Darstellung eines einzelnen Benutzers wie folgt zurückgibt:
{
"user" : {
"id" : 123 ,
"first_name" : " John " ,
"last_name" : " Doe "
}
}
Um Ihren API-Konsumenten mitzuteilen, wie sie die Daten für diesen bestimmten Benutzer abrufen können, müssen Sie Ihren allerersten Link zu dieser Darstellung hinzufügen. Nennen wir ihn self
, da es sich um den URI für diesen bestimmten Benutzer handelt:
{
"user" : {
"id" : 123 ,
"first_name" : " John " ,
"last_name" : " Doe " ,
"_links" : {
"self" : { "href" : " http://example.com/api/users/123 " }
}
}
}
Lassen Sie uns jetzt näher auf Hateoas eingehen.
In der Hateoas-Terminologie werden Links als zu Ressourcen hinzugefügte Beziehungen angesehen. Es ist erwähnenswert, dass sich Beziehungen auch auf eingebettete Ressourcen beziehen. Dieses Thema wird jedoch im Abschnitt „Ressourcen einbetten“ behandelt.
Ein Link ist eine Relation, die durch einen name
identifiziert wird (z. B. self
) und einen href
Parameter hat:
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 () {}
}
Im obigen Beispiel konfigurieren wir eine self
, die aufgrund des href
Parameters ein Link ist. Sein Wert, der auf den ersten Blick seltsam erscheinen mag, wird im Abschnitt „Die Ausdruckssprache“ ausführlich behandelt. Dieser spezielle Wert wird zum Generieren eines URI verwendet.
In diesem Abschnitt werden Anmerkungen/Attribute zum Konfigurieren von Hateoas verwendet. Auch XML- und YAML -Formate werden unterstützt. Wenn Sie möchten, können Sie auch einfaches PHP verwenden.
Wichtig: Sie müssen sowohl den Serializer als auch Hateoas auf die gleiche Weise konfigurieren. Wenn Sie beispielsweise YAML zum Konfigurieren des Serializers verwenden, verwenden Sie YAML zum Konfigurieren von Hateoas.
Der einfachste Weg, HATEOAS auszuprobieren, ist mit dem HateoasBuilder
. Der Builder verfügt über zahlreiche Methoden zum Konfigurieren des Hateoas-Serialisierungsprogramms, auf die wir uns jetzt jedoch nicht näher einlassen (siehe Der HateoasBuilder). Im Auslieferungszustand funktioniert alles einwandfrei:
use Hateoas HateoasBuilder ;
$ hateoas = HateoasBuilder:: create ()-> build ();
$ user = new User ( 42 , ' Adrien ' , ' Brault ' );
$ json = $ hateoas -> serialize ( $ user , ' json ' );
$ xml = $ hateoas -> serialize ( $ user , ' xml ' );
Das Objekt $hateoas
ist eine Instanz von JMSSerializerSerializerInterface
und stammt aus der Serializer-Bibliothek. Hateoas verfügt nicht über einen eigenen Serializer, sondern lässt sich in den JMS-Serializer einbinden.
Standardmäßig verwendet Hateoas die Hypertext Application Language (HAL) für die JSON-Serialisierung. Dies gibt die Struktur der Antwort an (z. B. dass „Links“ unter einem _links
-Schlüssel leben sollten):
{
"id" : 42 ,
"first_name" : " Adrien " ,
"last_name" : " Brault " ,
"_links" : {
"self" : {
"href" : " /api/users/42 "
}
}
}
Für XML werden standardmäßig Atom-Links verwendet:
< user id = " 42 " >
< first_name > <![CDATA[ Adrien ]]> </ first_name >
< last_name > <![CDATA[ Brault ]]> </ last_name >
< link rel = " self " href = " /api/users/42 " />
</ user >
Es ist erwähnenswert, dass es sich bei diesen Formaten um die Standardformate und nicht um die einzigen verfügbaren handelt. Sie können verschiedene Formate über verschiedene Serialisierer verwenden und sogar eigene hinzufügen.
Nachdem Sie nun wissen, wie Sie Links hinzufügen, sehen wir uns an, wie Sie eingebettete Ressourcen hinzufügen.
Manchmal ist es effizienter, verwandte Ressourcen einzubetten, anstatt sie zu verlinken, da so verhindert wird, dass Clients zusätzliche Anfragen stellen müssen, um diese Ressourcen abzurufen.
Eine eingebettete Ressource ist eine benannte Beziehung , die Daten enthält, die durch den embedded
Parameter dargestellt werden.
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 ;
}
Hinweis: Sie müssen die Manager-Eigenschaft von der Serialisierung ausschließen, da sie sonst sowohl vom Serializer als auch von Hateoas serialisiert wird. Sie müssen auch die Manager-Beziehung ausschließen, wenn der Manager null
ist, da sonst beim Erstellen des href
Links (Aufruf von getId()
für null
) ein Fehler auftritt.
Tipp: Wenn es sich bei der Manager-Eigenschaft um ein Objekt handelt, das bereits über einen _self
-Link verfügt, können Sie diesen Wert für die href
wiederverwenden, anstatt ihn hier zu wiederholen. Siehe 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 ' );
Für json
platziert die HAL-Darstellung diese eingebetteten Beziehungen in einem _embedded
-Schlüssel:
{
"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 werden durch die Serialisierung embedded
Beziehungen neue Elemente erstellt:
< 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 >
Der Tag-Name einer eingebetteten Ressource wird aus der @XmlRoot
Annotation ( xml_root_name
in YAML, xml-root-name
in XML) abgeleitet, die aus der Serializer-Konfiguration stammt.
Die Bibliothek stellt mehrere Klassen im Namensraum HateoasRepresentation*
bereit, um Sie bei allgemeinen Aufgaben zu unterstützen. Dabei handelt es sich um einfache Klassen, die mit den Anmerkungen der Bibliothek konfiguriert sind.
Die Klassen PaginatedRepresentation
, OffsetRepresentation
und CollectionRepresentation
sind wahrscheinlich die interessantesten. Diese sind hilfreich, wenn Ihre Ressource tatsächlich eine Sammlung von Ressourcen ist (z. B. /users
ist eine Sammlung von Benutzern). Diese helfen Ihnen bei der Darstellung der Sammlung und beim Hinzufügen von Paginierung und Begrenzungen:
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 ' );
Die CollectionRepresentation
bietet eine grundlegende Darstellung einer eingebetteten Sammlung.
Die PaginatedRepresentation
dient zum Hinzufügen von self
, first
und, wenn möglich, last
, next
und previous
-Links.
Die OffsetRepresentation
funktioniert genau wie PaginatedRepresentation
, ist jedoch nützlich, wenn die Paginierung durch offset
, limit
und total
ausgedrückt wird.
Die RouteAwareRepresentation
fügt eine self
basierend auf einer bestimmten Route hinzu.
Sie können absolute URIs generieren, indem Sie den absolute
Parameter sowohl in der PaginatedRepresentation
als auch in der RouteAwareRepresentation
auf true
setzen.
Die Hateoas-Bibliothek bietet außerdem eine PagerfantaFactory
, um PaginatedRepresentation
einfach aus einer Pagerfanta-Instanz zu erstellen. Wenn Sie die Pagerfanta-Bibliothek verwenden, ist dies eine einfachere Möglichkeit, die Sammlungsdarstellungen zu erstellen:
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 ' );
Sie würden den folgenden JSON-Inhalt erhalten:
{
"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 }
]
}
}
Und der folgende XML-Inhalt:
<? 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 >
Wenn Sie die inline CollectionRepresentation
anpassen möchten, übergeben Sie eines als drittes Argument der Methode 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 ' );
Wenn Sie den XML-Root-Namen der Sammlung ändern möchten, erstellen Sie eine neue Klasse mit konfiguriertem XML-Root und verwenden Sie den Inline-Mechanismus:
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 );
Wie im vorherigen Abschnitt erwähnt, handelt es sich bei Darstellungen um Klassen, die mit den Anmerkungen der Bibliothek konfiguriert sind, um Sie bei allgemeinen Aufgaben zu unterstützen. Die Sammlungsdarstellungen werden im Umgang mit Sammlungen beschrieben.
Mit der VndErrorRepresentation
können Sie eine Fehlerreaktion gemäß der vnd.error
-Spezifikation beschreiben.
$ error = new VndErrorRepresentation (
' Validation failed ' ,
42 ,
' http://.../ ' ,
' http://.../ '
);
Die Serialisierung einer solchen Darstellung in XML und JSON würde zu den folgenden Ergebnissen führen:
<? 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://.../ "
}
}
}
Hinweis: Es wird empfohlen, eigene Fehlerklassen zu erstellen, die die VndErrorRepresentation
-Klasse erweitern.
Hateoas verlässt sich auf die leistungsstarke Symfony ExpressionLanguage-Komponente, um Werte wie Links, IDs oder Objekte zum Einbetten abzurufen.
Jedes Mal, wenn Sie einen Wert eingeben (z. B. eine Relation href
in Anmerkungen oder YAML), können Sie entweder einen fest codierten Wert oder einen Ausdruck übergeben. Um die Ausdruckssprache verwenden zu können, müssen Sie die expr()
-Notation verwenden:
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()) " )]
Weitere Informationen zur Ausdruckssyntax finden Sie in der offiziellen Dokumentation: Die Ausdruckssyntax.
In jedem Ausdruck ist nativ eine spezielle Variable namens object
verfügbar, die das aktuelle Objekt darstellt:
expr(object.getId())
Wir nennen eine solche Variable eine Kontextvariable .
Sie können Ihre eigenen Kontextvariablen zum Ausdruckssprachenkontext hinzufügen, indem Sie sie dem Ausdrucksauswerter hinzufügen.
Rufen Sie mit HateoasBuilder
die Methode setExpressionContextVariable()
auf, um neue Kontextvariablen hinzuzufügen:
use Hateoas HateoasBuilder ;
$ hateoas = HateoasBuilder:: create ()
-> setExpressionContextVariable ( ' foo ' , new Foo ())
-> build ();
Die foo
Variable ist jetzt verfügbar:
expr(foo !== null)
Weitere Informationen zum Hinzufügen von Funktionen zur Ausdruckssprache finden Sie unter https://symfony.com/doc/current/components/expression_sprache/extending.html
Da Sie die Ausdruckssprache zum Definieren der Beziehungslinks ( href
Schlüssel) verwenden können, können Sie standardmäßig viel tun. Wenn Sie jedoch ein Framework verwenden, besteht die Möglichkeit, dass Sie Routen zum Erstellen von Links verwenden möchten.
Sie müssen zunächst einen UrlGenerator
im Builder konfigurieren. Sie können entweder HateoasUrlGeneratorUrlGeneratorInterface
implementieren oder HateoasUrlGeneratorCallableUrlGenerator
verwenden:
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 ()
;
Anschließend können Sie die Annotation @Route verwenden:
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 "
}
}
}
Beachten Sie, dass die Bibliothek mit einem SymfonyUrlGenerator
geliefert wird. Um es beispielsweise in Silex zu verwenden:
use Hateoas UrlGenerator SymfonyUrlGenerator ;
$ hateoas = HateoasBuilder:: create ()
-> setUrlGenerator ( null , new SymfonyUrlGenerator ( $ app [ ' url_generator ' ]))
-> build ()
;
Hateoas bietet eine Reihe von Hilfsmitteln, um den Prozess der API-Erstellung zu vereinfachen.
Die LinkHelper
-Klasse stellt eine getLinkHref($object, $rel, $absolute = false)
-Methode bereit, mit der Sie den href- Wert eines beliebigen Objekts für jeden gegebenen Beziehungsnamen abrufen können. Es ist in der Lage, aus jeder Linkbeziehung einen URI (entweder absolut oder relativ) zu generieren:
$ 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
Funktion Die obige Funktion ist auch in Ihren Ausdrücken (vgl. Die Ausdruckssprache) über die Funktion link(object, rel, absolute)
verfügbar:
/ * *
* @ 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 );
}
}
Achten Sie auf die href
-Ausdrücke für den post
und relative
Beziehungen sowie auf die entsprechenden Werte im folgenden JSON-Inhalt:
{
"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 " }
}
}
}
Erwähnenswert ist, dass Sie erzwingen können, ob Sie einen absoluten oder relativen URI wünschen, indem Sie das dritte Argument sowohl in der getLinkHref()
Methode als auch in der link
-Funktion verwenden.
Wichtig: Standardmäßig sind alle URIs relativ , auch diejenigen, die in ihrer Konfiguration als absolut definiert sind.
$ 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 bietet auch eine Reihe von Twig-Erweiterungen an.
Mit der LinkExtension
können Sie den LinkHelper in Ihren Twig-Vorlagen verwenden, um beispielsweise Links in Ihren HTML-Vorlagen zu generieren.
Diese Erweiterung macht die Methode des getLinkHref()
-Helfers über die Twig-Funktion link_href
verfügbar:
{{ 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 stellt eine Reihe von Serialisierern bereit. Mit jedem Serialisierer können Sie entweder XML- oder JSON-Inhalte in einem bestimmten Format generieren, beispielsweise HAL oder Atom Links.
Mit dem JsonHalSerializer
können Sie HAL-kompatible Beziehungen in JSON generieren. Es ist der Standard-JSON-Serializer in Hateoas.
HAL stellt seine Verknüpfungsfunktion mit einer Konvention bereit, die besagt, dass ein Ressourcenobjekt eine reservierte Eigenschaft namens _links
hat. Diese Eigenschaft ist ein Objekt, das Links enthält. Diese Links sind durch ihre Linkbeziehung gekennzeichnet.
HAL beschreibt auch eine andere Konvention, die besagt, dass eine Ressource eine andere reservierte Eigenschaft namens _embedded
haben kann. Diese Eigenschaft ähnelt _links
darin, dass eingebettete Ressourcen durch den Beziehungsnamen verschlüsselt werden. Der Hauptunterschied besteht darin, dass es sich bei den Werten nicht um Links, sondern um Ressourcenobjekte handelt.
{
"message" : " Hello, World! " ,
"_links" : {
"self" : {
"href" : " /notes/0 "
}
},
"_embedded" : {
"associated_events" : [
{
"name" : " SymfonyCon " ,
"date" : " 2013-12-12T00:00:00+0100 "
}
]
}
}
Mit dem XmlSerializer
können Sie Atom-Links in Ihre XML-Dokumente generieren. Es ist der Standard-XML-Serialisierer.
<? 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 >
Mit dem XmlHalSerializer
können Sie HAL-kompatible Beziehungen in XML generieren.
HAL in XML ähnelt HAL in JSON in dem Sinne, dass es link
-Tags und resource
-Tags beschreibt.
Hinweis: Die self
wird tatsächlich zu einem Attribut der Hauptressource und nicht zu einem link
-Tag. Andere Links werden als link
Tags generiert.
<? 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 >
Sie müssen das SerializerInterface
implementieren, das zwei Methoden zum Serialisieren von Links und eingebetteten Beziehungen beschreibt.
Die HateoasBuilder
Klasse wird dank einer leistungsstarken und flüssigen API zur einfachen Konfiguration von Hateoas verwendet.
use Hateoas HateoasBuilder ;
$ hateoas = HateoasBuilder:: create ()
-> setCacheDir ( ' /path/to/cache/dir ' )
-> setDebug ( $ trueOrFalse )
-> setDefaultXmlSerializer ()
. . .
-> build ();
Alle folgenden Methoden geben den aktuellen Builder zurück, sodass Sie sie verketten können.
setXmlSerializer(SerializerInterface $xmlSerializer)
: legt den zu verwendenden XML-Serializer fest. Standard ist: XmlSerializer
;setDefaultXmlSerializer()
: legt den Standard-XML-Serializer ( XmlSerializer
) fest. setJsonSerializer(SerializerInterface $jsonSerializer)
: Legt den zu verwendenden JSON-Serializer fest. Standard ist: JsonHalSerializer
;setDefaultJsonSerializer()
: legt den Standard-JSON-Serializer ( JsonHalSerializer
) fest. setUrlGenerator($name = null, UrlGeneratorInterface $urlGenerator)
: fügt einen neuen benannten URL-Generator hinzu. Wenn $name
null
ist, ist der URL-Generator der Standardgenerator. setExpressionContextVariable($name, $value)
: fügt eine neue Ausdruckskontextvariable hinzu;setExpressionLanguage(ExpressionLanguage $expressionLanguage)
; includeInterfaceMetadata($include)
: ob die Metadaten von den Schnittstellen einbezogen werden sollen;setMetadataDirs(array $namespacePrefixToDirMap)
: legt eine Zuordnung von Namespace-Präfixen zu Verzeichnissen fest. Diese Methode überschreibt alle zuvor definierten Verzeichnisse;addMetadataDir($dir, $namespacePrefix = '')
: Fügt ein Verzeichnis hinzu, in dem der Serializer nach Klassenmetadaten sucht;addMetadataDirs(array $namespacePrefixToDirMap)
: fügt Verzeichnissen eine Karte von Namespace-Präfixen hinzu;replaceMetadataDir($dir, $namespacePrefix = '')
: ähnlich wie addMetadataDir()
, überschreibt jedoch einen vorhandenen Eintrag.Weitere Informationen finden Sie in der offiziellen Serializer-Dokumentation.
setDebug($debug)
: aktiviert oder deaktiviert den Debug-Modus;setCacheDir($dir)
: legt das Cache-Verzeichnis fest.Sowohl der Serializer als auch die Hateoas-Bibliotheken sammeln Metadaten zu Ihren Objekten aus verschiedenen Quellen wie YML, XML oder Anmerkungen. Um diesen Prozess so effizient wie möglich zu gestalten, wird empfohlen, dass Sie der Hateoas-Bibliothek erlauben, diese Informationen zwischenzuspeichern. Konfigurieren Sie dazu ein Cache-Verzeichnis:
$ builder = Hateoas HateoasBuilder:: create ();
$ hateoas = $ builder
-> setCacheDir ( $ someWritableDir )
-> build ();
Hateoas unterstützt mehrere Metadatenquellen. Standardmäßig werden Doctrine-Annotationen (PHP < 8.1) oder native PHP-Attribute (PHP >= 8.1) verwendet, Sie können Metadaten jedoch auch in XML- oder YAML-Dateien speichern. Für Letzteres ist es notwendig, ein Metadatenverzeichnis zu konfigurieren, in dem sich diese Dateien befinden:
$ hateoas = Hateoas HateoasBuilder:: create ()
-> addMetadataDir ( $ someDir )
-> build ();
Hateoas würde erwarten, dass die Metadatendateien wie die vollständig qualifizierten Klassennamen benannt werden, wobei alle durch ersetzt werden
.
. Wenn Ihre Klasse VendorPackageFoo
heißen würde, müsste sich die Metadatendatei unter $someDir/Vendor.Package.Foo.(xml|yml)
befinden.
Hateoas ermöglicht Frameworks das dynamische Hinzufügen von Beziehungen zu Klassen, indem es einen Erweiterungspunkt auf Konfigurationsebene bereitstellt. Diese Funktion kann für diejenigen nützlich sein, die eine neue Ebene über Hateoas erstellen oder „globale“ Beziehungen hinzufügen möchten, anstatt die gleiche Konfiguration für jede Klasse zu kopieren.
Um diesen Mechanismus nutzen zu können, muss die Schnittstelle ConfigurationExtensionInterface
implementiert werden:
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 ' ,
' / '
)
);
}
}
}
Sie können mit $classMetadata->getRelations()
auf die vorhandenen Beziehungen zugreifen, die aus Annotations, XML oder YAML geladen wurden.
Wenn die $classMetadata
Beziehungen hat oder Sie Beziehungen hinzufügen, werden ihre Beziehungen zwischengespeichert. Wenn Sie also Konfigurationsdateien (Anmerkungen, XML oder YAML) lesen, stellen Sie sicher, dass Sie in den Klassenmetadaten darauf verweisen:
$ 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 >
Weitere Informationen finden Sie in der Datei hateoas.xsd
.
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())" ]
Diese Annotation kann für eine Klasse definiert werden.
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: ' ... ' ,
)]
Eigentum | Erforderlich | Inhalt | Ausdruckssprache |
---|---|---|---|
Name | Ja | Zeichenfolge | NEIN |
href | Wenn eingebettet, ist nicht festgelegt | string / @Route | Ja |
eingebettet | Wenn href nicht gesetzt ist | string / @Embedded | Ja |
Attribute | NEIN | Array | Ja zu Werten |
Ausschluss | NEIN | @Ausschluss | N / A |
Wichtig: attributes
werden nur für Linkbeziehungen verwendet (dh kombiniert mit der href
Eigenschaft, nicht mit der 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 ' ,
),
)]
Diese Annotation kann in der href- Eigenschaft der @Relation-Annotation definiert werden. Dies ermöglicht Ihnen den Zugriff auf Ihren URL-Generator, sofern Sie einen konfiguriert haben.
Eigentum | Erforderlich | Inhalt | Ausdruckssprache |
---|---|---|---|
Name | Ja | Zeichenfolge | NEIN |
Parameter | Standardmäßig ist array() | Array / String | Ja (String + Array-Werte) |
Absolute | Der Standardwert ist „false“. | boolean / string | Ja |
Generator | NEIN | string / null | NEIN |
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 ' ,
),
)]
Diese Annotation kann in der eingebetteten Eigenschaft der @Relation-Annotation definiert werden. Dies ist nützlich, wenn Sie die exclusion
oder xmlElementName
-Optionen für die eingebettete Ressource konfigurieren müssen.
Eigentum | Erforderlich | Inhalt | Ausdruckssprache |
---|---|---|---|
Inhalt | Ja | Zeichenfolge / Array | Ja (Zeichenfolge) |
Ausschluss | Standardmäßig ist array() | @Ausschluss | N / A |
xmlElementName | Standardmäßig ist array() | Zeichenfolge | NEIN |
Diese Annotation kann in der Ausschlusseigenschaft sowohl der @Relation- als auch der @Embedded-Annotation definiert werden.
Eigentum | Erforderlich | Inhalt | Ausdruckssprache |
---|---|---|---|
Gruppen | NEIN | Array | NEIN |
seitVersion | NEIN | Zeichenfolge | NEIN |
bisVersion | NEIN | Zeichenfolge | NEIN |
maxDepth | NEIN | ganze Zahl | NEIN |
ausschließenWenn | NEIN | Zeichenfolge / boolescher Wert | Ja |
Alle Werte mit Ausnahme von excludeIf
verhalten sich auf die gleiche Weise wie bei direkter Verwendung für die regulären Eigenschaften mit dem Serializer.
excludeIf
erwartet einen booleschen Wert und ist hilfreich, wenn ein anderer Ausdruck unter bestimmten Umständen fehlschlagen würde. Wenn in diesem Beispiel die getManager
-Methode null
ist, sollten Sie sie ausschließen, um zu verhindern, dass die URL-Generierung fehlschlägt:
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 {}
}
Diese Annotation kann für eine Klasse definiert werden. Dies ist nützlich, wenn Sie mehrere Beziehungen (Links) serialisieren möchten. Als Beispiel:
{
"_links": {
"relation_name": [
{"href": "link1"},
{"href": "link2"},
{"href": "link3"}
]
}
}
Eigentum | Erforderlich | Inhalt | Ausdruckssprache |
---|---|---|---|
Name | Ja | Zeichenfolge | Ja |
Es kann „Name“ sein:
my_func
MyClass::getExtraRelations
expr(service('user.rel_provider').getExtraRelations())
Hier und ein Beispiel für die Verwendung der Ausdruckssprache:
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
{
...
}
Hier die UserRelPrvider
-Klasse:
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
implementiert CompilableExpressionEvaluatorInterface
und wird verwendet, um die Ausdruckssprache in einer Form zu analysieren, die zwischengespeichert und zur späteren Verwendung gespeichert werden kann. Wenn Sie die Ausdruckssprache in Ihren Beziehungen nicht benötigen, ist dieser Service nicht erforderlich.
Der Dienst user.rel_provider
ist wie folgt definiert:
user.rel_provider :
class : UserRelPrvider
arguments :
- ' @jms_serializer.expression_evaluator '
In diesem Fall ist jms_serializer.expression_evaluator
ein Dienst, CompilableExpressionEvaluatorInterface
implementiert.
Dieser Abschnitt bezieht sich auf die Hateoas-Interna und bietet Dokumentation zu versteckten Teilen dieser Bibliothek. Dies ist für Endbenutzer nicht immer relevant, aber interessant für Entwickler oder Leute, die erfahren möchten, wie die Dinge unter der Haube funktionieren.
willdurand/hateoas
folgt der semantischen Versionierung.
Seit Oktober 2013 werden die Versionen 1.x
und 0.x
offiziell nicht mehr unterstützt (beachten Sie, dass 1.x
nie veröffentlicht wurde).
Version 3.x
ist die aktuelle wichtigste stabile Version.
Version 2.x
wird nur zur Behebung von Sicherheitsfehlern und für möglicherweise auftretende größere Probleme gepflegt.
Siehe CONTRIBUTING-Datei.
Installieren Sie die Composer- dev
:
php composer.phar install --dev
Führen Sie dann die Testsuite mit PHPUnit aus:
bin/phpunit
Hateoas wird unter der MIT-Lizenz veröffentlicht. Einzelheiten finden Sie in der mitgelieferten LICENSE-Datei.