API Resource در Laravel — تبدیل Model به JSON حرفهای
JsonResource، collection، whenLoaded، conditional fields و Pagination wrapper — خروجی API یکدست و امن.
برگرداندن مستقیم Model از API یعنی افشای فیلد داخلی، شکل تاریخ ناهمگون و خطر N+1 در JSON. API Resource (JsonResource) لایه تبدیل رسمی Laravel است تا بازنمایی عمومی از مدل دامنه جدا بماند. اجباری فریمورک نیست، اما برای API پایدار تقریباً استاندارد است. جریان کلی REST در ساخت REST API؛ این مقاله فقط transformation پاسخ را عمیق میکند.
چرا Resource؟
- کلیدهای پایدار برای کلاینت، مستقل از نام ستونهای داخلی
- پنهان کردن فیلدهای حساس یا فقطداخلی
- یکدستسازی تاریخ، پول، enum و ساختار تو در تو
- کنترل صریح رابطههای لودشده
ساخت JsonResource
php artisan make:resource PostResource
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class PostResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
'excerpt' => $this->excerpt,
'published_at' => $this->published_at?->toIso8601String(),
'category' => CategoryResource::make($this->whenLoaded('category')),
'author' => UserResource::make($this->whenLoaded('user')),
'can' => [
'update' => $request->user()?->can('update', $this->resource),
],
];
}
}
در Controller: return new PostResource($post); یا PostResource::make($post).
whenLoaded و رابطه غایب
whenLoaded اگر relation eager نشده باشد، آن کلید را در خروجی نمیآورد و از lazy load تصادفی در حین serialization جلوگیری میکند. در Controller از قبل with() بزنید — Eager Loading.
تفاوت مهم: «لود نشده» معمولاً یعنی کلید نیست؛ با «لود شده ولی null» اشتباه نگیرید.
Collection و Pagination
return PostResource::collection(Post::query()->with('user')->paginate(20));
برای LengthAwarePaginator، Laravel ساختار تقریبی زیر را میسازد (wrapping پیشفرض):
{
"data": [ ... ],
"links": { "first": "...", "next": "..." },
"meta": { "current_page": 1, "last_page": 5 }
}
جزئیات فیلتر و نوع paginator در Pagination و Filtering است؛ اینجا فقط نقطه اتصال Resource مطرح است.
فیلدهای شرطی و پنهانسازی
'email' => $this->when($request->user()?->isAdmin(), $this->email),
'legacy_code' => $this->whenAppended('legacy_code'),
فقط آنچه قرارداد عمومی API است را در toArray بگذارید. فیلد داخلی، hash، پرچم آزمایشی یا ستون موقت را بیرون ندهید.
تاریخ و داده تو در تو
یک قرارداد تاریخ انتخاب کنید (مثلاً ISO-8601) و در همه Resourceها یکسان بمانید. برای تو در تو، Resource جدا (CategoryResource) بهتر از array خام پراکنده است تا شکل entity در چند endpoint یکی بماند.
Wrapping
// AppServiceProvider::boot
JsonResource::withoutWrapping();
پیشفرض Laravel بسیاری پاسخها را زیر کلید data میپیچد. حذف wrapping مجاز است؛ مهم یکدست بودن کل API است، نه سلیقه تکendpoint.
additional برای متای کنار منبع
return (new PostResource($post))->additional([
'meta' => ['api_version' => '1.0'],
]);
تست ساختار JSON
$this->getJson('/api/posts/'.$post->id)
->assertOk()
->assertJsonPath('data.slug', $post->slug)
->assertJsonMissingPath('data.password')
->assertJsonMissingPath('data.remember_token');
تست را روی قرارداد عمومی قفل کنید تا refactor مدل، پاسخ کلاینت را بیخبر نشکند. خطاها و exception JSON جداگانه: API Error Handling.
اشتباهات رایج
- برگرداندن Model/Collection خام
- منطق سنگین کسبوکار داخل
toArray - فراموش
whenLoadedو سپس N+1 در تولید JSON - فرض اینکه Resource جایگزین authentication است — آن Sanctum است
جمعبندی
Resource مرز بین مدل داخلی و قرارداد عمومی API است: کلید پایدار، فیلد کنترلشده، رابطه صریح، تاریخ یکدست. اجباری نیست، اما بدون آن پایداری کلاینت سختتر میشود.