nilportugues / laravel5-haljson
Laravel 5 HAL+JSON API 转换器包
Requires
Requires (Dev)
- fabpot/php-cs-fixer: ^1.10
- laravel/laravel: ^5
- phpunit/phpunit: ^4
README
1. 安装
使用 Composer 安装包
$ composer require nilportugues/laravel5-haljson
2. 配置
打开 config/app.php
文件,并在 providers
数组下添加以下行
'providers' => [ //... \NilPortugues\Laravel5\HalJson\Laravel5HalJsonServiceProvider::class, ],
同时,通过取消注释启用 Facades
$app->withFacades();
3. 映射
例如,假设以下对象已从存储库中检索到,假设为 PostRepository
- 这是由 Eloquent 或您喜欢的任何其他东西实现的
use Acme\Domain\Dummy\Post; use Acme\Domain\Dummy\ValueObject\PostId; use Acme\Domain\Dummy\User; use Acme\Domain\Dummy\ValueObject\UserId; use Acme\Domain\Dummy\Comment; use Acme\Domain\Dummy\ValueObject\CommentId; //$postId = 9; //PostRepository::findById($postId); $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'), ] ), ] );
我们需要映射所有涉及的类。这可以作为一个单独的数组,或者一系列映射类来完成。
我们还需要有路由。路由必须是命名路由。
例如,我们的 app/Http/routes.php
文件包含以下路由
Route::get( '/docs/rels/{rel}', ['as' => 'get_example_curie_rel', 'uses' => 'ExampleCurieController@getRelAction'] ); Route::get( '/post/{postId}', ['as' => 'get_post', 'uses' => 'PostController@getPostAction'] ); Route::get( '/post/{postId}/comments', ['as' => 'get_post_comments', 'uses' => 'CommentsController@getPostCommentsAction'] ); //...
3.1 使用数组进行映射
在 config/
目录中创建一个 haljson.php
文件。该文件应返回一个返回所有类映射的数组。
并在 bootstrap/haljson.php
中放置一系列映射,这需要使用 命名路由,这样我们就可以使用 route()
辅助函数。
<?php //bootstrap/haljson.php return [ [ 'class' => 'Acme\Domain\Dummy\Post', 'alias' => 'Message', 'aliased_properties' => [ 'author' => 'author', 'title' => 'headline', 'content' => 'body', ], 'hide_properties' => [ ], 'id_properties' => [ 'postId', ], 'urls' => [ 'self' => ['name' => 'get_post'], //named route 'comments' => ['name' => 'get_post_comments'],//named route ], 'curies' => [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ] ], [ 'class' => 'Acme\Domain\Dummy\ValueObject\PostId', 'alias' => '', 'aliased_properties' => [], 'hide_properties' => [], 'id_properties' => [ 'postId', ], 'urls' => [ 'self' => ['name' => 'get_post'],//named route ], 'curies' => [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ] ], [ 'class' => 'Acme\Domain\Dummy\User', 'alias' => '', 'aliased_properties' => [], 'hide_properties' => [], 'id_properties' => [ 'userId', ], 'urls' => [ 'self' => ['name' => 'get_user'],//named route 'friends' => ['name' => 'get_user_friends'],//named route 'comments' => ['name' => 'get_user_comments'],//named route ], 'curies' => [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ] ], [ 'class' => 'Acme\Domain\Dummy\ValueObject\UserId', 'alias' => '', 'aliased_properties' => [], 'hide_properties' => [], 'id_properties' => [ 'userId', ], 'urls' => [ 'self' => ['name' => 'get_user'],//named route 'friends' => ['name' => 'get_user_friends'],//named route 'comments' => ['name' => 'get_user_comments'],//named route ], 'curies' => [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ] ], [ 'class' => 'Acme\Domain\Dummy\Comment', 'alias' => '', 'aliased_properties' => [], 'hide_properties' => [], 'id_properties' => [ 'commentId', ], 'urls' => [ 'self' => ['name' => 'get_comment'],//named route ], 'curies' => [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ] ], [ 'class' => 'Acme\Domain\Dummy\ValueObject\CommentId', 'alias' => '', 'aliased_properties' => [], 'hide_properties' => [], 'id_properties' => [ 'commentId', ], 'urls' => [ 'self' => ['name' => 'get_comment'],//named route ], 'curies' => [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ] ], ];
3.2 使用映射类进行映射
为了使用映射类进行映射,您需要为每个涉及的类创建一个新的类。
这种映射方式比使用数组具有更好的可扩展性。将每个映射放置在单独的文件中。
所有映射类都将扩展 \NilPortugues\Api\Mappings\HalMapping
接口。
<?php class PostMapping implements \NilPortugues\Api\Mappings\HalMapping { public function getClass() { return 'Acme\Domain\Dummy\Post'; } 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' => ['name' => 'get_post'], //named route 'comments' => ['name' => 'get_post_comments'],//named route ]; } public function getCuries() { return [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ]; } } class PostIdMapping implements \NilPortugues\Api\Mappings\HalMapping{ public function getClass() { return 'Acme\Domain\Dummy\ValueObject\PostId'; } public function getAlias() { return ''; } public function getAliasedProperties() { return []; } public function getHideProperties() { return []; } public function getIdProperties() { return ['postId']; } public function getUrls() { return [ 'self' => ['name' => 'get_post'],//named route ]; } public function getCuries() { return [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ]; } } class UserMapping implements \NilPortugues\Api\Mappings\HalMapping{ public function getClass() { return 'Acme\Domain\Dummy\User'; } public function getAlias() { return ''; } public function getAliasedProperties() { return []; } public function getHideProperties() { return []; } public function getIdProperties() { return ['userId']; } public function getUrls() { return [ 'self' => ['name' => 'get_user'],//named route 'friends' => ['name' => 'get_user_friends'],//named route 'comments' => ['name' => 'get_user_comments'],//named route ]; } public function getCuries() { return [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ]; } } class UserIdMapping implements \NilPortugues\Api\Mappings\HalMapping{ public function getClass() { return 'Acme\Domain\Dummy\ValueObject\UserId'; } public function getAlias() { return ''; } public function getAliasedProperties() { return []; } public function getHideProperties() { return []; } public function getIdProperties() { return ['userId']; } public function getUrls() { return [ 'self' => ['name' => 'get_user'],//named route 'friends' => ['name' => 'get_user_friends'],//named route 'comments' => ['name' => 'get_user_comments'],//named route ]; } public function getCuries() { return [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ]; } } class CommentMapping implements \NilPortugues\Api\Mappings\HalMapping{ public function getClass() { return 'Acme\Domain\Dummy\Comment'; } public function getAlias() { return ''; } public function getAliasedProperties() { return []; } public function getHideProperties() { return []; } public function getIdProperties() { return ['commentId']; } public function getUrls() { return [ 'self' => ['name' => 'get_comment'],//named route ]; } public function getCuries() { return [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ]; } } class CommentIdMapping implements \NilPortugues\Api\Mappings\HalMapping{ public function getClass() { return 'Acme\Domain\Dummy\ValueObject\CommentId'; } public function getAlias() { return ''; } public function getAliasedProperties() { return []; } public function getHideProperties() { return []; } public function getIdProperties() { return ['commentId']; } public function getUrls() { return [ 'self' => ['name' => 'get_comment'],//named route ]; } public function getCuries() { return [ 'name' => 'example', 'href' => "http://example.com/docs/rels/{rel}", ]; } }
所有映射都包含在 bootstrap/haljson.php
中的数组中,但这次需要完全限定的类名。
<?php //bootstrap/haljson.php return [ "\Acme\Mappings\PostMapping", "\Acme\Mappings\PostIdMapping", "\Acme\Mappings\UserMapping", "\Acme\Mappings\UserIdMapping", "\Acme\Mappings\CommentMapping", "\Acme\Mappings\CommentIdMapping", ];
3. HAL 序列化
所有这些设置都允许您轻松使用以下 Serializer
服务
<?php namespace App\Http\Controllers; use Acme\Domain\Dummy\PostRepository; use NilPortugues\Laravel5\HalJson\HalJsonResponseTrait; class PostController extends \App\Http\Controllers\Controller { use HalJsonResponseTrait; /** * @var PostRepository */ protected $postRepository; /** * @var HalJson */ protected $serializer; /** * @param PostRepository $postRepository * @param HalJson $HalJson */ public function __construct(PostRepository $postRepository, HalJson $HalJson) { $this->postRepository = $postRepository; $this->serializer = $HalJson; } /** * @param int $postId * * @return \Symfony\Component\HttpFoundation\Response */ public function getPostAction($postId) { $post = $this->postRepository->findById($postId); return $this->response($this->serializer->serialize($post)); } }
输出
HTTP/1.1 200 OK
Cache-Control: protected, 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" } } }
5. 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);
6. 响应对象
提供了以下 HalJsonResponseTrait
方法来返回正确的头信息和可用的 HTTP 状态码
protected function errorResponse($json); protected function resourceCreatedResponse($json); protected function resourceDeletedResponse($json); protected function resourceNotFoundResponse($json); protected function resourcePatchErrorResponse($json); protected function resourcePostErrorResponse($json); protected function resourceProcessingResponse($json); protected function resourceUpdatedResponse($json); protected function response($json); protected function unsupportedActionResponse($json);
质量
要运行命令行中的 PHPUnit 测试,请转到测试目录并发出 phpunit。
本库试图遵守PSR-1、PSR-2、PSR-4和PSR-7。
如果您发现任何遵守上的疏忽,请通过Pull Request发送补丁。
贡献
欢迎对包的贡献!
支持
您可以使用以下方式之一与我联系
- 通过contact@nilportugues.com给我发送邮件
- 打开一个问题
作者
许可证
代码库受MIT许可证许可。