كيف تنشئ مزود خدمة (Service provider) في Laravel


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

تعتمد بنية Laravel كثيرا على مزوّدي الخدمة Service providers لتحميل الأصناف Classes إلى الذاكرة مع بدء عمل التطبيق أو عند الحاجة إليها. يستخدم Laravel حاويةَ خدمة Service container لإدارة الاعتمادات بين الأصناف: ماهي الأصناف التي يعتمد عليها الصّنف الجديد للعمل؟ كيف يمكنه الحصول على كائن من هذه الأصناف؟ أين يوجد الصّنف وهل سبق لإطار العمل تحميلُه إلى الذاكرة؟ تمكننا بإعداد مزوّد خدمة وتعريفه إضافةُ صنف إلى حاويّة الخدمة ممّا يسهّل بالتالي استخدامه؛ إذ سيتعرّف إطار العمل على ما يحتاجه هذا الصّنف (أي الاعتمادات) وينشئه إن لم يكن موجودا سلفا.

سنشرح في هذا الدرس أساسيّات إنشاء مزود خدمة في Laravel.

laravel-service-provider.png

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

Route::get('/demo', 'DemoController@index');

ثم ننشئ المتحكّم:

php artisan make:controller DemoController

ثم نعرّف الدالة index في المتحكّم:

public function index()
{
    return view('demo.index');
}

تطلب الدالة index تقديم العرض demo.index؛ لذا سننشئ مجلّدا باسم demo في مجلّد العروض وننشئ فيه عرضا باسم index.blade.php. نضيف المحتوى التالي للعرض index:

@extends('layouts.master')
@section('content')
<h1>Demo Page</h1>
@endsection

يمدّد العرض index عرضا رئيسا باسم master ضمن مجلّد العروض layouts. أنشئ العرض الرئيس إن احتجت لذلك.

لم ندخل في صميم الموضوع لحد السّاعة، فقط ضبطنا بعض الإعدادات البدائية. تظهر عند الدخول إلى الرابط demo/ صفحة بها عنوان Demo Page.

إعداد مزود خدمة

سنشرح في الفقرات المواليّة كيفية إنشاء مزوّد خدمة. نبدأ بإنشاء مجلّد باسم Helpers في مجلّد app ثم ننشئ مجلّدا متفرعا عن Helpers باسم Contracts؛ بداخل الأخير ننشئ ملفّا باسم RocketShipContract.php نضيف إليه المحتوى التالي:

<?php

namespace App\Helpers\Contracts;

Interface RocketShipContract
{
    public function blastOff();
}

لاحظ تعريف الصّنف أعلاه. عرّفنا RocketShipContract على أنه واجهة Interface. تعرّف الواجهات في PHP (ولغات برمجيّة أخرى) "عقدا" يجب على الأصناف التي تطبّق الواجهة اتّباعه. يجب على الأصناف التي تنفّذ Implement الواجهة المعرَّفة أعلاه أن تحوي دالة عمومية public باسم blastOff.

نأتي الآن بعد تعريف الواجهة إلى الصّنف الذي ينفّذها. تحوي الواجهة خطوطا عريضة يأتي الصّنف المنفّذ لتخصيصها. ننشئ صنفا باسم RocketShip.php في مجلّد Helpers ونضيف إليه المحتوى التالي:

<?php

namespace app\Helpers;

use App\Helpers\Contracts\RocketShipContract;

class RocketShip implements RocketShipContract
{

    public function blastOff()
    {
        return 'Houston, we have ignition';
    }

}

يمكن أن تعالج في هذا الصّنف الكثير من الأمور، إلا أننا لا نودّ التعقيد. كل ما تفعله هذه الدالة هو إرجاع العبارة Houston, we have ignition.

عرفنا الواجهة وكيفيةً لتنفيذها. يأتي الآن دور مزوّد الخدمة الذي سننشئه بالأمر التالي: 

php artisan make:provider RocketShipServiceProvider

ينشئ الأمر ملفّا باسم RocketShipServiceProvider.php في المجلّد. تجد عند فتح الملف ما يلي (بعد نزع التعليقات):

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class RocketShipServiceProvider extends ServiceProvider
{
    public function boot()
    {
    }

    public function register()
    {
    }
}

عدّل على الملف ليصبح على النحو التالي:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Helpers\RocketShip;

class RocketShipServiceProvider extends ServiceProvider
{
    protected $defer = true;

    public function boot()
    {
    }
    public function register()
    {
        $this->app->bind('App\Helpers\Contracts\RocketShipContract', function(){

            return new RocketShip();

        });
    }
    public function provides()
    {
        return ['App\Helpers\Contracts\RocketShipContract'];
    }

}

