hackerboy/json-api

PHP JSON API 实现方案(服务器端)

2.1.8 2022-03-17 08:26 UTC

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);

创建其他元素

它将以相同的方式工作,可用方法有

您可以在 /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();