الأحداث (Events) واستخدامها في Laravel


محمد أحمد العيل

الأحداث Events هي إجراءات يمكن لبرنامج التعرّف عليها ومن ثمّ التعامل معها. يمكن أن نأخذ مثالا على ذاك عملية تسجيل مستخدم في الموقع. عندما يكمل زائر ملء حقول الاستمارة الخاصّة بالتسجيل في موقع فإنه يكون قد فعّل حدثا يمكننا تسميّته "التسجيل في الموقع"؛ نستطيع بعد ذلك التعامل مع هذا الحدث لنقرّر ما التالي.

laravel-events.png

كانت الطريقة القديمة تقضي بكتابة شفرة برمجية مباشرةً في المتحكّم Controller للتعامل مع الحدث؛ إلا أن توسّع التطبيق وزيادة الحاجة للتعامل مع أحداث جديدة يجعل الحاجة أكبر لطريقة جديدة أنظف وأكثر قابلية للتمديد. يستجيب Laravel لهذه الحاجة بالصنف Event والأدوات المساعدة المتعلّقة به.

لا تقتصر الأحداث على إرسال البريد؛ إلا أن هذا الأخير وسيلة سهلة لشرح المبدأ. سنرى في هذا الدرس الخطوات اللازمة لتعريف حدث واستخدامه.

نفترض أن لدينا جدولا ببيانات الاتصال ببعض الأشخاص؛ ونريد أن نرسل لأحدهم بريدا في كل مرة يُعرَض فيها الرابط contacthandler/id/ حيث id معرّف من نرسل له البريد.

سنستخدم في هذا الجدول نفس المشروع الذي أنشأناه في الدرس السابق ونزيد عليه بإنشاء نموذج Model وجدول بيانات لحفظ جهات الاتّصال.

تهيئة المشروع

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

إنشاء النموذج Contact

ننفذ الأمر التالي في مجلّد المشروع لإنشاء نموذج باسم Contact مع ملف التهجير الخاصّ به:

php artisan make:model Contact -m

نعدّل ملف التهجيرعلى النحو التالي:

public function up()
{
    Schema::create('contacts', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name');
        $table->string('email')->unique();
        $table->string('subject');
        $table->text('body');
        $table->timestamps();
    });
}

أضفنا الحقول التي نريد استخدامها في جدول البيانات. ننفذ أمر التهجير لإنشاء الجدول:

php artisan migrate

بذر البيانات

سنحتاج لبيانات اتصّال للتجربة عليها. نستخدم معمل نماذج Model factory لبذر الجدول contacts. ننشئ ملفا باسم ContactFactory.php في المسار /app/factories ونضيف إليه المحتوى التالي:

<?php
    $factory->define(App\Contact::class, function (Faker\Generator $faker){
        return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'subject' => $faker->word,
        'body' => $faker->text

        ];
});

يستدعي معمل النماذج مكتبة Faker لإدراج بيانات في جدول contacts عبر النموذج Contact.

ثم ننشئ صنفا للبذر:

php artisan make:seeder ContactTableSeeder

نستورد النموذج Contact في صنف البذر ContactTableSeeder ونستخدم معمل النماذج لتوليد بيانات عشوائية:

<?php
use Illuminate\Database\Seeder;
use App\Contact;

class ContactTableSeeder extends Seeder
{
    public function run()
    {
        Contact::truncate();
        factory(Contact::class, 50)->create();
    }
}

نأتي الآن للخطوة الأخيرة قبل تنفيذ أمر البذر وهي تحرير الملف DataBaseSeeder.php ليستدعي الصّنف ContactTableSeeder في الدالة run:

$this->call(ContactTableSeeder::class);

ثم ننفذ البذر:

php artisan db:seed

المسار والمتحكم

نفتح ملف routes.php لإضافة مسار للرابط contacthandler/id/:

Route::get('/contacthandler/{id}', 'HomeController@contactHandler');

يحيل ملف المسارات الطلب على الرابط إلى الدالة contacthandler في المتحكّم HomeController. نذهب للمتحكم ونضيف هذه الدالة:

public function contactHandler($id) {

    }

نترك الدالة خاوية لحد الساعة.

التعامل مع الأحداث

جهزنا المشروع ويمكننا الآن الانتقال إلى الأحداث والتعامل معها. نذكّر بأننا نريد إرسال بريد لصاحب جهة الاتصال عند طلب رابط به معرّفه. مثلا نرسل بريدا إلى جهة الاتصال ذات المعرّف 6 عند زيارة يُعرَض فيها الرابط contacthandler/6/. نحصُل على البريد الإلكتروني الذي سنرسل إليه الرسالة من خلال النموذج Contact؛ إضافة لاسمه وموضوع الرسالة وفحواها. ولدنا هذه البيانات تلقائيا أثناء البذر السابق.

