Dependency Injection w Magento 2 to temat, który na początku wygląda jak magia. Wpisujesz interfejs w konstruktorze, a Magento samo dostarcza właściwą implementację. Skąd wie co wstrzyknąć? Jak działa kompilacja DI? Co to są virtual types i po co są? Rozkładam to na czynniki pierwsze.
Podstawy – czym jest kontener DI
Kontener DI to mechanizm, który buduje obiekty za Ciebie – razem z ich zależnościami. Zamiast pisać:
<?php
// Bez DI – ręczne tworzenie zależności
$logger = new \Monolog\Logger('app');
$repository = new ProductRepository($logger, new DbConnection());
$service = new ProductService($repository, $logger);
Prosisz kontener o obiekt, a on sam rozwiązuje całe drzewo zależności:
<?php // Z DI – kontener buduje całe drzewo $service = $objectManager->get(ProductService::class);
W Magento 2 nigdy nie używasz ObjectManager bezpośrednio w swoim kodzie (poza Factory i Proxy). Zależności deklarujesz w konstruktorze, a Magento wstrzykuje je automatycznie.
Jak Magento rozwiązuje typy – di.xml i kompilacja
Gdy Magento widzi w konstruktorze interfejs, szuka w plikach di.xml zarejestrowanego preference:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<!-- Gdy ktoś poprosi o ProductRepositoryInterface, daj mu ProductRepository -->
<preference for="Magento\Catalog\Api\ProductRepositoryInterface"
type="Magento\Catalog\Model\ProductRepository"/>
</config>
Podczas kompilacji (bin/magento setup:di:compile) Magento analizuje wszystkie di.xml ze wszystkich modułów, buduje kompletny graf zależności i generuje klasy Interceptor (dla pluginów) oraz Proxy. Wynik trafia do generated/. Dlatego po zmianie di.xml trzeba uruchomić kompilację – w trybie developer Magento robi to automatycznie.
Argumenty konstruktora – wstrzykiwanie konkretnych wartości
Przez di.xml możesz nie tylko mapować interfejsy na klasy, ale też przekazywać konkretne wartości do konstruktorów:
<?xml version="1.0"?>
<config>
<type name="Vendor\Module\Model\ApiClient">
<arguments>
<argument name="timeout" xsi:type="number">30</argument>
<argument name="baseUrl" xsi:type="string">https://api.example.com</argument>
<argument name="logger" xsi:type="object">Psr\Log\LoggerInterface</argument>
</arguments>
</type>
</config>
<?php
namespace Vendor\Module\Model;
class ApiClient
{
public function __construct(
private \Psr\Log\LoggerInterface $logger,
private string $baseUrl,
private int $timeout = 10
) {}
}
Virtual Types – klasy bez pisania kodu PHP
Virtual Type to jeden z najbardziej niedocenianych mechanizmów Magento 2. Pozwala stworzyć „nową klasę” przez konfigurację XML – bez pisania ani jednej linii PHP. Przydatne gdy chcesz mieć dwie instancje tej samej klasy z różnymi parametrami:
<?xml version="1.0"?>
<config>
<!-- Tworzymy "wirtualną klasę" FileLogger opartą na Monolog\Logger -->
<virtualType name="Vendor\Module\Model\FileLogger"
type="Magento\Framework\Logger\Monolog">
<arguments>
<argument name="name" xsi:type="string">file_logger</argument>
</arguments>
</virtualType>
<!-- I drugą z inną konfiguracją -->
<virtualType name="Vendor\Module\Model\ApiLogger"
type="Magento\Framework\Logger\Monolog">
<arguments>
<argument name="name" xsi:type="string">api_logger</argument>
</arguments>
</virtualType>
<!-- Wstrzyknięcie konkretnego loggera do konkretnej klasy -->
<type name="Vendor\Module\Model\ApiClient">
<arguments>
<argument name="logger" xsi:type="object">Vendor\Module\Model\ApiLogger</argument>
</arguments>
</type>
</config>
Virtual Types istnieją tylko w kontenerze DI – nie możesz ich użyć jako type hinta w PHP. Ale możesz je wstrzykiwać jako argumenty przez xsi:type="object".
Shared vs Non-shared – singleton czy nowy obiekt?
Domyślnie Magento tworzy każdą klasę jako singleton w obrębie jednego requestu – kontener zwraca tę samą instancję przy kolejnych wywołaniach. Możesz to zmienić:
<?xml version="1.0"?>
<config>
<!-- shared="false" = nowa instancja przy każdym wstrzyknięciu -->
<type name="Vendor\Module\Model\Cart">
<arguments>
<argument name="shared" xsi:type="boolean">false</argument>
</arguments>
</type>
</config>
W praktyce gdy potrzebujesz wielu instancji tej samej klasy (np. przy przetwarzaniu wielu zamówień w pętli), Magento generuje dla Ciebie klasę Factory:
<?php
// Magento generuje Vendor\Module\Model\OrderFactory automatycznie
// jeśli poprosisz o nią w konstruktorze
class OrderProcessor
{
public function __construct(
private \Vendor\Module\Model\OrderFactory $orderFactory
) {}
public function processMany(array $orderData): void
{
foreach ($orderData as $data) {
// Każde create() zwraca nową instancję Order
$order = $this->orderFactory->create(['data' => $data]);
$order->process();
}
}
}
Podsumowanie
Kontener DI w Magento 2 to potężne narzędzie, które przy dobrym zrozumieniu pozwala pisać kod elastyczny i łatwy do testowania. Kluczowe rzeczy do zapamiętania: preference mapuje interfejsy na implementacje, Virtual Types pozwalają konfigurować warianty klas bez PHP, a Factory to rozwiązanie gdy potrzebujesz wielu instancji jednej klasy.
