深入浅析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 相关文章推荐
js判断一个元素是否为另一个元素的子元素的代码
Mar 21 Javascript
js中AppendChild与insertBefore的用法详细解析
Dec 16 Javascript
js实现TAB切换对应不同颜色的代码
Aug 31 Javascript
js调用百度地图及调用百度地图的搜索功能
Sep 07 Javascript
JS使用正则表达式除去字符串中重复字符的方法
Nov 05 Javascript
javascript日期验证之输入日期大于等于当前日期
Dec 13 Javascript
jQuery中事件与动画的总结分享
May 24 Javascript
JQueryEasyUI框架下的combobox的取值和绑定的方法
Jan 22 Javascript
js实现图片上传预览原理分析
Jul 13 Javascript
vue使用better-scroll实现下拉刷新、上拉加载
Nov 23 Javascript
Vue多组件仓库开发与发布详解
Feb 28 Javascript
解决微信小程序scroll-view组件无横向滚动的问题
Feb 04 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/03 冲泡冲煮
PHP超全局数组(Superglobals)介绍
2015/07/01 PHP
PHP导入导出Excel代码
2015/07/07 PHP
php将文件夹打包成zip文件的简单实现方法
2016/10/04 PHP
激活 ActiveX 控件
2006/10/09 Javascript
jquery dialog键盘事件代码
2010/08/01 Javascript
JS图片浏览组件PhotoLook的公开属性方法介绍和进阶实例代码
2010/11/09 Javascript
解决IE6的PNG透明JS插件使用介绍
2013/04/17 Javascript
浅谈JavaScript函数参数的可修改性问题
2013/12/05 Javascript
javascript替换已有元素replaceChild()使用介绍
2014/04/03 Javascript
angularjs中的e2e测试实例
2014/12/06 Javascript
jQuery实现复选框成对选择及对应取消的方法
2015/03/03 Javascript
javascript实现动态标签云
2015/10/16 Javascript
jQuery中的each()详细介绍(推荐)
2016/05/25 Javascript
JavaScript中style.left与offsetLeft的使用及区别详解
2016/06/08 Javascript
巧用weui.topTips验证数据的实例
2017/04/17 Javascript
详解用webpack2搭建angular2的项目
2017/06/22 Javascript
JavaScript使用享元模式实现文件上传优化操作示例
2018/08/07 Javascript
Vue-Quill-Editor富文本编辑器的使用教程
2018/09/21 Javascript
在微信小程序中使用图表的方法示例
2019/04/25 Javascript
nodejs实现日志读取、日志查找及日志刷新的方法分析
2019/05/20 NodeJs
JS实现的排列组合算法示例
2019/07/16 Javascript
vue组件内部引入外部js文件的方法
2020/01/18 Javascript
python实现二维码扫码自动登录淘宝
2016/12/27 Python
利用Python2下载单张图片与爬取网页图片实例代码
2017/12/25 Python
Django中使用celery完成异步任务的示例代码
2018/01/23 Python
Django unittest 设置跳过某些case的方法
2018/12/26 Python
Python利用字典破解WIFI密码的方法
2019/02/27 Python
python redis 批量设置过期key过程解析
2019/11/26 Python
Python读入mnist二进制图像文件并显示实例
2020/04/24 Python
Python使用tkinter制作在线翻译软件
2021/02/22 Python
size?法国官网:英国伦敦的球鞋精品店
2020/03/15 全球购物
数控技术专业推荐信
2013/11/01 职场文书
酒店管理失职检讨书
2014/09/16 职场文书
离婚协议书怎么写2014
2014/09/30 职场文书
幼儿园教师工作总结2015
2015/04/02 职场文书