视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
在PHP中如何使用JSONAPI
2020-11-27 19:36:38 责编:小采
文档


这篇文章主要介绍了深入浅析JSONAPI在PHP中的应用,需要的朋友可以参考下

现在服务端程序员的主要工作已经不再是套模版,而是编写基于 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 的实现,虽然只有一百多行代码,但是我却费了好大的力气才实现,可以说是行行皆辛苦啊。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在Vue中设置背景图片

使用vue + less如何实现简单换肤功能

使用angular、react和vue如何实现相同的面试题组件

利用jQuery实现滚动到底部时自动加载

在Angular2.0中如何实现modal对话框

在JS中如何实现运动缓冲效果(详细教程)

下载本文
显示全文
专题