ملخص سبتمبر 2026 في Laravel Framework: مفاتيح AI لكل Tenant وبحث دلالي وMercure

نشر فريق Laravel في 2 أكتوبر 2026 ملخص تحديثات سبتمبر. هذا المقال يغوص في جزء الـ Framework منه (الذكاء الاصطناعي، والبحث، والوقت الحقيقي، والملفات)، مع شيفرة من سجل التغييرات الرسمي. وللجزء الخاص بـ Laravel Cloud مقال مستقل.

1) مزودو الذكاء الاصطناعي في وقت التشغيل: Ai::build()

تخيل تطبيق SaaS يسمح لكل عميل (tenant) بإدخال مفتاح Anthropic الخاص به. المفاتيح ليست في ملفات الإعدادات، بل في قاعدة البيانات. الدالة Ai::build() تنشئ مزودًا من مصفوفة إعدادات دون أن تغيّر المزودين المُعدّين في تطبيقك. وصاحب الـ Pull Request هو pushpak1300 (laravel/ai#1071).

مثال من سجل التغييرات:

use Laravel\Ai\Ai;

$agent->prompt($text, provider: [
    Ai::build(['driver' => 'anthropic', 'key' => $tenant->ai_key]),
]);

وتذكر الوثائق أمرين:

  • المزودون المبنيون يعملون داخل قوائم التبديل عند الفشل (failover).
  • مع الـ Agents التي تعمل في الطابور (queued)، ابنِ المزود داخل دالة provider() الخاصة بالـ Agent نفسه، ليتمكن العامل (worker) من إعادة إنشائه، بدل أن تبنيه في مكان الاستدعاء فقط.

ملاحظة أمان (رأيي): مفاتيح العملاء أسرار. خزّنها مشفّرة (مثل cast مشفر)، ولا تسجلها في السجلات.

2) بحث دلالي وهجين في Scout

محرك Typesense في Scout صار يدعم semantic() وhybrid() (PR من tharropoulos، scout#1009):

  • البحث الدلالي يطابق المعنى لا الكلمات.
  • البحث الهجين يجمع نتائج المعنى مع نتائج النص عبر باني Scout.

لتوليد المتجهات (embeddings) عندك ثلاثة خيارات حسب السجل: استخدام Laravel AI عبر الدالة toSearchableEmbedding() في النموذج، أو التضمين التلقائي (auto-embedding) من Typesense نفسه، أو متجهات تحسبها أنت مسبقًا.

ولمحرك Meilisearch (PR من Strift، scout#1012) صار ممكنًا استخدام الـ embedders الأصلية في Meilisearch، بضبط الـ driver في إعدادات التضمين للنموذج على meilisearch وتحديد embedder. حينها يولّد Meilisearch التضمينات للمستندات والاستعلامات، وإذا مررت متجهًا عبر options(['vector' => ...]) فإنه يتجاوز تضمين الاستعلام. أما الـ driver الافتراضي laravel-ai فلم يتغير.

بعض الإعدادات التفصيلية (أسماء المفاتيح في مصفوفة التضمين) لا تظهر في ملخص سجل التغييرات، فارجع إلى وثائق Scout الرسمية لها قبل التطبيق.

3) Mercure في Laravel Echo

Laravel Echo (عميل الأحداث الفورية) أضاف موصّل Mercure (PR من dunglas، echo#549). ميزاته:

  • مبني على واجهة المتصفح الأصلية EventSource، فلا حاجة لمكتبة عميل إضافية.
  • يدعم القنوات الخاصة (private) وقنوات الحضور (presence) والمشفّرة، إضافة إلى الهمسات (whispers).
  • عنوان الـ hub الافتراضي هو /.well-known/mercure على الأصل الحالي، ويمكن ضبطه في تكاملات React وVue وSvelte عبر VITE_MERCURE_HUB_URL.

4) نسخ الملفات ونقلها بين الأقراص

دالتان جديدتان في أقراص التخزين (PR من jackbayliss، framework#61511):

use Illuminate\Support\Facades\Storage;

Storage::disk('local')->copyToDisk('s3', 'reports/report.csv', 'archive/report.csv');
Storage::disk('local')->moveToDisk('backup', 'reports/report.csv');

كلتاهما تنقل الملف بالتدفق (stream) وتغلق التدفق، ويمكنك اختياريًا تحديد مسار جديد في القرص الهدف (كما في المثال الأول). وmoveToDisk() تحذف الملف الأصلي فقط بعد نجاح النسخ، وهذا قيد أمان مهم.

5) تحسينات أصغر تستحق الانتباه

من سجل التغييرات الرسمي لشهر سبتمبر:

كاست AsVector لقراءة المتجهات وكتابتها (متجهات MariaDB الثنائية أو النص المتوافق مع PostgreSQL)، مع إصلاح استعلامات المسافة في MariaDB:

use Illuminate\Database\Eloquent\Casts\AsVector;
use Illuminate\Database\Eloquent\Model;

class Document extends Model
{
    protected $casts = [
        'embedding' => AsVector::class,
    ];
}

الدالة chunkBy() في المجموعات: تقسم عند تغير القيمة، وتجمع القيم المتجاورة فقط، بعكس groupBy():

collect([1, 1, 2, 2, 1])->chunkBy(fn ($value) => $value);
// [[1, 1], [2, 2], [1]]

AsCollection::nullable() وAsArrayObject::nullable(): تخزّنان NULL أصيلًا في SQL عند ضبط الحقل على null، فتعمل استعلامات whereNull(). السلوك القديم (تخزين JSON "null") لا يتغير إلا إذا اخترت النسخة nullable:

protected $casts = [
    'items' => AsCollection::nullable(),
];

inplace() في مهاجرات MySQL: لطلب ALGORITHM=INPLACE صراحةً، وتعمل مع lock('none'). وقد ترفض MySQL العملية إن لم تستطع تنفيذها بهذه القيود:

Schema::table('video_sessions', function (Blueprint $table) {
    $table->index('video_id')->inplace()->lock('none');
});

#[CountCrashesAsExceptions] للمهام: تحسب انهيار العامل (worker) المفاجئ ضمن حد maxExceptions، حتى لا تُعاد مهمة تُسبب الانهيار إلى ما لا نهاية. ولا تُحسب الانتهاءات العادية ولا انتهاء المهلة التي يديرها العامل. وتستخدم الميزة علامة (marker) في الكاش للمهام التي اشتركت فيها.

تأكيدات للاختبار: Mail::assertSentOnce() وMail::assertQueuedOnce() وNotification::assertSentToOnce() وNotification::assertSentOnDemandOnce().

Horizon: استراتيجية توسع جديدة log تزن تراكم الطوابير لوغاريتميًا، حتى لا يستحوذ طابور ضخم واحد على أغلب العمال.

Artisan dev: يمكن استبعاد أوامر الحزم أو الأوامر الافتراضية بـ DevCommands::withoutVendorCommands() وDevCommands::withoutDefaultCommands().

ما الذي أبدأ به؟

  • إذا كان تطبيقك متعدد المستأجرين ويستخدم AI: جرّب Ai::build().
  • إذا كان عندك بحث نصي ضعيف في المحتوى العربي: البحث الدلالي/الهجين قد يحسنه، لكن جرّبه على بياناتك أولًا (رأيي: جودة البحث الدلالي للعربية تتوقف على نموذج التضمين الذي تختاره).
  • إذا كانت مهامك تنهار أحيانًا: #[CountCrashesAsExceptions].

المصادر