مقدمة إلى رابط الكائنات بالعلاقات Eloquent

من موسوعة حسوب

مقدمة

يزود رابط الكائنات بالعلاقات Eloquent ORM المضمّن في Laravel تنفيذًا بسيطًا من أجل قواعد البيانات التي تتعامل مع الـ ActiveRecord. كل جدول في قاعدة البيانات يقابله نموذج (Model) يُستخدم للتفاعل مع هذا الجدول. تمكّنك النماذج من تنفيذ الاستعلامات على البيانات الموجودة في الجداول، إضافةً إلى إضافة البيانات الجديدة.

قبل البدء، تأكد من تهيئة اتصال قاعدة البيانات في الملف config/database.php. للمزيد من المعلومات حول تهيئة قاعدة البيانات، تفقّد التوثيق المناسب.

تعريف النماذج

للبدء، لننشئ نموذج Eloquent. تتواجد النماذج افتراضيًّا في المجلد app، لكن يمكنك وضعها في أي مكان قابل للتحميل تلقائيًّا بناءً على إعدادات composer.json الخاصة بك. ترث جميع نماذج Eloquent من الصنف Illuminate\Database\Eloquent\Model.

إن أسهل طريقة لإنشاء نموذج هي باستخدام الأمر artisan make:model:

php artisan make:model Flight

في حال أردت أن تولّد تهجير قاعدة بيانات عند إنشاء النموذج، يمكنك استخدام الخيار migration-- أو m-:

php artisan make:model Flight --migration

php artisan make:model Flight -m

أعراف نماذج Eloquent

لنطّلع الآن على مثال النموذج Flight، الذي سنستخدمه لقراءة وكتابة البيانات من الجدول flights الموجود في قاعدة البيانات:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{

  //

}

أسماء الجداول

لاحظ أننا لم نُعيّن اسم الجدول الذي سيُستخدم من قبل النموذج Flight. كعرف مستخدم في Eloquent، سيُستخدم جمع اسم النموذج بصيغة snake case كاسم الجدول المقابل للنموذج، في حال لم يُعيّن اسم جدول يدويًّا. لذلك في هذه الحالة سيتعرّف Eloquent على اسم الجدول المقابل للنموذج كـ flights. يمكنك تعيين اسم الجدول يدويًّاعن طريق تعريف الخاصية table في النموذج الخاص بك:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{

   /**
    * الجدول المقابل للنموذج
    *
    * @var string
    */

   protected $table = 'my_flights';

}

المفاتيح الرئيسية

يعتقد Eloquent أيضًا أن كل جدول يمتلك حقل مفتاح يدعى id. يمكنك تجاوز هذا العرف عن طريق تعريف الخاصية المحمية primaryKey.

علاوةً على ذلك، يعتقد Eloquent أن المفتاح الرئيسي هو عدد صحيح متزايد، مما يعني أن المفتاح الرئيسي سيُحوّل للنوع int تلقائيًّا. في حال أردت تعريف مفتاح رئيسي غير متزايد أو غير عددي، عيّن الخاصية العامة incrementing في النموذج الخاص بك إلى القيمة false. في حال كان المفتاح الرئيسي ليس عددًا صحيحًا، يجب أن تعيّن الخاصية المحمية keyType إلى string.

بصمات الوقت timestamps

يتوقع Eloquent بشكل افتراضي وجود الحقلين created_at و updated_at في جداولك. في حال لم تكن بحاجة لهذه الحقول، عيّن الخاصية timestamps إلى القيمة false:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
 
    /**
    * تدل على كون الجداول تحمل الطوابع الزمنية.
    *
    * @var bool
    */
    public $timestamps = false;

}

في حال أردت تعديل نمط عرض الطوابع الزمنية الخاصة بك، قم بتعريف الخاصية dateFormat على نموذجك. تحدد هذه الخاصية كيفية تخزين الخاصيات الزمنية في قاعدة البيانات، إضافةً إلى نمطها عند سلسلة النماذج إلى مصفوفات JSON:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{

   /**
    * نمط تخزين الخاصيات الزمنية
    *
    * @var string
    */
   protected $dateFormat = 'U';

}

