> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Eloquent API 資源

> 說明如何將 Eloquent 模型轉換為一致的 JSON API 回應。從建立資源類別到條件式欄位、與分頁的整合，實務性地介紹。

## API 資源是什麼

在建置 API 時，若把 Eloquent 模型直接以 JSON 回傳，可能會洩漏想隱藏的欄位，或送出用戶端不需要的大量資料。

**Eloquent API 資源**是在模型與 JSON 回應之間插入一層轉換層的機制。你可以在 `toArray()` 方法中明確定義「要以什麼形式、包含哪些內容於回應中」。

```mermaid theme={null}
flowchart LR
  A["Eloquent 模型<br>(User)"] --> B["資源類別<br>(UserResource)"]
  B --> C["JSON 回應<br>{ id, name, email }"]
```

主要優點如下：

* 可完全控管要放入回應的欄位
* 欄位改名、值加工等處理可集中在同一處
* 可依條件決定欄位是否出現
* 可將關聯巢狀化，保持一致的結構

## 建立資源

以 `make:resource` Artisan 指令建立資源類別。

```shell theme={null}
php artisan make:resource UserResource
```

產生的類別會放到 `app/Http/Resources` 目錄。

```php theme={null}
<?php

namespace App\Http\Resources;

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

class UserResource extends JsonResource
{
    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,
        ];
    }
}
```

透過 `$this` 可直接存取模型屬性，因為資源類別內部會把屬性存取代理給模型。

## 在控制器中使用

定義好的資源可從控制器或路由回傳。

```php theme={null}
use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/users/{user}', function (User $user) {
    return new UserResource($user);
});
```

或使用模型的 `toResource()` 方法。

```php theme={null}
Route::get('/users/{user}', function (User $user) {
    return $user->toResource();
});
```

`toResource()` 會依模型名稱自動尋找對應的資源類別（如 `UserResource`）。

預設情況下，回應會被包在 `data` 鍵中。

```json theme={null}
{
  "data": {
    "id": 1,
    "name": "山田 太郎",
    "email": "yamada@example.com",
    "created_at": "2024-01-15T10:00:00.000000Z",
    "updated_at": "2024-01-15T10:00:00.000000Z"
  }
}
```

## 資源集合

當要回傳多筆模型時使用 `collection()` 方法。

```php theme={null}
use App\Http\Resources\UserResource;
use App\Models\User;

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

或使用 Eloquent 集合的 `toResourceCollection()`。

```php theme={null}
return User::all()->toResourceCollection();
```

### 自訂集合資源

若要對整個集合加入 meta 資料，可建立專用的集合資源。

```shell theme={null}
php artisan make:resource UserCollection
```

```php theme={null}
<?php

namespace App\Http\Resources;

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

class UserCollection extends ResourceCollection
{
    public function toArray(Request $request): array
    {
        return [
            'data' => $this->collection,
            'links' => [
                'self' => route('users.index'),
            ],
        ];
    }
}
```

```php theme={null}
Route::get('/users', function () {
    return new UserCollection(User::all());
});
```

## 欄位加工與轉換

在 `toArray()` 內可以改變欄位名稱或加工值。

```php theme={null}
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'full_name' => $this->name,                         // 欄位改名
        'email_address' => $this->email,                    // 欄位改名
        'role' => strtoupper($this->role),                  // 加工值
        'registered_at' => $this->created_at->toDateString(), // 日期格式化
    ];
}
```

## 條件式欄位

### when() — 依條件加入欄位

若想在符合特定條件時才加入某欄位，可用 `when()`。

```php theme={null}
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        // 僅在已登入使用者為管理員時才包含
        'secret_token' => $this->when(
            $request->user()?->isAdmin(),
            $this->secret_token
        ),
    ];
}
```

當 `when()` 條件為 `false` 時，該 key 本身將自回應中移除。

### mergeWhen() — 一次條件加入多個欄位

若要以相同條件一次加入多個欄位，可使用 `mergeWhen()`。

```php theme={null}
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        $this->mergeWhen($request->user()?->isAdmin(), [
            'admin_note' => $this->admin_note,
            'internal_id' => $this->internal_id,
        ]),
    ];
}
```

### whenLoaded() — 只包含已載入的關聯

當關聯已被 Eager Load 時才包含，可在避免 N+1 問題的同時，建立彈性的回應。

```php theme={null}
use App\Http\Resources\PostResource;

public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        // 只有在 posts 已透過 with() 載入時才包含
        'posts' => PostResource::collection($this->whenLoaded('posts')),
    ];
}
```

在控制器端可控制是否要載入關聯：

```php theme={null}
// 包含 posts
return new UserResource($user->load('posts'));

// 不含 posts
return new UserResource($user);
```

### whenCounted() — 有條件地包含計數

包含以 `loadCount()` 取得的關聯計數。

```php theme={null}
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'posts_count' => $this->whenCounted('posts'),
    ];
}
```

```php theme={null}
return new UserResource($user->loadCount('posts'));
```

## 巢狀資源

將關聯以其他資源類別巢狀化，可維持一致的結構。

```php theme={null}
// app/Http/Resources/PostResource.php
class PostResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'body' => $this->body,
            'author' => new UserResource($this->whenLoaded('user')),
            'comments' => CommentResource::collection($this->whenLoaded('comments')),
            'published_at' => $this->published_at?->toDateString(),
        ];
    }
}
```

```php theme={null}
// 控制器
$post = Post::with(['user', 'comments'])->findOrFail($id);

