العقود (Contracts) في Laravel

من موسوعة حسوب

مقدمة

عقود Laravel هي مجموعة من الواجهات (interfaces) التي تعرِّف الخدمات المركزية التي يوفرها إطار العمل. على سبيل المثال، يعرِّف العقد Illuminate\Contracts\Queue\Queue  الدوال اللازمة لصف الأعمال في الطوابير، بينما يعرِّف عقد Illuminate\Contracts\Mail \Mailer الدوال اللازمة لإرسال رسائل البريد الإلكتروني. لكل عقد تعريف استخدام (implementation) يوفره إطار العمل. على سبيل المثال، يوفِّر Laravel تعريف استخدام للطابور لعدة أنواع من المشغلات (drivers) كما يوفِّر تعريف استخدام للمُرسِل مشغل بواسطة SwiftMailer.

توجد كل عقود Laravel في مستودعات في GitHub. هذا يوفر نقطة مرجعية سريعة لكل العقود المتوافرة كما يوفر حزمة (package) واحدة منفصلة يمكن لمطوري الحِزم إستخدامها.

مقارنة العقود والواجهات الساكنة (Facades)

توفر واجهات Laravel الساكنة والدوال البانية المساعدة (helper functions) طريقة بسيطة لاستخدام خدمات Laravel بدون الحاجة للتلميح إلى النوع (type-hint) أو استبيان العقود من حاوي الخدمات. في أغلب الحالات لكل واجهةٍ ساكنةٍ عقدٌ يعادلها.

عكس الواجهات الساكنة التي لا تتطلب أن منك تضمينها في الدالة البانية لصنفك، تسمح لك العقود بتعريف الاعتماديات المذكورة بوضوح (explicit dependencies) لأصنافك. يفضل بعض المطورين تعريف الاعتماديات بوضوح بهذه الطريقة وبالتالي استخدام العقود، بينما البعض الآخر يفضل سهولة أو راحة الواجهات المؤقتة.

ملاحظة: ستكون أغلب التطبيقات بخير سواء فضلت الواجهات أم العقود. لكن إن كنت بطور بناء حزمة من المنصوح به بشدة استخدام العقود لكون اختباراتها أسهل في سياق حزمة.

متى تُستَخدم العقود؟

كما ذكرنا سابقًا، يعتمد الاختيار بين الواجهات والعقود على الذوق الشخصي وذوق فريق التطوير. كلاهما يُمكِّن من صناعة تطبيقات Laravel قوية ومختبرة بشكل جيد. ما دمت تبقي مسؤوليات أصنافك مركزة، لن تلاحظ تطبيقيًا إلا فروقًا قليلةً جدًا بين العقود والواجهات.

مع ذلك قد تسأل عدة أسئلة حول العقود. على سبيل المثال، لم نستخدم الواجهات أصلًا؟ أليس استخدام الواجهات أكثر تعقيدًا؟ فلنلخص أسباب استخدام الواجهات للسببين التاليين: الارتباط الرخو (loose coupling) والبساطة.

الاقتران الرخو

فلنراجع أولًا بعض التعليمات البرمجية ضيقة الارتباط (tightly coupled) بتعريف استخدام ذاكرة تخزين مؤقتة (cache implementation). انظر في ما يلي:

<?php

namespace App\Orders;

class Repository
{
    /**
     * نسخة الذاكرة المؤقتة 
     */
    protected $cache;

    /**
     * أنشئ نسخة مستودع جديدة
     *
     * @param  \SomePackage\Cache\Memcached  $cache
     * @return void
     */
    public function __construct(\SomePackage\Cache\Memcached $cache)
    {
        $this->cache = $cache;
    }

    /**
     * Order by ID جلب 
     *
     * @param  int  $id
     * @return Order
     */
    public function find($id)
    {
        if ($this->cache->has($id))    {
           //
        }
    }
}

التعليمات البرمجية في هذا الصنف ضيقة الارتباط بتعريف استخدام ذاكرة تخزين مؤقتة (cache implementation). هي ضيقة الاقتران لأننا نعتمد على صنف ذاكرة مؤقتة ملموسة من بائع حِزم (package vendor). إن تغير API تلك الحِزمة علينا تغيير تعليماتنا البرمجية.

