nilportugues / json-api
序列化转换器,输出有效的API响应,格式为JSON、JSON API和HAL+JSON API。
2.7.2
2018-10-19 10:38 UTC
Requires
- php: >=5.5.0
- nilportugues/api-transformer: <=3.0.0
- symfony/psr-http-message-bridge: ^0.2|^0.3|^1.0
Requires (Dev)
- doctrine/orm: ^2.5
- friendsofphp/php-cs-fixer: ^1.9
- phpunit/phpunit: 4.*
- dev-master
- 3.0.x-dev
- 2.7.2
- 2.7.1
- 2.7.0
- 2.6.0
- 2.5.4
- 2.5.3
- 2.5.2
- 2.5.1
- 2.5.0
- 2.4.4
- 2.4.3
- 2.4.2
- 2.4.1
- 2.4.0
- 2.3.1
- 2.3.0
- 2.2.3
- 2.2.2
- 2.2.1
- 2.2.0
- 2.1.14
- 2.1.13
- 2.1.12
- 2.1.11
- 2.1.10
- 2.1.9
- 2.1.8
- 2.1.7
- 2.1.6
- 2.1.5
- 2.1.4
- 2.1.3
- 2.1.2
- 2.1.1
- 2.1.0.x-dev
- 2.1.0
- 2.0.x-dev
- 2.0.1
- 2.0.0
- 2.0.0-rc3
- 2.0.0-rc2
- 2.0.0-rc1
- 1.0.x-dev
- 1.0.31
- 1.0.30
- 1.0.29
- 1.0.28
- 1.0.27
- 1.0.26
- 1.0.25
- 1.0.24
- 1.0.23
- 1.0.22
- 1.0.21
- 1.0.20
- 1.0.19
- 1.0.18
- 1.0.17
- 1.0.16
- 1.0.15
- 1.0.14
- 1.0.13
- 1.0.12
- 1.0.11
- 1.0.10
- 1.0.9
- 1.0.8
- 1.0.7
- 1.0.6
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
- dev-getRequiredFields
- dev-repository
This package is auto-updated.
Last update: 2024-09-06 09:17:40 UTC
README
安装
使用 Composer 安装此包
$ composer require nilportugues/json-api
转换器类
给定一个PHP对象和一系列映射,JSON API 转换器将按照http://jsonapi.org
规范表示给定的数据。
例如,给定以下代码片段,定义一篇博客文章和一些评论
$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'), ] ), ] );
以及一系列实现JsonApiMapping
接口的类。
<?php namespace AcmeProject\Infrastructure\Api\Mappings; use NilPortugues\Api\Mappings\JsonApiMapping; class PostMapping implements JsonApiMapping { /** * {@inhertidoc} */ public function getClass() { return \Post::class; } /** * {@inheritdoc} */ public function getAlias() { return 'Message'; } /** * {@inheritdoc} */ public function getAliasedProperties() { return [ 'author' => 'author', 'title' => 'headline', 'content' => 'body', ]; } /** * {@inheritdoc} */ public function getHideProperties(){ return []; } /** * {@inheritdoc} */ public function getIdProperties() { return [ 'postId', ]; } /** * {@inheritdoc} */ public function getUrls() { return [ 'self' => 'http://example.com/posts/{postId}', 'comments' => 'http://example.com/posts/{postId}/comments' ]; } /** * {@inheritdoc} */ public function getRelationships() { return [ 'author' => [ //this key must match with the property or alias of the same name in Post class. 'related' => 'http://example.com/posts/{postId}/author', 'self' => 'http://example.com/posts/{postId}/relationships/author', ] ]; } /** * {@inheritdoc} */ public function getRequiredProperties() { return ['author', 'title', 'body']; } }
<?php namespace AcmeProject\Infrastructure\Api\Mappings; use NilPortugues\Api\Mappings\JsonApiMapping; class PostIdMapping implements JsonApiMapping { /** * {@inhertidoc} */ public function getClass() { return \PostId::class; } /** * {@inheritdoc} */ public function getAlias() { return ''; } /** * {@inheritdoc} */ public function getAliasedProperties() { return [], } /** * {@inheritdoc} */ public function getHideProperties(){ return []; } /** * {@inheritdoc} */ public function getIdProperties() return [ 'postId', ]; } /** * {@inheritdoc} */ public function getUrls() { return [ 'self' => 'http://example.com/posts/{postId}', ]; } /** * {@inheritdoc} */ public function getRelationships() { return [ 'comment' => [ //this key must match with the property or alias of the same name in PostId class. 'self' => 'http://example.com/posts/{postId}/relationships/comments', ], ], ]; } /** * {@inheritdoc} */ public function getRequiredProperties() { return []; } }
<?php namespace AcmeProject\Infrastructure\Api\Mappings; use NilPortugues\Api\Mappings\JsonApiMapping; class UserMapping implements JsonApiMapping { /** * {@inhertidoc} */ public function getClass() { return \User::class; } /** * {@inheritdoc} */ public function getAlias() { return ''; } /** * {@inheritdoc} */ public function getAliasedProperties() { return []; } /** * {@inheritdoc} */ public function getHideProperties(){ return []; } /** * {@inheritdoc} */ public function getIdProperties() return [ 'userId', ]; } /** * {@inheritdoc} */ public function getUrls() { return [ 'self' => 'http://example.com/users/{userId}', 'friends' => 'http://example.com/users/{userId}/friends', 'comments' => 'http://example.com/users/{userId}/comments', ]; } /** * {@inheritdoc} */ public function getRequiredProperties() { return []; } }
<?php namespace AcmeProject\Infrastructure\Api\Mappings; use NilPortugues\Api\Mappings\JsonApiMapping; class UserIdMapping implements JsonApiMapping { /** * {@inhertidoc} */ public function getClass() { return \UserId::class; } /** * {@inheritdoc} */ public function getAlias() { return ''; } /** * {@inheritdoc} */ public function getAliasedProperties() { return []; } /** * {@inheritdoc} */ public function getHideProperties(){ return []; } /** * {@inheritdoc} */ public function getIdProperties() return ['userId']; } /** * {@inheritdoc} */ public function getUrls() { return [ 'self' => 'http://example.com/users/{userId}', 'friends' => 'http://example.com/users/{userId}/friends', 'comments' => 'http://example.com/users/{userId}/comments', ]; } /** * {@inheritdoc} */ public function getRequiredProperties() { return []; } }
<?php namespace AcmeProject\Infrastructure\Api\Mappings; use NilPortugues\Api\Mappings\JsonApiMapping; class CommentMapping implements JsonApiMapping { /** * {@inhertidoc} */ public function getClass() { return \Comment::class; } /** * {@inheritdoc} */ public function getAlias() { return ''; } /** * {@inheritdoc} */ public function getAliasedProperties() { return []; } /** * {@inheritdoc} */ public function getHideProperties(){ return []; } /** * {@inheritdoc} */ public function getIdProperties() return [ 'commentId',]; } /** * {@inheritdoc} */ public function getUrls() { return [ 'self' => 'http://example.com/comments/{commentId}', ]; } /** * {@inheritdoc} */ public function getRelationships() { return [ 'post' => [ //this key must match with the property or alias of the same name in Comment class. 'self' => 'http://example.com/posts/{postId}/relationships/comments', ] ]; } /** * {@inheritdoc} */ public function getRequiredProperties() { return []; } }
<?php namespace AcmeProject\Infrastructure\Api\Mappings; use NilPortugues\Api\Mappings\JsonApiMapping; class CommentId implements JsonApiMapping { /** * {@inhertidoc} */ public function getClass() { return \CommentId::class; } /** * {@inheritdoc} */ public function getAlias() { return ''; } /** * {@inheritdoc} */ public function getAliasedProperties() { return []; } /** * {@inheritdoc} */ public function getHideProperties(){ return []; } /** * {@inheritdoc} */ public function getIdProperties() { return [ 'commentId', ]; } /** * {@inheritdoc} */ public function getUrls() { return [ 'self' => 'http://example.com/comments/{commentId}', ]; } /** * {@inheritdoc} */ public function getRelationships() { return [ 'post' => [ //this key must match with the property or alias of the same name in CommentId class. 'self' => 'http://example.com/posts/{postId}/relationships/comments', ] ]; } /** * {@inheritdoc} */ public function getRequiredProperties() { return []; } }
调用转换器将输出一个使用正确的格式的有效的JSON API响应
<?php use NilPortugues\Api\JsonApi\JsonApiSerializer; use NilPortugues\Api\JsonApi\JsonApiTransformer; use NilPortugues\Api\JsonApi\Http\Message\Response; use NilPortugues\Api\Mapping\Mapper; $mappings = [ \AcmeProject\Infrastructure\Api\Mappings\PostMapping::class, \AcmeProject\Infrastructure\Api\Mappings\PostIdMapping::class, \AcmeProject\Infrastructure\Api\Mappings\UserMapping::class, \AcmeProject\Infrastructure\Api\Mappings\UserIdMapping::class, \AcmeProject\Infrastructure\Api\Mappings\CommentMapping::class, \AcmeProject\Infrastructure\Api\Mappings\CommentId::class, ]; $mapper = new Mapper($mappings); $transformer = new JsonApiTransformer($mapper); $serializer = new JsonApiSerializer($transformer); echo $serializer->serialize($post);
输出(格式化)
{ "data": { "type": "message", "id": "9", "attributes": { "headline": "Hello World", "body": "Your first post" }, "links": { "self": { "href": "http://example.com/posts/9" }, "comments": { "href": "http://example.com/posts/9/comments" } }, "relationships": { "author": { "links": { "self": { "href": "http://example.com/posts/9/relationships/author" }, "related": { "href": "http://example.com/posts/9/author" } }, "data": { "type": "user", "id": "1" } } } }, "included": [ { "type": "user", "id": "1", "attributes": { "name": "Post Author" }, "links": { "self": { "href": "http://example.com/users/1" }, "friends": { "href": "http://example.com/users/1/friends" }, "comments": { "href": "http://example.com/users/1/comments" } } }, { "type": "user", "id": "2", "attributes": { "name": "Barristan Selmy" }, "links": { "self": { "href": "http://example.com/users/2" }, "friends": { "href": "http://example.com/users/2/friends" }, "comments": { "href": "http://example.com/users/2/comments" } } }, { "type": "comment", "id": "1000", "attributes": { "dates": { "created_at": "2015-08-13T21:11:07+02:00", "accepted_at": "2015-08-13T21:46:07+02:00" }, "comment": "Have no fear, sers, your king is safe." }, "relationships": { "user": { "data": { "type": "user", "id": "2" } } }, "links": { "self": { "href": "http://example.com/comments/1000" } } } ], "jsonapi": { "version": "1.0" } }
服务器类
JSON API 请求对象
JSON API附带其请求类,框架无关,实现PSR-7请求接口。
使用此请求对象将为您提供访问JSON API中预期交互的方法。
定义的查询参数
- &fields[resource]=field1,field2将只显示指定资源中的指定字段。
- &include=resource显示给定资源的关联关系。
- &include=resource.resource2显示依赖于resource2的关联字段。
- &sort=field1,-field2按field2降序和field1升序排序。
- &sort=-field1,field2按field1降序和field2升序排序。
- &page[number]将在基于页面的分页策略中返回当前页面的元素。
- &page[size]将在基于页面的分页策略中返回元素的总数。
- &page[limit]将在基于偏移量的分页策略中返回限制数。
- &page[offset]将在基于偏移量的分页策略中返回偏移量值。
- &page[cursor]将在基于游标的分页策略中返回游标值。
- &filter将返回通过过滤器参数传递的数据。
请求对象
给定上述查询参数,请求实现了解析并返回已准备数据的辅助方法。
namespace \NilPortugues\Api\JsonApi\Http\Request; class Request { public function __construct(ServerRequestInterface $request = null) { ... } public function getIncludedRelationships() { ... } public function getSort() { ... } public function getPage() { ... } public function getFilters() { ... } public function getFields() { ... } }
JSON API 响应对象
由于JSON API规范列出一组行为,因此为成功和错误情况提供了特定的响应对象。
成功
NilPortugues\Api\JsonApi\Http\Response\Response
NilPortugues\Api\JsonApi\Http\Response\ResourceUpdated
NilPortugues\Api\JsonApi\Http\Response\ResourceAccepted
NilPortugues\Api\JsonApi\Http\Response\ResourceCreated
NilPortugues\Api\JsonApi\Http\Response\ResourceDeleted
NilPortugues\Api\JsonApi\Http\Response\ResourceProcessing
错误
NilPortugues\Api\JsonApi\Http\Response\BadRequest
NilPortugues\Api\JsonApi\Http\Response\ResourceConflicted
NilPortugues\Api\JsonApi\Http\Response\ResourceNotFound
NilPortugues\Api\JsonApi\Http\Response\TooManyRequests
NilPortugues\Api\JsonApi\Http\Response\UnprocessableEntity
NilPortugues\Api\JsonApi\Http\Response\UnsupportedAction
禁止访问
还可以通过在代码中抛出以下异常来触发Forbidden
响应
NilPortugues\Api\JsonApi\Server\Actions\Exceptions\ForbiddenException
未提供访问控制逻辑。
动作对象
有了请求、响应对象和转换器,自然应该有一组将它们全部结合起来以获得更强大功能的类:动作。
提供以下动作
NilPortugues\Api\JsonApi\Server\Actions\CreateResource
NilPortugues\Api\JsonApi\Server\Actions\DeleteResource
NilPortugues\Api\JsonApi\Server\Actions\GetResource
NilPortugues\Api\JsonApi\Server\Actions\ListResource
NilPortugues\Api\JsonApi\Server\Actions\PatchResource
NilPortugues\Api\JsonApi\Server\Actions\PutResource
所有操作都共享一个 get
方法来运行资源。
这些 get
方法在所有情况下都期望一个或多个 callables
。这样做是为了避免与任何库或接口耦合,并能够扩展它。
质量
要在命令行运行PHPUnit测试,请转到测试目录并运行phpunit。
此库试图遵守PSR-1、PSR-2、PSR-4和PSR-7。
如果您发现遵守上的疏忽,请通过Pull Request发送补丁。
贡献
欢迎对包的贡献!
支持
使用以下方式之一与我联系
- 通过contact@nilportugues.com给我发邮件
- 打开一个问题
- 使用Gitter:
作者
许可证
代码库采用MIT许可证授权。