سرویس پرووایدرها

مجتبی پاکزاد لاراول 5.6.29 در حال تکمیل رایگان

مقدمه

سرویس پروویدارها قلب عملیات راه‌اندازی تمام اپلیکیشن‌های لاراول هستند. سرویس‌های اپلیکیشن‌تان و همچنین تمام سرویس‌های هسته لاراول توسط سرویس پرووایدرها راه‌اندازی می‌شوند.

اما، منظور از "راه‌اندازی" یا "بوت‌استرپینگ" چیست؟ به طور کلی، منظور از بوت‌استرپینگ، رجیستر کردن چیزهایی مانند سرویس کانتینر بایندینگ‌ها (service container bindings)، اِوِنت‌لِسِنرها (event listeners)، میدلور (middleware) و حتی روت‌ها (routes) است. سرویس‌پرووایدرها مکان اصلی پیکربندی اپلیکیشن‌تان هستند.

اگر فایل config/app.php که در لاراول گنجانده شده را باز کنید، آرایه providers را خواهید دید. کلاس‌های گنجانده شده در این آرایه، تمامی سرویس پرووایدرهایی هستند  که برای اپلیکیشن‌تان لود خواهند شد. طبیعتا، تعدادی از این پرووایدرها دیفرد (deferred) هستند، بدین معنی که برای تمام ریکوئست‌ها لود نخواهند شد و تنها هنگامی که سرویسی که ارائه می‌دهند در عمل مورد نیاز باشد، لود می‌شوند.

در این پست، مروری کلی بر سرویس پرووایدرها خواهیم داشت، در نتیجه نحوه نوشتن و رجیستر کردن سرویس پرووایدرها در اپلیکیشن خود را خواهید آموخت.

نوشتن سرویس پرووایدرها

تمام سرویس پرووایدرها از کلاس Illuminate\Support\ServiceProvider مشتق می‌شوند. اغلب سرویس پرووایدرها دارای یک متد register و یک متد boot هستند. در بدنه متد register، تنها باید چیزهایی را به سرویس پرووایدر بایند کنید. هرگز سعی نکنید که هیچ اِوِنت لِسِنر، روت، یا هر بخش دیگری از عملکرد اپلیکیشن را در متد register، رجیستر کنید.

رابط خط فرمان آرتیزان با استفاده از کامند make:provider می‌تواند یک پرووایدر جدید را برای اپلیکیشن‌تان ایجاد کند:

php artisan make:provider RiakServiceProvider

متد Register

همانطور که قبلا بیان گردید، تنها باید چیزهایی را داخل متد register، به سرویس کانتینر بایند کنید. هرگز سعی نکنید تا هیچ اِوِنت لِسِنر، روت، یا هر بخش دیگری از عملکرد اپلیکیشن را در متد register، رجیستر کنید. در غیر اینصورت، ممکن است به صورت غیرمنتظره از سرویسی، قبل از لود شدن سرویس پرووایدر آن استفاده کنید.

بیایید نگاهی به یک سرویس پرووایدر ساده بیندازیم. داخل هر کدام از متدهای سرویس پرووایدرتان، همیشه به پراپرتی $app دسترسی دارید که امکان دسترسی به سرویس کانتینر را فراهم می‌کند:

<?php

namespace App\Providers;

use Riak\Connection;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider
{
    /**
     * Register bindings in the container.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection(config('riak'));
        });
    }
}

در این سرویس پرووایدر تنها متد register، تعریف شده است و  از این متد برای تعریف یک پیاده‌سازی (implementation) از Riak\Connection در سرویس کانتینر استفاده می‌شود. اگر نحوه کار سرویس کانتینر را نمی‌دانید، داکیومنت آن را مطالعه کنید.

پراپرتی‌های bindings و singletons

اگر سرویس پرووایدرتان بایندینگ‌های ساده بسیاری را رجیستر کند، ممکن است بخواهید به جای رجیستر کردن دستی تک تک کانتینر بایندینگ‌ها، از پراپرتی‌های bindings و singletons استفاده کنید. هنگامی که سرویس پرووایدری توسط فریمورک لود شود، به صورت اتوماتیک این پراپرتی‌ها را بررسی کرده و بایندینگ‌های آن‌ها را رجیستر می‌کند:

<?php

namespace App\Providers;

use App\Contracts\ServerProvider;
use App\Contracts\DowntimeNotifier;
use Illuminate\Support\ServiceProvider;
use App\Services\PingdomDowntimeNotifier;
use App\Services\DigitalOceanServerProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * All of the container bindings that should be registered.
     *
     * @var array
     */
    public $bindings = [
        ServerProvider::class => DigitalOceanServerProvider::class,
    ];

    /**
     * All of the container singletons that should be registered.
     *
     * @var array
     */
    public $singletons = [
        DowntimeNotifier::class => PingdomDowntimeNotifier::class,
    ];
}