إذا أردت تعديل أسماء الحقول المستخدمة لتخزين بصمات الوقت، يمكنك تعيين الثوابت CREATED_AT و UPDATED_AT على نموذجك:

<?php

class Flight extends Model
{
   const CREATED_AT = 'creation_date';
   const UPDATED_AT = 'last_update';
}

اتصال قاعدة البيانات

يستخدم Eloquent الاتصال الافتراضي لقاعدة البيانات المهيّأ في تطبيقك. في حال أردت تحديد اتصال مختلف للنموذج، استخدم الخاصية connection:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
   /**
    * اسم الاتصال المستخدم للنموذج
    *
    * @var string
    */

   protected $connection = 'connection-name';

}

استرداد البيانات من النماذج

بعد إنشاء النموذج والجدول المقابل له في قاعدة البيانات، يمكنك البدء باسترداد البيانات من قاعدة بياناتك. اعتبر كل نموذج Eloquent منشئًا قويًّا للاستعلامات يمكّنك من استعلام الجدول المقابل للنموذج بطلاقة. على سبيل المثال:

<?php

use App\Flight;

$flights = App\Flight::all();

foreach ($flights as $flight) {
   echo $flight->name;
}

إضافة قيود إضافية

سيعيد التابع all الموجود في Eloquent جميع النتائج من جدول النموذج. بما أن كل نموذج Eloquent يمكنك اعتباره منشئ استعلامات، يمكنك أيضًا إضافة القيود إلى الاستعلامات، ثم استخدام التابع get لاسترداد النتائج:

$flights = App\Flight::where('active', 1)
              ->orderBy('name', 'desc')
              ->take(10)
              ->get();

ملاحظة: بما أن نماذج Eloquent هي بواني استعلامات، يجب عليك مراجعة جميع التوابع الموجودة في منشئ الاستعلامات. يمكنك استخدام أي من هذه التوابع في استعلامات Eloquent.

المجموعات

من أجل توابع Eloquent كـ all و get، التي تعيد أكثر من نتيجة، عاد كائن من نوع Illuminate\Database\Eloquent\Collection. يزوّد الصنف Collection بالعديد من التوابع المساعدة للعمل مع النتائج المعادة من Eloquent:

$flights = $flights->reject(function ($flight) {
   return $flight->cancelled;
});

طبعًا، يمكنك المرور على المجموعة تمامًا مثل المصفوفة:

foreach ($flights as $flight) {
   echo $flight->name;
}

تقطيع النتائج

إذا أردت معالجة آلاف السجلات، استخدم التابع chunk، الذي يقوم بإعادة "قطع" من سجلات Eloquent، ويمررها للنطاق المغلق المعطى من أجل المعالجة. يؤدي استخدام التابع chunk إلى الحفاظ على الذاكرة عند العمل مع مجموعات كبيرة من النتائج:

Flight::chunk(200, function ($flights) {
   foreach ($flights as $flight) {
      //
   }
});

يحدد أول وسيط ممرر للتابع عدد السجلات المراد وضعها في كل قطعة. أما عن للنطاق المغلق الممرر بالوسيط الثاني، فسيُنادى من أجل كل قطعة مستردة. سيُنفّذ استعلام لقاعدة البيانات من أجل كل قطعة.

استخدام المؤشرات

يمكّنك التابع cursor من المرور على سجلات قاعدة البيانات باستخدام مؤشر، الذي سينفذ استعلامًا وحيدًا. عند العمل مع كميات كبيرة من البيانات، قد يقلل التابع cursor من استخدام الذاكرة بشكل كبير:

foreach (Flight::where('foo', 'bar')->cursor() as $flight) {
  //
}

استرداد نماذج وحيدة / مجاميع

