深入浅析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 相关文章推荐
IE6图片加载的一个BUG解决方法
Jul 13 Javascript
关于UTF-8的客户端用AJAX方式获取GB2312的服务器端乱码问题的解决办法
Nov 30 Javascript
jquery 年会抽奖程序
Dec 22 Javascript
javascript完美拖拽的实现方法
Sep 29 Javascript
解读Bootstrap v4 sass设计
May 29 Javascript
jQuery增加和删除表格项目及实现表格项目排序的方法
May 30 Javascript
Jil,高效的json序列化和反序列化库
Feb 15 Javascript
vue.js实现单选框、复选框和下拉框示例
Jul 18 Javascript
JS+HTML5实现图片在线预览功能
Jul 22 Javascript
Vue CLI3中使用compass normalize的方法
May 30 Javascript
在Vue中创建可重用的 Transition的方法
Jun 02 Javascript
vue实现购物车结算功能
Jun 18 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
无线电波是什么?它是怎样传输的?
2021/03/01 无线电
PHP命令行脚本接收传入参数的三种方式
2014/08/20 PHP
wampserver改变默认网站目录的办法
2015/08/05 PHP
PHP常见漏洞攻击分析
2016/02/21 PHP
浅谈PHP定义命令空间的几个注意点(推荐)
2016/10/29 PHP
ubutu 16.04环境下,PHP与mysql数据库,网页登录验证实例讲解
2017/07/20 PHP
JQuery AJAX提交中文乱码的解决方案
2010/07/02 Javascript
分享14个很酷的jQuery导航菜单插件
2011/04/25 Javascript
js两行代码按指定格式输出日期时间
2011/10/21 Javascript
让你的博客飘雪花超出屏幕依然看得见
2013/01/04 Javascript
『JavaScript』限制Input只能输入数字实现思路及代码
2013/04/22 Javascript
jQuery toggleClass应用实例(附效果图)
2014/04/06 Javascript
node.js中的fs.unlink方法使用说明
2014/12/15 Javascript
JS制作简单的三级联动
2015/03/18 Javascript
JavaScript中计算网页中某个元素的位置
2015/06/10 Javascript
关于js原型的面试题讲解
2016/09/25 Javascript
javascript数据类型详解
2017/02/07 Javascript
vue 请求后台数据的实例代码
2017/06/22 Javascript
webpack3之loader全解析
2017/10/26 Javascript
JavaScript程序设计高级算法之动态规划实例分析
2017/11/24 Javascript
EasyUI的DataGrid绑定Json数据源的示例代码
2017/12/16 Javascript
JavaScript中this关键字用法实例分析
2018/08/24 Javascript
Javascript读写cookie的实例源码
2019/03/16 Javascript
[02:41]DOTA2英雄基础教程 亚巴顿
2014/01/02 DOTA
Python用csv写入文件_消除空余行的方法
2018/07/06 Python
浅谈dataframe中更改列属性的方法
2018/07/10 Python
Python中使用__new__实现单例模式并解析
2019/06/25 Python
win8.1安装Python 2.7版环境图文详解
2019/07/01 Python
面向对象学习之pygame坦克大战
2019/09/11 Python
150行python代码实现贪吃蛇游戏
2020/04/24 Python
使用Python获取爱奇艺电视剧弹幕数据的示例代码
2021/01/12 Python
Canvas与Image互相转换示例代码
2013/08/09 HTML / CSS
Java多态性的定义以及类型
2014/09/16 面试题
应届生煤化工求职信
2013/10/21 职场文书
秋季运动会通讯稿
2014/01/24 职场文书
幼儿园开学温馨提示
2015/07/15 职场文书