متد Boot

بسیار خب، اما اگر بخواهیم یک ویو کامپوزر را درون سرویس پرووایدرمان رجیستر کنیم، باید چه کاری انجام دهیم؟ این کار باید داخل متد boot انجام شود. این متد پس از اینکه تمامی سرویس پرووایدرها رجیستر شوند، فراخوانی خواهد شد، به این معنی که به تمامی سرویس‌های دیگری که توسط فریمورک رجیستر شده‌اند، دسترسی خواهید داشت:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class ComposerServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        view()->composer('view', function () {
            //
        });
    }
}

دیپندنسی اینجکشن برای متد Boot

می‌توانید وابستگی‌ها را به متد boot سرویس پرووایدرتان type-hint کنید. سرویس کانتینر به صورت خودکار تمامی دیپندنسی‌های مورد نیازتان را اینجکت می‌کند:

use Illuminate\Contracts\Routing\ResponseFactory;

public function boot(ResponseFactory $response)
{
    $response->macro('caps', function ($value) {
        //
    });
}

رجیستر کردن پرووایدرها

تمامی سرویس پرووایدرها در فایل کانفیگ config/app.php رجیستر شده‌اند. این فایل حاوی آرایه providers است که لیستی از نام کلاس‌های سرویس پرووایدرتان را می‌توانید در آن ببینید. به صورت پیش‌فرض، مجموعه‌ای از سرویس پرووایدرهای هسته لاراول در این آرایه لیست شده‌اند. این پرووایدرها، کامپوننت‌های هسته لاراول از جمله mailer و queue و cache و سایر کامپوننت‌ها را راه‌اندازی می‌کنند.

برای افزودن پرووایدر خود، کد زیر را به این آرایه اضافه کنید:

'providers' => [
    // Other Service Providers

    App\Providers\ComposerServiceProvider::class,
],

پرووایدرهای دیفر شده

اگر پرووایدرتان تنها بایندینگ‌ها را در سرویس کانتینر رجیستر می‌کند، می‌توانید آن را جدا کنید در نتیجه تا زمانی که در عمل به بایندینگ‌های رجیستر شده نیاز باشد، عملیات رجیستر کردن آن دیفر شود، یعنی به تاخیر افتد. دیفر کردن لودینگِ این قبیل پرووایدرها، پرفورمنس اپلیکیشن‌تان را بهبود می‌بخشد، زیرا این سرویس پروایدرها برای هر ریکوئست از filesystem لود نمی‌شوند.

لاراول لیستی از تمامی سرویس‌های ارائه شده توسط سرویس پرووایدرهای دیفر شده را به همراه نام کلاس سرویس پرووایدرشان، کامپایل و ذخیره می‌کند. سپس، تنها هنگامی که سعی کنید تا یکی از این سرویس‌ها را resolve کنید، لاراول سرویس پرووایدر مربوط به آن سرویس را لود می‌کند.

برای دیفر کردن عملیات لودینگِ یک پرووایدر، مقدار پراپرتی defer آن را true قرار دهید و متد provides را برای آن تعریف کنید. متد provides باید سرویس کانتینر بایندینگ‌های رجیستر شده توسط پرووایدر را برگرداند.

<?php

namespace App\Providers;

use Riak\Connection;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider
{
    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection($app['config']['riak']);
        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return [Connection::class];
    }

}