深入浅析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 相关文章推荐
用javascript实现无刷新更新数据的详细步骤 asp
Dec 26 Javascript
Javascript load Page,load css,load js实现代码
Mar 31 Javascript
jQuery getJSON()+.ashx 实现分页(改进版)
Mar 28 Javascript
用示例说明filter()与find()的用法以及children()与find()的区别分析
Apr 26 Javascript
JavaScript字符串对象的concat方法实例(用于连接两个或多个字符串)
Oct 16 Javascript
详解基于javascript实现的苹果系统底部菜单
Dec 02 Javascript
详解基于webpack搭建react运行环境
Jun 01 Javascript
JavaScript正则表达式校验与递归函数实际应用实例解析
Aug 04 Javascript
jQuery创建及操作xml格式数据示例
May 26 jQuery
Express的HTTP重定向到HTTPS的方法
Jun 06 Javascript
vue-cli3 项目从搭建优化到docker部署的方法
Jan 28 Javascript
Node.js操作MongoDB数据库实例分析
Jan 19 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
星际争霸, 教主第一视角, ZvT经典龙蛇演义
2020/03/02 星际争霸
通过html表格发电子邮件
2006/10/09 PHP
php无限分类且支持输出树状图的详细介绍
2013/06/19 PHP
php中get_meta_tags()、CURL与user-agent用法分析
2014/12/16 PHP
PHP中curl_setopt函数用法实例分析
2015/04/16 PHP
php批量删除操作代码分享
2017/02/26 PHP
PHP+Apache环境中如何隐藏Apache版本
2017/11/24 PHP
JQuery Easyui Tree的oncheck事件实现代码
2010/05/28 Javascript
JavaScript实现找出字符串中第一个不重复的字符
2014/09/03 Javascript
JS获取网页图片name属性的方法
2015/04/01 Javascript
用户代理字符串userAgent可实现的四个识别
2015/09/20 Javascript
Javascript 字符串模板的简单实现
2016/02/13 Javascript
javascript函数命名的三种方式及区别介绍
2016/03/22 Javascript
EasyUI学习之Combobox下拉列表(1)
2016/12/29 Javascript
详解NodeJS框架express的路径映射(路由)功能及控制
2017/03/24 NodeJs
JS判断Android、iOS或浏览器的多种方法(四种方法)
2017/06/29 Javascript
ES6学习教程之对象字面量详解
2017/10/09 Javascript
解决vue-cli webpack打包后加载资源的路径问题
2018/09/25 Javascript
Vue 页面状态保持页面间数据传输的一种方法(推荐)
2018/11/01 Javascript
vue商城中商品“筛选器”功能的实现代码
2020/07/01 Javascript
jQuery实现B2B网站后台管理系统侧导航
2020/07/08 jQuery
[02:32]DOTA2亚洲邀请赛 VG战队巡礼
2015/02/03 DOTA
[36:05]DOTA2亚洲邀请赛 3.31 小组赛 A组 Liquid vs Optic
2018/04/01 DOTA
python里将list中元素依次向前移动一位
2014/09/12 Python
Python基于PyGraphics包实现图片截取功能的方法
2017/12/21 Python
Python实现的本地文件搜索功能示例【测试可用】
2018/05/30 Python
Python内存读写操作示例
2018/07/18 Python
使用 Python ssh 远程登陆服务器的最佳方案
2020/03/06 Python
Pycharm内置终端及远程SSH工具的使用教程图文详解
2020/03/19 Python
Python学习笔记之装饰器
2020/08/06 Python
python 利用openpyxl读取Excel表格中指定的行或列教程
2021/02/06 Python
Python使用tkinter制作在线翻译软件
2021/02/22 Python
美国在线眼镜店:GlassesShop
2018/11/15 全球购物
《都江堰》教学反思
2014/02/07 职场文书
交通安全标语
2014/06/06 职场文书
python 如何将两个实数矩阵合并为一个复数矩阵
2021/05/19 Python