nilportugues / hal
HAL+JSON & HAL+XML API转换器,输出有效的API响应。
2.1.0
2017-03-21 13:10 UTC
Requires
- php: >=5.5.0
- nilportugues/api-transformer: ^1.5
Requires (Dev)
- friendsofphp/php-cs-fixer: ^1.9
- phpunit/phpunit: 4.4.*
README
1. 安装
使用Composer安装包
$ composer require nilportugues/hal
2. 映射
给定一个PHP对象和一系列映射,HAL+JSON和HAL+XML API转换器将根据https://tools.ietf.org/html/draft-kelly-json-hal-07
规范草案表示给定的数据。
例如,给定以下代码段,定义一篇博客文章和一些评论
$post = new Post( new PostId(9), 'Hello World', 'Your first post', new User( new UserId(1), 'Post Author' ), [ new Comment( new CommentId(1000), 'Have no fear, sers, your king is safe.', new User(new UserId(2), 'Barristan Selmy'), [ 'created_at' => (new DateTime('2015/07/18 12:13:00'))->format('c'), 'accepted_at' => (new DateTime('2015/07/19 00:00:00'))->format('c'), ] ), ] );
我们需要映射所有相关的类。这可以作为一个数组完成,或者一系列映射类。
2.1 使用数组进行映射
使用数组映射相关类如下
use NilPortugues\Api\Mapping\Mapper; $mappings = [ [ 'class' => Post::class, 'alias' => 'Message', 'aliased_properties' => [ 'author' => 'author', 'title' => 'headline', 'content' => 'body', ], 'hide_properties' => [ ], 'id_properties' => [ 'postId', ], 'urls' => [ // Mandatory 'self' => 'http://example.com/posts/{postId}', // Optional 'comments' => 'http://example.com/posts/{postId}/comments' ], 'curies' => [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ] ], [ 'class' => User::class, 'alias' => '', 'aliased_properties' => [], 'hide_properties' => [], 'id_properties' => [ 'userId', ], 'urls' => [ 'self' => 'http://example.com/users/{userId}', ], 'curies' => [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ] ], [ 'class' => Comment::class, 'alias' => '', 'aliased_properties' => [], 'hide_properties' => [], 'id_properties' => [ 'commentId', ], 'urls' => [ 'self' => 'http://example.com/comments/{commentId}', ], 'curies' => [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ] ] ]; $mapper = new Mapper($mappings);
2.2 使用映射类进行映射
为了使用映射类进行映射,你需要为每个相关类创建一个新的类。
这种映射方式比使用数组更可扩展。
所有映射类都将扩展\NilPortugues\Api\Mappings\HalMapping
接口。
// PostMapping.php class PostMapping implements \NilPortugues\Api\Mappings\HalMapping { public function getClass() { return Post::class; } public function getAlias() { return 'Message'; } public function getAliasedProperties() { return [ 'author' => 'author', 'title' => 'headline', 'content' => 'body', ]; } public function getHideProperties() { return []; } public function getIdProperties() { return ['postId']; } public function getUrls() { return [ 'self' => 'http://example.com/posts/{postId}', // Mandatory 'comments' => 'http://example.com/posts/{postId}/comments' // Optional ]; } public function getCuries() { return [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ]; } } // UserMapping.php class UserMapping implements \NilPortugues\Api\Mappings\HalMapping { public function getClass() { return User::class; } public function getAlias() { return ''; } public function getAliasedProperties() { return []; } public function getHideProperties() { return []; } public function getIdProperties() { return ['postId']; } public function getUrls() { return [ 'self' => 'http://example.com/users/{userId}' ]; } public function getCuries() { return [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ]; } } // CommentMapping.php class CommentMapping implements \NilPortugues\Api\Mappings\HalMapping { public function getClass() { return Comment::class; } public function getAlias() { return ''; } public function getAliasedProperties() { return []; } public function getHideProperties() { return []; } public function getIdProperties() { return ['commentId']; } public function getUrls() { return [ 'self' => 'http://example.com/comments/{commentId}', ]; } public function getCuries() { return [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ]; } } $mappings = [ PostMapping::class, UserMapping::class, CommentMapping::class, ]; $mapper = new Mapper($mappings);
3. HAL序列化
调用转换器将使用正确的格式输出一个有效的HAL响应。
use NilPortugues\Api\Hal\JsonTransformer; use NilPortugues\Api\Hal\HalSerializer; use NilPortugues\Api\Hal\Http\Message\Response; $transformer = new JsonTransformer($mapper); //For XML: $transformer = new XmlTransformer($mapper); //Output transformation $serializer = new HalSerializer($transformer); $output = $serializer->serialize($post); //PSR7 Response with headers and content. $response = new Response($output); header( sprintf( 'HTTP/%s %s %s', $response->getProtocolVersion(), $response->getStatusCode(), $response->getReasonPhrase() ) ); foreach($response->getHeaders() as $header => $values) { header(sprintf("%s:%s\n", $header, implode(', ', $values))); } echo $response->getBody();
3.1 HAL+JSON
HTTP/1.1 200 OK
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/hal+json
输出
{ "post_id": 9, "headline": "Hello World", "body": "Your first post", "_embedded": { "author": { "user_id": 1, "name": "Post Author", "_links": { "self": { "href": "http://example.com/users/1" }, "example:friends": { "href": "http://example.com/users/1/friends" }, "example:comments": { "href": "http://example.com/users/1/comments" } } }, "comments": [ { "comment_id": 1000, "dates": { "created_at": "2015-08-13T22:47:45+02:00", "accepted_at": "2015-08-13T23:22:45+02:00" }, "comment": "Have no fear, sers, your king is safe.", "_embedded": { "user": { "user_id": 2, "name": "Barristan Selmy", "_links": { "self": { "href": "http://example.com/users/2" }, "example:friends": { "href": "http://example.com/users/2/friends" }, "example:comments": { "href": "http://example.com/users/2/comments" } } } }, "_links": { "example:user": { "href": "http://example.com/users/2" }, "self": { "href": "http://example.com/comments/1000" } } } ] }, "_links": { "curies": [ { "name": "example", "href": "http://example.com/docs/rels/{rel}", "templated": true } ], "self": { "href": "http://example.com/posts/9" }, "example:author": { "href": "http://example.com/users/1" }, "example:comments": { "href": "http://example.com/posts/9/comments" } } }
3.2 HAL+XML
对于XML输出,使用示例代码,但使用XML转换器代替
$transformer = new XmlTransformer($mapper);
输出
HTTP/1.1 200 OK
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/hal+xml
<?xml version="1.0" encoding="UTF-8"?> <resource href="http://example.com/posts/9"> <post_id><![CDATA[9]]></post_id> <headline><![CDATA[Hello World]]></headline> <body><![CDATA[Your first post]]></body> <embedded> <resource href="http://example.com/users/1" rel="author"> <user_id><![CDATA[1]]></user_id> <name><![CDATA[Post Author]]></name> <links> <link rel="self" href="http://example.com/users/1"/> <link rel="example:friends" href="http://example.com/users/1/friends"/> <link rel="example:comments" href="http://example.com/users/1/comments"/> </links> </resource> <comments> <resource href="http://example.com/comments/1000"> <comment_id><![CDATA[1000]]></comment_id> <dates> <created_at><![CDATA[2015-07-18T12:13:00+00:00]]></created_at> <accepted_at><![CDATA[2015-07-19T00:00:00+00:00]]></accepted_at> </dates> <comment><![CDATA[Have no fear, sers, your king is safe.]]></comment> <embedded> <resource href="http://example.com/users/2" rel="user"> <user_id><![CDATA[2]]></user_id> <name><![CDATA[Barristan Selmy]]></name> <links> <link rel="self" href="http://example.com/users/2"/> <link rel="example:friends" href="http://example.com/users/2/friends"/> <link rel="example:comments" href="http://example.com/users/2/comments"/> </links> </resource> </embedded> <links> <link rel="example:user" href="http://example.com/users/2"/> <link rel="self" href="http://example.com/comments/1000"/> </links> </resource> </comments> </embedded> <links> <curies> <link rel="resource" href="http://example.com/docs/rels/{rel}"> <name><![CDATA[example]]></name> <templated><![CDATA[true]]></templated> </link> </curies> <link rel="self" href="http://example.com/posts/9"/> <link rel="example:author" href="http://example.com/users/1"/> <link rel="example:comments" href="http://example.com/posts/9/comments"/> </links> </resource>
4. HAL分页资源
提供了一个分页对象,以简化此包的使用。
对于XML和JSON输出,使用HalPagination
对象构建当前资源的分页表示。
HalPagination
提供以下方法
setSelf($self)
setFirst($first)
setPrev($prev)
setNext($next)
setLast($last)
setCount($count)
setTotal($total)
setEmbedded(array $embedded)
为了使用它,创建一个新的HalPagination实例,使用设置器,并将其传递给序列化器的serialize($value)
方法。
其他一切都将由序列化器本身处理。就这么简单!
use NilPortugues\Api\Hal\HalPagination; use NilPortugues\Api\Hal\HalSerializer; use NilPortugues\Api\Hal\JsonTransformer; // ... //$objects is an array of objects, such as Post::class. // ... $page = new HalPagination(); //set the amounts $page->setTotal(20); $page->setCount(10); //set the objects $page->setEmbedded($objects); //set up the pagination links $page->setSelf('/post?page=1'); $page->setPrev('/post?page=1'); $page->setFirst('/post?page=1'); $page->setLast('/post?page=1'); $output = $serializer->serialize($page);
5. 响应对象
以下PSR-7响应对象提供了正确的头和HTTP状态码。
使用它是可选的,并作为起点提供。
NilPortugues\Api\Hal\Http\Message\ErrorResponse($body)
NilPortugues\Api\Hal\Http\Message\ResourceCreatedResponse($body)
NilPortugues\Api\Hal\Http\Message\ResourceDeletedResponse($body)
NilPortugues\Api\Hal\Http\Message\ResourceNotFoundResponse($body)
NilPortugues\Api\Hal\Http\Message\ResourcePatchErrorResponse($body)
NilPortugues\Api\Hal\Http\Message\ResourcePostErrorResponse($body)
NilPortugues\Api\Hal\Http\Message\ResourceProcessingResponse($body)
NilPortugues\Api\Hal\Http\Message\ResourceUpdatedResponse($body)
NilPortugues\Api\Hal\Http\Message\Response($body)
NilPortugues\Api\Hal\Http\Message\UnsupportedActionResponse($body)
质量
要运行命令行中的PHPUnit测试,请转到测试目录并运行phpunit。
这个库试图遵守PSR-1、PSR-2、PSR-4和PSR-7。
如果您发现遵守上的疏漏,请通过Pull Request发送补丁。
贡献
我们始终欢迎对这个包的贡献!
支持
您可以使用以下方式之一与我联系
- 通过contact@nilportugues.com给我发邮件
- 创建一个问题
作者
许可协议
代码库采用MIT许可协议。