nilportugues / json-api
序列化转换器,输出有效的API响应,格式为JSON、JSON API和HAL+JSON API。
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 转换器将按照https://jsonapi.fullstack.org.cn规范表示给定的数据。
例如,给定以下代码片段,定义一篇博客文章和一些评论
$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\ResponseNilPortugues\Api\JsonApi\Http\Response\ResourceUpdatedNilPortugues\Api\JsonApi\Http\Response\ResourceAcceptedNilPortugues\Api\JsonApi\Http\Response\ResourceCreatedNilPortugues\Api\JsonApi\Http\Response\ResourceDeletedNilPortugues\Api\JsonApi\Http\Response\ResourceProcessing
错误
NilPortugues\Api\JsonApi\Http\Response\BadRequestNilPortugues\Api\JsonApi\Http\Response\ResourceConflictedNilPortugues\Api\JsonApi\Http\Response\ResourceNotFoundNilPortugues\Api\JsonApi\Http\Response\TooManyRequestsNilPortugues\Api\JsonApi\Http\Response\UnprocessableEntityNilPortugues\Api\JsonApi\Http\Response\UnsupportedAction
禁止访问
还可以通过在代码中抛出以下异常来触发Forbidden响应
NilPortugues\Api\JsonApi\Server\Actions\Exceptions\ForbiddenException
未提供访问控制逻辑。
动作对象
有了请求、响应对象和转换器,自然应该有一组将它们全部结合起来以获得更强大功能的类:动作。
提供以下动作
NilPortugues\Api\JsonApi\Server\Actions\CreateResourceNilPortugues\Api\JsonApi\Server\Actions\DeleteResourceNilPortugues\Api\JsonApi\Server\Actions\GetResourceNilPortugues\Api\JsonApi\Server\Actions\ListResourceNilPortugues\Api\JsonApi\Server\Actions\PatchResourceNilPortugues\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许可证授权。