بالطبع، إضافةً إلى استرداد جميع السجلات من جدول محدد، يمكنك أيضًا استرداد سجلات وحيدة باستخدام التوابع find أو first. بدلاً من إعادة مجموعة نتائج، تعيد هذه التوابع نتيجة وحيدة ممثلة بكائن نموذج وحيد:

// استرداد سجل باستخدام مفتاحه الرئيسي..
$flight = App\Flight::find(1);

// استرداد أول سجل يحقق الشروط المعطاة..
$flight = App\Flight::where('active', 1)->first();

يمكنك أيضًا مناداة التابع find من أجل مصفوفة من المفاتيح الرئيسية، مما يعيد مجموعة من السجلات المحققة:

$flights = App\Flight::find([1, 2, 3]);

استثناءات عدم التوفّر Not found

قد تحتاج في بعض الأوقات إلى رمي استثناء عند عدم توفّر نموذج. يساعد ذلك بشكل محدد في المسارات ووحدات التحكم. تعيد التوابع findOrFail و firstOrFail أول سجل من الاستعلام؛ لكن في حال عدم وجود أي نتيجة، سيُرمى استثناء من نوع Illuminate\Database\Eloquent\ModelNotFoundException:

$model = App\Flight::findOrFail(1);

$model = App\Flight::where('legs', '>', 100)->firstOrFail();

في حال لم يُعالج الاستثناء، سيُعاد رد HTTP 404 للمستخدم. ليس من الضروري كتابة تحققات إضافية لإعادة رد 404 عند استخدام هذه التوابع:

Route::get('/api/flights/{id}', function ($id) {
   return App\Flight::findOrFail($id);
});

استرداد المجاميع

يمكنك استخدام التوابع count و sum و max، وتوابع المجاميع الأخرى المزودة في منشئ الاستعلامات. تعيد هذه التوابع القيمة العددية المناسبة بدلًا من كائن نموذج كامل:

$count = App\Flight::where('active', 1)->count();

$max = App\Flight::where('active', 1)->max('price');

إضافة وتعديل النماذج

الإضافات

لإنشاء سجل جديد في قاعدة البيانات، أنشئ كائن نموذج جديد، عيّن الخاصّيات على النموذج، ثم استدعِ التابع save:

<?php

namespace App\Http\Controllers;

use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class FlightController extends Controller
{

   /**
    * إنشاء كائن flight جديد
    *
    * @param  Request $request
    * @return Response
    */

   public function store(Request $request)
   {

      // تحقق من الطلب..

       $flight = new Flight;
       $flight->name = $request->name;
       $flight->save();
   }
}

في هذا المثال، عيّنّا الخاصية name المحددة في النموذج App\Flight إلى الوسيط name القادم من طلب الـ HTTP. عند استدعاء التابع save، سيضاف سجل إلى قاعدة البيانات. يتم تعيين الحقلين created_at و updated_at تلقائياً عند استدعاء التابع save، لذا ما من داع لتعيينها يدوياً.

التعديلات

يمكن استخدام التابع save أيضاً لتعديل السجلات الموجودة مسبقًا في قاعدة البيانات. لتعديل سجل، يجب أن تسترده، ثم تعيّن الخاصيات المراد تعديلها، ثم تستدعي التابع save. مجددًا، يُعيّن الحقل updated_at تلقائيًّاعند تعديل سجل ما.

$flight = App\Flight::find(1);

$flight->name = 'New Flight Name';

$flight->save();

التعديلات الجماعية

يمكن تنفيذ التعديلات على مجموعة من السجلات الموافقة لاستعلام معطى. في هذا المثال، جميع السجلات التي تحمل قيمة active معينة إلى 1 وتملك destination معين إلى San Diego، ستُعيّن إلى delayed:

App\Flight::where('active', 1)
         ->where('destination', 'San Diego')
         ->update(['delayed' => 1]);

يستقبل التابع update مصفوفة من الحقول والقيم المراد تعديلها للسجل.