تعريف الحدث

يتلخّص الأمر في أننا سننفّذ إجراءً عند وقوع الحدث "زيارة رابط المعرّف الخاص بجهة الاتصال". سنسميّ الصنف الموافق للحدث بـ ContactHandlerAction. نستعين بأداة artisan لإنشاء الحدث على النحو التالي:

php artisan make:event ContactHandlerAction

ستجد أن الأداة أنشأت الملف ContactHandlerAction.php على المسار app/Events وأضافت إليه المحتوى التالي (بعد نزع التعليقات):

class ContactHandlerAction extends Event
{
    use SerializesModels;
    public function __construct()
    {

    }
    public function broadcastOn()
    {
        return [];
    }
}

سنضيف خاصيّة إلى الصّنف ليصبح كالتالي:

class ContactHandlerAction extends Event
{
    use SerializesModels;

    public $contact;
    public function __construct($contact)
    {
        $this->contact = $contact;
    }

    public function broadcastOn()
    {
        return [];
    }
}

أضفنا متغيّرا جديدا لحفظ بيانات الاتصال واستخدامها عند الحاجة.
نعود لدالّة contactHandler في المتحكّم HomeController ونعدّلها لتصبح كما يلي:

public function contactHandler($id) {
    $contact = Contact::find($id);
    event (new ContactHandlerAction($contact));

   return view('mail.success_view');
}

نحتاج لاستيراد الصنفين المستخدمين في الدالة (النموذج والحدث) إلى المتحكّم:

use App\Contact;
use App\Events\ContactHandlerAction;

عند استدعاء الدالة contactHandler نأخذ المعطى id المُمرَّر في الرابط ونحيله إلى الدالة find في النموذج Contact من أجل العثور عليه في قاعدة البيانات.
في التعليمة التالية يبدأ عمل آلية إدارة الأحداث في Laravel: نستدعي الدالة المساعدة event ونمرّر لها كائنا من الحدث ContactHandlerAction الذي أنشأناه قبل قليل. نستخدم جهة الاتصال التي عثرنا عليها contact$ من أجل بناء الحدث. يعني هذا أننا أطلقنا الحدث “زيارة رابط المعرّف الخاص بجهة الاتصال” وزوّدناه بجهة الاتصال المعنيّة. مالذي يترتّب عن إطلاق الحدث؟ هذا هو ما سنراه الآن.

التعامل مع الحدث

توجد في Laravel ما تُعرَف بالمستمعات Listeners ومهمتها انتظار وقوع أحداث لتبدأ عملها. نعود لأداة artisan وننفّذ الأمر التالي: 

php artisan make:listener EmailContactListener --event="ContactHandlerAction"

ينشئ الأمر صنفا باسم EmailContactListener للاستماع للأحداث ContactHandlerAction. يوجد الصّنف EmailContactListener في المجلّد /app/Listeners ومحتواه التالي:

class EmailContactListener
{
    public function __construct()
    {
        //
    }

    public function handle(ContactHandlerAction $event)
    {
        //
    }
}

لاحظ أن الدالة handle تنتظر معطى من نوع ContactHandlerAction. يشغّل المستمِع الدالة handle بعد تلقيه إخطارا بإطلاق الحدث ContactHandlerAction. سنعود بعد قليل للدالة handle بعد أن نعرّف الآلية التي سيُخطَر بها المستمِع بوقوع الحدث.

ربط الحدث بآلية التعامل معه

يوفّر Laravel مزوّد خدمة خاصًّا بالأحداث اسمه EventServiceProvider يوجد في المجلّد /app/Providers. إن فتحت الملف EventServiceProvider.php ستجد التالي (إن لم يسبق لأحد التعديل عليه):

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
    'App\Events\ContactHandlerAction' => [
        'App\Listeners\EmailContactListener',
    ],
];

    public function boot(DispatcherContract $events)
    {
        parent::boot($events);

        //
    }
}

ما يهمّنا هنا هو الخاصيّة listen$. نعدّلها لتصبح على النحو التالي:

 protected $listen = [
    'App\Events\ContactHandlerAction' => [
        'App\Listeners\EmailContactListener',
    ],
];

يمكن تعريف أكثر من مستمِع لنفس الحدث (لاحظ أن قيمة App\Events\ContactHandlerAction في الخاصيّة هي مصفوفة Array).

