العقود في 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()
|