跳到主要內容

Eloquent API 資源

📝 此頁面為 Laravel 官方文檔的繁體中文翻譯。查看原始英文版本

Eloquent: API 資源

簡介

在建立 API 時,您可能需要一個轉換層,它位於您的 Eloquent 模型與實際返回給應用程式使用者的 JSON 回應之間。例如,您可能希望為某些使用者子集顯示某些屬性而不為其他使用者顯示,或者您可能希望在模型的 JSON 表示中始終包含某些關係。Eloquent 的資源類別允許您以表達性且簡便的方式將您的模型和模型集合轉換為 JSON。

當然,您可以隨時使用它們的 toJson 方法將 Eloquent 模型或集合轉換為 JSON;但是,Eloquent 資源提供了對模型及其關係的 JSON 序列化的更細粒度和更強大的控制。

生成資源

要生成一個資源類別,您可以使用 make:resource Artisan 命令。預設情況下,資源將放置在應用程式的 app/Http/Resources 目錄中。資源擴展 Illuminate\Http\Resources\Json\JsonResource 類別:

php artisan make:resource UserResource

資源集合

除了生成轉換單個模型的資源外,您還可以生成負責轉換模型集合的資源。這使您的 JSON 回應能夠包含與給定資源的整個集合相關的連結和其他元資訊。

要建立資源集合,您應該在建立資源時使用 --collection 標誌。或者,在資源名稱中包含單詞 Collection 將向 Laravel 指示它應該建立一個集合資源。集合資源擴展 Illuminate\Http\Resources\Json\ResourceCollection 類別:

php artisan make:resource User --collection

php artisan make:resource UserCollection

概念概述

[!NOTE] 這是對資源和資源集合的高階概述。強烈建議您閱讀本文件的其他部分,以更深入地了解資源為您提供的自訂和強大功能。

