Symfony 3 Dependency Injection + Service Container

Pracując Symfony 3 na pewno używałeś Service Container i Dependency Injection. To bardzo podstawowy – corowy mechanizm. Jest czymś za co bardzo lubię Symfony, wiem, że inne frameworki też z niego korzystają, ale tutaj akurat najbardziej przypadł mi do gustu. Dlatego jeśli chcesz bliżej poznać sposób korzystania z DI, SC i parametrów to zapraszam do lektury.

Czym jest Dependency Injection

DI to wzorzec projektowy i architektoniczny, który uprasza zależności między klasami. Dawniej, jeśli jedna klasa potrzebowała użyć w drugiej to w najlepszym wypadku w swoim konstruktorze tworzyła obiekt klasy, który potrzebowała. DI zmienia tą logikę na rzecz architektury plug-in czyli takiej gdzie do konstruktora jest już przekazywany gotowy obiekt klasy, która jest potrzeba. Dla zobrazowania najpierw stara wersja:

public function __contruct() {
$this->relatedClass = new RelatedClass()
}

A to nowa zgodna z DI:

public function __contruct(RelatedClass $relatedClass) {
$this->relatedClass = $relatedClass;
}

DI moim zdaniem jest bardzo popularnym wzorcem stosowanym w każdym liczącym się frameworku. Bardzo dobrze sprawdza się również w projektach wykorzystujących podejście Test Driven Development.

Symfony Service Container

Kolejnym krokiem do sprawnego działania w Symfony poza DI wprowadzono Service Container. Polega on na tym, że mamy jeden kontener, do którego możemy wrzucić klasy. Do kontenera mamy dostęp w każdej klasie więc pośrednio do każdej klasy, która się znajduje w kontenerze. Wiem, brzmi skomplikowanie, ale nie jest taki trudne. Pokażę to ponownie na przykładzie.

Od samego początku mamy w kontenerze wrzucone pare ogólnych serwisów jak Mailer czy Logger. Dzięki temu w kontrolerze możemy zrobić coś takiego:

$logger = $container->get('logger');

Oznacza to, że w naszym kontenerze pod kluczem “logger” znajduje się klasa Logger.php, zbieżność nazwy klasy z kluczem nie jest obowiązkowa. Teraz kiedy pod zmienną logger mamy już OBIEKT klasy Logger możemy używać wszystkich jego publicznych metod. Proste prawda?

Oczywiście możemy wrzucać do kontenera swoje klasy. Na przykład tworzymy sobie klasę FeedingProccess, która ma metodę process($animal) przyjmującą jako parametr obiekt Animal. Dla zrozumienia kontekstu chodzi o to, że mamy klasę odpowiedzialną za proces karmienia zwierząt. Zwierze, które ma nakarmić przekazujemy jako parametr metody process().

Jeśli np. w kontrolerze chcemy wywołać metodę process() z klasy FeedingProcess mamy pare opcji. Moglibyśmy stworzyć bezpośrednio obiekt tej klasy lub jak pozwala nam Service Container zrobić tak:

$feedingProcess = $container->get('feeding_process');

Jeśli jednak chcemy wybrać drugą opcję w Symfony 3 musimy jeszcze tą klasę wrzucić do kontenera pod kluczem “feeding_process”. Chciałabym jeszcze zwrócić Ci szczególną uwagę, że mówimy o Symfony 3, ponieważ w S4 wygląda to inaczej za sprawą autowritingu, ale o tym w innym poście. .

Aby zarejestrować naszą klasę w kontenerze musimy stworzyć plik Resources/config/services/service.yml lub xml w naszym bundlu lub rozbudować plik app/config/service.yml. Rejestracja wygląda następująco:

services:
feeding_process:
class:
MyBundle\Process\FeedingProcess

Brawo, masz już dostęp do klasy w kontenerze. Ale co wspólnego z tym ma DI? Już piszę.

Dependecy Injection + Service Container

Kiedy wiesz już co to SC to wiesz, jak rejestrować serwisy w kontenerze. Jednak co w sytuacji, gdy mają one zależności w konstruktorze? Korzystając z DI możemy w bardzo prosty i elastyczny sposób budować zależności między klasami. Po prostu, konfigurujemy zależności między kluczami, resztę załatwia Symfony.

Jeśli nasza klasam FeedingProcess przyjmowałaby w konstruktorze klasę CleaningProcess, która jest odpowiedzialna za sprzątanie po każdym karmieniu to wystarczy zrobić tak:

services:
  cleaning_process:
  class: MyBundle\ProcessCleaningProcess
feeding_process:
class:
MyBundle\Process\FeedingProcess
arguments:
- '@cleaning_process'

Możemy też klasy cleaning_process nie rejestrować jako serwis, wtedy zapis wygląda następująco:

services:
feeding_process:
class:
MyBundle\Process\FeedingProcess
arguments:
- MyBundle\ProcessCleaningProcess

Jeśli argumentów jest więcej wypisujemy kolejno kolejne klasy. Proste prawda?

Typy parametrów

Jako argumenty możemy wrzucać różne rzeczy. Relacje do klas jak było w przykładzie powyżej, ale również zmienne z konfiguracji, tablice czy stałe. Zasada jest analogiczna, różnią się one jedynie zapisem.

Klasa

services:
feeding_process:
class:
MyBundle\Process\FeedingProcess
arguments:
- MyBundle\ProcessCleaningProcess

Zmienna

services:
feeding_process:
class:
MyBundle\Process\FeedingProcess
arguments:
- '%zmienna_a%'

Tablica

services:
feeding_process:
class:
MyBundle\Process\FeedingProcess
arguments:
- [ @klasa_a, @klasa_b, @klasa_c ]

lub

services:
feeding_process:
class:
MyBundle\Process\FeedingProcess
arguments:
- @klasa_a
- @klasa_b
- @klasa_c

Oto praktycznie komplet podstawowej wiedzy o DI, SC i parametrach Symfony 3. Pamiętam, że w Symfony 4 SC wygląda inaczej co będzie w najbliższych postach więc bądź czujny. Oczywiście jest jeszcze masa kruczków, które pojawią się w trakcie Twojej pracy z Symfony. Jeśli masz jakieś dodatkowe pytania, coś jest niejasne albo napotkałeś problem, z którym nie potrafisz sobie poradzić, pisz śmiało!

Podobne posty

Jestem programistką, która lubi mieć ręce pełne roboty. Do życia potrzebuje komputera z internetem i kubka gorącej kawy. Więcej na stronie o mnie.

Comments

  1. Zajrzałem tu przypadkiem, poszukując obiecanych przykładów z DDD w PHP 🙂 Ciekawa lektura o tym podejściu, jednak nie mogę tu znaleźć przykładów z PHP-a. Czy źle szukam ;)?

    P.S. Powyższego wpisu nie czytałem, jednak rzuciło mi się w oczy Server Container – myślę, że powinno być Service Container 🙂

    • Cześć Michał, cieszę się i dziękuje 🙂
      Nie ma przykładowego kodu dla DDD, to prawda, ponieważ DDD to bardzo szeroki temat, bardziej podejście niż rozwiązanie. Szykuję jednak coś bardziej szczegółowego w tym temacie razem z przykładowym kodem więc zapraszam niedługo 🙂

ZOSTAW ODPOWIEDŹ

Please enter your comment!
Please enter your name here