Wstrzykiwanie zależności w XOOPS
Przegląd
Dział zatytułowany „Przegląd”Wstrzykiwanie zależności (DI) to wzorzec projektowy, który pozwala komponentom otrzymywać swoje zależności z źródeł zewnętrznych zamiast tworzyć je wewnętrznie. XOOPS 4.0 wprowadza obsługę kontenera DI kompatybilnego z PSR-11.
Dlaczego wstrzykiwanie zależności?
Dział zatytułowany „Dlaczego wstrzykiwanie zależności?”Bez DI (ścisłe powiązanie)
Dział zatytułowany „Bez DI (ścisłe powiązanie)”class ArticleService{ private ArticleRepository $repository; private EventDispatcher $dispatcher;
public function __construct() { // Twarde zależności - trudne do testowania i modyfikacji $this->repository = new ArticleRepository(new XoopsDatabase()); $this->dispatcher = new EventDispatcher(); }}Z DI (luźne powiązanie)
Dział zatytułowany „Z DI (luźne powiązanie)”class ArticleService{ public function __construct( private readonly ArticleRepositoryInterface $repository, private readonly EventDispatcherInterface $dispatcher ) {}}Kontener PSR-11
Dział zatytułowany „Kontener PSR-11”Podstawowe użycie
Dział zatytułowany „Podstawowe użycie”use Psr\Container\ContainerInterface;
// Pobierz kontener$container = \Xmf\Module\Helper::getHelper('mymodule')->getContainer();
// Pobierz usługę$articleService = $container->get(ArticleService::class);
// Sprawdź czy usługa istniejeif ($container->has(ArticleService::class)) { // Użyj usługę}Konfiguracja kontenera
Dział zatytułowany „Konfiguracja kontenera”use Psr\Container\ContainerInterface;
return [ // Prosta instancjacja klasy ArticleRepository::class => ArticleRepository::class,
// Wiązanie interfejsu do implementacji ArticleRepositoryInterface::class => ArticleRepository::class,
// Funkcja fabryki ArticleService::class => function (ContainerInterface $c): ArticleService { return new ArticleService( $c->get(ArticleRepositoryInterface::class), $c->get(EventDispatcherInterface::class) ); },
// Współdzielona instancja (singleton) 'database' => function (): XoopsDatabase { return XoopsDatabaseFactory::getDatabaseConnection(); },];Rejestracja usługi
Dział zatytułowany „Rejestracja usługi”Automatyczne kierowanie
Dział zatytułowany „Automatyczne kierowanie”// Kontener automatycznie rozwiązuje zależności// gdy są dostępne wskazówki typów
class ArticleController{ public function __construct( private readonly ArticleService $service, private readonly ViewRenderer $renderer ) {}}
// Kontener tworzy ArticleController z jego zależnościami$controller = $container->get(ArticleController::class);Ręczna rejestracja
Dział zatytułowany „Ręczna rejestracja”return [ ArticleService::class => [ 'class' => ArticleService::class, 'arguments' => [ ArticleRepositoryInterface::class, EventDispatcherInterface::class, ], 'shared' => true, // Singleton ],
'article.handler' => [ 'factory' => [ArticleHandlerFactory::class, 'create'], 'arguments' => ['@database'], // Odwołanie do innej usługi ],];Injection przez konstruktor
Dział zatytułowany „Injection przez konstruktor”Preferowana metoda
Dział zatytułowany „Preferowana metoda”final class ArticleService{ public function __construct( private readonly ArticleRepositoryInterface $repository, private readonly EventDispatcherInterface $dispatcher, private readonly LoggerInterface $logger ) {}
public function create(CreateArticleDTO $dto): Article { $this->logger->info('Creating article', ['title' => $dto->title]);
$article = Article::create($dto); $this->repository->save($article); $this->dispatcher->dispatch(new ArticleCreatedEvent($article));
return $article; }}Injection przez metodę
Dział zatytułowany „Injection przez metodę”Dla opcjonalnych zależności
Dział zatytułowany „Dla opcjonalnych zależności”class ArticleController{ public function __construct( private readonly ArticleService $service ) {}
public function show(int $id, ?CacheInterface $cache = null): Response { $cacheKey = "article_{$id}";
if ($cache && $cached = $cache->get($cacheKey)) { return $this->render($cached); }
$article = $this->service->findById($id);
$cache?->set($cacheKey, $article, 3600);
return $this->render($article); }}Wiązanie interfejsu
Dział zatytułowany „Wiązanie interfejsu”Definiuj interfejsy
Dział zatytułowany „Definiuj interfejsy”interface ArticleRepositoryInterface{ public function findById(int $id): ?Article; public function save(Article $article): void; public function delete(Article $article): void;}Wiąż implementację
Dział zatytułowany „Wiąż implementację”return [ ArticleRepositoryInterface::class => XoopsArticleRepository::class,
// Lub z fabryką ArticleRepositoryInterface::class => function (ContainerInterface $c) { return new XoopsArticleRepository( $c->get('database') ); },];Testowanie z DI
Dział zatytułowany „Testowanie z DI”Łatwe mockowanie
Dział zatytułowany „Łatwe mockowanie”class ArticleServiceTest extends TestCase{ public function testCreateArticle(): void { // Utwórz mocki $repository = $this->createMock(ArticleRepositoryInterface::class); $dispatcher = $this->createMock(EventDispatcherInterface::class); $logger = $this->createMock(LoggerInterface::class);
// Wstrzyknij mocki $service = new ArticleService($repository, $dispatcher, $logger);
// Ustaw oczekiwania $repository->expects($this->once())->method('save'); $dispatcher->expects($this->once())->method('dispatch');
// Testuj $dto = new CreateArticleDTO('Title', 'Content'); $article = $service->create($dto);
$this->assertInstanceOf(Article::class, $article); }}Integracja z starszym kodem XOOPS
Dział zatytułowany „Integracja z starszym kodem XOOPS”Łączenie starego i nowego
Dział zatytułowany „Łączenie starego i nowego”// Pobierz usługę z kontenera w starszym kodziefunction mymodule_get_articles(int $limit): array{ $container = \Xmf\Module\Helper::getHelper('mymodule')->getContainer(); $service = $container->get(ArticleService::class);
return $service->findRecent($limit);}Zawijanie starszych obsługiwaczy
Dział zatytułowany „Zawijanie starszych obsługiwaczy”return [ 'article.handler' => function () { return xoops_getModuleHandler('article', 'mymodule'); },
ArticleRepositoryInterface::class => function (ContainerInterface $c) { return new LegacyArticleRepository( $c->get('article.handler') ); },];Najlepsze praktyki
Dział zatytułowany „Najlepsze praktyki”- Wstrzykuj interfejsy - Zależ od abstrakcji, nie implementacji
- Injection przez konstruktor - Preferuj konstruktor nad setterem
- Jedna odpowiedzialność - Każda klasa powinna mieć mało zależności
- Unikaj świadomości kontenera - Usługi nie powinny wiedzieć o kontenerze
- Konfiguruj, nie koduj - Użyj plików konfiguracyjnych do połączenia
Powiązana dokumentacja
Dział zatytułowany „Powiązana dokumentacja”- ../07-XOOPS-4.0/Implementation-Guides/PSR-11-Dependency-Injection-Guide - Przewodnik implementacji PSR-11
- ../03-Module-Development/Patterns/Service-Layer - Wzorzec serwisu
- ../03-Module-Development/Best-Practices/Testing - Testowanie z DI
- ../07-XOOPS-4.0/XOOPS-4.0-Architecture - Przegląd architektury