深入浅析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 相关文章推荐
一个基于jquery的图片切换效果
Jul 06 Javascript
javascript中方便增删改cookie的一个类
Oct 11 Javascript
JQuery触发事件例如click
Sep 11 Javascript
node.js使用nodemailer发送邮件实例
Mar 10 Javascript
jQuery打印指定区域Html页面并自动分页
Jul 04 Javascript
jquery实现动态画圆
Dec 04 Javascript
JavaScript动态修改背景颜色的方法
Apr 16 Javascript
TinyMCE汉化及本地上传图片功能实例详解
May 31 Javascript
JS字符串长度判断,超出进行自动截取的实例(支持中文)
Mar 06 Javascript
layer.open关闭父窗口 以及调用父页面的方法
Aug 17 Javascript
JS时间戳与日期格式互相转换的简单方法示例
Jan 30 Javascript
使用Ajax实现无刷新上传文件
Apr 12 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自动生成月历代码
2006/10/09 PHP
PHP网页游戏学习之Xnova(ogame)源码解读(一)
2014/06/23 PHP
php采用ajax数据提交post与post常见方法总结
2014/11/10 PHP
php打印一个边长为N的实心和空心菱型的方法
2015/03/02 PHP
PHP MPDF中文乱码的解决方式
2015/12/08 PHP
PHP实现自动识别原编码并对字符串进行编码转换的方法
2016/07/13 PHP
Laravel实现autoload方法详解
2017/05/07 PHP
Yii2结合Workerman的websocket示例详解
2018/09/10 PHP
JS将所有对象s的属性复制给对象r(原生js+jquery)
2014/01/25 Javascript
Javascript实现Web颜色值转换
2015/02/05 Javascript
jQuery实现浮动层随浏览器滚动条滚动的方法
2015/09/22 Javascript
基于Bootstrap仿淘宝分页控件实现代码
2016/11/07 Javascript
jQuery之动画效果大全
2016/11/09 Javascript
学习JS中的DOM节点以及操作
2018/04/30 Javascript
解决webpack dev-server不能匹配post请求的问题
2018/08/24 Javascript
在create-react-app中使用sass的方法示例
2018/10/01 Javascript
详解Vue.directive 自定义指令
2019/03/27 Javascript
layui自定义插件citySelect实现省市区三级联动选择
2019/07/26 Javascript
详解mpvue实现对苹果X安全区域的适配
2019/07/31 Javascript
微信小程序3D轮播实现代码
2019/09/19 Javascript
基于JavaScript实现表格隔行换色
2020/05/08 Javascript
VueCli4项目配置反向代理proxy的方法步骤
2020/05/17 Javascript
详解js中的原型,原型对象,原型链
2020/07/16 Javascript
Python爬虫DOTA排行榜爬取实例(分享)
2017/06/13 Python
Random 在 Python 中的使用方法
2018/08/09 Python
使用Python给头像戴上圣诞帽的图像操作过程解析
2019/09/20 Python
python实现将视频按帧读取到自定义目录
2019/12/10 Python
基于Python的身份证验证识别和数据处理详解
2020/11/14 Python
详解Python中的Lock和Rlock
2021/01/26 Python
2019年分享net面试的经历和题目
2016/08/07 面试题
Linux如何为某个操作添加别名
2015/02/05 面试题
超市创业计划书
2014/04/24 职场文书
社会工作专业自荐信
2014/09/26 职场文书
个人批评与自我批评发言稿
2014/09/28 职场文书
学习习近平主席讲话心得体会
2016/01/20 职场文书
Sql Server之数据类型详解
2022/02/28 SQL Server