الموارد في رابط الكائنات بالعلاقات Eloquent

من موسوعة حسوب
< Laravel
مراجعة 13:49، 23 أكتوبر 2018 بواسطة رؤيا-بنعطية (نقاش | مساهمات)
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)
اذهب إلى التنقل اذهب إلى البحث

مقدمة

عند بناء واجهات برمجة التطبيقات (المدعوة اختصاراً 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');
   }
}

مصادر