Afhankelijkheidsinjectie in XOOPS
Overzicht
Section titled “Overzicht”Dependency Injection (DI) is een ontwerppatroon waarmee componenten hun afhankelijkheden van externe bronnen kunnen ontvangen in plaats van deze intern te creëren. XOOPS 4.0 introduceert PSR-11-compatibele DI-containerondersteuning.
Waarom afhankelijkheidsinjectie?
Section titled “Waarom afhankelijkheidsinjectie?”Zonder DI (strakke koppeling)
Section titled “Zonder DI (strakke koppeling)”class ArticleService{ private ArticleRepository $repository; private EventDispatcher $dispatcher;
public function __construct() { // Hard dependencies - difficult to test and modify $this->repository = new ArticleRepository(new XoopsDatabase()); $this->dispatcher = new EventDispatcher(); }}Met DI (losse koppeling)
Section titled “Met DI (losse koppeling)”class ArticleService{ public function __construct( private readonly ArticleRepositoryInterface $repository, private readonly EventDispatcherInterface $dispatcher ) {}}PSR-11-container
Section titled “PSR-11-container”Basisgebruik
Section titled “Basisgebruik”use Psr\Container\ContainerInterface;
// Get the container$container = \Xmf\Module\Helper::getHelper('mymodule')->getContainer();
// Retrieve a service$articleService = $container->get(ArticleService::class);
// Check if service existsif ($container->has(ArticleService::class)) { // Use the service}Containerconfiguratie
Section titled “Containerconfiguratie”use Psr\Container\ContainerInterface;
return [ // Simple class instantiation ArticleRepository::class => ArticleRepository::class,
// Interface to implementation binding ArticleRepositoryInterface::class => ArticleRepository::class,
// Factory function ArticleService::class => function (ContainerInterface $c): ArticleService { return new ArticleService( $c->get(ArticleRepositoryInterface::class), $c->get(EventDispatcherInterface::class) ); },
// Shared instance (singleton) 'database' => function (): XoopsDatabase { return XoopsDatabaseFactory::getDatabaseConnection(); },];Serviceregistratie
Section titled “Serviceregistratie”Automatische bedrading
Section titled “Automatische bedrading”// The container automatically resolves dependencies// when type hints are available
class ArticleController{ public function __construct( private readonly ArticleService $service, private readonly ViewRenderer $renderer ) {}}
// Container creates ArticleController with its dependencies$controller = $container->get(ArticleController::class);Handmatige registratie
Section titled “Handmatige registratie”return [ ArticleService::class => [ 'class' => ArticleService::class, 'arguments' => [ ArticleRepositoryInterface::class, EventDispatcherInterface::class, ], 'shared' => true, // Singleton ],
'article.handler' => [ 'factory' => [ArticleHandlerFactory::class, 'create'], 'arguments' => ['@database'], // Reference other service ],];Constructorinjectie
Section titled “Constructorinjectie”Voorkeursaanpak
Section titled “Voorkeursaanpak”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; }}Methode Injectie
Section titled “Methode Injectie”Voor optionele afhankelijkheden
Section titled “Voor optionele afhankelijkheden”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); }}Interfacebinding
Section titled “Interfacebinding”Interfaces definiëren
Section titled “Interfaces definiëren”interface ArticleRepositoryInterface{ public function findById(int $id): ?Article; public function save(Article $article): void; public function delete(Article $article): void;}Bind-implementatie
Section titled “Bind-implementatie”return [ ArticleRepositoryInterface::class => XoopsArticleRepository::class,
// Or with factory ArticleRepositoryInterface::class => function (ContainerInterface $c) { return new XoopsArticleRepository( $c->get('database') ); },];Testen met DI
Section titled “Testen met DI”Gemakkelijk spotten
Section titled “Gemakkelijk spotten”class ArticleServiceTest extends TestCase{ public function testCreateArticle(): void { // Create mocks $repository = $this->createMock(ArticleRepositoryInterface::class); $dispatcher = $this->createMock(EventDispatcherInterface::class); $logger = $this->createMock(LoggerInterface::class);
// Inject mocks $service = new ArticleService($repository, $dispatcher, $logger);
// Set expectations $repository->expects($this->once())->method('save'); $dispatcher->expects($this->once())->method('dispatch');
// Test $dto = new CreateArticleDTO('Title', 'Content'); $article = $service->create($dto);
$this->assertInstanceOf(Article::class, $article); }}XOOPS Legacy-integratie
Section titled “XOOPS Legacy-integratie”Een brug slaan tussen oud en nieuw
Section titled “Een brug slaan tussen oud en nieuw”// Get service from container in legacy codefunction mymodule_get_articles(int $limit): array{ $container = \Xmf\Module\Helper::getHelper('mymodule')->getContainer(); $service = $container->get(ArticleService::class);
return $service->findRecent($limit);}Verouderde handlers inpakken
Section titled “Verouderde handlers inpakken”return [ 'article.handler' => function () { return xoops_getModuleHandler('article', 'mymodule'); },
ArticleRepositoryInterface::class => function (ContainerInterface $c) { return new LegacyArticleRepository( $c->get('article.handler') ); },];Beste praktijken
Section titled “Beste praktijken”- Injecteer interfaces - Afhankelijk van abstracties, niet van implementaties
- Constructor-injectie - Geef de voorkeur aan constructor-injectie boven setter-injectie
- Eén verantwoordelijkheid - Elke klasse zou weinig afhankelijkheden moeten hebben
- Vermijd containerbewustzijn - Services mogen niet op de hoogte zijn van de container
- Configureren, niet coderen - Gebruik configuratiebestanden voor bedrading
Gerelateerde documentatie
Section titled “Gerelateerde documentatie”- ../07-XOOPS-4.0/Implementation-Guides/PSR-11-Dependency-Injection-Guide - PSR-11 implementatie
- ../03-Module-Development/Patterns/Service-Layer - Servicepatroon
- ../03-Module-Ontwikkeling/Best-Practices/Testen - Testen met DI
- ../07-XOOPS-4.0/XOOPS-4.0-Architectuur - Architectuuroverzicht