谈谈JSONAPI在PHP中的应用

栏目: PHP · 发布时间: 8年前

内容简介:谈谈JSONAPI在PHP中的应用

现在服务端 程序员 的主要工作已经不再是套模版,而是编写基于 JSON 的 API 接口。可惜大家编写接口的风格往往迥异,这就给系统集成带来了很多不必要的沟通成本,如果你有类似的困扰,那么不妨关注一下 JSONAPI ,它是一个基于 JSON 构建 API 的规范标准,一个简单的 API 接口大致如下所示:

谈谈JSONAPI在 <a href='https://www.codercto.com/topics/18749.html'>PHP</a> 中的应用

JSONAPI

简单说明一下:根节点中的 data 用来放置主对象的内容,其中 type 和 id 是必须要有的字段,用来表示主对象的类型和标识,其它简单的属性统统放置到 attributes 里,如果主对象存在一对一、一对多等关联对象,那么放置到 relationships 里,不过只是通过 type 和 id 字段放置一个链接,关联对象的实际内容统统放置在根接点中的 included 里。

有了 JSONAPI,数据解析的过程变得规范起来,节省了不必要的沟通成本。不过如果要手动构建 JSONAPI 数据还是很麻烦的,好在通过使用 Fractal 可以让实现过程相对自动化一些,上面的例子如果用 Fractal 实现大概是这个样子:

<?php

use League\Fractal\Manager;
use League\Fractal\Resource\Collection;

$articles = [
    [
        'id' => 1,
        'title' => 'JSON API paints my bikeshed!',
        'body' => 'The shortest article. Ever.',
        'author' => [
            'id' => 42,
            'name' => 'John',
        ],
    ],
];

$manager = new Manager();

$resource = new Collection($articles, new ArticleTransformer());

$manager->parseIncludes('author');

$manager->createData($resource)->toArray();

?>

如果让我选最喜爱的 PHP 工具包,Fractal 一定榜上有名,它隐藏了实现细节,让使用者完全不必了解 JSONAPI 协议即可上手。不过如果你想在自己的项目里使用的话,与直接使用 Fractal 相比,可以试试 Fractalistic ,它对 Fractal 进行了封装,使其更好用:

<?php

Fractal::create()
   ->collection($articles)
   ->transformWith(new ArticleTransformer())
   ->includeAuthor()
   ->toArray();

?>

如果你是裸写 PHP 的话,那么 Fractalistic 基本就是最佳选择了,不过如果你使用了一些全栈框架的话,那么 Fractalistic 可能还不够优雅,因为它无法和框架本身已有的功能更完美的融合,以 Lavaral 为例,它本身内置了一个 API Resources 功能,在此基础上我实现了一个 JsonApiSerializer,可以和框架完美融合,代码如下:

<?php

namespace App\Http\Serializers;

use Illuminate\Http\Resources\MissingValue;
use Illuminate\Http\Resources\Json\Resource;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Pagination\AbstractPaginator;

class JsonApiSerializer implements \JsonSerializable
{
    protected $resource;
    protected $resourceValue;

    protected $data = [];

    protected static $included = [];

    public function __construct($resource, $resourceValue)
    {
        $this->resource = $resource;
        $this->resourceValue = $resourceValue;
    }

    public function jsonSerialize()
    {
        foreach ($this->resourceValue as $key => $value) {
            if ($value instanceof Resource) {
                $this->serializeResource($key, $value);
            } else {
                $this->serializeNonResource($key, $value);
            }
        }

        if (!$this->isRootResource()) {
            return $this->data;
        }

        $result = [
            'data' => $this->data,
        ];

        if (static::$included) {
            $result['included'] = static::$included;
        }

        if (!$this->resource->resource instanceof AbstractPaginator) {
            return $result;
        }

        $paginated = $this->resource->resource->toArray();

        $result['links'] = $this->links($paginated);
        $result['meta'] = $this->meta($paginated);

        return $result;
    }

    protected function serializeResource($key, $value, $type = null)
    {
        if ($type === null) {
            $type = $key;
        }

        if ($value->resource instanceof MissingValue) {
            return;
        }

        if ($value instanceof ResourceCollection) {
            foreach ($value as $k => $v) {
                $this->serializeResource($k, $v, $type);
            }
        } elseif (is_string($type)) {
            $included = $value->resolve();

            $data = [
                'type' => $included['type'],
                'id' => $included['id'],
            ];

            if (is_int($key)) {
                $this->data['relationships'][$type]['data'][] = $data;
            } else {
                $this->data['relationships'][$type]['data'] = $data;
            }

            static::$included[] = $included;
        } else {
            $this->data[] = $value->resolve();
        }
    }

    protected function serializeNonResource($key, $value)
    {
        switch ($key) {
            case 'id':
                $value = (string)$value;
            case 'type':
            case 'links':
                $this->data[$key] = $value;
                break;
            default:
                $this->data['attributes'][$key] = $value;
        }
    }

    protected function links($paginated)
    {
        return [
            'first' => $paginated['first_page_url'] ?? null,
            'last' => $paginated['last_page_url'] ?? null,
            'prev' => $paginated['prev_page_url'] ?? null,
            'next' => $paginated['next_page_url'] ?? null,
        ];
    }

    protected function meta($paginated)
    {
        return [
            'current_page' => $paginated['current_page'] ?? null,
            'from' => $paginated['from'] ?? null,
            'last_page' => $paginated['last_page'] ?? null,
            'per_page' => $paginated['per_page'] ?? null,
            'to' => $paginated['to'] ?? null,
            'total' => $paginated['total'] ?? null,
        ];
    }

    protected function isRootResource()
    {
        return isset($this->resource->isRoot) && $this->resource->isRoot;
    }
}

?>

对应的 Resource 基本还和以前一样,只是返回值改了一下:

<?php

namespace App\Http\Resources;

use App\Article;
use Illuminate\Http\Resources\Json\Resource;
use App\Http\Serializers\JsonApiSerializer;

class ArticleResource extends Resource
{
    public function toArray($request)
    {
        $value = [
            'type' => 'articles',
            'id' => $this->id,
            'name' => $this->name,
            'author' => $this->whenLoaded('author'),
        ];

        return new JsonApiSerializer($this, $value);
    }
}

?>

对应的 Controller 也和原来差不多,只是加入了一个 isRoot 属性,用来识别根:

<?php

namespace App\Http\Controllers;

use App\Article;
use App\Http\Resources\ArticleResource;

class ArticleController extends Controller
{
    protected $article;

    public function __construct(Article $article)
    {
        $this->article = $article;
    }

    public function show($id)
    {
        $article = $this->article->with('author')->findOrFail($id);

        $resource = new ArticleResource($article);
        $resource->isRoot = true;

        return $resource;
    }
}

?>

整个过程没有对 Laravel 的架构进行太大的侵入,可以说是目前 Laravel 实现 JSONAPI 的最优解决方案了,有兴趣的可以研究一下 JsonApiSerializer 的实现,虽然只有一百多行代码,但是我却费了好大的力气才实现,可以说是行行皆辛苦啊。


以上所述就是小编给大家介绍的《谈谈JSONAPI在PHP中的应用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Convergence Culture

Convergence Culture

Henry Jenkins / NYU Press / 2006-08-01 / USD 30.00

"Convergence Culture" maps a new territory: where old and new media intersect, where grassroots and corporate media collide, where the power of the media producer, and the power of the consumer intera......一起来看看 《Convergence Culture》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具