willdurand/hateoas

一个支持实现 HATEOAS REST 服务的表示的 PHP 库

3.10.0 2023-12-12 13:35 UTC

README

GitHub Actions GitHub Actions Latest Stable Version PHP Version Require

一个支持实现 HATEOAS REST 服务的表示的 PHP 库。

安装

推荐通过 Composer 安装 Hateoas。运行以下命令以要求 willdurand/hateoas

composer require willdurand/hateoas

这将解析最新稳定版本。

否则,您可以自己安装库并设置自动加载器。

与 Symfony 一起使用

有一个相应的包!安装 BazingaHateoasBundle 并享受吧!

用法

重要

对于使用 1.0 版本的用户,您可以 跳转到此文档页面

对于使用 2.0 版本的用户,您可以 跳转到此文档页面

以下文档是为 Hateoas 3.0 及以上版本编写的。

介绍

Hateoas 利用 Serializer 库提供了一种构建 HATEOAS REST 服务的优雅方式。HATEOAS 代表 Hypermedia as the Engine of Application State(超媒体作为应用状态引擎),并为您的 表示(即您的 API 响应)添加了 超媒体链接HATEOAS 是关于资源上操作的可发现性

例如,假设您有一个返回单个 用户 表示的用户 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() {}
}

在上面的示例中,我们配置了一个self关系,它是一个链接,因为它具有href参数。这个值可能乍一看很奇怪,将在表达式语言部分进行详细说明。这个特殊值用于生成URI。

在本节中,使用注解来配置Hateoas。也支持XMLYAML格式。如果您愿意,也可以使用纯PHP。

重要:您必须以相同的方式配置序列化和Hateoas。例如,如果您使用YAML配置序列化器,则使用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对象是来自序列化库的JMS\Serializer\SerializerInterface实例。Hateoas没有自己的序列化器,它钩入JMS序列化器。

默认情况下,Hateoas使用超文本应用语言(HAL)进行JSON序列化。它指定了响应的结构(例如,“links”应位于_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;
}

注意:您需要从序列化中排除manager属性,否则序列化器和Hateoas都会对其进行序列化。当manager为null时,您还必须排除manager关系,因为否则在创建href链接(在null上调用getId())时将发生错误。

技巧:如果manager属性是一个已经具有_self链接的对象,您可以使用该值代替href,而不是在此处重复它。请参阅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');

对于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>

嵌入资源的标签名称是从序列化配置中的@XmlRoot注解(在YAML中为xml_root_name,在XML中为xml-root-name)推断出来的。

处理集合

库在Hateoas\Representation\*命名空间中提供了几个类来帮助您处理常见任务。这些是使用库的注解配置的简单类。

《分页表示》类、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设计用于添加selffirst,以及在可能的情况下添加lastnextprevious链接。

OffsetRepresentation的工作方式与PaginatedRepresentation类似,但在分页通过offsetlimittotal表示时很有用。

RouteAwareRepresentation基于给定的路由添加一个self关系。

您可以通过将PaginatedRepresentationRouteAwareRepresentation中的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&amp;limit=10" />
    <link rel="first" href="/api/users?page=1&amp;limit=10" />
    <link rel="last" href="/api/users?page=1&amp;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);

表示

如前所述,表示是配置了库的注解的类,旨在帮助您完成常见任务。有关集合表示的更多信息,请参阅处理集合

VndErrorRepresentation

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 表达式语言组件来检索值,如链接、id或要嵌入的对象。

每次您填写一个值(例如,在注解或YAML中传递的链接href),您可以选择传递一个硬编码值或一个表达式。为了使用表达式语言,您必须使用expr()表示法