ملاحظة: عند تنفيذ تعديل جماعي عن طريق Eloquent، لن تُشغّل الأحداث saved و updated من أجل السجلات المعدلة. هذا بسبب أن السجلات المعدلة لا تُستردّ عند تنفيذ تعديل جماعي.

التعيين الجماعي

يمكنك استخدام التابع create لحفظ سجل جديد في سطر وحيد. يُعاد السجل المضاف ككائن نموذج من التابع. لكن قبل القيام بذلك، يجب أن تُعيّن أحد الخاصيات fillable أو guarded على نموذجك، بسبب حماية نماذج Eloquent افتراضيًّا من التعيين الجماعي.

تحدث ثغرة التعيين الجماعي عند تمرير مستخدم لوسيط HTTP غير متوقع ضمن الطلب، مما يغير من حقل في الجدول بشكل غير متوقع. على سبيل المثال، يمرّر مستخدم خبيث وسيط is_admin عن طريق طلب الـ HTTP، الذي يُمرّر إلى التابع create، مما يجعل المستخدم قابلًا على تعيين نفسه كمدير.

لذلك، عيّن خاصيات النموذج التي تريد تُعيّن جماعيًّا. يمكنك القيام بذلك عن طريق تعريف الخاصية fillable على نموذجك. على سبيل المثال، لنجعل الخاصية name الخاصة بالنموذج Flight قابلة للتعيين الجماعي:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{

   /**
    * الخاصيات القابلة للتعيين الجماعي.
    *
    * @var array
    */

   protected $fillable = ['name'];

}

عند تحديد الخاصيات القابلة للتعيين الجماعي، يمكننا استخدام التابع create لإنشاء سجل جديد في قاعدة البيانات. يعيد التابع create الكائن المضاف:

$flight = App\Flight::create(['name' => 'Flight 10']);

في حال كان لديك بالفعل كائن نموذج، يمكنك استخدام التابع fill لملئه بمصفوفة الخاصيات:

$flight->fill(['name' => 'Flight 22']);

حماية الخاصيات

بما أن الخاصية fillable يمكن اعتبارها قائمة بيضاء للخاصيات التي يمكن تعيينها جماعيًّا، يمكنك استخدام الخاصية guarded. تحتوي الخاصية guarded على مصفوفة من الخاصيات التي لا تريد أن تُعيّن جماعيًّا. جميع الخاصيات الأخرى يمكن تعيينها جماعيًّا. لذلك، يمكن اعتبار الخاصية guarded قائمة سوداء. بالطبع، يجب عليك استخدام أحد الخاصيتين guarded أو fillable - ليس الاثنين معًا. في المثال الموضّح أدناه، جميع الخاصيات ما عدا الخاصية price قابلة للتعيين الجماعي:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{

   /**
    * الخاصيات غير القابلة للتعيين الجماعي.
    *
    * @var array
    */

   protected $guarded = ['price'];

}

إذا أردت جعل جميع الخاصيات قابلة للتعيين الجماعي، يمكنك تعيين الخاصية guarded إلى مصفوفة فارغة.

protected $guarded = [];

توابع الإنشاء الأخرى

firstOrCreate / firstOrNew

هناك تابعين آخرين يمكنك استخدامها لإنشاء السجلات عن طريق التعيين الجماعي للخاصيات: firstOrCreate و firstOrNew. يحاول التابع firstOrCreate أن يجد السجل الموافق للحقول والقيم المحددة. في حال عدم وجود السجل المطلوب، سينشأ باستخدام الخاصيات والقيم المحددة باستخدام الوسيط الأول، إضافةً إلى الخاصيات والقيم الاختيارية المحددة في الوسيط الثاني.

يحاول التابع firstOrNew، بشكل مشابه للتابع firstOrCreate، أن يجد السجل الموافق للخاصيات المحددة، وفي حال عدم وجود هذا السجل، سيعيد كائن نموذج جديد. تجب الملاحظة هنا أن الكائن المعاد بالتابع firstOrNew لم يُحفظ في قاعدة البيانات بعد، إذ يجب أن تستدعي التابع save يدويًّا لحفظه.

