hackerboy / json-api
PHP JSON API 实现方案(服务器端)
Requires
- php: >=7.1.0
- art4/json-api-client: ^1.0
- illuminate/support: ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0
Requires (Dev)
- phpunit/phpunit: ^8.3 || ^9.0
README
Packagist: hackerboy/json-api
让您轻松实现 JSON API 实现方案(服务器端)
安装
composer require hackerboy/json-api
运行示例
- 示例代码:/example/index.php
- 设置 /examples/index.php 以查看使用示例的指南。
git clone https://github.com/hackerboydotcom/json-api ./hackerboy-json-api;
cd hackerboy-json-api;
composer install --dev;
然后配置您本地的 nginx/apache 以访问 [LOCALHOST_PATH]/examples/index.php
目录
如何使用?
创建您的资源模式
通过创建模式类将您的模型对象中的 id、类型、属性映射。在模式类内部,您可以通过 $this->model
获取您的模型对象。例如,我将以 Laravel 项目为例。首先,让我们创建一个资源文件:/app/Http/JsonApiResources/UserResource.php
<?php
namespace App\Http\JsonApiResources;
use HackerBoy\JsonApi\Abstracts\Resource;
class UserResource extends Resource {
protected $type = 'users';
public function getId()
{
// $this->model is the instance of model, in this case, it's App\User
return $this->model->id;
}
public function getAttributes()
{
return [
'name' => $this->model->name,
'email' => $this->model->email
];
}
/**
* Meta is optional
*/
public function getMeta()
{
return [
'meta-is-optional' => $this->model->some_value
];
}
}
配置和映射您的资源
现在我们可以轻松地生成一个 JSON API 文档对象,如下所示
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use HackerBoy\JsonApi\Document;
class UserController extends Controller {
public function index()
{
// User to return
$user = \App\User::find(1);
$users = \App\User::take(10)->get();
// Config and mapping
$config = [
'resource_map' => [
\App\User::class => \App\Http\JsonApiResources\UserResource::class
// Map your other model => resource
],
'api_url' => 'http://example.com',
'auto_set_links' => true, // Enable this will automatically add links to your document according to JSON API standard
];
// Let's test it
$document = new Document($config);
$document->setData($user) // or set data as a collection by using ->setData($users)
->setMeta(['key' => 'value']);
return response()->json($document)->header('Content-Type', 'application/vnd.api+json');
}
}
文档方法
"设置方法"和"添加方法"之间的区别是:"设置方法"将覆盖数据,而"添加方法"将追加到数据。
从 $document 对象中可用的"设置"方法
- setData($resourceOrCollection, $type = 'resource') // 默认 $type = 'resource',如果您需要将数据作为关系对象返回,则将 $type 更改为 'relationship'。或者您可以使用下面的 setDocumentType() 方法
- setDocumentType($type) // $type = "resource" 或 "relationship"。
- setIncluded($resourceOrCollection)
- setErrors($errors) // 数组或 HackerBoy\JsonApi\Elements\Error 对象 - 单个错误或多条错误数据均可用于此方法
- setLinks($links) // 链接数据的数组或 HackerBoy\JsonApi\Elements\Links 对象
- setMeta($meta) // 元数据数组或 HackerBoy\JsonApi\Elements\Meta 对象
从 $document 对象中可用的"获取"方法
- getQuery() // 获取 查询 对象以查找资源
- getConfig() // 获取文档配置
- getData() // 获取文档数据
- getIncluded() // 获取文档包含数据
- getErrors() // 获取文档错误数据
- getMeta() // 获取文档元数据
- getLinks() // 获取文档链接
- getUrl($path = '') // 获取 API URL
- getResourceInstance($modelObject) // 获取模型对象的资源实例
从 $document 对象中可用的"添加"方法
- addIncluded($resourceOrCollection)
- addErrors($errors) // 数组或 HackerBoy\JsonApi\Elements\Error 对象 - 单个错误或多条错误数据均可用于此方法
- addLinks($links) // 链接数据的数组或 HackerBoy\JsonApi\Elements\Links 对象
- addMeta($meta) // 元数据数组或 HackerBoy\JsonApi\Elements\Meta 对象
示例
<?php
$document->setData([$post1, $post2]) // or ->setData($post) will also work
->setIncluded([$comment1, $comment2])
->setMeta([
'meta-key' => 'meta-value',
'meta-key-2' => 'value 2'
])
->setLinks($document->makePagination([
'first' => $document->getUrl('first-link'),
'last' => $document->getUrl('last-link'),
'prev' => $document->getUrl('prev-link'),
'next' => $document->getUrl('last-link'),
]));
// Get document data
$documentData = $document->getData();
实现关系
在 getRelationships() 方法中简单地返回一个数组
<?php
namespace HackerBoy\JsonApi\Examples\Resources;
use HackerBoy\JsonApi\Abstracts\Resource;
class PostResource extends Resource {
protected $type = 'posts';
public function getId()
{
return $this->model->id;
}
public function getAttributes()
{
return [
'title' => $this->model->title,
'content' => $this->model->content
];
}
public function getRelationships()
{
$relationships = [
'author' => $this->model->author, // Post has relationship with author
// If post has comments, return a collection
// Not? Return a blank array (implement empty to-many relationship)
'comments' => $this->model->comments ? $this->model->comments : []
];
return $relationships;
}
}
将数据作为关系设置
第二个参数允许您将数据设置为关系(对于请求如:/api/posts/1/relationships/comments)
<?php
// Set data as relationship
$document->setData($resourceOrCollection, 'relationship');
// Or
$document->setData($resourceOrCollection);
$document->setDocumentType('relationship');
toArray() 和 toJson() 方法
v1.1 中新增的方法。可用于文档、元素和资源
<?php
$data = $document->toArray();
$json = $document->toJson();
轻松创建文档元素
假设我们创建了一个 $document 对象
创建错误
<?php
// Create an error
$errorData = [
'id' => '123',
'status' => '500',
'code' => '456',
'title' => 'Test error'
];
// Return an error
$error = $document->makeError($errorData);
// Return multiple errors
$errors = [$document->makeError($errorData), $document->makeError($errorData)];
// Attach error to document
$document->setErrors($error);
// Or
$document->setErrors($errors);
// It'll even work if you just put in an array data
$document->setErrors($errorData);
创建链接
<?php
$linkData = [
'self' => $document->getUrl('self-url'),
'ralated' => $document->getUrl('related-url')
];
// Create links
$links = $document->makeLinks($linkData);
// Attach links to document
$document->setLinks($links);
// this will also work
$document->setLinks($linkData);
// Create pagination
$pagination = $document->makePagination([
'first' => $document->getUrl('first-link'),
'last' => $document->getUrl('last-link'),
'prev' => $document->getUrl('prev-link'),
'next' => $document->getUrl('last-link'),
]);
// Attach pagination to document
$document->setLinks($pagination);
创建其他元素
它将以相同的方式工作,可用方法有
- makeError(),
- makeErrorResource()
- makeLink()(在 links 数据内部创建链接对象: https://jsonapi.fullstack.org.cn/format/#document-links)
- makeLinks()
- makePagination()(创建需要分页标准的特殊 Links 对象)
- makeMeta()
- makeRelationship()(在 relationships 数据内部创建关系对象: https://jsonapi.fullstack.org.cn/format/#document-resource-object-relationships)
- makeRelationships()
您可以在 /examples/index.php 中查看更多示例
文档查询
在 v2 中的新功能,现在您可以使用 $document->getQuery()
方法对文档进行查询以找到资源(HackerBoy\JsonApi\Abstracts\Resource)。$document->getQuery()
返回 Laravel Illuminate\Support\Collection 的实例(查看链接以获取查询方法的完整文档)。
查询返回的对象是 \HackerBoy\JsonApi\Abstracts\Resource
文档查询示例
<?php
$findResourceById = $document->getQuery()->where('type', '...')->where('id', '...')->first();
$findResourceByAttributes = $document->getQuery()->where('attributes.title', '...')->first();
客户端使用
灵活的文档和资源
- 灵活的文档可以像正常文档一样使用,但 $config 是可选的,灵活的资源被允许... 您可以将它视为文档的“自由模式”版本。
- 灵活的文档可以使用与正常文档相同的 $config,然后您可以使用它来处理同一文档中的灵活资源和映射资源。
- 对于没有 ORM 的项目,灵活的文档可能非常有用,无需配置即可快速构建 JSON API 数据,将 JSON API 数据 POST 到另一个 JSON API 端点...
- 无论如何,不推荐使用灵活的文档,因为它允许以自由的方式构建文档,可能会对您的 API 产生不可预测的错误,例如缺少元素、无效格式等... 因此请谨慎和明智地使用
灵活文档的示例
<?php
$flexibleDocument = new HackerBoy\JsonApi\Flexible\Document; // $config is the same format with normal document but it is optional
// Create a flexible resource
$flexibleResource = $flexibleDocument->makeFlexibleResource();
$flexibleResource->setId(1234);
->setType('flexible')
->setAttributes([
'attribute_name' => 'attribute value'
])
->setMeta([
'meta-key' => 'meta value'
])
->setLinks([
'self' => '/link'
]);
// Or with faster way to set type and id
$flexibleResource = $flexibleDocument->makeFlexibleResource('resource-type', 'resource-id');
// Attach flexible resource to document
$flexibleDocument->setData($flexibleResource); // You can put in a collection as well, all other methods are the same
echo $flexibleDocument->toJson();
从字符串或数组解析
从字符串解析
<?php
use HackerBoy\JsonApi\Helpers\Validator;
$jsonapiString = '{
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "Rails is Omakase"
},
"relationships": {
"author": {
"links": {
"self": "/articles/1/relationships/author",
"related": "/articles/1/author"
},
"data": { "type": "people", "id": "9" }
},
"images": {
"data": [
{
"type": "images",
"id": "1"
},
{
"type": "images",
"id": "2"
}
]
}
}
},
"included": [
{
"type": "people",
"id": "1",
"attributes": {
"name": "John Doe"
}
},
{
"type": "images",
"id": "1",
"attributes": {
"src": "http://example.com/1.jpg"
}
},
{
"type": "images",
"id": "2",
"attributes": {
"src": "http://example.com/2.jpg"
}
}
]
}';
// Helper method to validate request JSON:API string
if (!Validator::isValidRequestString($jsonapiString)) {
throw new Exception('Invalid request string');
}
// Helper method to validate response JSON:API string
if (!Validator::isValidResponseString($jsonapiString)) {
throw new Exception('Invalid response string');
}
$document = FlexibleDocument::parseFromString($jsonapiString);
// Get primary data
$article = $document->getData();
// Get article's images and author
$articleAuthor = $article->getRelationshipData('author');
$articleImages = $article->getRelationshipData('images');
// Find the people resource
$peopleResource = $document->getQuery()->where(['type' => 'people', 'id' => '1'])->first();
if ($articleAuthor === $peopleResource) {
echo 'Great! They are the same';
}
// Get data
echo $peopleResource->getId(); // Expect '1'
echo $peopleResource->getType(); // Expect 'people'
echo $peopleResource->getAttribute('name'); // Expect 'John Doe'
echo $peopleResource->getAttribute('not-found-attribute', 'Default value'); // Expect 'Default value'
var_dump($peopleResource->getAttributes()); // Expect ['name' => 'John Doe']
// Modify resource data
$peopleResource->setAttribute('email', 'example@example.com');
// Check document after modification
echo $document->toJson();