Salta ai contenuti

Iniezione di Dipendenze in XOOPS

L’iniezione di dipendenze (DI) è un pattern di design che consente ai componenti di ricevere le loro dipendenze da fonti esterne piuttosto che crearle internamente. XOOPS 4.0 introduce il supporto nativo per un contenitore DI compatibile con PSR-11.

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();
}
}
class ArticleService
{
public function __construct(
private readonly ArticleRepositoryInterface $repository,
private readonly EventDispatcherInterface $dispatcher
) {}
}
use Psr\Container\ContainerInterface;
// Ottieni il contenitore
$container = \Xmf\Module\Helper::getHelper('mymodule')->getContainer();
// Recupera un servizio
$articleService = $container->get(ArticleService::class);
// Verifica se il servizio esiste
if ($container->has(ArticleService::class)) {
// Usa il servizio
}
config/services.php
use Psr\Container\ContainerInterface;
return [
// Istanziazione semplice della classe
ArticleRepository::class => ArticleRepository::class,
// Binding interfaccia a implementazione
ArticleRepositoryInterface::class => ArticleRepository::class,
// Funzione factory
ArticleService::class => function (ContainerInterface $c): ArticleService {
return new ArticleService(
$c->get(ArticleRepositoryInterface::class),
$c->get(EventDispatcherInterface::class)
);
},
// Istanza condivisa (singleton)
'database' => function (): XoopsDatabase {
return XoopsDatabaseFactory::getDatabaseConnection();
},
];
// Il contenitore risolve automaticamente le dipendenze
// quando gli hint dei tipi sono disponibili
class ArticleController
{
public function __construct(
private readonly ArticleService $service,
private readonly ViewRenderer $renderer
) {}
}
// Il contenitore crea ArticleController con le sue dipendenze
$controller = $container->get(ArticleController::class);
config/services.php
return [
ArticleService::class => [
'class' => ArticleService::class,
'arguments' => [
ArticleRepositoryInterface::class,
EventDispatcherInterface::class,
],
'shared' => true, // Singleton
],
'article.handler' => [
'factory' => [ArticleHandlerFactory::class, 'create'],
'arguments' => ['@database'], // Riferimento ad altro servizio
],
];
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;
}
}
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);
}
}
interface ArticleRepositoryInterface
{
public function findById(int $id): ?Article;
public function save(Article $article): void;
public function delete(Article $article): void;
}
config/services.php
return [
ArticleRepositoryInterface::class => XoopsArticleRepository::class,
// Oppure con factory
ArticleRepositoryInterface::class => function (ContainerInterface $c) {
return new XoopsArticleRepository(
$c->get('database')
);
},
];
class ArticleServiceTest extends TestCase
{
public function testCreateArticle(): void
{
// Crea mock
$repository = $this->createMock(ArticleRepositoryInterface::class);
$dispatcher = $this->createMock(EventDispatcherInterface::class);
$logger = $this->createMock(LoggerInterface::class);
// Inietta mock
$service = new ArticleService($repository, $dispatcher, $logger);
// Imposta aspettative
$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);
}
}
// Ottieni servizio dal contenitore nel codice legacy
function mymodule_get_articles(int $limit): array
{
$container = \Xmf\Module\Helper::getHelper('mymodule')->getContainer();
$service = $container->get(ArticleService::class);
return $service->findRecent($limit);
}
config/services.php
return [
'article.handler' => function () {
return xoops_getModuleHandler('article', 'mymodule');
},
ArticleRepositoryInterface::class => function (ContainerInterface $c) {
return new LegacyArticleRepository(
$c->get('article.handler')
);
},
];
  1. Inietta interfacce - Dipendi da astrazioni, non da implementazioni
  2. Iniezione nel costruttore - Preferisci l’iniezione nel costruttore rispetto al setter
  3. Responsabilità unica - Ogni classe dovrebbe avere poche dipendenze
  4. Evita la consapevolezza del contenitore - I servizi non dovrebbero conoscere il contenitore
  5. Configura, non codificare - Usa i file di configurazione per il wiring
  • ../07-XOOPS-4.0/Implementation-Guides/PSR-11-Dependency-Injection-Guide - Implementazione PSR-11
  • ../03-Module-Development/Patterns/Service-Layer - Pattern Service
  • ../03-Module-Development/Best-Practices/Testing - Test con DI
  • ../07-XOOPS-4.0/XOOPS-4.0-Architecture - Panoramica dell’architettura