نگهدارندهی سرویس
مقدمه
نگهدارندهی سرویس (Service Container) در لاراول یک ابزار قدرتمند برای مدیریت وابستگیهای کلاس و انجام عملیات تزریق وابستگی (Dependency Injection). تزریق وابستگی یک عبارت جذاب است که اساساً به این معناست که: وابستگیهای کلاس ازطریق سازنده یا در برخی موارد، متدهای نسبتدهنده (Setter) «تزریق میشوند».
به این مثال ساده دقت کنید:
<?php
namespace AppHttpControllers;
use AppUser;
use AppRepositoriesUserRepository;
use AppHttpControllersController;
class UserController extends Controller
{
/**
* The user repository implementation.
*
* @var UserRepository
*/
protected $users;
/**
* Create a new controller instance.
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* Show the profile for the given user.
*
* @param int $id
* @return Response
*/
public function show($id)
{
$user = $this->users->find($id);
return view('user.profile', ['user' => $user]);
}
}
در این مثال
UserController نیاز به استخراج کاربران از یک منبع دادهها دارد. بنابراین ما یک سرویس را
تزریق میکنیم که قادر به استخراج کاربران است.
UserRepository ما به احتمال زیاد از
Eloquent برای استخراج اطلاعات کاربر از پایگاه دادهها استفاده میکند. هرچند از آنجا که مخزن تزریق شده است، ما قادر به جایگزینی راحت آن با یک پیادهسازی دیگر هستیم. همچنین میتوانیم در زمان تست برنامهی خود، بهراحتی پیادهسازی
UserRepository را جعلکنیم یا یک پیادهسازی ساختگی از آن ایجاد نماییم.
انتساب (Binding)
مفاهیم پایهی انتساب
نسبتدهی تقریباً تمام نگهدارندههای سرویس شما در داخل
عرضهکنندگان سرویس ثبت میشود، بنابراین اغلب این مثالها نحوهی استفاده از نگهدارنده را در آن حوزه نشان میدهند.
نقل قول:نکته: اگر کلاسهای شما به هیچ رابطی (Interface) وابسته نیستند، لازمنیست آنها را به نگهدارنده نسبتدهید. نگهدارنده به هیچ دستورالعملی برای نحوهی ایجاد این قبیل اشیاء نیاز ندارد، زیرا میتواند بطور خودکار این اشیاء را ازطریق انعکاس (Reflection) بیابد.
انتسابهای ساده
همیشه درون یک عرضهکنندهی سرویس به نگهدارنده ازطریق خصوصیت
$this->app دسترسی دارید. میتوانیم یک انتساب را با کمک متد
bind و ارسال اسم کلاس یا رابط موردنظر جهت ثبتکردن، درکنار یک بَستار (
Closure) که آن رابط یا کلاس را باز میگرداند، ثبت کنیم:
$this->app->bind('HelpSpotAPI', function ($app) {
return new HelpSpotAPI($app->make('HttpClient'));
});
دقتکنید که ما خود نگهدارنده را بعنوان یک آرگومان در تابع Closure که وظیفهی پیداکردن کلاس یا رابط را دارد (Resolver) دریافت میکنیم و سپس میتوانیم از نگهدارنده برای یافتن وابستگیهای زیرمجموعهی کلاس یا رابطی که قصد ایجاد شئ از آن داریم، استفاده کنیم.
انتساب یک Singleton
متد
singleton یک کلاس یا رابط را به نگهدارنده بهنحوی اضافه میکند که فقط یکبار یافته و ایجاد شود. زمانیکه یک انتساب singleton یافت شد، فراخوانیهای بعدی آن در نگهدارنده همان شئ قبلی را باز میگرداند:
$this->app->singleton('HelpSpotAPI', function ($app) {
return new HelpSpotAPI($app->make('HttpClient'));
});
انتساب اشیاء نمونه
میتوانید یک شئ از قبل موجود را نیز به نگهدارنده با کمک متد
instance نسبتدهید. نمونهی ارائهشده همیشه در فراخوانیهای بعدی به نگهدارنده بازگردانده میشود:
$api = new HelpSpotAPI(new HttpClient);
$this->app->instance('HelpSpotAPI', $api);
انتساب مقادیر اولیه
گاهی اوقات ممکناست نیاز به کلاسی داشته باشید که برخی کلاسهای تزریقشده را دریافت میکند، اما علاوهبر آن، یک مقدار اولیهی تزریقشده نظیر یک عدد صحیح را هم نیاز دارد. میتوانید بهسادگی از انتساب محتوایی برای تزریق هر مقداری به کلاسی که ممکناست نیاز داشته باشید، استفاده کنید:
$this->app->when('AppHttpControllersUserController')
->needs('$variableName')
->give($value);
انتساب رابطها به پیادهسازی (Implementation)
یک قابلیت بسیار قدرتمند نگهدارندهی سرویس، توانایی آن در انتساب یک رابط به یک پیادهسازی است. برای مثال، اجازهدهید فرض کنیم که یک رابط
EventPusher داریم و یک کلاس بهنام
ReditEventPusher که آنرا پیادهسازی کرده است. زمانی که کد کلاس
ReditEventPusher خود را نوشتیم، میتوانیم آنرا در نگهدارنده بهصورت زیر اضافه کنیم:
$this->app->bind(
'AppContractsEventPusher',
'AppServicesRedisEventPusher'
);
این دستور به نگهدارنده میگوید که باید کلاس
RedisEventPusher را هرگاه که نیاز به یک پیادهسازی از رابط
EventPusher وجود داشت، تزریقکند. اکنون میتوانیم از راهنمای نوع رابط
EventPusher در سازنده یا هر مکان دیگر که وابستگیها توسط نگهدارندهی سرویس تزریق میشوند، استفاده کنیم:
use AppContractsEventPusher;
/**
* Create a new class instance.
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
انتساب محتوایی
برخی اوقات ممکناست دو کلاس که از یک رابط یکسان استفاده میکنند داشتهباشید ولی بخواهید که پیادهسازیهای متفاوتی را در هر کلاس تزریق کنید. برای مثال دو کنترلر ممکناست وابسته به پیادهسازیهای مختلفی از
قرارداد IlluminateContractsFilesystemFileststem باشند. لاراول یک راهکار روان و ساده برای تعریف این رفتار ارائه میکند:
use IlluminateSupportFacadesStorage;
use AppHttpControllersPhotoController;
use AppHttpControllersVideoController;
use IlluminateContractsFilesystemFilesystem;
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
$this->app->when([VideoController::class, UploadController::class])
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});
شناسهگذاری
در موقعیتهای خاصی احتمال دارد که نیاز به یافتن تمامی انواع یک «دستهبندی» خاص از انتسابها باشید. برای مثال شاید مشغول ساخت یک گزارش تجمیع هستید که آرایهای از انواع مختلف پیادهسازیهای رابط
Report را دریافت میکند. پساز ثبت پیادهسازیهای
Report میتوانید به آنها یک شناسه با کمک متد
tag بدهید:
$this->app->bind('SpeedReport', function () {
//
});
$this->app->bind('MemoryReport', function () {
//
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
وقتی که سرویسها شناسهگذاری شدند، میتوانید بهراحتی آنها را ازطریق متد
tagged بیابید:
$this->app->bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports'));
});
گسترش انتسابها
متد
extend اجازهی ویرایش سرویسهای یافتهشده را میدهد. برای مثال وقتیکه یک سرویس یافت شد، میتوانید یک کد اضافه اجرا کنید تا سرویس را تزئین یا تنظیم کند. متد
extend یک بَستار (Closure) را بعنوان تنها پارامتر خود دریافت میکند که باید سرویس ویرایششده را بعنوان نتیجه بازگرداند:
$this->app->extend(Service::class, function ($service) {
return new DecoratedService($service);
});
یافتن
متد make
میتوانید از متد
make برای یافتن یک نمونه از یک کلاس در خارج از نگهدارنده استفاده کنید. متد
make اسم کلاس یا رابطی را که نیاز دارید بیابید، بعنوان پارامتر دریافت میکند:
$api = $this->app->make('HelpSpotAPI');
اگر در بخشی از کدتان هستید که به متغیر
$app دسترسی ندارید، میتوانید از متد کمکی
resolve استفاده کنید:
$api = resolve('HelpSpotAPI');
اگر برخی از وابستگیهای کلاسهای شما ازطریق نگهدارندهی سرویس قابل یافتن نیستند، میتوانید آنها را با ارسال بهصورت یک آرایهی انجمنی برای متد
makeWith تزریق کنید:
$api = $this->app->makeWith('HelpSpotAPI', ['id' => 1]);
تزریق خودکار
بعنوان یک راهکار جایگزین (و مهم) این امکان وجود دارد که نوع وابستگی را بعنوان راهنمای نوع (Type-Hint) در سازنده یا کلاسی که توسط نگهدارنده یافته میشود قید کنید و این موضوع شامل
کنترلرها،
شنوندههای رویداد،
کارهای صفبندیشده،
میانافزارها و... میشود. در زمان تمرین، این روشی است که اغلب اشیاء شما باید توسط نگهدارنده یافتهشوند.
برای مثال، ممکناست با راهنمای نوع، یک مخزن را که در برنامهی شما در سازندهی یک کنترلر معرفی شده است، مشخص کنید. مخزن بطور خودکار پیدا شده و به درون کلاس تزریق خواهد شد:
<?php
namespace AppHttpControllers;
use AppUsersRepository 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)
{
//
}
}
رویدادهای نگهدارنده
نگهدارندهی سرویس یک رویداد را هر بار که یک شئ را مییابد، فراخوانی میکند. میتوانید به این رویداد توسط متد
resolving گوش کنید:
$this->app->resolving(function ($object, $app) {
// Called when container resolves object of any type...
});
$this->app->resolving(HelpSpotAPI::class, function ($api, $app) {
// Called when container resolves objects of type "HelpSpotAPI"...
});
همانطور که ملاحظه میکنید، شئ یافتهشده بعنوان پارامتر به متد Closure ارسال میگردد که به شما اجازهی تنظیم هرگونه خصوصیتهای اضافه را برروی شئ قبلاز ارسال آن برای مصرفکننده، میدهد.
استاندارد PSR-11
نگهدارندهی سرویس Laravel استاندارد رابط
PSR-11 را پیادهسازی میکند. بنابراین میتوانید از راهنمای نوع رابط نگهدارندهی PSR-11 برای دریافت یک شئ نگهدارندهی لاراول استفاده کنید:
use PsrContainerContainerInterface;
Route::get('/', function (ContainerInterface $container) {
$service = $container->get('Service');
//
});
اگر شناسهی موردنظر قابل یافتن نباشد، یک استثنا تولید میشود. اگر شناسه هرگز نسبت داده نشده باشد، این استثنا از نوع
PsrContainerNotFoundExceptionInterface خواهد بود. اگر شناسه نسبتداده شده باشد ولی قابل یافتن نباشد، یک نمونه از
PsrContainerContainerExceptionInterface پرتاب (Throw) خواهد شد.