深入浅析JSONAPI在PHP中的应用


Posted in Javascript onDecember 24, 2017

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

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中的应用,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

Javascript 相关文章推荐
use jscript Create a SQL Server database
Jun 16 Javascript
JavaScript在IE中“意外地调用了方法或属性访问”
Nov 19 Javascript
围观tangram js库
Dec 28 Javascript
jquery+css+ul模拟列表菜单具体实现思路
Apr 15 Javascript
Javascript核心读书有感之语言核心
Feb 01 Javascript
JavaScript三元运算符的多种使用技巧
Apr 16 Javascript
JavaScript的instanceof运算符学习教程
Jun 08 Javascript
js实现颜色阶梯渐变效果(Gradient算法)
Mar 21 Javascript
原生JavaScript实现精美的淘宝轮播图效果示例【附demo源码下载】
May 27 Javascript
详解Webpack如何引入CDN链接来优化编译后的体积
Jun 21 Javascript
js中比较两个对象是否相同的方法示例
Sep 02 Javascript
JavaScript实现轮播图效果代码实例
Sep 28 Javascript
Parcel.js + Vue 2.x 极速零配置打包体验教程
Dec 24 #Javascript
jquery中ajax请求后台数据成功后既不执行success也不执行error的完美解决方法
Dec 24 #jQuery
解决Vue 浏览器后退无法触发beforeRouteLeave的问题
Dec 24 #Javascript
通过fastclick源码分析彻底解决tap“点透”
Dec 24 #Javascript
anime.js 实现带有描边动画效果的复选框(推荐)
Dec 24 #Javascript
vue项目常用组件和框架结构介绍
Dec 24 #Javascript
JavaScript数组排序reverse()和sort()方法详解
Dec 24 #Javascript
You might like
php 用sock技术发送邮件的函数
2007/07/21 PHP
使用openssl实现rsa非对称加密算法示例
2014/01/24 PHP
php调用C代码的实现方法
2014/03/11 PHP
PHP图像处理之使用imagecolorallocate()函数设置颜色例子
2014/11/19 PHP
基于thinkPHP框架实现留言板的方法
2016/10/17 PHP
PHP实现上传图片到数据库并显示输出的方法
2018/05/31 PHP
把textarea中字符串里含有的回车换行替换成&amp;lt;br&amp;gt;的javascript代码
2007/04/20 Javascript
利用js对象弹出一个层
2008/03/26 Javascript
js sort 二维数组排序的用法小结
2014/01/24 Javascript
Javascript冒泡排序算法详解
2014/12/03 Javascript
javascript图片滑动效果实现
2021/01/28 Javascript
JavaScript生成二维码图片小结
2015/12/27 Javascript
WordPress 单页面上一页下一页的实现方法【附代码】
2016/03/10 Javascript
超漂亮的Bootstrap 富文本编辑器summernote
2016/04/05 Javascript
JS作用域深度解析
2016/12/29 Javascript
Vue ElementUI之Form表单验证遇到的问题
2017/08/21 Javascript
JavaScript实现带有子菜单和控件的slider轮播图效果
2017/11/01 Javascript
node实现爬虫的几种简易方式
2019/08/22 Javascript
pyqt4教程之messagebox使用示例分享
2014/03/07 Python
TensorFlow如何实现反向传播
2018/02/06 Python
python程序快速缩进多行代码方法总结
2019/06/23 Python
python join方法使用详解
2019/07/30 Python
python sorted函数原理解析及练习
2020/02/10 Python
python 计算概率密度、累计分布、逆函数的例子
2020/02/25 Python
PyCharm上安装Package的实现(以pandas为例)
2020/09/18 Python
Python 中 sorted 如何自定义比较逻辑
2021/02/02 Python
处理textarea中的换行和空格
2019/12/12 HTML / CSS
英国外籍人士的在线超市:British Corner Shop
2019/06/03 全球购物
杠杆的科学教学反思
2014/01/10 职场文书
小学生红领巾广播稿
2014/01/21 职场文书
行政求职信
2014/07/04 职场文书
升学宴演讲稿
2014/09/01 职场文书
党的群众路线教育实践活动总结材料
2014/10/30 职场文书
2016年“世界气象日”广播稿
2015/12/17 职场文书
2019个人工作态度自我评价
2019/04/24 职场文书
详解JS WebSocket断开原因和心跳机制
2021/05/07 Javascript