Laravel Service Container (IoC) – Co to jest?
To jest pytanie które pada nawet częściej od tego związnego z Middleware. Jest to bardzo prosta rzecz choć brzmi skomplikowanie a dokumentacja Laravela w zrozumieniu niestety nie pomaga.
I cóż począć? A no wiadomo co, przeczytać ten artykuł ! Ponieważ wyłożę ci wszystko jak należy !
Zatem czym jest Service Container?
Service Container (inaczej IoC Container) jest to nic innego jak narzędzie do zarządzania zależnościami między klasami oraz do wstrzykiwania zależności. Brzmi skomplikowani, prawda? Ale takie nie jest . Bo czymże jest w ogóle to „wstrzykiwanie zależności”?
Zrozumienie dobrze tego zagadnienia jest kluczowe w budowaniu potężnych, dużych i skalowalnych aplikacji w Laravelu .
Dependency Injection (Wstrzykiwanie zależności)
Ano jest to po prostu dostarczanie klasom tego czego potrzebują do prawidłowego funkcjonowania. Zazwyczaj są to inne klasy które zwykle się „wstrzykuje” przez konstruktor bądź tzw. setter. Jest to innymi słowy przekazywanie argumentów. Po prostu .
Świeże spojrzenie
Możesz myśleć o tym, jak o takim szczególnym miejscu, gdzie możesz zdefiniować całą kompozycję jakiejś konkretnej klasy – jak należy ją zbudować według twojej wizji, jak chcesz by została zbudowana. Dzięki temu nie musisz pamiętać w jaki sposób należy stworzyć instancje jakiejś konkretnej klasy za każdym jednym razem. Zamiast tego możesz napisać to tylko raz w jednym konkretnym miejscu, a potem w przyszłości kiedy zajdzie potrzeba dostępu do tego obiektu, będziesz mógł po prostu go wywołać z kontenera IoC . W związku z tym rejestruje się tzw. wiązanie w kontenerze IoC.
Również możesz na to patrzeć jak na zwykły kontener/pudełko gdzie przechowujemy wszystkie powiązania w naszej aplikacji (bindings).
Automatic Resolution
A to dopiero początek, ponieważ Laravel dostarczy ci na własną rękę potrzebne zależności, dokładnie tam gdzie ich potrzebujesz . Wyobraź sobie klasę A która do działania potrzebuje klasę B. Przyjmujesz ją jako argument poprzez konstruktor tejże klasy A. Wówczas musisz utworzyć klasę A poprzez „new A(new B)”. Natomiast wykorzystując Service Container wystarczy, że zrobisz „new A” o ile wcześniej zarejestrujesz odpowiednie wiązanie między klasą A i B (kiedy zarejestrujesz sposób kompozycji klasy A która polega na klasie B w IoC).
Reflection
Co więcej, Laravel jest nawet na tyle sprytny, że znając twoje type-hinty z konstruktora (zależności) pomimo tego że nie są one zarejestrowane w kontenerze IoC, w sposób automatyczny wstrzyknie to co niezbędne używając mechanizmu pod nazwą „Reflection” poprzez „Automatic Resolution” . Automatycznie stworzy niezbędne instancje danych klas. I jakby tego było mało, działa to w pełni rekursywnie, dostarczy ci wszystkie potrzebne zależności i zależności tych zależności itd… Cudowna technika .
Co to jest Reflection?
Co do reflection słów kilka; Jest to API które pozwala na dokonanie inżynierii wstecznej na klasach, interfejsach, funkcjach, metodach bądź bibliotekach.
Czy można tylko i wyłącznie rejestrować klasy w kontenerze IoC?
Nie . Niemalże wszystko możesz tam zarejestrować oraz stamtąd wyciągnąć . Więc jest to niesamowicie przydatne narzędzie które jest kluczowym elementem tego frameworka .
Podstawowe wiązanie
Prawie wszystkich wiązań dokonujemy w specjalnie do tego przeznaczonych klasach – Service Providers. Podstawowe wiązanie tworzymy bind-ując dany identyfikator (zazwyczaj nazwę klasy czy interfejsu) do konkretnego sposobu jej kompozycji czy nawet innej klasy.
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
Pojedyncze wiązanie – Singleton
Jest to wiązanie w wyniku którego za każdym razem gdy zwrócimy się do kontenera IoC z żądaniem o konkretną klasę, to zawsze dostaniemy jedną i tę samą jej instancję. Zostanie utworzona tylko raz i nigdy nie dostaniemy innej lub nowej instancji tejże klasy . Jest to bardzo przydatna możliwość którą można wykorzystać na bardzo wiele sposobów.
$this->app->singleton('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
Wiązanie konkretnych instancji
$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\API', $api);
Masz również możliwość z-bind-ować konkretną instancję do kontenera IoC, w związku z czym za każdym razem zostanie zwrócona właśnie ta konkretna instancja.
Wiązanie typów podstawowych
Równie przydatną możliwością jaką masz, jest powiązanie jakiejś wartości z zakresu podstawowych typów danych (np. integer) do konkretnej klasy pod konkretnym identyfikatorem zmiennej . Wówczas żądanie o wspomnianą zmienną zwróci tę właśnie wcześniej powiązaną przez ciebie konkretną wartość.
$this->app->when('App\Http\Controllers\UserController')
->needs('$variableName')
->give($value);
Wiązanie interfejsu do implementacji
Jeśli potrzebujesz powiązać interfejs z konkretną jego implementacją, też masz taką możliwość. Gdy będziesz potrzebował instancji tego interfejsu to zwrócony zostanie obiekt klasy który sam wcześniej powiązałeś z tym interfejsem w kontenerze IoC .
$this->app->bind(
'App\Contracts\EventPusher',
'App\Services\RedisEventPusher'
);
Wiązanie interfejsu w zależności od kontekstu
Co więcej, gdy potrzebujesz powiązać dwie ale wyciągnąć jedną implementacje danego interfejsu - w zależności od klasy która domaga się jego instancji – też masz taką możliwość !
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
Wyciąganie
Są trzy sposoby wyciągania z kontenera IoC. Możesz to zrobić wykorzystując wstrzyknięcie poprzez konstruktor, wykorzystując wstrzyknięcie poprzez sygnaturę metody/funkcji lub po prostu wyciągnąć z użyciem specjalnie do tego przeznaczonych funkcji pomocniczych, takich jak „resolve()”.
$api = resolve('HelpSpot\API');
Jeżeli danej klasy nie wyciągasz z wykorzystaniem kontenera IoC, możesz to zrobić z wykorzystaniem tablicy asocjacyjnej do metody „makeWith”.
$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);
Tagowanie
Masz również możliwość łączenia konkretnych wiązań w tagi/kategorie. Wówczas masz możliwość wyciągnąć je wszystkie na raz posługując się zdefiniowanym tagiem .
$this->app->bind('SpeedReport', function () {
//
});
$this->app->bind('MemoryReport', function () {
//
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
Wydarzenia kontenera IoC
Warto wspomnieć, że za każdym razem gdy Service Container wyciąga jakikolwiek obiekt, to wyrzuca event którego powstanie możesz nasłuchiwać . Możesz w ten sposób przechwycić obiekt zanim zostanie przesłany na żądanie i dokonać jego modyfikacji w locie .
$this->app->resolving(function ($object, $app) {
// Called when container resolves object of any type...
});
$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
// Called when container resolves objects of type "HelpSpot\API"...
});
PSR-11
Laravelowy kontener IoC implementuje interfejs PSR-11. W związku z czym możesz wprowadzić „ContainerInterface” jako type-hint by uzyskać instancję całego kontenera IoC . Następnie możesz wyciągnąć z niego jakikolwiek serwis tylko potrzebujesz. Lub dokonać jakiejkolwiek innej akcji na nim.
Route::get('/', function (ContainerInterface $container) {
$service = $container->get('Service');
I to już wszystko w temacie Service Container-a
Wyłożone jak należy . Dziękuję za lekturę i zapraszam do czytania innych moich artykułów .
Jeśli nurtuje cię kwestia Middleware w Laravelu - zapraszam do tego artykułu !
Zapraszam również do tematu poświęconego ochronie przeciw CSRF .
Bywaj!