Observers في Laravel: تنظيم منطق الأحداث بعيدًا عن الكونترولر
مقدمة
لنفترض أن لدينا في المشروع فورم تسجيل مستخدمين، ونريد أنه عند أي عملية تسجيل، أو تعديل، أو حذف، أن يتم تنفيذ إجراء معين تلقائيًا — مثل إرسال إشعار لمدير الموقع عند تسجيل مستخدم جديد:
public function store(Request $request)
{
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password)
]);
$user->notify(new NewUser());
return $user;
}الكود أعلاه صحيح ويعمل، لكنه يخالف مبدأ المسؤولية الواحدة (Single Responsibility Principle)، حيث يُفترض أن تكون دالة store مسؤولة عن شيء واحد فقط، وهو حفظ بيانات المستخدم — لا عن إرسال الإشعارات أيضًا.
من هنا تبرز أهمية Observers في Laravel، كحل أنيق لفصل منطق الاستجابة لأحداث الموديل عن الكونترولر.
ما هو Observer
حسب التوثيق الرسمي لـ Laravel، عند الاستماع لعدة أحداث (Events) على موديل معين، يمكن استخدام Observer لتجميع كل هذه المستمعات (Listeners) داخل كلاس واحد. تحمل دوال الـ Observer أسماء تعكس أحداث Eloquent المطلوب الاستماع إليها، وتستقبل كل دالة من هذه الدوال الموديل نفسه كوسيط وحيد.
باختصار، يساعدنا Observer في:
- الحفاظ على الكونترولر نظيفًا (Clean Controller) ومركّزًا على وظيفته الأساسية فقط.
- تنفيذ أي إجراء تلقائيًا عند حدوث تغيير على الموديل (إنشاء، تعديل، حذف...) بغض النظر عن مصدر هذا التغيير في الكود.
- تجميع كل منطق الاستجابة لأحداث موديل معين في مكان واحد، بدلاً من توزيعه بين عدة كونترولرات.
كيفية إنشاء Observer
يمكن إنشاء Observer جديد عبر أمر Artisan التالي:
php artisan make:observer UserObserver --model=User
- UserObserver: اسم كلاس الـ Observer.
- --model=User: تحديد الموديل الذي سيتم ربط الـ Observer به.
بعد تنفيذ الأمر، يتم إنشاء مجلد جديد باسم Observers داخل مجلد app، وبداخله كلاس UserObserver الذي يحتوي على الدوال التالية افتراضيًا:
<?php
namespace App\Observers;
use App\Models\User;
class UserObserver
{
public function created(User $user)
{
//
}
public function updated(User $user)
{
//
}
public function deleted(User $user)
{
//
}
public function restored(User $user)
{
//
}
public function forceDeleted(User $user)
{
//
}
}بالإضافة إلى هذه الدوال، يمكن أيضًا إضافة دوال أخرى غير موجودة افتراضيًا في الكلاس، مثل: retrieved، creating، updating، saving، saved، deleting، restoring.
متى يتم تنفيذ كل دالة
| الدالة | متى يتم تنفيذها |
|---|---|
| retrieved | عند جلب سجل من قاعدة البيانات، مثلاً عند استدعاء Model::findOrFail($id) |
| creating | عند حفظ سجل جديد، لكن قبل إتمام الحفظ فعليًا (قبل تعيين id وtimestamps) |
| created | بعد حفظ السجل الجديد بنجاح في قاعدة البيانات |
| updating | أثناء عملية التعديل، لكن قبل تنفيذ التعديل فعليًا في قاعدة البيانات |
| updated | بعد إتمام عملية التعديل بنجاح |
| saving / saved | تُنفَّذ عند الإنشاء والتعديل معًا. عند الإنشاء: saving ثم creating ثم created ثم saved. عند التعديل: saving ثم updating ثم updated ثم saved |
| deleting | أثناء عملية الحذف، قبل إتمامها |
| deleted | بعد إتمام عملية الحذف بنجاح |
| restoring / restored | عند استرجاع سجل محذوف (باستخدام Soft Delete) |
تسجيل الـ Observer
إنشاء كلاس الـ Observer وحده لا يكفي لتفعيله؛ فلا بد من تسجيله حتى يبدأ Laravel بالاستماع لأحداث الموديل المرتبط به. يتم ذلك داخل ملف AppServiceProvider.php، ضمن الدالة boot:
public function boot()
{
User::observe(new UserObserver());
}مثال عملي أول: إرسال إشعار عند تسجيل مستخدم جديد
قبل استخدام Observer
public function store(Request $request)
{
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password)
]);
$user->notify(new NewUser());
return $user;
}بعد استخدام Observer
الكونترولر يصبح مسؤولًا فقط عن إنشاء المستخدم:
public function store(Request $request)
{
return User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password)
]);
}بينما يتم نقل منطق إرسال الإشعار إلى داخل UserObserver، ضمن الدالة created (لأننا نريد تنفيذ الإشعار بعد نجاح عملية الحفظ):
public function created(User $user)
{
$user->notify(new NewUser());
}بهذا الشكل، أصبحت دالة store مسؤولة عن إنشاء المستخدم فقط، بينما أصبح UserObserver مسؤولًا عن أي إجراء يجب تنفيذه بعد الإنشاء، بغض النظر عن مصدر عملية الإنشاء (سواء من هذا الكونترولر، أو من Seeder، أو من أي مكان آخر في المشروع).
مثال عملي ثانٍ: حذف تلقائي للتعليقات عند حذف مقال
لنفترض أن لدينا Article Model وComment Model، بحيث يملك كل مقال مجموعة من التعليقات، ونريد أنه عند حذف مقال معين، يتم حذف كل التعليقات التابعة له تلقائيًا.
أولًا، نحتاج لتعريف العلاقة بين الموديلين:
public function comments()
{
return $this->hasMany(Comment::class);
}الحذف قبل استخدام Observer
في هذه الحالة، تكون دالة destroy مسؤولة عن حذف المقال وحذف التعليقات التابعة له معًا:
public function destroy(Article $article)
{
$article->delete();
$article->comments()->delete();
}وكما ذكرنا سابقًا، يُفترض أن تكون كل دالة مسؤولة عن شيء واحد فقط، وفي هذه الحالة يجب أن تكون دالة destroy مسؤولة عن حذف المقال فقط، لا عن حذف التعليقات المرتبطة به.
الحذف بعد استخدام Observer
إنشاء ArticleObserver
php artisan make:observer ArticleObserver --model=Article
تسجيل ArticleObserver في AppServiceProvider ضمن الدالة boot:
public function boot()
{
Article::observe(new ArticleObserver());
}بما أننا نتعامل مع عملية حذف، نذهب إلى الدالة deleted داخل ArticleObserver (أو deleting إذا أردنا تنفيذ الحذف قبل إتمام حذف المقال نفسه)، ونضع فيها منطق حذف التعليقات:
public function deleted(Article $article)
{
$article->comments()->delete();
}تنظيف ArticleController
نعود إلى دالة destroy ونزيل منها سطر حذف التعليقات، لتصبح مسؤولة عن حذف المقال فقط:
public function destroy(Article $article)
{
$article->delete();
}ملاحظة: deleted أم deleting؟
في المثال السابق تم استخدام deleted (بعد إتمام الحذف)، وهذا مناسب هنا لأن حذف التعليقات لا يعتمد على وجود المقال نفسه بعد الحذف. لكن في حالات أخرى، قد يكون من الضروري استخدام deleting (قبل إتمام الحذف) بدلاً من deleted، خصوصًا إذا كان الإجراء المطلوب يحتاج إلى الوصول لبيانات مرتبطة بالمقال لا تزال موجودة فقط قبل حذفه فعليًا من قاعدة البيانات.
جدول ملخّص لأهم دوال الـ Observer
| الدالة | الاستخدام الشائع |
|---|---|
| created | تنفيذ إجراء بعد إنشاء سجل جديد بنجاح (إرسال إشعار، تسجيل نشاط...) |
| updated | تنفيذ إجراء بعد تعديل سجل بنجاح (تحديث ذاكرة تخزين مؤقت، إرسال إشعار تغيير...) |
| deleting / deleted | تنفيذ إجراءات مرتبطة بحذف سجل، مثل حذف السجلات التابعة له |
| restored | تنفيذ إجراء عند استرجاع سجل محذوف عبر Soft Delete |
| observe() | تسجيل الـ Observer وربطه بالموديل داخل AppServiceProvider |
الخلاصة
توفّر Observers في Laravel طريقة نظيفة ومنظمة للاستجابة لأحداث الموديل (إنشاء، تعديل، حذف، استرجاع...) دون حشو هذا المنطق داخل الكونترولرات. باستخدام Observer:
- تبقى الكونترولرات مركّزة على مسؤوليتها الأساسية فقط.
- يصبح منطق الاستجابة للأحداث مركزيًا وقابلًا لإعادة الاستخدام من أي مكان في المشروع (كونترولر، Seeder، Job، Command...).
- يسهُل اختبار وصيانة هذا المنطق بمعزل عن باقي الكود.
هذا النمط مفيد بشكل خاص عند وجود إجراءات جانبية مرتبطة بأحداث الموديل (إشعارات، سجلات تدقيق، حذف بيانات مرتبطة، تحديث ذاكرة تخزين مؤقت...)، ويُعد من أفضل الممارسات للحفاظ على كود منظم يتبع مبدأ المسؤولية الواحدة.
رائع استمر ،، لدي بعض الاستفسارات حول تطويرك لهذا الموقع اذا ممكن تتواصل معي عبر الايميل
رووووووووووعة
أنت رائع
شكرا
جزاك الله خيرا
جزاك الله عنا خيرا
شرح بسيط وجميل.. شكرا لك.
تسلم ايدك استمر
رائع , جزاك الله كل خير , استمر و اتمنى لو تشرح اي مواضيع جديدة مهمة جدا تم اضافتها في لارافيل 9
شرح سهل نتمني المزيد
شرح سهل نتمني المزيد
يعطيك العافية انا من متابعين موقعك بشكل دوري شرح جدا منظم و مفيد و جميل بارك الله فيك
الشرح واضح جدا شكرا ?
عاااش جداااا والله شرح ممتاز ❤❤
شرح أكثر من رائع، عاشت ايدك