// استرداد السجل باستخدام الاسم، أو إنشاؤه في حال عدم وجوده
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);

// استرداد السجل بالاسم، أو إنشاؤه بالاسم والخاصية delayed..
$flight = App\Flight::firstOrCreate(
   ['name' => 'Flight 10'], ['delayed' => 1]
);

// استرداد بالاسم، أو إنشاء كائن جديد
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);

// استرداد بالاسم، أو إنشاء كائن جديد بالاسم والخاصية delayed..
$flight = App\Flight::firstOrNew(
   ['name' => 'Flight 10'], ['delayed' => 1]
);

updateOrCreate

قد تمر أيضًا ببعض الحالات التي يجب عليك فيها أن تحدث سجل موجود أو تنشئه في حال عدم وجوده. يزود Laravel التابع updateOrCreate لفعل ذلك في خطوة واحدة. مثل التابع firstOrCreate، يحفظ التابع updateOrCreate السجل مباشرةً في قاعدة البيانات، لذا ما من داع لمناداة التابع save:

// إذا كان هناك سجل موافق للخاصيات الموجودة في الوسيط الأول، عين السعر إلى 99
// في حال عدم وجود السجل، أنشئه

$flight = App\Flight::updateOrCreate(
   ['departure' => 'Oakland', 'destination' => 'San Diego'],
   ['price' => 99]
);

حذف النماذج

لحذف نموذج ما، استدع التابع delete على كائن نموذج:

$flight = App\Flight::find(1);

$flight->delete();

حذف نموذج موجود عن طريق المفتاح

في المثال الموضح أعلاه، قمنا باسترداد السجل من قاعدة البيانات قبل استدعاء التابع delete. لكن في حال كان المفتاح الرئيسي للسجل معلوم لديك، يمكنك حذف السجل دون استرداده. لفعل ذلك، استدعِ التابع destroy:

App\Flight::destroy(1);

App\Flight::destroy([1, 2, 3]);

App\Flight::destroy(1, 2, 3);

حذف السجلات عن طريق الاستعلام

بالطبع، يمكنك تنفيذ تعليمة حذف على مجموعة من السجلات. في المثال الموضح أدناه، سنحذف جميع سجلات الـ flights المحددة كغير نشطة (active = 0). بشكل مشابه للتعديل الجماعي، لن يشغل الحذف الجماعي أي أحداث للسجلات المحذوفة:

$deletedRows = App\Flight::where('active', 0)->delete();

الحذف الناعم (Soft Deleting)

إضافةً إلى حذف السجلات فعليًّا من قاعدة البيانات، يمكن لـ Eloquent أن تحذف السجلات "حذفاً ناعمًا". عند الحذف الناعم للسجل، لن تُحذف فعليًّا، وإنما ستُعيّن الخاصية deleted_at على السجل وتُحفظ في قاعدة البيانات. في حال كان السجل يملك قيمة deleted_at غير فارغة non-null، هذا يعني أنه قد حُذف. لتمكين الحذف الناعم لسجل ما، استخدم السمة Illuminate\Database\Eloquent\SoftDeletes على النموذج وأضف الحقل deleted_at إلى الخاصية dates:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Flight extends Model
{

   use SoftDeletes;

   /**
    * الخاصيات التي يجب تحويلها إلى تواريخ.
    *
    * @var array
    */

   protected $dates = ['deleted_at'];

}

بالطبع، يجب عليك إضافة الحقل deleted_at إلى جدول قاعدة البيانات. يزوّد منشئ المخططات الخاص بـ Laravel بتابع مساعد لإنشاء هذا السجل:

Schema::table('flights', function ($table) {
   $table->softDeletes();
});

