fsc / hateoas-bundle
Requires
- php: >=5.3.0
- adrienbrault/form-serializer: ~0.1.2
- jms/metadata: ~1.1
- jms/serializer: >=0.12.0,<0.15.0-dev
- jms/serializer-bundle: *
- symfony/framework-bundle: ~2.1
- symfony/property-access: ~2.2
Requires (Dev)
Suggests
- pagerfanta/pagerfanta: *
- symfony/form: ~2.1
- symfony/validator: ~2.1
This package is auto-updated.
Last update: 2024-09-12 03:33:36 UTC
README
该组件挂载到JMSSerializerBundle的序列化过程,并提供了HATEOAS功能。目前仅支持添加链接。
尽管有一些测试,请注意这是一个正在进行中的项目。例如,仅支持yaml和注解元数据配置。
安装
composer.json
{ "require": { "fsc/hateoas-bundle": "0.5.x-dev" }, "minimum-stability": "dev" }
示例应用程序
您可以在以下链接找到使用此组件的symfony 2.1示例应用程序:https://github.com/adrienbrault/symfony-hateoas-sandbox。
添加链接
使用以下配置和实体
路由和序列化/ HATEOAS元数据
# routing.yml api_user_get: pattern: /api/users/{id} api_user_list: pattern: /api/users user_profile: pattern: /profile/{user_id}
注意,您还可以使用yaml配置序列化/ HATEOAS元数据,以将序列化排除在模型之外
<?php // src/Acme/FooBundle/Entity/User.php use JMS\Serializer\Annotation as Serializer; use FSC\HateoasBundle\Annotation as Rest; /** * @Rest\Relation("self", href = @Rest\Route("api_user_get", parameters = { "id" = ".id" })) * @Rest\Relation("alternate", href = @Rest\Route("user_profile", parameters = { "user_id" = ".id" })) * @Rest\Relation("users", href = @Rest\Route("api_user_list")) * @Rest\Relation("rss", href = "http://domain.com/users.rss") * @Rest\Relation("from_property_path", href = ".dynamicHref") * * @Serializer\XmlRoot("user") */ class User { /** @Serializer\XmlAttribute */ public $id; public $username; public function getDynamicHref() { return "dynamic/Href/here"; } }
注意,href可以是@Route注解、字符串或属性路径,这些路径将在序列化时解析。
用法
<?php $user = new User(); $user->id = 24; $user->username = 'adrienbrault'; $serializedUser = $container->get('serializer')->serialize($user, $format);
结果
<user id="24"> <username><![CDATA[adrienbrault]]></username> <link rel="self" href="http://localhost/api/users/24"/> <link rel="alternate" href="http://localhost/profile/24"/> <link rel="users" href="http://localhost/api/users"/> <link rel="rss" href="http://domain.com/users.rss"/> </user>
或
{ "id": 24, "links": [ "self": { "href": "http:\/\/localhost\/api\/users\/24" }, "alternate": { "href": "http:\/\/localhost\/profile\/24" }, "users": { "href": "http:\/\/localhost\/api\/users" }, "rss": { "href": "http:\/\/domain.com\/users.rss" } ] }
在运行时对对象添加关系
在某些情况下,您可能希望在运行时对对象添加关系。例如,如果您想要一个根控制器,其中包含对您的不同集合的链接,则可以创建一个带有HATEOAS元数据的Root对象。但如果您只想在用户连接时创建一个指向当前连接用户的“me”关系呢?
我们将使用此示例。
<?php // src/Acme/FooBundle/Model/Model.php use JMS\Serializer\Annotation as Serializer; use FSC\HateoasBundle\Annotation as Rest; /** * @Rest\Relation("users", href = @Rest\Route("api_user_list")) * @Rest\Relation("posts", href = @Rest\Route("api_post_list")) * * @Serializer\XmlRoot("root") */ class Root { }
<?php class RootController extends Controller { public function indexAction() { $root = new Root(); if (null !== ($user = $this->getUser())) { $relationsBuilder = $this->get('fsc_hateoas.metadata.relation_builder.factory')->create(); $relationsBuilder->add('me', array( 'route' => 'api_user_get', 'parameters' => array('id' => $user->getId()) )); $relationsBuilder->add('me2', 'http://api.com/users/32'); // if you want to use the router here $this->get('fsc_hateoas.metadata.factory')->addObjectRelations($root, $relationsBuilder->build()); } return new Response($this->get('serializer')->serialize($root, $request->get('_format'))); } }
结果
无用户连接
GET /api
会导致
<root> <link rel="users" href="http://localhost/api/users"/> <link rel="posts" href="http://localhost/api/posts"/> </root>
用户32已连接
GET /api
会导致
<root> <link rel="users" href="http://localhost/api/users"/> <link rel="posts" href="http://localhost/api/posts"/> <link rel="me" href="http://localhost/api/users/32"/> <link rel="me2" href="http://localhost/api/users/32"/> </root>
禁用链接
在特定情况下,可能希望禁用关系链接的输出。可以像这样关闭输出
$container->get('fsc_hateoas.serializer.metadata_helper')->disableLinks();
JSON格式
该组件支持在序列化为JSON时自定义链接和嵌入式关系的键。它们由以下配置控制
# app/config/config.yml fsc_hateoas: json: links_key: _links # default: links relations_key: _embedded # default: relations
上述配置将导致有效的hal+json序列化。
Pagerfanta处理器
默认配置
fsc_hateoas: pagerfanta: xml_elements_names_use_serializer_metadata: true
使用此配置,pagerfanta处理器将使用序列化器的xml根名称元数据来确定每个结果应使用哪个xml元素名称。(即:/** @Serializer\XmlRootName("user") */ class User {}
)
示例
<?php use Pagerfanta\Pagerfanta; use Pagerfanta\Adapter\DoctrineORMAdapter; public function getListAction($page = 1, $limit = 10) { $query = $this->get('doctrine')->getRepository('User')->createQueryXXX(); $pager = new Pagerfanta(new DoctrineORMAdapter($query)); // or any Pagerfanta adapter $pager->setCurrentPage($page); $pager->setMaxPerPage($limit); $this->get('serializer')->getSerializationVisitor('xml')->setDefaultRootName('users'); return new Response($this->get('serializer')->serialize($pager, 'xml'))); }
GET /list?page=3
会导致
<users page="3" limit="10" total="234"> <user> <link rel="self" href="..."/> </user> <user> <link rel="self" href="..."/> </user> <user> <link rel="self" href="..."/> </user> ... </users>
添加Pagerfanta导航链接
Pagerfanta本身不创建指向自我/下一个/上一个/最后一页/第一页的链接(仅在嵌入式关系时)。
示例
<?php use Symfony\Component\HttpFoundation\Request; use Pagerfanta\Pagerfanta; use Pagerfanta\Adapter\DoctrineORMAdapter; public function getListAction(Request $request, $page = 1, $limit = 10) { $query = $this->get('doctrine')->getRepository('User')->createQueryXXX(); $pager = new Pagerfanta(new DoctrineORMPager($query)); // or any Pagerfanta adapter $pager->setCurrentPage($page); $pager->setMaxPerPage($limit); $this->get('fsc_hateoas.metadata.relations_manager')->addBasicRelations($pager); // Automatically add self/first/last/prev/next links $this->get('serializer')->getSerializationVisitor('xml')->setDefaultRootName('users'); return new Response($this->get('serializer')->serialize($pager, 'xml'))); }
GET /list?page=3
会导致
<users page="3" limit="10" total="234"> <link rel="self" href="http://localhost/api/users?limit=10&page=3"/> <link rel="first" href="http://localhost/api/users?limit=10&page=1"/> <link rel="last" href="http://localhost/api/users?limit=10&page=24"/> <link rel="previous" href="http://localhost/api/users?limit=10&page=2"/> <link rel="next" href="http://localhost/api/users?limit=10&page=4"/> <user> <link rel="self" href="..."/> </user> <user> <link rel="self" href="..."/> </user> <user> <link rel="self" href="..."/> </user> ... </users>
嵌入关系
有时,您的表示具有需要获取服务或需要分页的嵌入式关系。要使用此组件嵌入关系,您创建一个简单的Relation元数据(例如使用注解),并添加额外的"content"参数。
示例
路由和控制台
# routing.yml api_user_get: pattern: /api/users/{id} api_user_friends_list: pattern: /api/users/{id}/friends
<?php class UserController extends Controller { public function getUserFriendsAction($id, $page = 1, $limit = 20) { $pager = $this->get('acme.foo.user_manager')->getUserFriendsPager($id, $page, $limit); $this->get('fsc_hateoas.metadata.relations_manager')->addBasicRelations($pager); // Automatically add self/first/last/prev/next links $this->get('serializer')->getSerializationVisitor('xml')->setDefaultRootName('users'); return new Response($this->get('serializer')->serialize($pager, 'xml')); } public function getUserAction($id) { $user = ...; return new Response($this->get('serializer')->serialize($user, 'xml')); } }
模型和序列化/ HATEOAS元数据
注意,您还可以使用yaml配置序列化/ HATEOAS元数据,以将序列化排除在模型之外
<?php // src/Acme/FooBundle/Entity/User.php use JMS\Serializer\Annotation as Serializer; use FSC\HateoasBundle\Annotation as Rest; // The bundle will automatically add navigation links to the embedded pagerfanta using the correct route /** * @Rest\Relation("self", href = @Rest\Route("api_user_get", parameters = { "id" = ".id" })) * @Rest\Relation("friends", * href = @Rest\Route("api_user_friends_list", parameters = { "id" = ".id" }), * embed = @Rest\Content( * provider = {"acme.foo.user_manager", "getUserFriendsPager"}, * providerArguments = { ".id", 1, 5 }, * serializerXmlElementName = "users" * ) * ) * * @Serializer\XmlRoot("user") */ class User { ... }
定义用于获取嵌入数据的提供者服务
<?php // This is the class behing the "acme.foo.user_manager" service class UserManager { public function getUserFriendsPager($userId, $page = 1, $limit = 20) { $doctrineQuery = ...; $pager = Pagerfanta(new DoctrineORMAdapter($doctrineQuery)); $pager->setCurrentPage($page); $pager->setMaxPerPage($limit); return $pager; } }
结果
GET /api/users/42
会导致
<user> <link rel="self" href="http://localhost/api/users/42"/> <link rel="friends" href="http://localhost/api/users/42/friends"/> <users rel="friends" page="1" limit="5" total="134"> <link rel="self" href="http://localhost/api/users/42/friends?limit=10&page=1"/> <link rel="first" href="http://localhost/api/users/42/friends?limit=10&page=1"/> <link rel="last" href="http://localhost/api/users/42/friends?limit=10&page=27"/> <link rel="next" href="http://localhost/api/users/42/friends?limit=10&page=2"/> <user> <link rel="self" href="..."/> </user> <user> <link rel="self" href="..."/> </user> <user> <link rel="self" href="..."/> </user> <user> <link rel="self" href="..."/> </user> <user> <link rel="self" href="..."/> </user> </users> </user>
以及GET /api/users/42/friends
会导致
<users rel="friends" page="1" limit="20" total="134"> <link rel="self" href="http://localhost/api/users/42/friends?limit=20&page=1"/> <link rel="first" href="http://localhost/api/users/42/friends?limit=20&page=1"/> <link rel="last" href="http://localhost/api/users/42/friends?limit=20&page=7"/> <link rel="next" href="http://localhost/api/users/42/friends?limit=20&page=2"/> <user> <link rel="self" href="..."/> </user> <user> <link rel="self" href="..."/> </user> <user> <link rel="self" href="..."/> </user> <user> <link rel="self" href="..."/> </user> <user> <link rel="self" href="..."/> </user> </users>
从属性嵌入关系
除了定义嵌入资源的服务之外,您还可以嵌入主资源属性的资源。
<?php // src/Acme/FooBundle/Entity/User.php use JMS\Serializer\Annotation as Serializer; use FSC\HateoasBundle\Annotation as Rest; /** * @Rest\Relation("self", href = @Rest\Route("api_user_get", parameters = { "id" = ".id" })) * @Rest\Relation("friends", * href = @Rest\Route("api_user_friends_list", parameters = { "id" = ".id" }), * embed = @Rest\Content( * property = ".friends" * ) * ) * * @Serializer\XmlRoot("user") */ class User { ... /** * @var array<User> */ private $friends; }
这将序列化friends
属性并将其嵌入为关系。
表单视图处理器
您可以对表单视图进行序列化。(仅在XML中可用,如果需要在JSON中,请尝试提交PR :) )
建议您的客户开发者根据表单构建请求,这具有许多优势,并可以从客户端移除一些逻辑。测试您的API也非常简单,只需跟随表单的链接,然后使用symfony DomCrawler填充并提交表单。
<?php class UserController extends Controller { public function getEditFormAction(User $user) { $formFactory = $this->getKernel()->getContainer()->get('form.factory'); $form = $formFactory->createBuilder('user') ->add('name', 'text') ->add('email', 'email') ->add('gender', 'choice', array( 'choices' => array('m' => 'male', 'f' => 'female') )) ->getForm(); $formView = $this->get('fsc_hateoas.factory.form_view')->create($form, 'PUT', 'api_user_edit'); // Create form view and add method/action data to the FormView $this->get('fsc_hateoas.metadata.relations_manager')->addBasicRelations($formView); // Automatically add self links to the form $this->get('serializer')->getSerializationVisitor('xml')->setDefaultRootName('form'); return new Response($this->get('serializer')->serialize($formView, $request->get('_format'))); } }
结果
<form method="PUT" action="http://localhost/api/users/25"> <input type="text" name="form[name]" required="required" value="Adrien"/> <input type="email" name="form[email]" required="required" value="monsti@gmail.com"/> <select name="form[gender]" required="required"> <option value="m" selected="selected">male</option> <option value="f">female</option> </select> </form>
RelationUrlGenerator
您可以利用hateoas组件知道如何创建对象关系的URL。如果您想生成对象的self
URL,这将非常有用。
$user = ... $userUrl = $container->get('fsc_hateoas.routing.relation_url_generator')->generateUrl($user, 'self')
您甚至可以使用控制器特性。
<?php use FSC\HateoasBundle\Controller\HateoasTrait; class UserController extends Controller { public function createUserAction(Request $request) { $user = new User(); ... // you own stuff return Response('', 201, array( 'Location' => $this->generateSelfUrl($user), )); } }
关系属性
有一个属性数组可以设置在关系上,这些属性将被序列化为链接的属性。这可以用于标记链接为模板化等。示例
/** * @Rest\Relation("search", href = "http://domain.com/search?{&q}", attributes = { "templated" = "true" }) */ class User { }
结果
<user id="24"> <link rel="search" href="http://domain.com/search?{&q}" templated="true" /> </user>
或
{ "id": 24, "links": [ "search": { "href": "http:\/\/domain.com/search?{&q}", "templated": "true" } ] }
路由选项
使用不同的路由器
此组件支持与其注册不同的路由器。例如,可以使用不同的路由器用于模板化URL。要注册路由器,您需要将服务标记为fsc_hateoas.url_generator
,并且您可以提供一个alias
,这样您就不必写出完整的服务名称。例如,如果您想通过使用Hautelook Templated URI Bundle来提供URI模板(RFC-6570),这非常有用。
示例
services: test.url_generator.prepend: class: FSC\HateoasBundle\Tests\Functional\TestBundle\Routing\PrependUrlGenerator arguments: - @router tags: - { name: fsc_hateoas.url_generator, alias: prepend }
然后您可以通过在注解中使用options
来使用此路由器。示例
/** * @Rest\Relation("self", href = @Rest\Route("api_user_get", parameters = { "id" = ".id" }, options = { "router" = "prepend" })) */ class User { }
创建绝对/相对URL
您可以通过指定为路由的option
来强制链接为绝对或相对。示例
/** * @Rest\Relation("self", href = @Rest\Route("api_user_get", parameters = { "id" = ".id" }, options = { "absolute" = true })) */ class User { }
条件性地排除链接
您可以在关系上添加条件,以确定是否应该排除链接。例如
/** * @Rest\Relation( * "parent", * href = @Rest\Route("api_post_get", parameters = { "id" = ".parent.id"}), * excludeIf = { ".parent" = null } * ) */ class Post { }
如果parent
的值为null
,则不会包含parent
链接。这也可以通过YAML配置来完成。
relations: - rel: parent href: route: api_post_get parameters: { id: .parent.id } exclude_if: ".parent": ~