/**
 * @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

URL 生成器

由于您可以使用Expression Language定义关系链接(href键),因此默认情况下可以完成很多事情。但是,如果您正在使用框架,那么您很可能会希望使用路由来构建链接。

您首先需要在构建器上配置一个 UrlGenerator。您可以选择实现 Hateoas\UrlGenerator\UrlGeneratorInterface,或者使用 Hateoas\UrlGenerator\CallableUrlGenerator

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
{
    "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

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 函数

上述功能也适用于您的表达式(cf. 表达式语言)中的 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);
    }
}

请注意 postrelative 关系及其在以下 JSON 内容中的相应值中的 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 都将是 相对的,即使它们在配置中定义为 绝对

$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

Twig 扩展

Hateoas 还提供了一套 Twig 扩展。

LinkExtension

LinkExtension 允许您将 LinkHelper 用于您的 Twig 模板中,这样您就可以在 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 内容,例如 HALAtom Links

JsonHalSerializer

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

XmlSerializer 允许您在 XML 文档中生成 Atom Links。它是默认的 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

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

HateoasBuilder

HateoasBuilder 类用于通过强大的流畅 API 轻松配置 Hateoas。

use Hateoas\HateoasBuilder;

$hateoas = HateoasBuilder::create()
    ->setCacheDir('/path/to/cache/dir')
    ->setDebug($trueOrFalse)
    ->setDefaultXmlSerializer()
    ...
    ->build();

以下所有方法都返回当前构建器,以便您可以链接它们。

XML 序列化器

  • setXmlSerializer(SerializerInterface $xmlSerializer):设置要使用的XML序列化器。默认为:XmlSerializer;
  • setDefaultXmlSerializer():设置默认的XML序列化器(XmlSerializer)。

JSON 序列化器

  • setJsonSerializer(SerializerInterface $jsonSerializer):设置要使用的JSON序列化器。默认为:JsonHalSerializer;
  • setDefaultJsonSerializer():设置默认的JSON序列化器(JsonHalSerializer)。

URL 生成器

  • setUrlGenerator($name = null, UrlGeneratorInterface $urlGenerator):添加一个命名URL生成器。如果$namenull,则URL生成器将是默认的。

表达式评估器/表达式语言

  • setExpressionContextVariable($name, $value):添加一个新的表达式上下文变量;
  • setExpressionLanguage(ExpressionLanguage $expressionLanguage);

(JMS) 序列化器特定

  • 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注解,但您也可以将元数据存储在XML或YAML文件中。对于后者,有必要配置一个元数据目录,其中包含这些文件

$hateoas = \Hateoas\HateoasBuilder::create()
    ->addMetadataDir($someDir)
    ->build();

Hateoas期望元数据文件以完全限定的类名命名,其中所有\都替换为.。如果您的类名为Vendor\Package\Foo,则元数据文件需要位于$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('Acme\Foo\Model', $classMetadata->getName())) {
            // Add a "root" relation to all classes in the `Acme\Foo\Model` namespace
            $classMetadata->addRelation(
                new Relation(
                    'root',
                    '/'
                )
            );
        }
    }
}

您可以使用$classMetadata->getRelations()访问从注解、XML或YAML加载的现有关系。

如果$classMetadata有关系,或您向其中添加关系,其关系将被缓存。因此,如果您读取配置文件(注解、XML或YAML),请确保在类元数据中引用它们

$classMetadata->fileResources[] = $file;

参考

XML

<?xml version="1.0" encoding="UTF-8"?>
<serializer>
<class name="Acme\Demo\Representation\User" 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文件。

YAML

Acme\Demo\Representation\User:
    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())" ]

注解

@Relation

此注解可以定义在类上。

use Hateoas\Configuration\Annotation as Hateoas;

/**
 * @Hateoas\Relation(
 *     name = "self",
 *     href = "http://hello",
 *     embedded = "expr(object.getHello())",
 *     attributes = { "foo" = "bar" },
 *     exclusion = ...,
 * )
 */

重要: attributes仅在链接关系上使用(即与href属性结合,而不是与embedded属性结合)。

@Route

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"
 *     )
 * )
 */

此注解可以定义在@Relation注解的href属性中。这允许您使用URL生成器,如果您已配置了它。

@Embedded

use Hateoas\Configuration\Annotation as Hateoas;

/**
 * @Hateoas\Relation(
 *     name = "friends",
 *     embedded = @Hateoas\Embedded(
 *         "expr(object.getFriends())",
 *         exclusion = ...,
 *         xmlElementName = "users"
 *     )
 * )
 */

此注解可以定义在@Relation注解的embedded属性中。如果您需要配置嵌入式资源的exclusionxmlElementName选项,则非常有用。

@Exclusion

此注解可以定义在@Relation@Embedded注解的exclusion属性中。

除了excludeIf之外的所有值在序列化器直接应用于常规属性时表现相同。

excludeIf期望一个布尔值,在某种情况下其他表达式可能失败时很有用。在这个例子中,如果getManager方法是null,应该排除它以防止URL生成失败。

/**
 * @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() {}
}

@RelationProvider

这个注解可以定义在类上。如果你希望序列化多关系(链接),这将很有用。例如

{
  "_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
{
    ...
}

这里使用的是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.x0.x已不再官方支持(注意1.x从未发布)。

稳定版本

版本3.x是当前的主要稳定版本。

版本2.x仅维护用于安全漏洞修复和可能出现的重大问题。

贡献

请参阅CONTRIBUTING文件。

运行测试

安装Composer dev依赖项

php composer.phar install --dev

然后,使用PHPUnit运行测试套件

bin/phpunit

许可证

Hateoas在MIT许可证下发布。有关详细信息,请参阅捆绑的LICENSE文件。