從Laravel 5.5+開始,加入了API Resources這個概念。
我們先來看一下官網(wǎng)如何定義這個概念的:
When building an API, you may need a transformation layer that sits between your Eloquent models and the JSON responses that are actually returned to your application's users. Laravel's resource classes allow you to expressively and easily transform your models and model collections into JSON.
可能看完這個概念之后,你仍然有點不明白,畢竟這個定義說的有點含糊。
如果你熟悉使用API進行輸出,構架前后端分離的網(wǎng)絡應用,那么你應該會發(fā)現(xiàn),當我們使用Eloquent從數(shù)據(jù)庫中取出數(shù)據(jù)后,如果想以JSON格式進行輸出,那么我們可以使用->toJson()這個方法,這個方法可以直接將我們的model序列化(這個方法從Laravel 5.1+開始就可以使用了):
$user = App\User::find(1);
return $user->toJson();
使用多了,我們會發(fā)現(xiàn),在model較為復雜,或者model中有很多我們API輸出可能用不到的字段的情況下,toJson()仍然會忠實地幫我們把這些字段序列化出來。
這個時候,我們會想,如何將model中的某些字段隱藏起來,不輸出到JSON中。另外一種情況,比如字段是password等一些敏感信息的時候,我們不希望JSON數(shù)據(jù)里包含這樣的敏感信息。
要解決這個問題,我們可以在model里定義$hidden或者$visible這兩個數(shù)組來進行字段的隱藏或者顯示:
?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 不希望在序列化中出現(xiàn)的字段放入該數(shù)組中
*
* @var array
*/
protected $hidden = ['password', 'some', 'secret'];
}
?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 只有在以下數(shù)組中出現(xiàn)的字段會被序列化
*
* @var array
*/
protected $visible = ['first_name', 'last_name'];
}
那么你可能會想,我們已經(jīng)有了可以自動序列化的方法,以及可以隱藏或者顯示指定字段的方法,這樣不就足夠了嗎?
現(xiàn)在我們來假設一個簡單的應用場景。假設我們在輸出一個客戶列表,里面包含了客戶名字和送貨地址。我們使用Customer這個model定義客戶,使用ShippingAddress這個model進行定義送貨地址。為了簡化場景,我們的客戶只有一個送貨地址,所以只會出現(xiàn)一一對應的情況。
那么在ShippingAddress對應的數(shù)據(jù)庫表shipping_addresses中,我們可能會有如下定義:
| id | country_id | province_id | city_id | address |
字段類型我就不贅述了,其中country_id、province_id以及city_id這三個外鍵分別對應了國家、省份以及城市表中的id。
而Customer對應的customers表中,會有shipping_address_id這個外鍵指向shipping_addresses表中的id。
那么我們要輸出顧客和送貨地址,我們需要先在model中定義好relationship:
?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Customer extends Model
{
public function shippingAddress()
{
return $this->belongsTo(ShippingAddress::class);
}
}
?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class ShippingAddress extends Model
{
public function country()
{
return $this->belongsTo(Country::class);
}
public function province()
{
return $this->belongsTo(Province::class);
}
public function city()
{
return $this->belongsTo(City::class);
}
}
在我們的控制器中,我們拉取出所有客戶:
?php
namespace App\Http\Controllers;
use App\Customer;
use App\Http\Controllers\Controller;
class CustomerController extends Controller
{
/**
* Simple function to fetch all customers with their shipping addresses
*
* @return String
*/
public function index()
{
$customers = Customer::with(['shippingAddress', 'shippingAddress.country', 'shippingAddress.province', 'shippingAddress.city'])->get();
//這里可以直接返回Eloquent Collections或Objects,toJson()將自動被調(diào)用
return $customers;
}
}
那么輸出的JSON將會包含了多個層級的關系,那么在我們前端調(diào)用的時候,將會非常麻煩,因為我們需要一層一層剝開Object關系。
但是如果你熟悉Laravel,你可能會說,慢著!這個情況我可以用accessor不就完事兒了嗎?
是的,我們確實可以使用accessor來簡化我們的數(shù)據(jù)層級:
/**
* Get the customer's full shipping address
*
* @return string
*/
public function getFullShippingAddressAttribute()
{
return "{$this->shippingAddress->country->name} {$this->shippingAddress->province->name} {$this->shippingAddress->city->name} {$this->shippingAddress->address}";
}
但是我們還需要一步操作。由于customers這張表本身沒有full_shipping_address這個字段,要使我們的JSON輸出包含full_shipping_address,我們需要添加$appends數(shù)組:
?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Customer extends Model
{
/**
* The accessors to append to the model's array form.
*
* @var array
*/
protected $appends = ['full_shipping_address'];
}
對于每一個我們想自定義的JSON字段,我們都需要進行上面兩部的操作。這樣一來其實非常麻煩,并且不利于代碼的維護,因為這會讓原本簡潔的model顯得很復雜。
基于以上原因,我們需要一個中間層,在我們輸出model成為JSON的時候,可以進行一次信息的過濾及加工。
那么還是使用我們上面的應用場景。要輸出自定義的字段再簡單不過了。我們不需要在model里定義各種accessor,也不需要使用黑白名單過濾字段,只需要新建一個Resource類:
$ php artisan make:resource Customer
然后我們可以看到,在app/Http文件夾下,多出了一個名為Resources文件夾下,其中含有一個名為Customer.php的文件:
?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class Customer extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}
這里我們看到其中有且僅有一個名為toArray的方法。這就是我們要自定字段的地方:
public function toArray($request)
{
return [
'fullName' => $this->first_name . $this->last_name,
'fullShippingAddress' => $this->shippingAddress->country->name . $this->shippingAddress->province->name . $this->shippingAddress->city->name . $this->shippingAddress->address,
];
}
注意到,無論是fullName還是fullShippingAddress,都是不存在于customers表中的字段。
接著,我們只需要簡單修改一下我們的控制器:
?php
namespace App\Http\Controllers;
use App\Customer;
use App\Http\Resources\Customer as CustomerResource;
use App\Http\Controllers\Controller;
class CustomerController extends Controller
{
/**
* Simple function to fetch all customers with their shipping addresses
*
* @return String
*/
public function index()
{
$customers = Customer::with(['shippingAddress', 'shippingAddress.country', 'shippingAddress.province', 'shippingAddress.city'])->get();
//這里我們使用了新的Resource類
return CustomerResource::collection($customers);
}
}
這樣就OK了!我們輸出的JSON數(shù)據(jù)中,將會僅僅含有以上兩個字段,即fullName和fullShippingAddress,非常干凈,并且前端直接可用,不需要二次再加工。
唯一需要注意的是,這里由于我們拉取了多個Customer,所以我們用了每個Resource類都自帶有的collection方法,將一個Collection中的所有對象都進行處理。而若要處理單個對象,我們需要使用以下代碼:
public function show($id)
{
$customer = Customer::findOrFail($id);
return new CustomerResource($customer);
}
要了解更多關于API Resources的詳情,請戳官網(wǎng)文檔:
https://laravel.com/docs/5.7/eloquent-resources
本文主要講解了Laravel5.5+ 使用API Resources快速輸出自定義JSON方法詳解,更多關于Laravel框架的使用技巧請查看下面的相關鏈接
您可能感興趣的文章:- Laravel如何實現(xiàn)適合Api的異常處理響應格式
- laravel接管Dingo-api和默認的錯誤處理方式
- 在Laravel中使用GuzzleHttp調(diào)用第三方服務的API接口代碼
- Laravel實現(xiàn)ApiToken認證請求
- laravel框架 api自定義全局異常處理方法
- laravel dingo API返回自定義錯誤信息的實例
- laravel 配置路由 api和web定義的路由的區(qū)別詳解
- Laravel5.4簡單實現(xiàn)app接口Api Token認證方法
- 詳解Laravel制作API接口