نقع في نفس الإشكالية إن رغبنا في تغيير التقنية المستخدمة ضمنيًا للذاكرة المؤقتة (Memcached) بأخرى (Redis)، سنضطر مجددا لتعديل مستودعنا. لا يجب على المستودع أن يعرف الكثير عن مُوفِّر البيانات أو كيفية توفيرها.

بإمكاننا تحسين تعليماتنا البرمجية بالاعتماد على واجهة (interface) بسيطة محايدة البائع (vendor agnostic) بدل هذه المقاربة:

<?php

namespace App\Orders;

use Illuminate\Contracts\Cache\Repository as Cache;

class Repository
{
    /**
     * نسخة ذاكرة التخزين المؤقتة
     */
    protected $cache;

    /**
     * أنشئ نسخة مستودع جديدة
     *
     * @param  Cache  $cache
     * @return void
     */
    public function __construct(Cache $cache)
    {
        $this->cache = $cache;
    }
}

الآن لا تقترن التعليمات البرمجية بأي بائع محدد أو حتى  Laravel. يمكنك الآن كتابة نسخة بديلة عن أي عقد تريده، بما أن حِزم العقود لا تحتوي على أي اعتماديات (dependencies) أو أي تعريف استخدام، مما يسمح لك باستبدال تعريف استخدام ذاكرة التخزين مؤقتة (cache implementation) بدون تعديل أي من تعليماتك المستهلكة لذاكرة التخزين المؤقتة.

البساطة

لمّا تُعرَّف كل خدمات Laravel بدقة داخل واجهات بسيطة يسهل تحديد الوظيفة التي توفرها الخدمة بشكل كبير. تصلح العقود كتوثيقات موجزة لخاصيات إطار العمل.

كيفية استخدام العقود

إذا كيف تتحصل على تعريف استخدام (implementation) عقد؟ المسألة بسيطة جدًا.

تُستَبْيَن العديد من أنواع الأصناف في Laravel عبر حاوي الخدمات بما فيها وحدات التحكم، ومنصتات الأحداث، والبرمجيات الوسيطة، والأعمال المصفوفة في الطوابير، وحتى تعابير المسارات المغلقة (route closure). إذا للحصول على تعريف استخدام لعقد يمكنك مجرد التلميح إلى نوع (type-hint) الواجهة في الدالة البانية (constructor) للصنف المُستَبْين.

لنلق نظرة على منصت الأحداث التالي كمثال:

<?php

namespace App\Listeners;

use App\User;
use App\Events\OrderWasPlaced;
use Illuminate\Contracts\Redis\Database;

class CacheOrderInformation
{
    /**
     * Redis تعريف إستخدام قاعدة البيانات
     */
    protected $redis;

    /**
     * أنشئ نسخة معالج أحداث جديدة
     *
     * @param  Database  $redis
     * @return void
     */
    public function __construct(Database $redis)
    {
        $this->redis = $redis;
    }

    /**
     * عالج الحدث.
     *
     * @param  OrderWasPlaced  $event
     * @return void
     */
    public function handle(OrderWasPlaced $event)
    {
       //
    }
}

سيقرأ حاوي الخدمات التلميحات على النوع عند استبيان منصت الأحداث (event listener) ثم يضيف القيمة المناسبة. للمزيد من المعلومات حول تسجيل الأشياء في حاوي الخدمات، أنظر توثيقه.

مرجع العقد

يُوفِّر هذا الجدول مرجعًا سريعًا لكل عقود Laravel وواجهاتها الساكنة المعادلة:

العقود

الواجهة الساكنة المرجعية

Illuminate\Contracts\Auth\Access\Authorizable   
  Illuminate\Contracts\Auth\Access\Gate Gate
Illuminate\Contracts\Auth\Authenticatable    
  Illuminate\Contracts\Auth\CanResetPassword