تمكننا الآن العودة إلى المنصِت بعد أن أعلمنا Laravel بالرابط بينه والحدث.

تنفيذ آلية التعامل مع الحدث

نعدّل الدالة handle لتصبح على النحو التالي:

public function handle(ContactHandlerAction $event)
{
  $data = [
    'name'      => $event->contact->name,
    'email'     => $event->contact->email,
    'subject'   => $event->contact->subject,
    'body'      => $event->contact->body
  ];

  Mail::send('mail.email_view',$data , function($message) use($data)
  {
    $message->from('hsoub@academy.com', 'Hsoub Academy');
    $message->to($data['email'])->subject($data['subject']);
  });
  if (Mail::failures()){
    throw new EmailSendingException;
  }
}

نعرّف مصفوفةً باسم data$ تحوي اسم جهة الاتصال، بريدها الإلكتروني، موضوع الرسالة وفحواها. نعثُر على هذه البيانات من الكائن contact الذي مررناه للحدث event عند زيارة الرابط. نستخدم هذه البيانات لجدولة إرسال بريد عبر الدالة Mail::send. تأخذ الدالة Mail::send عرضا View تستخدمه لتنسيق الرسالة والبيانات التي أخذناها من جهة الاتصال. ثم نختبر أخطاء إرسال البريد الإلكتروني بالدالة Mail::failures؛ فإن وجدت أخطاء استخدمنا استثناءً Exception مخصّصًا لمعالجتها. يمكنك الحصول على هذا الاستثناء في الملف المرفق أو مراجعة الدرس الخاص بالاستثناءات.

نحن الآن جاهزون تقريبا لتجربة الأحداث والتعامل معها في Laravel. بقي لنا إنشاء بضعة عروض لحاجات تطبيقنا المختلفة: mail.success_view في المتحكّم، mail.email_view لتنسيق الرسالة، إضافة لعروض أخرى قد تحتاجها أثناء تخصيص الاستثناء. يمكنك الحصول عليها في الملف المرفق.

بقيت لنا خطوة أخيرة قبل اختبار التطبيق: إعداد Laravel لإرسال البريد. يوفّر Laravel وسيلة لمحاكاة إرسال البريد يمكننا استخدامها للتجربة. ابحث في ملف متغيّرات النظام env. عن المعطى MAIL_DRIVER وعيّن قيمته بـlog:

MAIL_DRIVER=log

نطلُب بهذه الطريقة من Laravel كتابة البريد في ملفّ السجل بدلا من إرساله. هذه الطريقة مفيدة أثناء الاختبار.

افتح الرابط التالي في المتصفّح (ضع اسم المضيف المناسب بدلا من laraveltips):

http://laraveltips.dev/contacthandler/45

ملحوظة: لا يتعدّى عدد جهات الاتصال التي أضفناها الخمسين؛ سيؤدي البحث عن جهة اتصال بمعرّف أكبر من 50 إلى خطأ يمكنك التقاطه في استثناء كما فعلنا في درس استخدام الاستثناءات Exceptions المخصَّصة في Laravel.

ثم اذهب إلى ملف السجل storage/logs/laravel.log للتأكد من نجاح معالجة الحدث؛ ستجد في آخر الملف ما يشبه التالي:

[2016-03-30 22:16:29] local.DEBUG: Message-ID: <8c79163440df2d3b5bbb3efd97ad549f@laraveltips.dev>
Date: Wed, 30 Mar 2016 22:16:29 +0000
Subject: distinctio
From: Hsoub Academy <hsoub@academy.com>
To: fFranecki@example.net
MIME-Version: 1.0
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable

يليه محتوى العرض المستخدم لتنسيق الرسالة (mail.email_view في المثال). إن حدثت مشكلة في إرسال البريد فسيتولّى الاستثناء -إن أعددتَه - إظهار العرض المناسب للزائر؛ وفي حال سريان الأمور على الوجه الأمثل فسيُظهر المتحكّم العرض mail.success_view للدلالة على نجاح الإرسال.

01_eventing_laravel.png

الملف المرفق: الأحداث في Laravel.zip

ترجمة -وبتصرّف- للمقال Eventing in Laravel 5.1 لصاحبه Bill Keck.





تفاعل الأعضاء


جميل اخي :) ولكن ممكن تفسر لنا اخي لماذا سيكون فعل (كتابة الكود) في ال Event افضل من كتابته في Controller ؟

شارك هذا التعليق


رابط هذا التعليق
شارك على الشبكات الإجتماعية

شكرا جدا ..

شارك هذا التعليق


رابط هذا التعليق
شارك على الشبكات الإجتماعية


يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن