الموارد في رابط الكائنات بالعلاقات Eloquent
مقدمة
عند بناء واجهات برمجة التطبيقات (المدعوة اختصاراً APIs)، قد تحتاج إلى طبقة تحويل تقع بين نماذج Eloquent وردود JSON المعادة فعليًّا إلى مستخدمي تطبيقك. تمكّنك الموارد في Laravel من تحويل النماذج بشكل فعال وسهل، بما يتضمن مجموعات النماذج.
توليد الموارد
لتوليد صنف مورد، يمكنك استخدام أمر artisan make:resource
. افتراضيًّا، تقع الموارد في المجلد app/Http/Resources
الخاص بتطبيقك. ترث الموارد من الصنف الأساسي Illuminate\Http\Resources\Json\JsonResource
:
php artisan make:resource User
موارد المجموعات
إضافةً إلى توليد موارد لتحويل النماذج الأحادية، يمكنك توليد موارد مسؤولة عن تحويل مجموعات من النماذج. يمكنك ذلك من تضمين روابط ومعلومات مهمة في الردود والتي تكون متعلقة بالمجموعة ككل للمورد المعطى.
لإنشاء مورد مجموعة، استخدم الخيار collection--
عند إنشاء المورد.أو بتضمين الكلمة Collection
في اسم المورد، الذي يخبر Laravel بإنشاء المورد كمورد مجموعة. ترث موارد المجموعات من الصنف Illuminate\Http\Resources\Json\ResourceCollection
:
php artisan make:resource Users --collection
php artisan make:resource UserCollection
نظرة عامة لمفهوم الموارد
ملاحظة: إن هذا شرح عام على الموارد وموارد المجموعات. من المفضّل جدًا أن تقوم بقراءة الأقسام الأخرى من هذا التوثيق وذلك لضمان فهم أكبر لقوة ومدى التخصيص المزوّدَين من الموارد.
قبل الدخول بالخيارات المتاحة لك عند كتابة الموارد، لنقم بجولة عامة على كيفية استخدام الموارد في Laravel. يمثل صنف المورد نموذجًا وحيدًا يجب أن يحوّل إلى كائن JSON. لنطلع مثلًا على صنف المورد البسيط User
:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class User extends JsonResource
{
/**
* تحويل المورد إلى مصفوفة.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
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\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
موارد المجموعات
في حال أردت إعادة مجموعة من الموارد أو مجموعة مقسمة إلى صفحات، يمكنك استخدام التابع collection
عند إنشاء كائن المورد في مسار أو وحدة تحكم:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return UserResource::collection(User::all());
});
لا يسمح ذلك بإعادة المعلومات المتعلقة بالمجموعة مع الرد. إذا أردت تخصيص الرد المرفق بمورد المجموعة، يمكن أن تنشئ مورد مخصص للمجموعة بحد ذاتها:
php artisan make:resource UserCollection
بعد توليد صنف مورد المجموعة، يمكنك تعريف أي معلومات تريد أن تُضمّن في الرد:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* تحويل مورد المجموعة إلى مصفوفة.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
بعد تعريف مورد المجموعة، يمكن إعادته مباشرةً من مسار أو وحدة تحكم:
use App\User;
use App\Http\Resources\UserCollection;
Route::get('/users', function () {
return new UserCollection(User::all());
});
كتابة الموارد
ملاحظة: في حال لم تطلع على النظرة العامة لمفهوم الموارد، من المفضل أن تطلع عليها قبل الاستكمال بقراءة هذا الجزء من التوثيق.
إن الموارد بسيطة بمضمونها. هي فقط بحاجة إلى تحويل نموذج معين إلى مصفوفة. لذلك، كل مورد يحتوي على التابع toArray
الذي يترجم مصفوفة حقول النموذج إلى مصفوفة API مناسبة يمكن إعادتها للمستخدمين:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class User extends JsonResource
{
/**
* تحويل المورد إلى مصفوفة.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
بعد تعريف المورد، يمكن إعادته مباشرةً من مسار أو وحدة تحكم:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
العلاقات
إذا أردت تضمين علاقات لموارد معينة في ردك، يمكنك إضافتها إلى المصفوفة الموجودة في التابع toArray
. في هذا المثال، سنستخدم التابع collection
الخاص بالمورد Post
لإضافة منشورات المستخدم إلى رد المورد:
/**
* تحويل المورد إلى مصفوفة.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
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,
];
}
ملاحظة: إذا أردت تضمين العلاقات فقط عند تحميلها مسبقًا، اطلع على العلاقات الشرطية أدناه.
موارد المجموعات
بينما تترجم الموارد نموذجًا وحيدًا إلى مصفوفة، تترجم موارد المجموعات مجموعة من النماذج إلى مصفوفة. ليس من الضروري تعريف صنف مورد مجموعة لكل من نماذجك، وذلك بسبب تزويد الموارد للتابع collection
لتوليد مجموعات بسيطة من الموارد بشكل سريع:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return UserResource::collection(User::all());
});
لكن إن أردت تخصيص المعلومات المتعلقة بالمجموعة المعادة للمستخدم، من الضروري تعريف صنف منفصل لمورد المجموعة:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* تحويل مورد المجموعة إلى مصفوفة.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
كالموارد الأحادية، يمكن إعادة موارد المجموعات مباشرةً من المسارات ووحدات التحكم:
use App\User;
use App\Http\Resources\UserCollection;
Route::get('/users', function () {
return new UserCollection(User::all());
});
تغليف البيانات
يغلّف افتراضيًّا القسم الخارجي من موردك في المفتاح data
عند تحويل رد المورد إلى JSON. فعلى سبيل المثال، يبدو رد مورد المجموعة البدائيكالتالي:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28@example.com",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort@example.com",
}
]
}
إذا أردت إلغاء تفعيل التغليف للقسم الخارجي من المورد، يمكنك استخدام التابع withoutWrapping
على صنف المورد الأساسي. بشكل نموذجي، يجب استدعاء هذا التابع في مزود الخدمة الرئيسي AppServiceProvider
أو في مزود خدمة منفصل يُحمّل في كل طلب إلى تطبيقك:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Resources\Json\Resource;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Resource::withoutWrapping();
}
public function register()
{
//
}
}
ملاحظة: يؤثر التابع withoutWrapping
فقط على القسم الخارجي من الرد، ولن يزيل أي مفاتيح data
تضيفها يدويًّا إلى موارد المجموعات.
تغليف الموارد المتداخلة
أنت تملك الحرية المطلقة لتحديد كيفية تغليف علاقات الموارد. إذا أردت تغليف كل موارد المجموعات في المفتاح data
، بغض النظر عن تداخلها، يجب أن تعرف صنف منفصل لمورد المجموعة لكل مورد، وأن تعيد المجموعة ضمن المفتاح data
.
بالطبع، قد تتسائل إن كان ذلك سيتسبب في تغليف القسم الخارجي من موردك في مفتاحي data
. لا تقلق، لن يدع Laravel مواردك تُغلّف نفسها مرّتين في نفس المفتاح، لذلك لا داعي للقلق حيال مستويات التداخل لمورد المجموعة الذي تحوّله:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class CommentsCollection extends ResourceCollection
{
/**
* تحويل مورد المجموعة إلى مصفوفة.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return ['data' => $this->collection];
}
}
تغليف البيانات وتقسيم الصفحات
عند إعادة مجموعة مقسمة إلى صفحات في رد المورد، سيغلّف Laravel بيانات المورد في المفتاح data
حتى عند استدعاء التابع withoutWrapping
. ذلك بسبب أن الردود المقسمة إلى صفحات تحتوي على المفاتيح meta
و links
، مع معلومات عن حالة مقسم الصفحات:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28@example.com",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort@example.com",
}
],
"links":{
"first": "http://example.com/pagination?page=1",
"last": "http://example.com/pagination?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/pagination",
"per_page": 15,
"to": 10,
"total": 10
}
}
تقسيم الصفحات
يمكنك دائماً تمرير كائن من المقسم (Paginator) إلى التابع collection
الخاص بمورد أو إلى مورد مجموعة مخصص:
use App\User;
use App\Http\Resources\UserCollection;
Route::get('/users', function () {
return new UserCollection(User::paginate());
});
تحوي دائمًا الردود المقسمة إلى صفحات على المفاتيح meta
و links
، التي تحتوي على معلومات حول حالة مقسم الصفحات:
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28@example.com",
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort@example.com",
}
],
"links":{
"first": "http://example.com/pagination?page=1",
"last": "http://example.com/pagination?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://example.com/pagination",
"per_page": 15,
"to": 10,
"total": 10
}
}
الخاصيات الشرطية
تحتاج أحيانًا إلى تضمين خاصيات محددة في المورد فقط عند تحقق شرط معيّن. مثلًا، قد تحتاج إلى تضمين قيمة معينة فقط عند كون المستخدم الحالي مديرًا (administrator). يزوّد Laravel بمجموعة من التوابع المساعدة لمساعدتك في تلك الحالة. يستخدم التابع when
لإضافة خاصيات بشكل شرطي إلى المورد:
/**
* تحويل المورد إلى مصفوفة.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'secret' => $this->when(Auth::user()->isAdmin(), 'secret-value'),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
في هذا المثال، سيتم تضمين الخاصية secret
في المورد فقط عندما يعيد التابع isAdmin
للمستخدم المسجل دخوله القيمة true
. في حال أعاد هذا التابع القيمة false
، سيزال هذا المفتاح من رد المورد بشكل كامل قبل إرساله للمستخدم. يمكّنك التابع when
من تعريف المورد بشكل مخصص دون الحاجة إلى اللجوء إلى الجمل الشرطية عند بناء المصفوفة.
يقبل التابع when
نطاقًا مغلقًا كوسيطه الثاني، مما يمكّنك من احتساب القيمة الناتجة عند تحقق الشرط الموجود في الوسيط الأول:
'secret' => $this->when(Auth::user()->isAdmin(), function () {
return 'secret-value';
}),
دمج الخاصيات الشرطية
قد تحتاج أحيانًا إلى دمج مجموعة من الحقول ضمن شرط وحيد ومشترك. في هذه الحالة، يمكنك استخدام التابع mergeWhen
لتضمين الخاصيات في الرد عند تحقق الشرط المعطى:
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
$this->mergeWhen(Auth::user()->isAdmin(), [
'first-secret' => 'value',
'second-secret' => 'value',
]),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
مجدداً، بمجرّد كون الشرط المعطى false
، ستُزال هذه الحقول من رد المورد قبل إرساله إلى المستخدم.
ملاحظة: لا يجب استخدام التابع mergeWhen
ضمن المصفوفات التي تجمع بين المفاتيح الرقمية والنصية. أيضًا، لا يجب استخدامه ضمن المصفوفات ذات المفاتيح الرقمية غير المرتبة بشكل مسلسل.
العلاقات الشرطية
إضافةً إلى التضمين الشرطي للخاصيات، يمكنك تضمين العلاقات شرطيًّا في ردود الموارد، وذلك إن حُمّلت العلاقة فعليًّا على النموذج؛ الأمر الذي يمكّن متحكّمك من تحديد العلاقات التي يجب تحميلها على النموذج، ويمكن للمورد أن يضمّنها مباشرةً إن حُمّلت.
بشكل مثالي، يضمن ذلك تجنّب مشكلة الـ N+1 استعلام ضمن موردك. يمكن استخدام التابع whenLoaded
لتحميل العلاقة شرطيًّا. لضمان عدم تحميل العلاقة بشكل غير ضروري، يقبل هذا التابع اسم العلاقة بدلًا من العلاقة بحد ذاتها:
public function toArray($request)
{
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
من رد المورد قبل إرساله للمستخدم.
المعلومات الوسيطية الشرطية
إضافةً إلى تضمين معلومات العلاقات في ردود الموارد، يمكنك تضمين البيانات من الجداول الوسيطية لعلاقات الكثير إلى كثير شرطيًّاوذلك باستخدام التابع whenPivotLoaded
. يقبل التابع whenPivotLoaded
اسم جدول الكسر كالوسيط الأول. يجب أن يكون الوسيط الثاني نطاقًا مغلقًا يحدد القيمة المعادة في حال كانت المعلومات الوسيطية متاحة على نموذجك:
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'expires_at' => $this->whenPivotLoaded('role_users', function () {
return $this->pivot->expires_at;
}),
];
}
إضافة المعلومات الوصفية
تتطلب بعض معايير واجهات JSON إضافة معلومات وصفية للموارد الأحادية وموارد المجموعات. يتضمن ذلك الخيارات مثل الروابط (links
) للمورد أو الموارد المرتبطة، أو معلومات وصفية حول المورد نفسه. إذا أردت إضافة معلومات وصفية إضافية حول مورد، ضمّنها في التابع toArray
. مثلًا، قد تضمّن معلومات ربطية في المفتاح link
عند التحويل إلى مورد مجموعة:
public function toArray($request)
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
عند إضافة معلومات وصفية إضافية إلى المورد، لست بحاجة إلى التجاوز غير المتعمد للمفاتيح links
و meta
المضافة تلقائيًّا من قبل Laravel عند استخدام الردود المقسمة لصفحات. ستُدمج أي معلومات إضافية تزودها في المفتاح links
ضمن المفتاح المزود مسبقًا من قبل مقسم الصفحات.
المعلومات الوصفية عالية المستوى
قد تضطر في بعض الحالات إلى تضمين معلومات وصفية محددة ضمن رد مورد فقط عندما يكون المورد ضمن القسم الخارجي من الرد المعاد. بشكل نموذجي، يتضمن ذلك المعلومات الوصفية حول الرد ككل. لتعريف المعلومات الوصفية هذه، عرف التابع with
في صنف المورد. يجب أن يعيد هذا التابع مصفوفة من البيانات الوصفية المراد تضمينها في رد المورد فقط عندما يكون المورد في القسم الخارجي من الرد الذي يُعالج:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* تحويل مورد المجموعة إلى مصفوفة.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
/**
* الحصول على معلومات إضافية ليتم تضمينها في مصفوفة المورد.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function with($request)
{
return [
'meta' => [
'key' => 'value',
],
];
}
}
إضافة بيانات وصفية عند بناء الموارد
يمكنك أيضًا إضافة بيانات وصفية عالية المستوى عند بناء كائنات الموارد في المسار أو وحدة التحكم . يقبل التابع additional
المتاح في كل الموارد،مصفوفة من البيانات الوصفية الوارد إضافتها إلى رد المورد:
return (new UserCollection(User::all()->load('roles')))
->additional(['meta' => [
'key' => 'value',
]]);
ردود الموارد
كما قرأت مسبقًا، يمكن إعادة الموارد مباشرةً من المسارات ووحدات التحكم:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
لكن، قد تحتاج أحيانًا إلى تخصيص الرد المعاد قبل إرساله للمستخدم. هناك طريقتان لتحقيق ذلك. أولًا، يمكنك سلسلة التابع response
على المورد، إذ يعيد هذا التابع كائن من نوع Illuminate\Http\Response
، الأمر الذي يمكّنك من التحكم بترويسات الرد بشكل كامل:
use App\User;
use App\Http\Resources\User as UserResource;
Route::get('/user', function () {
return (new UserResource(User::find(1)))
->response()
->header('X-Value', 'True');
});
أيضاً، يمكنك تعريف التابع withResponse
ضمن المورد نفسه؛ إذ يُستدعى هذا التابع عند إعادة المورد في القسم الخارجي من الرد:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class User extends JsonResource
{
/**
* تحويل المورد إلى مصفوفة.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
];
}
/**
* تخصيص الرد المعاد من أجل المورد.
*
* @param \Illuminate\Http\Request
* @param \Illuminate\Http\Response
* @return void
*/
public function withResponse($request, $response)
{
$response->header('X-Value', 'True');
}
}