什麼是TreeQL
tree-ql 是一個laravel擴充套件,希望能夠從url中include你所需的資源,實現查詢的所見既所得.
// http://api.test/posts/{slug}?include=content,user,comments ↓
{
"data": {
"id": 1,
"slug": "quisquam-asperiores-est-necessitatibus-et.",
"title": "Quisquam asperiores est necessitatibus et.",
"description": "Officiis nihil sunt ut veritatis.",
"cover": "https://lorempixel.com/640/480/?63535",
"comment_count": 11,
"like_count": 11,
"content": "Omnis quisquam dolorem quasi sequi veritatis quia dolorem sed. Ut non voluptatem beatae eum. ",
"comments": [
{
"id": 303,
"content": "Quasi dignissimos dolor tempore exercitationem.",
"user_id": 2481,
"post_id": 1,
"like_count": 18,
"reply_count": 9,
"floor": 303,
}
],
"user": {
"id": 1221,
"nickname": "Ashleigh McKenzie",
"avatar": "https://lorempixel.com/640/480/?29515"
}
}
}
更加深入的使用
// http://api.test/posts/{slug}?include=
// content,user,comments(sort_by:like_count){user,replies.user},is_like,select_comments
{
"data": {
"id": 1,
"slug": "quisquam-asperiores-est-necessitatibus-et.",
"title": "Quisquam asperiores est necessitatibus et.",
"description": "Officiis nihil sunt ut veritatis.",
"cover": "https://lorempixel.com/640/480/?63535",
"comment_count": 11,
"like_count": 11,
"user_id": 1221,
"content": "Omnis quisquam dolorem quasi sequi veritatis quia dolorem sed. Ut non voluptatem beatae eum.",
"is_like": true,
"comments": [
{
"id": 303,
"content": "Quasi dignissimos dolor tempore exercitationem.",
"user_id": 2481,
"post_id": 1,
"like_count": 18,
"reply_count": 9,
"floor": 303,
"user": {
"id": 2481,
"nickname": "Garett O'Connell",
"avatar": "https://lorempixel.com/640/480/?52652"
},
"replies": [
{
"id": 415,
"comment_id": 303,
"user_id": 2814,
"content": "Odit magnam sed ut.",
"call_user": null,
"created_at": "2018-12-12 02:26:08",
"updated_at": "2018-12-12 02:26:08",
"user": {
"id": 2814,
"nickname": "Ted Dickinson",
"avatar": "https://lorempixel.com/640/480/?19577"
}
}
]
}
],
"user": {
"id": 1221,
"nickname": "Ashleigh McKenzie",
"avatar": "https://lorempixel.com/640/480/?29515"
}
},
"meta": {
"selected_comments": [
{
"id": 303,
"content": "Quasi dignissimos dolor tempore exercitationem.",
"user_id": 2481,
"post_id": 1,
"like_count": 18,
"reply_count": 9,
"floor": 303,
"selected": 1,
"created_at": "2018-12-12 02:25:55",
"updated_at": "2018-12-12 02:25:55",
"user": {
"id": 2481,
"nickname": "Garett O'Connell",
"avatar": "https://lorempixel.com/640/480/?52652"
}
}
]
}
}
你可能會發現和GraphQL比起來並不是真正的所見既所得,這是由於http請求url長度的限制,所以加入了default的概念, TreeQL會結合include和default來返回相應的資源.
安裝
確保你的laravel版本在5.5以上,在專案目錄下執行
composer require weiwenhao/tree-ql
該版本目前為alpha版本,不推薦用於商業生產環境,推薦用於個人專案
使用
由於tree-ql是一個laravel的擴充套件包,接下來會從laravel的角度進行切入,實際上如果你熟悉 dingo/api的include,你會更加適應這種開發模式.
我可以include什麼東西?
由於include所見即所得,因此可以換個提問方式,我的response中可以返回些什麼資料?
response中的資料可以分為4類, 既 columns,relations,each,meta.
columns 既我們資料庫中的columns, 如 id,name,created_at,updated_at等
relations 既orm中的關聯關係, 比如post資源的relation有一對一的 user,一對多的 comments, 具體的定義都在laravel的model中定義
each 可以理解為沒有儲存在mysql中,由程式設計師計算得來的column, 其和column是平級的, 比如 一個user是否點讚了一篇post, 那麼在我們的post的response中可能會見到這樣的資料 ↓
{
"data": [
{
"id": 1,
"slug": "quisquam-asperiores-est-necessitatibus-et.",
"title": "Quisquam asperiores est necessitatibus et.",
"description": "Officiis nihil sunt ut veritatis.",
"is_like": true, // 該欄位由程式設計師計算得來, 沒有也不能儲存在資料庫中
},
{
"id": 2,
"slug": "quisquam-asperiores-est-necessitatibus-et.",
"title": "Quisquam asperiores est necessitatibus et.",
"description": "Officiis nihil sunt ut veritatis.",
"is_like": false
}
]
}
meta 用來儲存一些無法儲存在data中的資料, 最典型的例子既分頁資訊 ↓
{
"data": [
{
"id": 285,
"slug": "repellat-illo-molestias-quidem-ea-autem.",
"title": "Repellat illo molestias quidem ea autem.",
"description": "Sed harum.",
"cover": "https://lorempixel.com/640/480/?12347",
"comment_count": 8,
"like_count": 14,
"user_id": 2023
},
// ...
],
"meta": {
"pagination": {
"per_page": 15,
"total": 300,
"current": 1,
"next": "http://api.jianshu.test/api/posts?page=2",
"previous": null,
"last": 20
}
}
}
接下來看看如何在laravel中進行定義
Resource的定義
tree-ql預設使用app下的Resources目錄, 因此可能會有這樣的目錄結構
接下來以PostResource為例
<?php
namespace App\Resources;
use Weiwenhao\TreeQL\Resource;
class PostResource extends Resource
{
/**
* 從下面的 columns/relations/meta/each中抽取得來
*/
protected $default = [
'id',
'slug',
'title',
'description',
'cover',
'comment_count',
'like_count',
'user_id'
];
protected $columns = [
'id',
'slug',
'title',
'description',
'cover',
'comment_count',
'like_count',
'user_id',
'content'
];
protected $relations = [
'user',
'comments',
];
protected $meta = [
'selected_comments'
];
protected $each = ['is_like'];
public function isLike($item, $params)
{
return array_random([true, false]);
}
public function selectedComments($params)
{
$post = $this->getCollection()->first();
$comments = $post->selectedComments;
$resource = CommentResource::make($comments, 'user,replies.user');
return $resource->getResponseData();
}
}
Resource分為兩部分, 類屬性部分用來進行定義,除了default外,其餘部分 columns/relations/meta/each 中定義的value 都可以在include中被引入.
而default中的定義則是從 columns/relations/meta/each 中已經定義的value進行抽取,default中的key,會被預設include進來,而不需要再url中顯式的定義.
方法部分 目前的作用主要是回撥函式, 且只有each和meta中定義的value 需要callback. callback命名的規則也很簡單, 既將meta或者each中定義的值改為 小駝峰命名 作為方法名稱即可.
each的callback有兩個引數, 每一個resource下都有一個collection屬性, 其中存放了該Resource下的資源資料, 其型別為Illuminate\Database\Eloquent\Collection
,collection中的每一個item都會被callback一次, 所以 上面 isLike的第一個引數為 Collection中的一個item, item既model
在Resource中通過呼叫 $this->getCollection()可以獲取所有的資料
由於include支援params, 所以isLike的第二個引數為include中傳遞的params, 型別為array,格式為
$params = [
'sort_by' => 'created_at',
'order' => 'desc'
]
callback 中 return的值將會在response data中被原樣展示
關於meta
meta不同於each, 每個include meta在其生命週期中只會被呼叫一次.且只有一個引數 既params. 其return的值也將在response meta中被原樣展示
meta的另一個特點時,只有最外層的資料結構才存在meta, 即
{
data: {},
meta: {}
}
因此如果在更深層次的resource中進行include meta 那麼會產生的行為時, 該meta資料,被拉到了最外層. 舉個例子
include=meta1,post.meta2
那麼返回的結果是
{
data: {
post: {}
},
meta: {
meta1: {},
meta2: {}
}
}
這個行為我並不是很喜歡,所以在考慮更加合適的解決方案
Columns 中定義了orm select語句中可以被查詢的資料,既類似這樣的行為會使用columns
使用Resource
接下來看看PostController的index和show方法
/**
* Display a listing of the resource.
* @return \Weiwenhao\TreeQL\Resource
*/
public function index()
{
// $posts = Post::columns()->latest()->get(); 同樣支援
$posts = Post::columns()->latest()->paginate();
// 等價於 return PostResource::make($post, request('include'))
return PostResource::make($posts);
}
/**
* Display the specified resource.
*
* @param \App\Models\Post $post
* @return \Weiwenhao\TreeQL\Resource
*/
public function show($post)
{
return $resource = PostResource::make($post);
}
上面的使用非常的簡單, 唯一需要講解的便是 columns() 這個查詢構造器. 我不希望Post的查詢一下查詢出table中所有的column,而是根據url中include進行查詢. 所以columns()會解析url中include並結合resource中的定義進行合適的select.
include的語法規則
已例項進行講解
http://api.test/posts/{slug}?include=user
基礎使用,在post的基礎上 include 這篇post的作者
我想include PostResource中定義的更多的東西怎麼辦?
http://api.test/posts/{slug}?include=user,content,comments
使用逗號進行分割
我想引入comment中的user怎麼辦?
http://api.test/posts/{slug}?include=user,content,comments.user
使用.
進行巢狀
我想同時引入comment中的user和replies怎麼做?
http://api.test/posts/{slug}?include=user,content,comments{user,replies}
使用 {}
和 ,
來代替.
語法進行巢狀
在dingo/api中 你可能需要這麼做
include=comment.user,comment.replies
我想對include的comments新增一些條件我應該怎麼做?
http://api.test/posts/{slug}?include=user,content,comments(sort_by:created_at,order:desc){user,replies}
條件語法緊跟著comments, ()
中包圍的既params, 形式為 key1:value1,key2:value2
實際上 目前只有 each和meta支援回撥. 後續會對columns和relations新增回撥.到時params將會有更強大的作用
這就是 include的所有語法規則了, 理論上所有的語法規則都支援無限巢狀與任意組合
比如 include=a,b.c.d,c{b},c{b(f:b),a.b.c},c(b.a),c{f,b}.b(a:b).c
當然無論怎樣的組合巢狀,你都無需擔心n+1的問題