在深入探討編寫資源時可用的所有選項之前,讓我們首先從高階角度看看資源如何在 Laravel 中使用。一個資源類別代表一個需要轉換為 JSON 結構的單個模型。例如,以下是一個簡單的 UserResource 資源類別:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    /**
     * 將資源轉換為陣列。
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}

每個資源類別都定義了一個 toArray 方法,該方法返回當資源從路由或控制器方法作為回應返回時應轉換為 JSON 的屬性陣列。

注意我們可以直接從 $this 變數存取模型屬性。這是因為資源類別會自動將屬性和方法存取代理到基礎模型,以便於存取。一旦定義了資源,就可以從路由或控制器返回它。資源透過其構造函數接受基礎模型實例:

use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/user/{id}', function (string $id) {
    return new UserResource(User::findOrFail($id));
});

為了方便,您可以使用模型的 toResource 方法,它將使用框架慣例自動發現模型的基礎資源:

return User::findOrFail($id)->toResource();

呼叫 toResource 方法時,Laravel 將嘗試在最接近模型命名空間的 Http\Resources 命名空間中找到與模型名稱匹配並可選地以 Resource 為後綴的資源。

如果您的資源類別不遵循此命名慣例或位於不同的命名空間中,您可以使用 UseResource 屬性為模型指定預設資源:

<?php

namespace App\Models;

use App\Http\Resources\CustomUserResource;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Attributes\UseResource;

#[UseResource(CustomUserResource::class)]
class User extends Model
{
    // ...
}

或者,您可以透過將資源類別傳遞給 toResource 方法來指定它:

return User::findOrFail($id)->toResource(CustomUserResource::class);

資源集合

如果您返回一個資源集合或分頁回應,您應該在路由或控制器中建立資源實例時使用資源類別提供的 collection 方法:

use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/users', function () {
    return UserResource::collection(User::all());
});

或者,為了方便,您可以使用 Eloquent 集合的 toResourceCollection 方法,它將使用框架慣例自動發現模型的基礎資源集合:

return User::all()->toResourceCollection();

呼叫 toResourceCollection 方法時,Laravel 將嘗試在最接近模型命名空間的 Http\Resources 命名空間中找到與模型名稱匹配並以 Collection 為後綴的資源集合。

如果您的資源集合類別不遵循此命名慣例或位於不同的命名空間中,您可以使用 UseResourceCollection 屬性為模型指定預設資源集合:

<?php

namespace App\Models;

use App\Http\Resources\CustomUserCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Attributes\UseResourceCollection;

#[UseResourceCollection(CustomUserCollection::class)]
class User extends Model
{
    // ...
}

或者,您可以透過將資源集合類別傳遞給 toResourceCollection 方法來指定它:

return User::all()->toResourceCollection(CustomUserCollection::class);

自訂資源集合

預設情況下,資源集合不允許添加可能需要隨集合返回的任何自訂元資料。如果您希望自訂資源集合回應,您可以建立一個專用的資源來表示該集合:

php artisan make:resource UserCollection

生成資源集合類別後,您可以輕鬆定義應與回應一起包含的任何元資料:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    /**
     * 將資源集合轉換為陣列。
     *
     * @return array<int|string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'data' => $this->collection,
            'links' => [
                'self' => 'link-value',
            ],
        ];
    }
}

定義資源集合後,可以從路由或控制器返回它:

use App\Http\Resources\UserCollection;
use App\Models\User;

Route::get('/users', function () {
    return new UserCollection(User::all());
});

或者,為了方便,您可以使用 Eloquent 集合的 toResourceCollection 方法,它將使用框架慣例自動發現模型的基礎資源集合:

return User::all()->toResourceCollection();

呼叫 toResourceCollection 方法時,Laravel 將嘗試在最接近模型命名空間的 Http\Resources 命名空間中找到與模型名稱匹配並以 Collection 為後綴的資源集合。

保留集合鍵

從路由返回資源集合時,Laravel 會重設集合的鍵,使其按數字順序排列。但是,您可以在資源類別上使用 PreserveKeys 屬性來指示是否應保留集合的原始鍵:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Attributes\PreserveKeys;
use Illuminate\Http\Resources\Json\JsonResource;

#[PreserveKeys]
class UserResource extends JsonResource
{
    // ...
}

preserveKeys 屬性設定為 true 時,當從路由或控制器返回集合時,集合鍵將被保留:

use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/users', function () {
    return UserResource::collection(User::all()->keyBy->id);
});

自訂基礎資源類別

通常,資源集合的 $this->collection 屬性會自動填充將集合的每個項目映射到其單數資源類別的結果。單數資源類別被假設為集合的類別名稱(去掉類別名稱末尾的 Collection 部分)。此外,根據您的個人偏好,單數資源類別可能帶有或不帶 Resource 後綴。

例如,UserCollection 將嘗試將給定的使用者實例映射到 UserResource 資源。要自訂此行為,您可以在資源集合上使用 Collects 屬性:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Attributes\Collects;
use Illuminate\Http\Resources\Json\ResourceCollection;

#[Collects(Member::class)]
class UserCollection extends ResourceCollection
{
    // ...
}

編寫資源

[!NOTE] 如果您尚未閱讀概念概述,強烈建議您在繼續閱讀本文件之前先閱讀它。

資源只需要將給定模型轉換為陣列。因此,每個資源都包含一個 toArray 方法,該方法將您的模型屬性轉換為 API 友好的陣列,可以從應用程式的路由或控制器返回:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    /**
     * 將資源轉換為陣列。
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}

一旦定義了資源,就可以直接從路由或控制器返回它:

use App\Models\User;

Route::get('/user/{id}', function (string $id) {
    return User::findOrFail($id)->toUserResource();
});

關係

如果您希望在回應中包含相關資源,您可以將它們添加到資源的 toArray 方法返回的陣列中。在此範例中,我們將使用 PostResource 資源的 collection 方法將使用者的部落格文章添加到資源回應中:

use App\Http\Resources\PostResource;
use Illuminate\Http\Request;

/**
 * 將資源轉換為陣列。
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'posts' => PostResource::collection($this->posts),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

[!NOTE] 如果您希望僅在關係已被載入時才包含它們,請查閱條件關係的文件。

資源集合

雖然資源將單個模型轉換為陣列,但資源集合將模型集合轉換為陣列。但是,並非絕對需要為每個模型定義一個資源集合類別,因為所有 Eloquent 模型集合都提供了一個 toResourceCollection 方法來即時生成一個「臨時」資源集合:

use App\Models\User;

Route::get('/users', function () {
    return User::all()->toResourceCollection();
});

但是,如果您需要自訂隨集合返回的元資料,則有必要定義您自己的資源集合:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    /**
     * 將資源集合轉換為陣列。
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'data' => $this->collection,
            'links' => [
                'self' => 'link-value',
            ],
        ];
    }
}

像單數資源一樣,資源集合可以直接從路由或控制器返回:

use App\Http\Resources\UserCollection;
use App\Models\User;

Route::get('/users', function () {
    return new UserCollection(User::all());
});

或者,為了方便,您可以使用 Eloquent 集合的 toResourceCollection 方法,它將使用框架慣例自動發現模型的基礎資源集合:

return User::all()->toResourceCollection();

呼叫 toResourceCollection 方法時,Laravel 將嘗試在最接近模型命名空間的 Http\Resources 命名空間中找到與模型名稱匹配並以 Collection 為後綴的資源集合。

資料包裝

預設情況下,當資源回應轉換為 JSON 時,您的最外層資源會包裝在 data 鍵中。因此,例如,一個典型的資源集合回應如下所示:

{
    "data": [
        {
            "id": 1,
            "name": "Eladio Schroeder Sr.",
            "email": "[email protected]"
        },
        {
            "id": 2,
            "name": "Liliana Mayert",
            "email": "[email protected]"
        }
    ]
}

如果您希望禁用最外層資源的包裝,您應該在基礎 Illuminate\Http\Resources\Json\JsonResource 類別上呼叫 withoutWrapping 方法。通常,您應該從 AppServiceProvider 或在每次請求到應用程式時載入的其他服務提供者中呼叫此方法:

<?php

namespace App\Providers;

use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 註冊任何應用程式服務。
     */
    public function register(): void
    {
        // ...
    }

    /**
     * 啟動任何應用程式服務。
     */
    public function boot(): void
    {
        JsonResource::withoutWrapping();
    }
}

[!WARNING] withoutWrapping 方法只影響最外層回應,不會移除您手動添加到自己的資源集合中的 data 鍵。

包裝巢狀資源

您有完全的自由來決定如何包裝資源的關係。如果您希望所有資源集合都包裝在 data 鍵中,而不論其巢狀結構如何,您應該為每個資源定義一個資源集合類別,並在 data 鍵中返回該集合。

您可能想知道這是否會導致最外層資源被包裝在兩個 data 鍵中。不用擔心,Laravel 永遠不會讓您的資源被意外地雙重包裝,因此您不必擔心正在轉換的資源集合的巢狀層級:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class CommentsCollection extends ResourceCollection
{
    /**
     * 將資源集合轉換為陣列。
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return ['data' => $this->collection];
    }
}

資料包裝與分頁

透過資源回應返回分頁集合時,即使已呼叫 withoutWrapping 方法,Laravel 仍會將您的資源資料包裝在 data 鍵中。這是因為分頁回應始終包含帶有關於分頁器狀態資訊的 metalinks 鍵:

{
    "data": [
        {
            "id": 1,
            "name": "Eladio Schroeder Sr.",
            "email": "[email protected]"
        },
        {
            "id": 2,
            "name": "Liliana Mayert",
            "email": "[email protected]"
        }
    ],
    "links":{
        "first": "http://example.com/users?page=1",
        "last": "http://example.com/users?page=1",
        "prev": null,
        "next": null
    },
    "meta":{
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "path": "http://example.com/users",
        "per_page": 15,
        "to": 10,
        "total": 10
    }
}

分頁

您可以將 Laravel 分頁器實例傳遞給資源的 collection 方法或自訂資源集合:

use App\Http\Resources\UserCollection;
use App\Models\User;

Route::get('/users', function () {
    return new UserCollection(User::paginate());
});

或者,為了方便,您可以使用分頁器的 toResourceCollection 方法,它將使用框架慣例自動發現分頁模型的基礎資源集合:

return User::paginate()->toResourceCollection();

分頁回應始終包含帶有關於分頁器狀態資訊的 metalinks 鍵:

{
    "data": [
        {
            "id": 1,
            "name": "Eladio Schroeder Sr.",
            "email": "[email protected]"
        },
        {
            "id": 2,
            "name": "Liliana Mayert",
            "email": "[email protected]"
        }
    ],
    "links":{
        "first": "http://example.com/users?page=1",
        "last": "http://example.com/users?page=1",
        "prev": null,
        "next": null
    },
    "meta":{
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "path": "http://example.com/users",
        "per_page": 15,
        "to": 10,
        "total": 10
    }
}

自訂分頁資訊

如果您希望自訂分頁回應的 linksmeta 鍵中包含的資訊,您可以在資源上定義一個 paginationInformation 方法。此方法將接收 $paginated 資料和 $default 資訊陣列,該陣列包含 linksmeta 鍵:

/**
 * 自訂資源的分頁資訊。
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  array  $paginated
 * @param  array  $default
 * @return array
 */
public function paginationInformation($request, $paginated, $default)
{
    $default['links']['custom'] = 'https://example.com';

    return $default;
}

條件屬性

有時您可能希望僅在滿足給定條件時才在資源回應中包含屬性。例如,您可能希望僅在當前使用者是「管理員」時才包含一個值。Laravel 提供了各種輔助方法來協助您處理這種情況。when 方法可用於有條件地向資源回應添加屬性:

/**
 * 將資源轉換為陣列。
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'secret' => $this->when($request->user()->isAdmin(), 'secret-value'),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

在此範例中,secret 鍵僅在已認證使用者的 isAdmin 方法返回 true 時才會在最終資源回應中返回。如果該方法返回 falsesecret 鍵將在回應發送給客戶端之前從資源回應中移除。when 方法允許您以表達性的方式定義資源,而無需在建立陣列時使用條件語句。

when 方法還接受一個閉包作為其第二個參數,允許您僅在給定條件為 true 時才計算結果值:

'secret' => $this->when($request->user()->isAdmin(), function () {
    return 'secret-value';
}),

whenHas 方法可用於在基礎模型上實際存在該屬性時包含它:

'name' => $this->whenHas('name'),

此外,whenNotNull 方法可用於在屬性不為 null 時將其包含在資源回應中:

'name' => $this->whenNotNull($this->name),

合併條件屬性

有時您可能有幾個屬性僅基於相同條件才應包含在資源回應中。在這種情況下,您可以使用 mergeWhen 方法,僅在給定條件為 true 時才將這些屬性包含在回應中:

/**
 * 將資源轉換為陣列。
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        $this->mergeWhen($request->user()->isAdmin(), [
            'first-secret' => 'value',
            'second-secret' => 'value',
        ]),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

同樣,如果給定條件為 false,這些屬性將在回應發送給客戶端之前從資源回應中移除。

[!WARNING] mergeWhen 方法不應在混合字串和數字鍵的陣列中使用。此外,它不應在具有未按順序排列的數字鍵的陣列中使用。

條件關係

除了有條件地載入屬性外,您還可以基於關係是否已在模型上載入來有條件地在資源回應中包含關係。這使您的控制器可以決定應在模型上載入哪些關係,而您的資源可以輕鬆地僅在它們實際被載入時才包含它們。最終,這使得更容易避免資源中的「N+1」查詢問題。

whenLoaded 方法可用於有條件地載入關係。為了避免不必要的載入關係,此方法接受關係的名稱而不是關係本身:

use App\Http\Resources\PostResource;

/**
 * 將資源轉換為陣列。
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'posts' => PostResource::collection($this->whenLoaded('posts')),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

在此範例中,如果關係尚未載入,posts 鍵將在回應發送給客戶端之前從資源回應中移除。

條件關係計數

除了有條件地包含關係外,您還可以基於關係的計數是否已在模型上載入來有條件地在資源回應中包含關係「計數」:

new UserResource($user->loadCount('posts'));

whenCounted 方法可用於有條件地在資源回應中包含關係的計數。此方法避免在關係計數不存在時不必要地包含該屬性:

/**
 * 將資源轉換為陣列。
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'posts_count' => $this->whenCounted('posts'),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

在此範例中,如果 posts 關係的計數尚未載入,posts_count 鍵將在回應發送給客戶端之前從資源回應中移除。

其他類型的聚合,例如 avgsumminmax,也可以使用 whenAggregated 方法有條件地載入:

'words_avg' => $this->whenAggregated('posts', 'words', 'avg'),
'words_sum' => $this->whenAggregated('posts', 'words', 'sum'),
'words_min' => $this->whenAggregated('posts', 'words', 'min'),
'words_max' => $this->whenAggregated('posts', 'words', 'max'),

條件樞紐資訊

除了在資源回應中有條件地包含關係資訊外,您還可以使用 whenPivotLoaded 方法有條件地從多對多關係的中間表中包含資料。whenPivotLoaded 方法接受樞紐表的名稱作為其第一個參數。第二個參數應是一個閉包,該閉包返回如果模型上存在樞紐資訊時應返回的值:

/**
 * 將資源轉換為陣列。
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'expires_at' => $this->whenPivotLoaded('role_user', function () {
            return $this->pivot->expires_at;
        }),
    ];
}

如果您的關係使用自訂中間表模型,您可以將中間表模型的實例作為第一個參數傳遞給 whenPivotLoaded 方法:

'expires_at' => $this->whenPivotLoaded(new Membership, function () {
    return $this->pivot->expires_at;
}),

如果您的中間表使用的是 pivot 以外的存取器,您可以使用 whenPivotLoadedAs 方法:

/**
 * 將資源轉換為陣列。
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'expires_at' => $this->whenPivotLoadedAs('subscription', 'role_user', function () {
            return $this->subscription->expires_at;
        }),
    ];
}

添加元資料

某些 JSON API 標準要求在您的資源和資源集合回應中添加元資料。這通常包括像到資源或相關資源的 links,或關於資源本身的元資料等內容。如果您需要返回關於資源的額外元資料,請將其包含在您的 toArray 方法中。例如,您可能在轉換資源集合時包含 links 資訊:

/**
 * 將資源轉換為陣列。
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'data' => $this->collection,
        'links' => [
            'self' => 'link-value',
        ],
    ];
}

從資源返回額外元資料時,您無需擔心意外覆蓋 Laravel 在返回分頁回應時自動添加的 linksmeta 鍵。您定義的任何額外 links 都將與分頁器提供的連結合併。

頂層元資料

有時您可能希望僅在資源是最外層資源時才在資源回應中包含某些元資料。通常,這包括關於整個回應的元資訊。要定義此元資料,請在您的資源類別中添加一個 with 方法。此方法應返回一個元資料陣列,僅當資源是最外層正在轉換的資源時才與資源回應一起包含:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    /**
     * 將資源集合轉換為陣列。
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return parent::toArray($request);
    }

    /**
     * 獲取應與資源陣列一起返回的額外資料。
     *
     * @return array<string, mixed>
     */
    public function with(Request $request): array
    {
        return [
            'meta' => [
                'key' => 'value',
            ],
        ];
    }
}

建立資源時添加元資料

您也可以在路由或控制器中建立資源實例時添加頂層資料。所有資源上都可用的 additional 方法接受一個資料陣列,該資料應添加到資源回應中:

return User::all()
    ->load('roles')
    ->toResourceCollection()
    ->additional(['meta' => [
        'key' => 'value',
    ]]);

JSON:API 資源

Laravel 附帶了 JsonApiResource,一個產生符合 JSON:API 規範的回應的資源類別。它擴展了標準 JsonResource 類別,並自動處理資源物件結構、關係、稀疏欄位集、包含、惰性屬性求值,並將 Content-Type 標頭設定為 application/vnd.api+json

[!NOTE] Laravel 的 JSON:API 資源處理回應的序列化。如果您還需要解析傳入的 JSON:API 查詢參數(如過濾器和排序),Spatie 的 Laravel Query Builder 是一個很棒的配套套件。

生成 JSON:API 資源

要生成一個 JSON:API 資源,請使用帶有 --json-api 標誌的 make:resource Artisan 命令:

php artisan make:resource PostResource --json-api

生成的類別將擴展 Illuminate\Http\Resources\JsonApi\JsonApiResource,並包含供您定義的 $attributes$relationships 屬性:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\JsonApi\JsonApiResource;

class PostResource extends JsonApiResource
{
    /**
     * 資源的屬性。
     */
    public $attributes = [
        // ...
    ];

    /**
     * 資源的關係。
     */
    public $relationships = [
        // ...
    ];
}

JSON:API 資源可以像標準資源一樣從路由和控制器返回:

use App\Http\Resources\PostResource;
use App\Models\Post;

Route::get('/api/posts/{post}', function (Post $post) {
    return new PostResource($post);
});

或者,為了方便,您可以使用模型的 toResource 方法:

Route::get('/api/posts/{post}', function (Post $post) {
    return $post->toResource();
});

這將產生一個符合 JSON:API 的回應:

{
    "data": {
        "id": "1",
        "type": "posts",
        "attributes": {
            "title": "Hello World",
            "body": "This is my first post."
        }
    }
}

要返回一個 JSON:API 資源集合,請使用 collection 方法或 toResourceCollection 便利方法:

return PostResource::collection(Post::all());

return Post::all()->toResourceCollection();

定義屬性

有兩種方式可以定義 JSON:API 資源中包含哪些屬性。

最簡單的方法是在資源上定義一個 $attributes 屬性。您可以列出作為值的屬性名稱,這些名稱將直接從基礎模型讀取:

public $attributes = [
    'title',
    'body',
    'created_at',
];

如果某個屬性的計算成本很高,您可以從 toAttributes 返回一個閉包,以便僅在回應中實際需要該屬性時才對其求值。

