一个 PHP 库,支持实现 HATEOAS REST Web 服务的表示。
推荐的安装 Hateoas 的方法是通过 Composer。通过运行以下命令需要willdurand/hateoas
包:
composer require willdurand/hateoas
这将解决最新的稳定版本。
否则,请自行安装库并设置自动加载器。
如果你想使用注释进行配置,你需要安装doctrine/annotations
包:
composer require doctrine/annotations
如果您的应用程序使用 PHP 8.1 或更高版本,建议使用本机 PHP 属性。在这种情况下,您不需要安装 Doctrine 包。
有一个捆绑包!安装 BazingaHateoasBundle,然后享受吧!
重要的:
对于使用
1.0
版本的用户,可以跳转到这个文档页面。对于使用
2.0
版本的用户,可以跳转到这个文档页面。以下文档是为Hateoas 3.0及更高版本编写的。
Hateoas利用 Serializer 库提供了一种构建 HATEOAS REST Web 服务的好方法。 HATEOAS 代表超媒体作为应用程序状态引擎,并将超媒体链接添加到您的表示(即您的 API 响应)。 HATEOAS 是关于资源操作的可发现性。
例如,假设您有一个 User API,它返回单个用户的表示,如下所示:
{
"user" : {
"id" : 123 ,
"first_name" : " John " ,
"last_name" : " Doe "
}
}
为了告诉您的 API 使用者如何检索此特定用户的数据,您必须将第一个链接添加到此表示形式,我们将其称为self
因为它是此特定用户的 URI:
{
"user" : {
"id" : 123 ,
"first_name" : " John " ,
"last_name" : " Doe " ,
"_links" : {
"self" : { "href" : " http://example.com/api/users/123 " }
}
}
}
现在让我们深入研究 Hateoas。
在 Hateoas 术语中,链接被视为添加到资源中的关系。值得一提的是,关系也指嵌入资源,但该主题将在嵌入资源部分中介绍。
链接是一种由name
(例如self
)标识并具有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 () {}
}
在上面的示例中,我们由于href
参数而配置了一个链接的self
关系。它的值乍一看可能看起来很奇怪,但将在“表达式语言”部分中进行广泛介绍。这个特殊值用于生成 URI。
在本节中,注释/属性用于配置 Hateoas。还支持XML和YAML格式。如果您愿意,您也可以使用纯 PHP。
重要提示:您必须以相同的方式配置 Serializer 和 Hateoas。例如,如果您使用 YAML 来配置 Serializer,请使用 YAML 来配置 Hateoas。
尝试 HATEOAS 最简单的方法是使用HateoasBuilder
。该构建器有多种方法来配置 Hateoas 序列化器,但我们现在不会深入研究它们(请参阅 HateoasBuilder)。开箱即用,一切正常:
use Hateoas HateoasBuilder ;
$ hateoas = HateoasBuilder:: create ()-> build ();
$ user = new User ( 42 , ' Adrien ' , ' Brault ' );
$ json = $ hateoas -> serialize ( $ user , ' json ' );
$ xml = $ hateoas -> serialize ( $ user , ' xml ' );
$hateoas
对象是JMSSerializerSerializerInterface
的实例,来自 Serializer 库。 Hateoas 没有自带序列化器,它连接到 JMS 序列化器。
默认情况下,Hateoas 使用超文本应用程序语言 (HAL) 进行 JSON 序列化。这指定了响应的结构(例如,“链接”应该位于_links
键下):
{
"id" : 42 ,
"first_name" : " Adrien " ,
"last_name" : " Brault " ,
"_links" : {
"self" : {
"href" : " /api/users/42 "
}
}
}
对于 XML,默认使用 Atom Links:
< user id = " 42 " >
< first_name > <![CDATA[ Adrien ]]> </ first_name >
< last_name > <![CDATA[ Brault ]]> </ last_name >
< link rel = " self " href = " /api/users/42 " />
</ user >
值得一提的是,这些格式是默认格式,而不是唯一可用的格式。您可以通过不同的序列化器使用不同的格式,甚至可以添加您自己的格式。
现在您已经知道如何添加链接,让我们看看如何添加嵌入资源。
有时,嵌入相关资源比链接到它们更有效,因为它可以防止客户端必须发出额外的请求来获取这些资源。
嵌入资源是包含由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 ;
}
注意:您需要从序列化中排除 manager 属性,否则序列化程序和 Hateoas 都会对其进行序列化。当管理器为null
时,您还必须排除管理器关系,否则在创建href
链接(在null
上调用getId()
)时会发生错误。
提示:如果 manager 属性是一个已经具有_self
链接的对象,您可以重新使用该值作为href
而不是在此处重复。请参阅链接帮助程序。
$ 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 ' );
对于json
,HAL 表示将这些嵌入关系放置在_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 "
}
}
}
}
}
在 XML 中,序列化embedded
关系将创建新元素:
< 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 >
嵌入资源的标签名称是从来自 Serializer 配置的@XmlRoot
注释(YAML 中的xml_root_name
,XML 中的xml-root-name
)推断出来的。
该库在HateoasRepresentation*
命名空间中提供了多个类来帮助您完成常见任务。这些是使用库注释配置的简单类。
PaginatedRepresentation
、 OffsetRepresentation
和CollectionRepresentation
类可能是最有趣的。当您的资源实际上是资源的集合(例如/users
是用户的集合)时,这些很有用。这些可以帮助您表示集合并添加分页和限制:
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
提供了嵌入式集合的基本表示。
PaginatedRepresentation
旨在添加self
、 first
以及可能的情况下的last
、 next
和previous
链接。
OffsetRepresentation
工作方式与PaginatedRepresentation
类似,但当分页由offset
、 limit
和total
表示时很有用。
RouteAwareRepresentation
添加基于给定路由的self
关系。
您可以通过在PaginatedRepresentation
和RouteAwareRepresentation
中将absolute
参数设置为true
来生成绝对 URI 。
Hateoas 库还提供了PagerfantaFactory
,可以轻松地从 Pagerfanta 实例构建PaginatedRepresentation
。如果您使用 Pagerfanta 库,这是创建集合表示的更简单方法:
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 ' );
您将获得以下 JSON 内容:
{
"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 }
]
}
}
以及以下 XML 内容:
<? 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 >
如果要自定义内联CollectionRepresentation
,请将其作为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 ' );
如果要更改集合的 xml 根名称,请创建一个配置了 xml 根的新类并使用内联机制:
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 );
如上一节所述,表示是使用库注释配置的类,以帮助您完成常见任务。集合表示在处理集合中进行了描述。
VndErrorRepresentation
允许您描述遵循vnd.error
规范的错误响应。
$ error = new VndErrorRepresentation (
' Validation failed ' ,
42 ,
' http://.../ ' ,
' http://.../ '
);
在 XML 和 JSON 中序列化此类表示将提供以下输出:
<? 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://.../ "
}
}
}
提示:建议创建您自己的错误类来扩展VndErrorRepresentation
类。
Hateoas 依靠强大的 Symfony ExpressionLanguage 组件来检索要嵌入的链接、id 或对象等值。
每次填写值(例如注释或 YAML 中的 Relation href
)时,您可以传递硬编码值或表达式。为了使用表达式语言,您必须使用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()) " )]
您可以通过阅读官方文档:表达式语法来了解有关表达式语法的更多信息。
本质上,每个表达式中都有一个名为object
的特殊变量,它代表当前对象:
expr(object.getId())
我们将这样的变量称为上下文变量。
您可以通过将自己的上下文变量添加到表达式计算器来将它们添加到表达式语言上下文中。
使用HateoasBuilder
,调用setExpressionContextVariable()
方法来添加新的上下文变量:
use Hateoas HateoasBuilder ;
$ hateoas = HateoasBuilder:: create ()
-> setExpressionContextVariable ( ' foo ' , new Foo ())
-> build ();
foo
变量现在可用:
expr(foo !== null)
有关如何向表达式语言添加函数的更多信息,请参阅 https://symfony.com/doc/current/components/expression_language/extending.html
由于您可以使用表达式语言来定义关系链接( href
键),因此默认情况下您可以执行很多操作。但是,如果您使用框架,您很可能希望使用路由来构建链接。
您首先需要在构建器上配置UrlGenerator
。您可以实现HateoasUrlGeneratorUrlGeneratorInterface
,或使用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 ()
;
然后您将能够使用@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 "
}
}
}
请注意,该库附带了SymfonyUrlGenerator
。例如,要在 Silex 中使用它:
use Hateoas UrlGenerator SymfonyUrlGenerator ;
$ hateoas = HateoasBuilder:: create ()
-> setUrlGenerator ( null , new SymfonyUrlGenerator ( $ app [ ' url_generator ' ]))
-> build ()
;
Hateoas 提供了一组帮助程序来简化构建 API 的过程。
LinkHelper
类提供了getLinkHref($object, $rel, $absolute = false)
方法,该方法允许您获取任何给定关系名称的任何对象的href值。它能够从任何链接关系生成 URI(绝对或相对):
$ 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
功能上述功能也可以通过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 );
}
}
请注意以下 JSON 内容中post
和relative
关系的href
表达式及其对应值:
{
"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 " }
}
}
}
值得一提的是,您可以通过在getLinkHref()
方法和link
函数中使用第三个参数来强制需要绝对 URI 还是相对 URI。
重要提示:默认情况下,所有 URI 都是相对的,即使是那些在其配置中定义为绝对的URI。
$ 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 还提供了一组 Twig 扩展。
LinkExtension
允许您在 Twig 模板中使用 LinkHelper,以便您可以在 HTML 模板中生成链接。
此扩展通过link_href
Twig 函数公开getLinkHref()
帮助程序的方法:
{{ 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 提供了一组序列化器。每个序列化程序都允许您按照特定格式生成 XML 或 JSON 内容,例如 HAL 或 Atom Links。
JsonHalSerializer
允许您生成 JSON 格式的 HAL 兼容关系。它是 Hateoas 中默认的 JSON 序列化器。
HAL 通过约定提供链接功能,该约定表示资源对象具有名为_links
保留属性。该属性是一个包含链接的对象。这些链接由它们的链接关系来控制。
HAL 还描述了另一个约定,即资源可以有另一个名为_embedded
的保留属性。此属性类似于_links
,因为嵌入式资源是按关系名称键入的。主要区别在于,值不是链接,而是资源对象。
{
"message" : " Hello, World! " ,
"_links" : {
"self" : {
"href" : " /notes/0 "
}
},
"_embedded" : {
"associated_events" : [
{
"name" : " SymfonyCon " ,
"date" : " 2013-12-12T00:00:00+0100 "
}
]
}
}
XmlSerializer
允许您在 XML 文档中生成 Atom 链接。它是默认的 XML 序列化程序。
<? 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 >
XmlHalSerializer
允许您在 XML 中生成符合 HAL 的关系。
XML 中的 HAL 与 JSON 中的 HAL 类似,都描述了link
标签和resource
标签。
注意: self
关系实际上会成为主资源的一个属性,而不是link
标签。其他链接将作为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 >
您必须实现SerializerInterface
,它描述了两种序列化链接和嵌入关系的方法。
凭借强大且流畅的 API, HateoasBuilder
类可用于轻松配置 Hateoas。
use Hateoas HateoasBuilder ;
$ hateoas = HateoasBuilder:: create ()
-> setCacheDir ( ' /path/to/cache/dir ' )
-> setDebug ( $ trueOrFalse )
-> setDefaultXmlSerializer ()
. . .
-> build ();
下面的所有方法都会返回当前的构建器,以便您可以链接它们。
setXmlSerializer(SerializerInterface $xmlSerializer)
:设置要使用的 XML 序列化器。默认为: XmlSerializer
;setDefaultXmlSerializer()
:设置默认的 XML 序列化程序 ( XmlSerializer
)。 setJsonSerializer(SerializerInterface $jsonSerializer)
:设置要使用的 JSON 序列化器。默认为: JsonHalSerializer
;setDefaultJsonSerializer()
:设置默认的 JSON 序列化器 ( JsonHalSerializer
)。 setUrlGenerator($name = null, UrlGeneratorInterface $urlGenerator)
:添加一个新的命名 URL 生成器。如果$name
为null
,则 URL 生成器将是默认生成器。 setExpressionContextVariable($name, $value)
:添加一个新的表达式上下文变量;setExpressionLanguage(ExpressionLanguage $expressionLanguage)
; includeInterfaceMetadata($include)
:是否包含接口中的元数据;setMetadataDirs(array $namespacePrefixToDirMap)
:设置名称空间前缀到目录的映射。此方法会覆盖任何先前定义的目录;addMetadataDir($dir, $namespacePrefix = '')
:添加序列化器将在其中查找类元数据的目录;addMetadataDirs(array $namespacePrefixToDirMap)
:将名称空间前缀映射添加到目录;replaceMetadataDir($dir, $namespacePrefix = '')
:类似于addMetadataDir()
,但覆盖现有条目。请阅读官方序列化器文档以获取更多详细信息。
setDebug($debug)
:启用或禁用调试模式;setCacheDir($dir)
:设置缓存目录。序列化器和 Hateoas 库都从各种来源(例如 YML、XML 或注释)收集有关对象的元数据。为了使此过程尽可能高效,建议您允许 Hateoas 库缓存此信息。为此,请配置缓存目录:
$ builder = Hateoas HateoasBuilder:: create ();
$ hateoas = $ builder
-> setCacheDir ( $ someWritableDir )
-> build ();
Hateoas 支持多种元数据源。默认情况下,它使用 Doctrine 注释 (PHP < 8.1) 或本机 PHP 属性 (PHP >= 8.1),但您也可以将元数据存储在 XML 或 YAML 文件中。对于后者,需要配置这些文件所在的元数据目录:
$ hateoas = Hateoas HateoasBuilder:: create ()
-> addMetadataDir ( $ someDir )
-> build ();
Hateoas 希望元数据文件的命名类似于完全限定的类名,其中所有都替换为
.
。如果您的类被命名为VendorPackageFoo
元数据文件需要位于$someDir/Vendor.Package.Foo.(xml|yml)
。
Hateoas 允许框架通过在配置级别提供扩展点来动态添加与类的关系。对于那些想要在 Hateoas 之上创建新层或添加“全局”关系而不是在每个类上复制相同配置的人来说,此功能非常有用。
为了利用此机制,必须实现ConfigurationExtensionInterface
接口:
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 ' ,
' / '
)
);
}
}
}
您可以使用$classMetadata->getRelations()
访问从注释、XML 或 YAML 加载的现有关系。
如果$classMetadata
有关系,或者如果您向其中添加关系,则其关系将被缓存。因此,如果您阅读配置文件(注释、XML 或 YAML),请确保在类元数据上引用它们:
$ 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 >
有关更多详细信息,请参阅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())" ]
该注释可以在类上定义。
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: ' ... ' ,
)]
财产 | 必需的 | 内容 | 表达语言 |
---|---|---|---|
姓名 | 是的 | 细绳 | 不 |
链接地址 | 如果未设置嵌入 | 字符串/@Route | 是的 |
嵌入式 | 如果没有设置href | 字符串/@Embedded | 是的 |
属性 | 不 | 大批 | 是的价值观 |
排除 | 不 | @排除 | 不适用 |
重要提示: attributes
仅用于链接关系(即与href
属性结合使用,而不是与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 ' ,
),
)]
该注解可以在@Relation注解的href属性中定义。这允许您使用 URL 生成器(如果您已配置)。
财产 | 必需的 | 内容 | 表达语言 |
---|---|---|---|
姓名 | 是的 | 细绳 | 不 |
参数 | 默认为 array() | 数组/字符串 | 是(字符串 + 数组值) |
绝对 | 默认为 false | 布尔值/字符串 | 是的 |
发电机 | 不 | 字符串/空 | 不 |
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 ' ,
),
)]
该注解可以在@Relation注解的embedded属性中定义。如果您需要为嵌入资源配置exclusion
或xmlElementName
选项,它会非常有用。
财产 | 必需的 | 内容 | 表达语言 |
---|---|---|---|
内容 | 是的 | 字符串/数组 | 是(字符串) |
排除 | 默认为 array() | @排除 | 不适用 |
xml元素名称 | 默认为 array() | 细绳 | 不 |
该注释可以在 @Relation 和 @Embedded 注释的排除属性中定义。
财产 | 必需的 | 内容 | 表达语言 |
---|---|---|---|
团体 | 不 | 大批 | 不 |
自版本 | 不 | 细绳 | 不 |
直到版本 | 不 | 细绳 | 不 |
最大深度 | 不 | 整数 | 不 |
排除如果 | 不 | 字符串/布尔值 | 是的 |
除excludeIf
之外的所有值的行为方式与通过序列化程序直接在常规属性上使用它们时的方式相同。
excludeIf
需要一个布尔值,当另一个表达式在某些情况下失败时会很有帮助。在此示例中,如果getManager
方法为null
,您应该将其排除以防止 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 {}
}
该注释可以在类上定义。如果您希望序列化多个关系(链接),它很有用。举个例子:
{
"_links": {
"relation_name": [
{"href": "link1"},
{"href": "link2"},
{"href": "link3"}
]
}
}
财产 | 必需的 | 内容 | 表达语言 |
---|---|---|---|
姓名 | 是的 | 细绳 | 是的 |
它可以是“名称”:
my_func
MyClass::getExtraRelations
expr(service('user.rel_provider').getExtraRelations())
这里是使用表达式语言的示例:
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
{
...
}
这里是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
实现CompilableExpressionEvaluatorInterface
用于以可缓存并保存以供以后使用的形式解析表达式语言。如果您的关系中不需要表达语言,则不需要此服务。
user.rel_provider
服务定义为:
user.rel_provider :
class : UserRelPrvider
arguments :
- ' @jms_serializer.expression_evaluator '
在本例中, jms_serializer.expression_evaluator
是实现CompilableExpressionEvaluatorInterface
的服务。
本节涉及 Hateoas 内部结构,提供有关该库隐藏部分的文档。这并不总是与最终用户相关,但对于开发人员或有兴趣了解底层工作原理的人来说很有趣。
willdurand/hateoas
遵循语义版本控制。
截至 2013 年 10 月,官方不再支持版本1.x
和0.x
(请注意, 1.x
从未发布)。
版本3.x
是当前主要稳定版本。
维护版本2.x
仅用于安全错误修复和可能发生的重大问题。
请参阅贡献文件。
安装 Composer dev
依赖项:
php composer.phar install --dev
然后,使用 PHPUnit 运行测试套件:
bin/phpunit
Hateoas 是根据 MIT 许可证发布的。有关详细信息,请参阅捆绑的许可证文件。