Illuminate\Contracts\Auth\Factory Auth
Illuminate\Contracts\Auth\Guard Auth::guard()‎
Illuminate\Contracts\Auth\PasswordBroker Password::broker()‎
Illuminate\Contracts\Auth\PasswordBrokerFactory Password
Illuminate\Contracts\Auth\StatefulGuard
Illuminate\Contracts\Auth\SupportsBasicAuth
Illuminate\Contracts\Auth\UserProvider
Illuminate\Contracts\Bus\Dispatcher Bus
Illuminate\Contracts\Bus\QueueingDispatcher Bus::dispatchToQueue()‎
Illuminate\Contracts\Broadcasting\Factory Broadcast
Illuminate\Contracts\Broadcasting\Broadcaster Broadcast::connection()‎
Illuminate\Contracts\Broadcasting\ShouldBroadcast
Illuminate\Contracts\Broadcasting\ShouldBroadcastNow
Illuminate\Contracts\Cache\Factory Cache
Illuminate\Contracts\Cache\Lock
Illuminate\Contracts\Cache\LockProvider
Illuminate\Contracts\Cache\Repository Cache::driver()‎
Illuminate\Contracts\Cache\Store
Illuminate\Contracts\Config\Repository Config
Illuminate\Contracts\Console\Application
Illuminate\Contracts\Console\Kernel Artisan
Illuminate\Contracts\Container\Container App
Illuminate\Contracts\Cookie\Factory Cookie
Illuminate\Contracts\Database\ModelIdentifier
Illuminate\Contracts\Debug\ExceptionHandler
Illuminate\Contracts\Encryption\Encrypter Crypt
Illuminate\Contracts\Events\Dispatcher Event
Illuminate\Contracts\Filesystem\Cloud Storage::cloud()‎
Illuminate\Contracts\Filesystem\Factory Storage
Illuminate\Contracts\Filesystem\Filesystem Storage::disk()‎
Illuminate\Contracts\Foundation\Application App
Illuminate\Contracts\Hashing\Hasher Hash
Illuminate\Contracts\Http\Kernel
Illuminate\Contracts\Mail\MailQueue Mail::queue()‎
Illuminate\Contracts\Mail\Mailable
Illuminate\Contracts\Mail\Mailer Mail
Illuminate\Contracts\Notifications\Dispatcher Notification
Illuminate\Contracts\Notifications\Factory Notification
Illuminate\Contracts\Pagination\LengthAwarePaginator
Illuminate\Contracts\Pagination\Paginator
Illuminate\Contracts\Pipeline\Hub
Illuminate\Contracts\Pipeline\Pipeline
Illuminate\Contracts\Queue\EntityResolver
Illuminate\Contracts\Queue\Factory Queue
Illuminate\Contracts\Queue\Job
Illuminate\Contracts\Queue\Monitor Queue
Illuminate\Contracts\Queue\Queue Queue::connection()‎
Illuminate\Contracts\Queue\QueueableCollection
Illuminate\Contracts\Queue\QueueableEntity
Illuminate\Contracts\Queue\ShouldQueue
Illuminate\Contracts\Redis\Factory Redis
Illuminate\Contracts\Routing\BindingRegistrar Route
Illuminate\Contracts\Routing\Registrar Route
Illuminate\Contracts\Routing\ResponseFactory Response
Illuminate\Contracts\Routing\UrlGenerator URL
Illuminate\Contracts\Routing\UrlRoutable
Illuminate\Contracts\Session\Session Session::driver()‎
Illuminate\Contracts\Support\Arrayable
Illuminate\Contracts\Support\Htmlable
Illuminate\Contracts\Support\Jsonable
Illuminate\Contracts\Support\MessageBag
Illuminate\Contracts\Support\MessageProvider
Illuminate\Contracts\Support\Renderable
Illuminate\Contracts\Support\Responsable
Illuminate\Contracts\Translation\Loader
Illuminate\Contracts\Translation\Translator Lang
Illuminate\Contracts\Validation\Factory Validator
Illuminate\Contracts\Validation\ImplicitRule
Illuminate\Contracts\Validation\Rule
Illuminate\Contracts\Validation\ValidatesWhenResolved
Illuminate\Contracts\Validation\Validator Validator::make()‎
Illuminate\Contracts\View\Engine
Illuminate\Contracts\View\Factory View
Illuminate\Contracts\View\View View::make()‎

مصادر