或者,要完全控制資源的屬性,您可以覆蓋資源上的 toAttributes 方法:

/**
 * 獲取資源的屬性。
 *
 * @return array<string, mixed>
 */
public function toAttributes(Request $request): array
{
    return [
        'title' => $this->title,
        'body' => $this->body,
        'is_published' => fn () => $this->published_at !== null,
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

定義關係

JSON:API 資源支援定義遵循 JSON:API 規範的關係。關係僅在客戶端透過 include 查詢參數請求時才被序列化。

$relationships 屬性

您可以透過資源上的 $relationships 屬性定義資源的可包含關係:

public $relationships = [
    'author',
    'comments',
];

當將關係名稱列為值時,Laravel 將解析相應的 Eloquent 關係並自動發現適當的資源類別。如果您需要明確指定資源類別,您可以將關係定義為鍵/類別對:

use App\Http\Resources\UserResource;

public $relationships = [
    'author' => UserResource::class,
    'comments',
];

或者,您可以覆蓋資源上的 toRelationships 方法:

/**
 * 獲取資源的關係。
 */
public function toRelationships(Request $request): array
{
    return [
        'author' => UserResource::class,
        'comments' => fn () => CommentResource::collection(
            $request->user()->is($this->resource)
                ? $this->comments
                : $this->comments->where('is_public', true),
        ),
    ];
}

使用閉包可以讓您對關係有效載荷有更多控制,同時仍僅在客戶端請求時才解析關係。

包含關係

客戶端可以使用 include 查詢參數請求相關資源:

GET /api/posts/1?include=author,comments

這會產生一個回應,其中 relationships 鍵中包含資源標識物件,頂層 included 陣列中包含完整的資源物件:

{
    "data": {
        "id": "1",
        "type": "posts",
        "attributes": {
            "title": "Hello World"
        },
        "relationships": {
            "author": {
                "data": {
                    "id": "1",
                    "type": "users"
                }
            },
            "comments": {
                "data": [
                    {
                        "id": "1",
                        "type": "comments"
                    }
                ]
            }
        }
    },
    "included": [
        {
            "id": "1",
            "type": "users",
            "attributes": {
                "name": "Taylor Otwell"
            }
        },
        {
            "id": "1",
            "type": "comments",
            "attributes": {
                "body": "Great post!"
            }
        }
    ]
}

巢狀關係可以使用點號表示法包含:

GET /api/posts/1?include=comments.author

關係深度

預設情況下,巢狀關係包含的深度有限制。您可以使用 maxRelationshipDepth 方法自訂此限制,通常在應用程式的其中一個服務提供者中:

use Illuminate\Http\Resources\JsonApi\JsonApiResource;

JsonApiResource::maxRelationshipDepth(3);

資源類型與 ID

預設情況下,資源的 type 派生自資源類別名稱。例如,PostResource 產生類型 postsBlogPostResource 產生 blog-posts。資源的 id 從模型的主鍵解析。

如果您需要自訂這些值,您可以覆蓋資源上的 toTypetoId 方法:

/**
 * 獲取資源的類型。
 */
public function toType(Request $request): string
{
    return 'articles';
}

/**
 * 獲取資源的 ID。
 */
public function toId(Request $request): string
{
    return (string) $this->uuid;
}

當資源的類型應與其類別名稱不同時,這特別有用,例如當 AuthorResource 包裝一個 User 模型並應輸出類型 authors 時。

稀疏欄位集與包含

JSON:API 資源支援稀疏欄位集,允許客戶端使用 fields 查詢參數僅請求每種資源類型的特定屬性:

GET /api/posts?fields[posts]=title,created_at&fields[users]=name

這將僅為 posts 資源包含 titlecreated_at 屬性,為 users 資源包含 name 屬性。

忽略查詢字串

如果您希望為給定的資源回應禁用稀疏欄位集篩選,您可以呼叫 ignoreFieldsAndIncludesInQueryString 方法:

return $post->toResource()
    ->ignoreFieldsAndIncludesInQueryString();

包含先前載入的關係

預設情況下,僅在透過 include 查詢參數請求時才在回應中包含關係。如果您希望不論查詢字串如何都包含所有先前預先載入的關係,您可以呼叫 includePreviouslyLoadedRelationships 方法:

return $post->load('author', 'comments')
    ->toResource()
    ->includePreviouslyLoadedRelationships();

連結與元資料

您可以透過覆蓋資源上的 toLinkstoMeta 方法來向 JSON:API 資源物件添加連結和元資訊:

/**
 * 獲取資源的連結。
 */
public function toLinks(Request $request): array
{
    return [
        'self' => route('api.posts.show', $this->resource),
    ];
}

/**
 * 獲取資源的元資訊。
 */
public function toMeta(Request $request): array
{
    return [
        'readable_created_at' => $this->created_at->diffForHumans(),
    ];
}

這將向回應中的資源物件添加 linksmeta 鍵:

{
    "data": {
        "id": "1",
        "type": "posts",
        "attributes": {
            "title": "Hello World"
        },
        "links": {
            "self": "https://example.com/api/posts/1"
        },
        "meta": {
            "readable_created_at": "2 hours ago"
        }
    }
}

資源回應

如您已經閱讀的,資源可以直接從路由和控制器返回:

use App\Models\User;

Route::get('/user/{id}', function (string $id) {
    return User::findOrFail($id)->toResource();
});

但是,有時您可能需要在發送給客戶端之前自訂傳出的 HTTP 回應。有兩種方法可以實現這一點。首先,您可以在資源上鏈式呼叫 response 方法。此方法將返回一個 Illuminate\Http\JsonResponse 實例,使您可以完全控制回應的標頭:

use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/user', function () {
    return User::find(1)
        ->toResource()
        ->response()
        ->header('X-Value', 'True');
});

或者,您可以在資源本身內定義一個 withResponse 方法。當資源作為最外層資源從回應中返回時,將呼叫此方法:

<?php

namespace App\Http\Resources;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    /**
     * 將資源轉換為陣列。
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
        ];
    }

    /**
     * 自訂資源的傳出回應。
     */
    public function withResponse(Request $request, JsonResponse $response): void
    {
        $response->header('X-Value', 'True');
    }
}