現在服務端程序員的主要工作已經不再是套模版,而是編寫基于 JSON 的 API 接口。可惜大家編寫接口的風格往往迥異,這就給系統集成帶來了很多不必要的溝通成本,如果你有類似的困擾,那么不妨關注一下 JSONAPI,它是一個基于 JSON 構建 API 的規范標準,一個簡單的 API 接口大致如下所示:

簡單說明一下:根節點中的 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 $value;
protected $isRoot;
protected $data = [];
protected static $included = [];
public function __construct($value)
{
static $count = 0;
$this->value = $value;
$this->isRoot = (++$count == 1);
}
public function jsonSerialize()
{
foreach ($this->value as $key => $value) {
if ($value instanceof Resource) {
$this->serializeResource($key, $value);
} else {
$this->serializeNonResource($key, $value);
}
}
if (!$this->isRoot) {
return $this->data;
}
$result = [
'data' => $this->data,
];
if (static::$included) {
$result['included'] = static::$included;
}
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;
}
}
}
?>
~~~
對應的 Resource 基本還和以前一樣,只是返回值改了一下:
~~~
<?php
namespace App\Http\Resources;
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' => new AuthorResource($this->whenLoaded('author')),
];
return new JsonApiSerializer($value);
}
}
?>
對應的 Controller 也和原來差不多:
<?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->additional(['meta' => [...]]);
return $resource;
}
}
?>
~~~
整個過程沒有對 Laravel 的架構進行太大的侵入,可以說是目前 Laravel 實現 JSONAPI 的最優解決方案了,有興趣的可以研究一下 JsonApiSerializer 的實現,雖然只有一百多行代碼,但是我卻費了好大的力氣才實現,可以說是行行皆辛苦啊。
- PHP發表心情投票功能示例(附源碼)
- TP5驗證碼實現
- 談談JSONAPI在PHP中的應用
- Laravel artisan optimize 源碼解讀
- PHP中的閉包和匿名函數
- 65條最常用正則表達式,你要的都在這里了
- PHP 斷點續傳實例詳解
- 從配置文件的角度去了解Yii2
- 高效的PHP郵件發送庫:Swiftmailer
- 對于php-fpm和cgi,還有并發響應的理解
- 詳解 Cookie 紀要
- Cookie詳解
- Cookie格式
- Cookie的創建
- Cookie 基礎知識*
- Cookie的使用
- Cookie的基本操作
- Cookie的域概念
- Session詳解
- session與cookie的區別
- Cookie與Session問答
- php如何解決中文亂碼問題?
- 微信小程序 PHP生成帶參數二維碼
- PHP實現QQ快速登錄
- mysql 隊列 實現并發讀
- php+redis消息隊列實現搶購功能
- js購物車實現思路及代碼(個人感覺不錯)
- PHP curl 抓取AJAX異步內容示例
- PHP curl 并發最佳實踐代碼分享
- php 字符串中是否包含指定字符串的多種方法
- PHP7如何開啟Opcode打造強悍性能詳解
- PHP實現用戶登錄的案例代碼
- yii2多圖上傳組件的使用教程
- PHP數組去重的更快實現方式分析
- 購物車實現的幾種方式優缺點對比
- laravel項目利用twemproxy部署redis集群的完整步驟
- PHP+memcache實現消息隊列案例分享
- PHP CURL CURLOPT參數說明
- php實現可以設置中獎概率的抽獎程序代碼分享
- 基于在生產環境中使用php性能測試工具xhprof的詳解
- 一個PHP并發訪問實例代碼
- php解決搶購秒殺抽獎等大流量并發入庫導致的庫存負數的問題
- PHP設計模式之工廠模式定義與用法詳解