Filament 5.8: تحميل الـ Schema المؤجّل بـ deferLoading وأهم الجديد

أضاف Filament 5.8 الدالة deferLoading() لتأجيل تحميل الـ Schema الداخلي، مع حفظ التجميع في الجلسة وhooks بأسماء الـ traits. إليك الجديد وكيف تستفيد منه.

إذا كانت لوحتك تحتوي نماذج طويلة فيها أقسام مطوية وتبويبات وRepeaters ثقيلة، فأنت تدفع ثمن رسم كل حقل منها عند أول تحميل للصفحة حتى لو لم يفتحها المستخدم أبدًا. هذا بالضبط ما يحاول Filament 5.8 تخفيفه. الإصدار يضيف أيضًا تحسينات عملية صغيرة لكنها مفيدة في الجداول والتصدير والتحقق بخطوتين. بحسب تغطية Laravel News في 27 أكتوبر 2026 وملاحظات الإصدار على GitHub، هذه أهم التغييرات.

ما هو deferLoading() ولماذا يهم؟

افتراضيًا يرسم Filament كل مكوّنات الصفحة في الطلب الأول، بما فيها حقول الأقسام المطوية والتبويبات غير النشطة. مع deferLoading() على الـ Schema الداخلي يظهر مؤشر تحميل أولًا، وعندما يدخل هذا المؤشر نطاق الرؤية في الشاشة (viewport) يرسم Filament الـ Schema عبر طلب جديد.

للاستخدام، مرّر كائن Schema إلى schema() بدل المصفوفة:

use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;

Section::make('Customer details')
    ->key('customerDetails')
    ->schema(
        Schema::make()
            ->components([
                TextInput::make('name'),
                TextInput::make('email')
                    ->email(),
            ])
            ->deferLoading(),
    )
    ->collapsed()

عنوان القسم يظهر فورًا، أما محتواه فلا يُحمَّل إلا بعد أن يفتحه المستخدم ويصل إلى مجال رؤيته. التبويبات غير النشطة تُحمَّل عند اختيارها، وخطوات الـ Wizard عند الوصول إليها.

مع Repeater وBuilder

الفكرة نفسها تنطبق على عناصر الـ Repeater وكتل الـ Builder، وهي مفيدة خصوصًا عندما تكون العناصر مطوية افتراضيًا:

use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Schema;

Repeater::make('members')
    ->schema(
        Schema::make()
            ->components([
                TextInput::make('name'),
                TextInput::make('email')
                    ->email(),
            ])
            ->deferLoading(),
    )
    ->collapsed()

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

  • استخدمه عندما يكون المحتوى المؤجَّل ثقيلًا (حقول كثيرة، علاقات، خيارات تأتي من قاعدة البيانات) ومخفيًا غالبًا.
  • تجنّبه للحقول التي يحتاجها معظم المستخدمين فورًا؛ فالتأجيل يضيف طلبًا إضافيًا وسيرى المستخدم مؤشر التحميل.
  • قياس الفرق في صفحتك بأدوات مثل Telescope أو الـ Network tab أفضل من التخمين.

مثال تطبيقي: نموذج عميل طويل

تخيّل صفحة تعديل عميل فيها خمسة أقسام: البيانات الأساسية، العناوين، جهات الاتصال، المستندات، وسجل الملاحظات. المستخدم يفتح غالبًا القسم الأول فقط. بدون التأجيل يُرسم كل شيء في الطلب الأول. مع التأجيل يمكنك تقسيم القرار هكذا:

  • البيانات الأساسية: تبقى كما هي، لأنها ظاهرة دائمًا ولا فائدة من تأجيلها.
  • العناوين وجهات الاتصال: مرشحان جيدان، فهما غالبًا Repeaters مطوية.
  • المستندات وسجل الملاحظات: مرشحان قويان إن كانا يعتمدان على علاقات أو خيارات تُجلب من قاعدة البيانات.

القاعدة العامة: أجّل ما هو مطوي وثقيل، واترك ما هو ظاهر وخفيف. وبما أن الرسم المؤجَّل يتم عبر طلب منفصل، فجرّب سلوك الصفحة كاملًا (الحفظ، والتحقق من الحقول، والتنقل بين التبويبات) بعد تفعيله، ولا تفترض أن كل شيء سيعمل كما كان.

تحسينات الجداول

حفظ التجميع في الجلسة

كان بالإمكان حفظ الفرز والبحث والفلاتر والأعمدة في الجلسة. الآن يمكنك حفظ التجميع المختار أيضًا ليُستعاد في الزيارة التالية:

use Filament\Tables\Table;

public function table(Table $table): Table
{
    return $table
        ->groups([
            'status',
            'category',
        ])
        ->persistGroupInSession();
}

