القيم الافتراضية الديناميكية في Eloquent: شرح الدالة defaults() القادمة مع Laravel 14

كل من كتب نماذج Eloquent وصل إلى هذه اللحظة: تريد قيمة افتراضية لا تعرفها إلا وقت التشغيل، مثل لغة التطبيق من ملف الإعدادات، أو تاريخ انتهاء فترة تجريبية بعد 14 يومًا من الآن. تفتح الخاصية $attributes فتكتشف أنها لا تقبل إلا قيمًا ثابتة. في الأول من أكتوبر 2026 نشر Laravel News أن هذه المشكلة لها حل رسمي: دالة جديدة اسمها defaults().

ما الذي حدث بالضبط؟

بحسب مقال Laravel News، أضاف Jack Bayliss عبر الـ Pull Request رقم #61688 دالة محمية (protected) إلى نماذج Eloquent بهذا التوقيع:

protected function defaults(): array

المقال يصف الميزة ضمن Laravel 14، وهي مدموجة في الفرع master. سنعود في آخر المقال إلى معنى ذلك عمليًا.

المشكلة مع $attributes

الخاصية $attributes تقبل تعبيرات ثابتة فقط (constant expressions)، لذلك لا يمكنك فيها استدعاء config() ولا now() ولا فحص Feature flag. أما defaults() فهي دالة تنفَّذ فيها شيفرة حقيقية، فتستطيع استخدام الإعدادات والتواريخ وأعلام الميزات، كما يذكر المقال.

مثال توضيحي

هذا المثال من تصميمي أنا لشرح القواعد، وليس منقولًا من المقال:

use Illuminate\Database\Eloquent\Model;

class Subscription extends Model
{
    protected $attributes = [
        'status' => 'trial',
    ];

    protected function defaults(): array
    {
        return [
            'locale' => config('app.locale'),
            'trial_ends_at' => now()->addDays(14)->toDateTimeString(),
        ];
    }
}

عند إنشاء Subscription جديد تُملأ locale وtrial_ends_at تلقائيًا من الإعدادات ومن الوقت الحالي، بدل أن تكتب هذا المنطق في المُنشئ أو في مكان آخر.

ترتيب الأولوية

عندما تتعارض مصادر القيم، يحدد المقال الترتيب التالي من الأضعف إلى الأقوى:

  1. قيم الخاصية $attributes.
  2. القيم العائدة من defaults()، وهي تَكتب فوق المفاتيح المتطابقة من الخطوة السابقة.
  3. الوسائط التي تمررها إلى المُنشئ أو create() أو fill()، وهي الأعلى أولوية.

يعني هذا أن defaults() لا تمنعك من تحديد القيمة بنفسك: أي قيمة تمررها صراحةً تتغلب عليها.

لا تُحتسب تغييرات «dirty»

نقطة مهمة يذكرها المقال: القيم الافتراضية تُدمج قبل أن يزامن النموذج خصائصه الأصلية (original attributes)، لذلك لا تُعد تغييرات غير محفوظة (dirty). النتيجة العملية أن النموذج الجديد لا يبدو وكأنك عدّلت حقولًا لم تلمسها.

قيم خام، لا casts ولا mutators

مثل $attributes تمامًا، يجب أن تكون القيم في defaults() بالشكل الذي يُخزَّن به الحقل في قاعدة البيانات، فهي لا تمر عبر الـ casts ولا الـ mutators. ولهذا ستعيد التاريخ كنص بصيغة قاعدة البيانات كما في المثال أعلاه. وللحقول من نوع JSON يجب أن تعيد نصًا من json_encode() لا مصفوفة PHP:

protected function defaults(): array
{
    return [
        'preferences' => json_encode(['theme' => 'dark']),
    ];
}

تنبيه: الدالة تعمل مع كل نموذج يُنشأ

يوضح المقال أن defaults() تُنفَّذ في كل مرة يُبنى فيها كائن نموذج، بما في ذلك الصفوف التي تُجلب من قاعدة البيانات، حيث تُستبدل القيم الافتراضية فورًا بالبيانات الفعلية.

وهذا يقودنا إلى استنتاج عملي مني (وليس من المقال): إذا كنت تجلب آلاف الصفوف، فكل عملية داخل defaults() تتكرر لكل صف. فاجعلها خفيفة، وتجنب فيها الاستعلامات وأي عمل مكلف.

متى تستخدمها؟

رأيي الشخصي:

  • استخدمها للقيم الافتراضية التي تعتمد على الإعدادات أو الوقت أو علم ميزة، وتكون رخيصة الحساب.
  • فكّر في بديل آخر إذا كانت القيمة تعتمد على سجلات أخرى أو تتطلب منطقًا مشروطًا ثقيلًا أو آثارًا جانبية، فقد تناسبها الأحداث والـ Observers أكثر.

هل أستعملها اليوم؟

المقال يربط الميزة بـ Laravel 14 ويذكر أنها مدموجة في master. وسجل التغييرات الرسمي لشهر سبتمبر ما زال مكتوبًا تحت Laravel 13.x. لذلك لا تفترض أنها متاحة في إصدارك الحالي: راجع ملاحظات الإصدار الرسمية قبل الاعتماد عليها، ولا تضعها في مشروع إنتاجي قبل أن تتوفر في إصدار مستقر تستخدمه.

الخلاصة

defaults() تسد فجوة صغيرة لكنها مزعجة: قيم افتراضية ديناميكية داخل النموذج نفسه، مرتبة بوضوح بين $attributes وبين ما تمرره أنت، دون أن تُلوّث حالة النموذج بتغييرات «dirty». تذكّر فقط أنها خام (بلا casts)، وأنها تعمل مع كل نموذج يُنشأ.

المصادر