الآن، عند استدعائك للتابع delete على سجل من النموذج، ستُعيّن الخاصية deleted_at إلى التاريخ والوقت الحالي. علاوةً على ذلك، عند تنفيذ الاستعلامات على النموذج الذي يستخدم الحذف الناعم، ستُقصى السجلات المحذوفة ناعمًا تلقائيًّا من نتائج الاستعلام. لمعرفة فيما إذا تم الحذف الناعم لكائن نموذج محدد، استخدم التابع trashed:

if ($flight->trashed()) {
  //
}

استعلام السجلات المحذوفة ناعمًا

تضمين السجلات المحذوفة ناعمًا

كما ذُكر أعلاه، ستُقصى السجلات المحذوفة ناعمًا تلقائيًّا من نتائج الاستعلام. لكن، في حال أردت أن تضمن هذه السجلات في نتائج الاستعلام، استخدم التابع withTrashed على الاستعلام:

$flights = App\Flight::withTrashed()
               ->where('account_id', 1)
               ->get();

يمكن أيضًا استخدام التابع withTrashed على استعلام علائقي:

$flight->history()->withTrashed()->get();

استرداد السجلات المحذوفة ناعمًا فقط

يستردّ التابع onlyTrashed السجلات المحذوفة ناعمًا فقط:

$flights = App\Flight::onlyTrashed()
               ->where('airline_id', 1)
               ->get();

استعادة السجلات المحذوفة ناعمًا

في بعض الأوقات، تحتاج إلى استعادة سجل ما من الحذف الناعم. للقيام بذلك، استخدم التابع restore على كائن النموذج:

$flight->restore();

يمكنك أيضًا استخدام التابع restore على استعلام لاستعادة مجموعة من السجلات المحذوفة. مجددًا، كما هي الحال في جميع العمليات الجماعية، لن يشغل هذا أي أحداث من أجل السجلات المستعادة:

App\Flight::withTrashed()
       ->where('airline_id', 1)
       ->restore();

بشكل مشابه للتابع withTrashed، يمكن استخدام التابع restore على العلاقات:

$flight->history()->restore();

حذف السجلات بشكل دائم

في بعض الأوقات، تحتاج إلى حذف سجل ما بشكل فعلي من قاعدة البيانات. للقيام بذلك، استخدم التابع forceDelete:

// الحذف الدائم لكائن وحيد..
$flight->forceDelete();

// الحذف الدائم لكل الكائنات المرتبطة..
$flight->history()->forceDelete();

نطاقات الاستعلام

النطاقات العامة

تمكنك النطاقات العامة من إضافة القيود على جميع الاستعلامات المنفذة على نموذج معين. تستخدم تقنية الحذف الناعم الخاصة بـ Laravel النطاقات العامة لاسترداد فقط السجلات غير المحذوفة من قاعدة البيانات. تمكنّك النطاقات العامة أن تضمن أن جميع الاستعلامات المنفذة على السجل تستقبل الشروط المحددة.

كتابة النطاقات العامة

إن كتابة النطاقات العامة أمر بسيط. اكتب صنفا يستخدم الواجهة Illuminate\Database\Eloquent\Scope. تتضمن هذه الواجهة تابعًا وحيدًا يجب كتابته، وهو التابع apply. يمكن استخدام التابع apply لإضافة قيود where على الاستعلام:

<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class AgeScope implements Scope
{
   /**
    * تطبيق المجال على منشئ الاستعلامات المعطى.
    *
    * @param  \Illuminate\Database\Eloquent\Builder  $builder
    * @param  \Illuminate\Database\Eloquent\Model  $model
    * @return void
    */
   public function apply(Builder $builder, Model $model)
   {
       $builder->where('age', '>', 200);
   }
}

ملاحظة: في حال كان النطاق العام الخاص بك يضيف سجلات لجملة الـ select الخاصة باستعلامك، يجب أن تستخدم التابع addSelect بدلًا من select. سيحول ذلك دون التبديل غير المتوقع لجمل الـ select المكتوبة مسبقًا للاستعلام.