بحسب Laravel News، أُصلح أيضًا hook في Livewire لم يكن يعمل بسبب عدم تطابق اسمه مع خاصية $tableGrouping، وتغيير التجميع يعيد الجدول الآن إلى الصفحة الأولى.

اتجاه التجميع الافتراضي

صارت defaultGroup() تقبل وسيطًا direction على غرار defaultSort()، فلم يعد التجميع الافتراضي مقيَّدًا بالترتيب التصاعدي:

return $table
    ->defaultGroup('status', direction: 'desc');

Hooks بأسماء الـ Traits

المشكلة السابقة: إذا عرّف الـ trait دالة مثل afterSave() ثم عرّفت الصفحة دالتها الخاصة بالاسم نفسه، فإن دالة الصفحة تطغى على دالة الـ trait. الحل في 5.8 يتبع اصطلاح Eloquent في boot{TraitName}(): أضف اسم الـ trait إلى اسم الـ hook.

use Filament\Resources\Pages\EditRecord;

trait HandlesDrafts
{
    protected function afterSaveHandlesDrafts(): void
    {
        // يعمل بالإضافة إلى hook الصفحة afterSave()
    }
}

class EditUser extends EditRecord
{
    use HandlesDrafts;

    protected function afterSave(): void
    {
        // يعمل أولًا، ثم afterSaveHandlesDrafts()
    }
}

يعمل hook الصفحة أولًا ثم hooks الـ traits. ينطبق هذا على صفحات اللوحة (بما فيها الإنشاء والتعديل والعرض) وعلى الـ importers. الفائدة العملية: تعيد استخدام الـ trait نفسه في عدة صفحات دون أن تستدعيه يدويًا من كل hook.

MultiFactorChallenge قابل لإعادة الاستخدام

تحدّي رمز التحقق الذي يظهر عند تسجيل الدخول صار في صنف مستقل اسمه MultiFactorChallenge، ويمكنك استخدامه في مكوّنات Livewire الخاصة بك، مثلًا لطلب عامل ثانٍ قبل تنفيذ إجراء خطير:

use Filament\Auth\MultiFactor\MultiFactorChallenge;

$multiFactorChallenge = MultiFactorChallenge::make();

abort_unless($multiFactorChallenge->hasEnabledProviders($user), 403);

$schema
    ->components($multiFactorChallenge->getSchemaComponents($user))
    ->statePath('multiFactorData');

تنبيه أمني: التحقق من Schema فارغ ينجح دائمًا. لذلك استدعِ hasEnabledProviders() مباشرة قبل التحقق، كما في المثال، حتى لا يمرّ مستخدم لم يفعّل أي مزوّد.

تفاصيل صغيرة مفيدة

  • ارتفاع المحررات: صار لـ RichEditor وMarkdownEditor الدالتان minHeight() وmaxHeight() وتقبلان أي طول CSS. عند تجاوز المحتوى للحد الأقصى يتوقف المحرر عن النمو ويظهر التمرير.
  • Placeholder للإحصائيات: يمكن لـ Stat عرض نص بديل عند غياب القيمة عبر ->placeholder('-')، وبحسب Laravel News يمكن ضبط قيمة عامة بـ Stat::configureUsing().
  • تنزيل مخصص للتصدير: بحسب ملاحظات الإصدار صار بالإمكان استخدام downloader مخصص، وهذا مفيد لمن يستضيفون على Laravel Vapor حيث قد تُمنع الاستجابات المتدفقة (streamed) للملفات الكبيرة. راجع التوثيق الرسمي لاسم الدالة وتوقيعها قبل الاعتماد عليها.
  • مهارة Boost: صارت إرشادات Filament لـ Laravel Boost Agent Skill. بدل إضافة الإرشادات كاملة إلى سياق الوكيل في كل جلسة، تُحمَّل فقط عندما يعمل على كود Filament. لتفعيلها شغّل php artisan boost:install واختر Agent Skills ثم filament/filament (skills). تنبيه: boost:update وحده لا يضيفها.

كيف تترقى؟

  1. ارفع الحزمة بـ composer update filament/filament ضمن قيود الإصدار 5.x لديك.
  2. جرّب deferLoading() أولًا على أثقل قسم مطوي في أكثر نموذج استخدامًا.
  3. إن كانت لديك hooks مكررة في صفحات متعددة، انقلها إلى trait بالاسم الجديد.
  4. راجع الـ changelog للتفاصيل الكاملة قبل الترقية في الإنتاج.

الخلاصة

أهم ما في Filament 5.8 هو deferLoading(): طريقة بسيطة لتخفيف أول تحميل للنماذج الثقيلة دون إعادة هيكلة. وبقية التحسينات، من حفظ التجميع إلى hooks الـ traits، تقلل الكود المتكرر وتجعل اللوحة أكثر راحة للمستخدم. ابدأ بقسم واحد ثقيل، وقِس الفرق، ثم عمّم.

المصادر