مشخصات مقاله
-
1646
-
0.0
-
6618
-
0
-
0
آموزش Service Container در لاراول
Service Container
- مقدمه
-
Binding (اتصال داده)
- Bind کردن interface ها به implementation ها
- Binding به صورت شرطی (Contextual Binding)
- متد Tag
- دسترسی به سرویس های یک container (resolving)
- رخدادها (Event container)
مقدمه
Service container یکی از ابزارهای بسیار قدرتمند برای مدیریت dependency های کلاس می باشد. dependency injection یک اصطلاح دهان پرکن است که این روزها به وفور شندیه می شود و اما معنی واقعی آن به شرح زیر می باشد: dependency های کلاس از طریق متد سازنده (constructor) یا در برخی موارد توابع setter به داخل کلاس تزریق می شوند.
به شرح مثال ساده ای می پردازیم:
<?php
namespace App\Jobs;
use App\User;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Bus\SelfHandling;
class PurchasePodcast implements SelfHandling
{
/**
* The mailer implementation.
*/
protected $mailer;
/**
* Create a new instance.
*
* @param Mailer $mailer
* @return void
*/
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
/**
* Purchase a podcast.
*
* @return void
*/
public function handle()
{
//
}
}
در این مثال، PurchasePodcast یک job (عملیات) است که با خریده شدن هر podcast ایمیلی را به کاربر مربوطه ارسال می کند. برای این منظور یک سرویس تزریق شده که وظیفه ی ارسال email ها را بر عهده دارد. از آنجایی که سرویس مورد نظر تزریق شده، به راحتی می توان آن را با یک پیاده سازی (implementation) دیگر جانشین کرد. همچنین می توانیم به هنگام تست برنامه به راحتی یک پیاده سازی ساختگی از سرویسmailer ایجاد کنیم که آزمایش پذیری و تست برنامه را تسهیل می بخشد.
فهم عمیق از service container لاراول نه تنها در ساخت یک برنامه ی کاربردی بزرگ و قدرتمند ضروی تلقی می شود بلکه در توسعه ی هسته ی خود این فریم ورک کمک شایانی به برنامه نویسان آن می کند.
Binding (اتصال داده)
به طور کلی تمامی عملیات binding و معرفی سرویس ها داخل service providers رخ می دهد. بنابراین کلیه ی این مثال های این درس استفاده ازcontainer در پروژه را به نمایش می گذارند. لازم به ذکر است که در صورتی که کلاس ها وابسته به هیچ interface ای نباشند، نیازی به bindکردن آن ها در container هم نیست. Container نیازی به دستورالعمل برای نحوه ی ساختن این اشیا ندارد زیرا که خود قادر است این اشیا را به صورت خودکار با استفاده از سرویس های reflection پی اچ پی بسازد.
در service provider، همیشه به راحتی می توان به وسیله ی متغیر نمونه $this->app به container دسترسی داشت. می توان به وسیله ی متدbind یک binding ثبت نموده و سپس اسم کلاس و interface ای که مایلید ثبت کنید را به همراه یک تابع Closure که یک نمونه از کلاس را برمی گرداند، به عنوان آرگومان به متد bind ارسال کنید:
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app['HttpClient']);
});
همان طور که می بینید خود container را به عنوان آرگومان ارسالی به resolver دریافت کردیم. حال می توانیم با استفاده از کانتینر، sub-dependency های شی ای که در حال ساخت آن هستیم را resolve کنیم.
متد Singleton
متد singleton یک کلاس یا interface را در container ثبت می کند که فقط می بایست یک بار بارگذاری شده و در دفعات بعدی همان نمونه با فراخوانی های بعدی در container بازگردانده می شود:
$this->app->singleton('FooBar', function ($app) {
return new FooBar($app['SomethingElse']);
});
متد instance
شما همچنین می توانید یک شی ساخته شده از کلاس را با استفاده از تابع instance در container ثبت کنید. نمونه ی ثبت شده همیشه با فراخوانی های بعدی در container بازگردانده می شود:
$fooBar = new FooBar(new SomethingElse);
$this->app->instance('FooBar', $fooBar);
Bind کردن interface ها به implementation ها
یکی از امکانات ویژه ی service container توانایی آن در bind کردن interface به یک implementation است. برای مثال، فرض بگیرید یک اینترفیس EventPusher و یک پیاده سازی (implementation) RedisEventPusher داریم. پس از نوشتن کد پیاده سازی (implementation) RedisEventPusher این اینترفیس، می توانیم آن را در service container به صورت زیر ثبت کنیم:
$this->app->bind('App\Contracts\EventPusher', 'App\Services\RedisEventPusher');
این کد به container اعلان می کند که باید زمانی که کلاسی به پیاده سازی EventPusher نیاز پیدا کرد، RedisEventPusher را تزریق کند. اکنون می توانیم اینترفیس EventPusher را در یک متد سازنده (constructor) یا هر جای دیگری که dependency ها توسط service container در آن تزریق شده اند اعلان نوع (type-hint) کنیم:
use App\Contracts\EventPusher;
/**
* Create a new class instance.
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
Bind کردن به صورت شرطی
گاهی ممکن است دو کلاس داشته باشید که هر دو از یک interface استفاده می کنند اما می خواهید در هر یک پیاده سازی های متفاوتی را تزریق کنید. برای مثال هنگامی که سیستم یک سفارش جدید را دریافت می کند، می خواهید event را بجای Pusher از طریق PubNub ارسال کنید. لاراول یک interface ساده و کارآمد برای تعریف این قابلیت در اختیار شما قرار می دهد:
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('App\Contracts\EventPusher')
->give('App\Services\PubNubEventPusher');
حتی می توانید یک Closure به متد give به عنوان آرگومان ارسال کنید:
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('App\Contracts\EventPusher')
->give(function () {
// Resolve dependency...
});
متد Tag
گاهی لازم می شود مجموعه ای از سرویس های یک دسته را resolve کنید. به عنوان مثال شاید در حال نوشتن برنامه جمع آوری خبر باشید که آرایه ای از پیاده سازی های مختلف اینترفیس Report را به عنوان آرگومان می پذیرد. پس از ثبت پیاده سازی های Report، می توانید به وسیله ی متدtag برای آن ها تگ تعریف کنید:
$this->app->bind('SpeedReport', function () {
//
});
$this->app->bind('MemoryReport', function () {
//
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
هنگامی که همه ی سرویس ها تگ شدند، می توانید به راحتی توسط متد tagged به آن ها دسترسی داشته باشید:
Once the services have been tagged, you may easily resolve them all via the tagged method:
$this->app->bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports'));
});
دسترسی به سرویس های یک container (resolving)
راه های مختلفی برای دسترسی به سرویس های container وجود دارد. می توانید از متد make استفاده کنید که اسم کلاس و interface ای که می خواهید به آن دسترسی داشته باشید را به عنوان پارامتر می پذیرد:
$fooBar = $this->app->make('FooBar');
همچنین، از آنجایی که container، اینترفیس پی اچی پی ArrayAccess را پیاده سازی می کند، می توانید به container به صورت یک آرایه دسترسی داشته باشید.
$fooBar = $this->app['FooBar'];
در روش آخر، که از دو روش ذکر شده مهمتر است، فقط dependency مورد نظر (controller، event listeners ، queue jobs وmiddleware) را در متد سازنده ی کلاس اعلان نوع (type-hint) می کنید. در عمل بیشتر object ها به همین شکل توسط container ارائه (resolve) می شوند.
Container به صورت خودکار تمامی dependency های کلاس هایی که ارائه می دهد را inject می کند. به عنوان مثال، شما repository ای که برنامه تعریف کرده را در constructor کنترلر اعلان می کنید و repository به صورت اتوماتیک ارائه (resolve) شده و در کلاس تزریق می شود:
<?php
namespace App\Http\Controllers;
use Illuminate\Routing\Controller;
use App\Users\Repository as UserRepository;
class UserController extends Controller
{
/**
* The user repository instance.
*/
protected $users;
/**
* Create a new controller instance.
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* Show the user with the given ID.
*
* @param int $id
* @return Response
*/
public function show($id)
{
//
}
}
رخدادها (event container)
هر بار که درخواست یک سرویس از service container داده می شود، همزمان با آن یک رخداد فعال می گردد. شما می توانید از طریق متدresolving به این رخداد ها گوش فرا داده و آن ها را معرفی کنید:
$this->app->resolving(function ($object, $app) {
// Called when container resolves object of any type...
});
$this->app->resolving(FooBar::class, function (FooBar $fooBar, $app) {
// Called when container resolves objects of type "FooBar"...
});
همان طور که می بینید، شی مورد فراخوانی به عنوان پارامتر به تابع callback ارسال شده و این امکان را فراهم آورده که پیش از تحویل شی به مصرف کننده هر property اضافی را بر روی این شی تنظیم (set) نمایید.
ممنون - عالی بود