现在服务端程序员的主要工作已经不再是套模版,而是编写基于 JSON 的 API 接口。可惜大家编写接口的风格往往迥异,这就给系统集成带来了很多不必要的沟通成本,如果你有类似的困扰,那么不妨关注一下 JSONAPI,它是一个基于 JSON 构建 API 的规范标准,一个简单的 API 接口大致如下所示:
![](https://box.kancloud.cn/eb9bd477ea2ca15120c880b439fab53b_713x696.jpeg)
简单说明一下:根节点中的 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 $value;
protected $isRoot;
protected $data = [];
protected static $included = [];
public function __construct($value)
{
static $count = 0;
$this->value = $value;
$this->isRoot = (++$count == 1);
}
public function jsonSerialize()
{
foreach ($this->value as $key => $value) {
if ($value instanceof Resource) {
$this->serializeResource($key, $value);
} else {
$this->serializeNonResource($key, $value);
}
}
if (!$this->isRoot) {
return $this->data;
}
$result = [
'data' => $this->data,
];
if (static::$included) {
$result['included'] = static::$included;
}
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;
}
}
}
?>
~~~
对应的 Resource 基本还和以前一样,只是返回值改了一下:
~~~
<?php
namespace App\Http\Resources;
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' => new AuthorResource($this->whenLoaded('author')),
];
return new JsonApiSerializer($value);
}
}
?>
对应的 Controller 也和原来差不多:
<?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->additional(['meta' => [...]]);
return $resource;
}
}
?>
~~~
整个过程没有对 Laravel 的架构进行太大的侵入,可以说是目前 Laravel 实现 JSONAPI 的最优解决方案了,有兴趣的可以研究一下 JsonApiSerializer 的实现,虽然只有一百多行代码,但是我却费了好大的力气才实现,可以说是行行皆辛苦啊。
- PHP发表心情投票功能示例(附源码)
- TP5验证码实现
- 谈谈JSONAPI在PHP中的应用
- Laravel artisan optimize 源码解读
- PHP中的闭包和匿名函数
- 65条最常用正则表达式,你要的都在这里了
- PHP 断点续传实例详解
- 从配置文件的角度去了解Yii2
- 高效的PHP邮件发送库:Swiftmailer
- 对于php-fpm和cgi,还有并发响应的理解
- 详解 Cookie 纪要
- Cookie详解
- Cookie格式
- Cookie的创建
- Cookie 基础知识*
- Cookie的使用
- Cookie的基本操作
- Cookie的域概念
- Session详解
- session与cookie的区别
- Cookie与Session问答
- php如何解决中文乱码问题?
- 微信小程序 PHP生成带参数二维码
- PHP实现QQ快速登录
- mysql 队列 实现并发读
- php+redis消息队列实现抢购功能
- js购物车实现思路及代码(个人感觉不错)
- PHP curl 抓取AJAX异步内容示例
- PHP curl 并发最佳实践代码分享
- php 字符串中是否包含指定字符串的多种方法
- PHP7如何开启Opcode打造强悍性能详解
- PHP实现用户登录的案例代码
- yii2多图上传组件的使用教程
- PHP数组去重的更快实现方式分析
- 购物车实现的几种方式优缺点对比
- laravel项目利用twemproxy部署redis集群的完整步骤
- PHP+memcache实现消息队列案例分享
- PHP CURL CURLOPT参数说明
- php实现可以设置中奖概率的抽奖程序代码分享
- 基于在生产环境中使用php性能测试工具xhprof的详解
- 一个PHP并发访问实例代码
- php解决抢购秒杀抽奖等大流量并发入库导致的库存负数的问题
- PHP设计模式之工厂模式定义与用法详解