تطبيق النطاق العام

لتعيين نطاق عامّ لنموذج، أعد تعيين التابع boot الخاص بالنموذج واستخدم التابع addGlobalScope:

<?php

namespace App;

use App\Scopes\AgeScope;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
   /**
    * تابع “التشغيل” الخاص بالنموذج
    *
    * @return void
    */
   protected static function boot()
   {
       parent::boot();
       static::addGlobalScope(new AgeScope);
   }
}

بعد إضافة المجال، سيحوّل الاستدعاء User::all إلى الـ SQL التالي:

select * from `users` where `age` > 200

النطاقات العامة المجهولة

يمكّنك Eloquent أيضًا من كتابة النطاقات العامة باستخدام النطاقات المغلقة، التي تبسط الأمر عند الحاجة لإنشاء النطاقات البسيطة التي لا تحتاج إلى أصناف منفصلة.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class User extends Model
{
   /**
    * تابع “التشغيل” الخاص بالنموذج.
    *
    * @return void
    */
   protected static function boot()
   {
       parent::boot();

       static::addGlobalScope('age', function (Builder $builder) {
           $builder->where('age', '>', 200);
       });
   }
}

إزالة النطاقات العامة

في حال أردت إزالة نطاق عام من استعلام معين، يمكنك استخدام التابع withoutGlobalScope. يقبل هذا التابع اسم صنف النطاق العام كوسيطه الوحيد:

User::withoutGlobalScope(AgeScope::class)->get();

أو في حال عرّفت النطاق باستخدام النطاق المغلق:

User::withoutGlobalScope('age')->get();

في حال أردت إزالة مجموعة أو كل النطاقات العامة، استخدم التابع withoutGlobalScopes:

// إزالة جميع المجالات العامة..
User::withoutGlobalScopes()->get();

// إزالة بعض المجالات العامة..
User::withoutGlobalScopes([
   FirstScope::class, SecondScope::class
])->get();

النطاقات الخاصة

تمكّنك النطاقات الخاصة من تعيين مجموعة مكررة من القيود التي يمكنك إعادة استخدامها بشكل سلس ضمن تطبيقك. على سبيل المثال، قد تحتاج إلى استرداد المستخدمين المعيّنين كشعبيين "popular". لتعريف نطاق، أنشئ تابعًافي صنف النموذج باسم تسبقه الكلمة scope.

يجب على النطاقات دائمًا أن تعيد كائنًا منشئ استعلامات:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
   /**
    * نطاق لإعادة المستخدمين الشعبيين فقط.
    *
    * @param \Illuminate\Database\Eloquent\Builder $query
    * @return \Illuminate\Database\Eloquent\Builder
    */
   public function scopePopular($query)
   {
       return $query->where('votes', '>', 100);
   }

   /**
    * نطاق لإعادة المستخدمين المفعلّين فقط.
    *
    * @param \Illuminate\Database\Eloquent\Builder $query
    * @return \Illuminate\Database\Eloquent\Builder
    */
   public function scopeActive($query)
   {
       return $query->where('active', 1);
   }
}

استخدام نطاق خاص

بعد تعريف النطاق، يمكنك مناداة تابع النطاق عند القيام بالاستعلامات. لكنك لست بحاجة لتضمين الكلمة scope عند استدعاء التابع. يمكنك أيضًا سلسلة الاستدعاءات لمجموعة من النطاقات الخاصة، على سبيل المثال:

$users = App\User::popular()->active()->orderBy('created_at')->get();

النطاقات الديناميكية

قد تحتاج أحيانًا إلى إنشاء نطاق يقبل وسائط. للبدء، أضف وسائطك الإضافية في النطاق. يجب تعيين الوسائط الإضافية بعد الوسيط query:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
   /**
    * نطاق لتضمين المستخدمين من النوع المعطى فقط.
    *
    * @param \Illuminate\Database\Eloquent\Builder $query
    * @param mixed $type
    * @return \Illuminate\Database\Eloquent\Builder
    */
   public function scopeOfType($query, $type)
   {
       return $query->where('type', $type);
   }
}