return new PostResource($post);
```

```json theme={null}
{
  "data": {
    "id": 1,
    "title": "以 Laravel 學習 API 資源",
    "author": {
      "id": 5,
      "name": "山田 太郎",
      "email": "yamada@example.com"
    },
    "comments": [
      { "id": 10, "body": "很有幫助！" }
    ]
  }
}
```

## 加入 meta 資料

### with() — 頂層 meta 資料

要為整個集合加入 meta 資料，可覆寫 `with()`。

```php theme={null}
class UserCollection extends ResourceCollection
{
    public function toArray(Request $request): array
    {
        return parent::toArray($request);
    }

    public function with(Request $request): array
    {
        return [
            'meta' => [
                'version' => '1.0',
                'generated_at' => now()->toIso8601String(),
            ],
        ];
    }
}
```

回應範例：

```json theme={null}
{
  "data": [...],
  "meta": {
    "version": "1.0",
    "generated_at": "2024-01-15T10:00:00+00:00"
  }
}
```

### additional() — 動態加入 meta 資料

若想在控制器端動態加入 meta 資料，使用 `additional()`。

```php theme={null}
return User::all()
    ->load('roles')
    ->toResourceCollection()
    ->additional(['meta' => [
        'total_admins' => User::where('role', 'admin')->count(),
    ]]);
```

## 與分頁的結合

只要將分頁結果傳給資源，`meta` 與 `links` 就會自動加上。

```php theme={null}
Route::get('/users', function () {
    return UserResource::collection(User::paginate(15));
});
```

或：

```php theme={null}
return User::paginate(15)->toResourceCollection();
```

回應範例：

```json theme={null}
{
  "data": [
    { "id": 1, "name": "山田 太郎" },
    { "id": 2, "name": "鈴木 花子" }
  ],
  "links": {
    "first": "https://example.com/users?page=1",
    "last": "https://example.com/users?page=5",
    "prev": null,
    "next": "https://example.com/users?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 5,
    "per_page": 15,
    "to": 15,
    "total": 72
  }
}
```

<Info>
  在分頁回應中，即使呼叫了 `withoutWrapping()`，`data` 鍵仍會保留。這是為了與分頁的 `meta`、`links` 共存。
</Info>

## 停用資料包裝

預設情況下最外層的資源會被 `data` 鍵包裝。若要停用，可在 `AppServiceProvider` 的 `boot()` 中呼叫 `withoutWrapping()`。

```php theme={null}
use Illuminate\Http\Resources\Json\JsonResource;

public function boot(): void
{
    JsonResource::withoutWrapping();
}
```

<Warning>
  `withoutWrapping()` 只影響最外層包裝，不會移除你自己定義的 `data` 鍵。
</Warning>

## 實戰範例：使用者 API 的實作

以使用者管理 API 為例，展示如何運用資源設計一致的回應。

```mermaid theme={null}
flowchart TD
  A["GET /api/users"] --> B["UserController@index"]
  C["GET /api/users/:id"] --> D["UserController@show"]
  B --> E["UserResource::collection(paginate)"]
  D --> F["new UserResource(user)"]
  E --> G["{ data: [...], meta, links }"]
  F --> H["{ data: { id, name, email, ... } }"]
```

### UserResource

```php theme={null}
<?php

namespace App\Http\Resources;

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

class UserResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'avatar_url' => $this->avatar_url,
            'role' => $this->role,
            // 只顯示給管理員
            'created_at' => $this->when(
                $request->user()?->isAdmin(),
                $this->created_at->toDateString()
            ),
            // 僅在已載入時包含
            'posts' => PostResource::collection($this->whenLoaded('posts')),
            'posts_count' => $this->whenCounted('posts'),
        ];
    }
}
```

### UserController

```php theme={null}
<?php

namespace App\Http\Controllers\Api;

use App\Http\Resources\UserResource;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;

class UserController extends Controller
{
    public function index(): AnonymousResourceCollection
    {
        $users = User::withCount('posts')->paginate(20);

        return UserResource::collection($users);
    }

    public function show(User $user): UserResource
    {
        $user->loadCount('posts')->load('posts');

        return new UserResource($user);
    }
}
```

## 相關頁面

<Card title="Eloquent 關聯入門" icon="link" href="/zh-TW/eloquent-relationships">
  瞭解關聯的定義方式與 Eager Loading。
</Card>

<Card title="分頁" icon="list" href="/zh-TW/pagination">
  瞭解如何將分頁結果與 API 資源結合。
</Card>


## Related topics

- [Eloquent 存取器、修改器與型別轉換（Casts）](/zh-TW/eloquent-mutators.md)
- [Eloquent 序列化](/zh-TW/eloquent-serialization.md)
- [Laravel 13 新功能彙總](/zh-TW/blog/laravel-13-new-features.md)
- [2026 年 3 月 Laravel 更新](/zh-TW/blog/changelog/202603.md)
- [控制器](/zh-TW/controllers.md)
