深入淺析JSONAPI在PHP中的應(yīng)用
現(xiàn)在服務(wù)端程序員的主要工作已經(jīng)不再是套模版,而是編寫基于 JSON 的 API 接口。可惜大家編寫接口的風(fēng)格往往迥異,這就給系統(tǒng)集成帶來了很多不必要的溝通成本,如果你有類似的困擾,那么不妨關(guān)注一下 JSONAPI ,它是一個(gè)基于 JSON 構(gòu)建 API 的規(guī)范標(biāo)準(zhǔn),一個(gè)簡(jiǎn)單的 API 接口大致如下所示:
JSONAPI
簡(jiǎn)單說明一下:根節(jié)點(diǎn)中的 data 用來放置主對(duì)象的內(nèi)容,其中 type 和 id 是必須要有的字段,用來表示主對(duì)象的類型和標(biāo)識(shí),其它簡(jiǎn)單的屬性統(tǒng)統(tǒng)放置到 attributes 里,如果主對(duì)象存在一對(duì)一、一對(duì)多等關(guān)聯(lián)對(duì)象,那么放置到 relationships 里,不過只是通過 type 和 id 字段放置一個(gè)鏈接,關(guān)聯(lián)對(duì)象的實(shí)際內(nèi)容統(tǒng)統(tǒng)放置在根接點(diǎn)中的 included 里。
有了 JSONAPI,數(shù)據(jù)解析的過程變得規(guī)范起來,節(jié)省了不必要的溝通成本。不過如果要手動(dòng)構(gòu)建 JSONAPI 數(shù)據(jù)還是很麻煩的,好在通過使用 Fractal 可以讓實(shí)現(xiàn)過程相對(duì)自動(dòng)化一些,上面的例子如果用 Fractal 實(shí)現(xiàn)大概是這個(gè)樣子:
<?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 工具包,F(xiàn)ractal 一定榜上有名,它隱藏了實(shí)現(xiàn)細(xì)節(jié),讓使用者完全不必了解 JSONAPI 協(xié)議即可上手。不過如果你想在自己的項(xiàng)目里使用的話,與直接使用 Fractal 相比,可以試試 Fractalistic ,它對(duì) Fractal 進(jìn)行了封裝,使其更好用:
<?php Fractal::create() ->collection($articles) ->transformWith(new ArticleTransformer()) ->includeAuthor() ->toArray(); ?>
如果你是裸寫 PHP 的話,那么 Fractalistic 基本就是最佳選擇了,不過如果你使用了一些全??蚣艿脑?,那么 Fractalistic 可能還不夠優(yōu)雅,因?yàn)樗鼰o法和框架本身已有的功能更完美的融合,以 Lavaral 為例,它本身內(nèi)置了一個(gè) API Resources 功能,在此基礎(chǔ)上我實(shí)現(xiàn)了一個(gè) 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;
}
}
?>
對(duì)應(yīng)的 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);
}
}
?>
對(duì)應(yīng)的 Controller 也和原來差不多,只是加入了一個(gè) isRoot 屬性,用來識(shí)別根:
<?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;
}
}
?>
整個(gè)過程沒有對(duì) Laravel 的架構(gòu)進(jìn)行太大的侵入,可以說是目前 Laravel 實(shí)現(xiàn) JSONAPI 的最優(yōu)解決方案了,有興趣的可以研究一下 JsonApiSerializer 的實(shí)現(xiàn),雖然只有一百多行代碼,但是我卻費(fèi)了好大的力氣才實(shí)現(xiàn),可以說是行行皆辛苦啊。
總結(jié)
以上所述是小編給大家介紹的JSONAPI在PHP中的應(yīng)用,希望對(duì)大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會(huì)及時(shí)回復(fù)大家的!
- php post json參數(shù)的傳遞和接收處理方法
- PHP調(diào)用接口用post方法傳送json數(shù)據(jù)的實(shí)例
- JSON PHP中,Json字符串反序列化成對(duì)象/數(shù)組的方法
- php curl獲取到j(luò)son對(duì)象并轉(zhuǎn)成數(shù)組array的方法
- PHP給前端返回一個(gè)JSON對(duì)象的實(shí)例講解
- PHP以json或xml格式返回請(qǐng)求數(shù)據(jù)的方法
- PHP連接MySQL數(shù)據(jù)庫(kù)并以json格式輸出
- PHP 訪問數(shù)據(jù)庫(kù)配置通用方法(json)
- PHP基于CURL發(fā)送JSON格式字符串的方法示例
- php讀取本地json文件的實(shí)例
- PHP實(shí)現(xiàn)發(fā)送和接收J(rèn)SON請(qǐng)求
相關(guān)文章
uniapp封裝axios的詳細(xì)過程(大可不必那么麻煩)
在uniapp中使用axios進(jìn)行請(qǐng)求時(shí),uniapp無法使用axios的適配器,下面這篇文章主要給大家介紹了關(guān)于uniapp封裝axios的詳細(xì)過程,需要的朋友可以參考下2022-10-10
TypeScript高級(jí)用法的知識(shí)點(diǎn)匯總
這篇文章主要給大家介紹了關(guān)于TypeScript高級(jí)用法的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用TypeScript具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
通過JAVASCRIPT讀取ASP設(shè)定的COOKIE
通過JAVASCRIPT讀取ASP設(shè)定的COOKIE...2006-11-11
easyui form validate總是返回false的原因及解決方法
下面小編就為大家?guī)硪黄猠asyui form validate總是返回false的原因及解決方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11
js+HTML5實(shí)現(xiàn)canvas多種顏色漸變效果的方法
這篇文章主要介紹了js+HTML5實(shí)現(xiàn)canvas多種顏色漸變效果的方法,涉及html5屬性的相關(guān)技巧,需要的朋友可以參考下2015-06-06
js單頁hash路由原理與應(yīng)用實(shí)戰(zhàn)詳解
本篇文章主要介紹了js單頁hash路由原理與應(yīng)用實(shí)戰(zhàn)詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08
JS實(shí)現(xiàn)閃動(dòng)的title消息提醒效果
這篇文章主要介紹了JS實(shí)現(xiàn)閃動(dòng)的title消息提醒效果,考慮并兼容了大部份的瀏覽器,需要的朋友可以參考下2014-06-06