الآن، يمكنك تمرير الوسائط عند استدعاء النطاق:

$users = App\User::ofType('admin')->get();

مقارنة السجلات

قد تحتاج أحيانًا إلى معرفة فيما إذا كان سجلّان محددّان متطابقين. يمكن استخدام التابع is لتحديد فيما إذا كان السجلّان متطابقين بالمفتاح الرئيسي والجدول واتصال قاعدة البيانات:

if ($post->is($anotherPost)) {

  //

}

الأحداث

يشغّل Eloquent مجموعة من الأحداث، مما يمكّنك من مراقبة دورة حياة النموذج: retrieved و creating و created و updating و updated و saving و saved و deleting و deleted و restoring و restored. تمكّنك الأحداث من تنفيذ تعليمات محددة في كل مرة يحفظ أو يعدّل على قاعدة البيانات.

يشغّل الحدث retrieved عند استرداد سجل ما من قاعدة البيانات. عند إنشاء سجل جديد في قاعدة البيانات، سيتم تشغيل الحدثين created و creating. في حال كان السجل موجود في قاعدة البيانات، وعدّل، ستشغّل الأحداث updating و updated. وفي كلتا الحالتين السابقتين، ستشغّل الأحداث saving و saved.

للبدء، عيّن الخاصية dispatchedEvents على نموذج Eloquent، التي تحدد الأصناف التي تستقبل الأحداث المعطاة:

<?php

namespace App;

use App\Events\UserSaved;
use App\Events\UserDeleted;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{

   use Notifiable;

   /**
    * مصفوفة الأحداث الخاصة بالنموذج.
    *
    * @var array
    */
   protected $dispatchesEvents = [
       'saved' => UserSaved::class,
       'deleted' => UserDeleted::class,
   ];
}

المراقبون (Observers)

تعريف المراقبين

في حال التنصّت لمجموعة من الأحداث على سجل ما، يمكنك استخدام المراقبين لجمع كافة المنصتات في صنف وحيد. تحتوي أصناف المراقبين على التوابع التي تحمل أسماء أحداث Eloquent الوارد التنصّت إليها. كل من هذه التوابع تستقبل كائن من النموذج كوسيطها الوحيد. يمكن استخدام أمر artisan make:observer لتعريف صنف مراقب جديد.

php artisan make:observer UserObserver --model=User

سيقوم هذا الأمر بإنشاء مراقب جديد في المجلد App/Observers. في حال عدم وجود هذا المجلد، سينشئه Artisan. سيبدو المراقب الجديد كالتالي:

<?php

namespace App\Observers;

use App\User;

class UserObserver
{
   /**
    * معالجة الحدث “created”.
    *
    * @param  \App\User $user
    * @return void
    */
   public function created(User $user)
   {
      //
   }

   /**
    * معالجة الحدث “updated”.
    *
    * @param  \App\User $user
    * @return void
    */
   public function updated(User $user)
   {
      //
   }

   /**
    * معالجة الحدث “deleted”.
    *
    * @param  \App\User $user
    * @return void
    */
   public function deleted(User $user)
   {
      //
   }
}

لتسجيل مراقب ما، استخدم التابع observe على النموذج المراد مراقبته. يمكنك تسجيل المراقبين في التابع boot الخاص بأحد مزودات الخدمة. في هذا المثال، سنسجل المراقب في مزود الخدمة الأساسي AppServiceProvider:

<?php

namespace App\Providers;

use App\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
   /**
    * تشغيل أي خدمات خاصة بالتطبيق.
    *
    * @return void
    */
   public function boot()
   {
       User::observe(UserObserver::class);
   }

   /**
    * تسجيل مزودات الخدمة.
    *
    * @return void
    */
   public function register()
   {
      //
   }
}

مصادر