أولى التعديلات هي استدعاء الصّنف RocketShip

use App\Helpers\RocketShip;

ثم إضافة الخاصّية التاليّة:

protected $defer = true;

يعني تمكين الخاصيّة defer$ أننا نطلُب ألا يُحمَّل الصّنف إلا عند الضرورة؛ مما يسهِم في الرفع من أداء التطبيق.

ثم يأتي دور الدالة register التي تربط بين الواجهة والصّنف الذي ينفّذها.

public function register()
{
    $this->app->bind('App\Helpers\Contracts\RocketShipContract', function(){

        return new RocketShip();

    });
}

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

التعديل الأخير هو إضافة الدالة provides التي تُرجع الواجهة. يجب تعريف هذه الدالة عند تمكين الخاصيّة defer.

الخطوة الأخيرة من إعداد مزوّد الخدمة هي إضافته إلى ملف إعداد التطبيق config\app.php ضمن مصفوفة providers (أدرجنا بعضا من محتويات الملف في الشفرة أدناه، مزوّد الخدمة الخاص بنا هو الأخير):

    /*
     * Application Service Providers...
     */

    App\Providers\AppServiceProvider::class,
    App\Providers\EventServiceProvider::class,
    App\Providers\RouteServiceProvider::class,
    App\Providers\RocketShipServiceProvider::class,

استخدام مزود الخدمة

نعود الآن للمتحكّم DemoController ونعدّل الدالة index كالتالي:

public function index(RocketShipContract $rocketship)
{
        $boom = $rocketship->blastOff();

        return view('demo.index', compact('boom'));
}

لا تنس استيراد الواجهة: 

use App\Helpers\Contracts\RocketShipContract;

نمرّر للدّالة index كائنا من الصنف RocketShipContract (أي الواجهة). سيعرف Laravel تلقائيا أننا نريد الصّنف RocketShip الذي ينفّذ الواجهة. يعود السبب في ذلك إلى الربط الموجود في مزوّد الخدمة. نمرّر النتيجة المتحصّل عليها من استدعاء blastOff إلى العرض لتقديمه.

نعدّل العرض لاستخدام المتغيّر الذي ممرناه إليه:

@extends('layouts.master')
@section('content')
    {{ $boom }}
@endsection

تظهر العبارة التاليّة في المتصفّح عند زيارة الرابط demo/

Houston, we have ignition 

سننشئ لتوضيح المرونة التي تتيحها مزودات الخدمة صنفا آخر ينفّذ الواجهة RocketShipContract. سنسمّي هذا الصنف الجديد RocketLauncher ونضعه في نفس المجلد الذي يوجد به صنف التنفيذ السابق (Helpers):

<?php
namespace app\Helpers;

use App\Helpers\Contracts\RocketShipContract;

class RocketLauncher implements RocketShipContract
{
    public function blastOff()
    {
        return 'Houston, we have launched!';
    }

}

لم نغيّر الكثير بالمقارنة مع الصنف RocketShip السّابق؛ فقط العبارة.

نعود إلى مزوّد الخدمة ونغيّر ربط الواجهة كالتالي:

public function register()
    {
        $this->app->bind('App\Helpers\Contracts\RocketShipContract', function(){
            return new RocketLauncher();
        });
    }

دون أن ننسى استدعاء الصّنف:

use App\Helpers\RocketLauncher;

ستلاحظ الآن تغير العبارة الظاهرة في المتصفّح عند زيارة الرابط demo/.

لم نفعل أمورا معقّدة في الخطوات السّابقة إلا أن بالإمكان رؤية أن التعامل مع الواجهات مباشرة وترك التنفيذ الفعلي للأصناف المربوطة عبر مزوّد الخدمة يضيف الكثير من المرونة إلى بنية التطبيق.

يوجد أمران ينبغي الانتباه إليهما عند التعامل مع مزوّدات الخدمة في Laravel:

  • اجعل إضافة مزوّد الخدمة إلى ملف الإعداد config/app.php هي آخر الخطوات؛ بعد تعريف الواجهة ومزوّد الخدمة. إضافة صنف غير موجود إلى ملف الإعداد تؤدي إلى التشويش على عمل artisan.
  • من الأفضل حذف مزوّد الخدمة ثم إنشاء واحد جديد بأداة artisan؛ بدلا من إعادة تسميّته. قد تؤدي إعادة التسميّة إلى عدم تحميل مزوّد الخدمة فلا يتعرّف عليه التطبيق.

ترجمة -وبتصرّف- للمقال How to Create a Service Provider in Laravel 5.1 لصاحبه Bill Keck.





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


لا توجد أيّة تعليقات بعد



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

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

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


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

تسجيل الدخول

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


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