Injeção de Dependência no XOOPS
Visão Geral
Seção intitulada “Visão Geral”Injeção de Dependência (DI) é um padrão de design que permite aos componentes receber suas dependências de fontes externas em vez de criá-las internamente. O XOOPS 4.0 introduz suporte nativo de container compatível com PSR-11.
Por que Injeção de Dependência?
Seção intitulada “Por que Injeção de Dependência?”Sem DI (Acoplamento Forte)
Seção intitulada “Sem DI (Acoplamento Forte)”class ArticleService{ private ArticleRepository $repository; private EventDispatcher $dispatcher;
public function __construct() { // Dependências hard - difíceis de testar e modificar $this->repository = new ArticleRepository(new XoopsDatabase()); $this->dispatcher = new EventDispatcher(); }}Com DI (Acoplamento Fraco)
Seção intitulada “Com DI (Acoplamento Fraco)”class ArticleService{ public function __construct( private readonly ArticleRepositoryInterface $repository, private readonly EventDispatcherInterface $dispatcher ) {}}Container PSR-11
Seção intitulada “Container PSR-11”Uso Básico
Seção intitulada “Uso Básico”use Psr\Container\ContainerInterface;
// Obter o container$container = \Xmf\Module\Helper::getHelper('mymodule')->getContainer();
// Recuperar um serviço$articleService = $container->get(ArticleService::class);
// Verificar se serviço existeif ($container->has(ArticleService::class)) { // Usar o serviço}Configuração do Container
Seção intitulada “Configuração do Container”use Psr\Container\ContainerInterface;
return [ // Instanciação de classe simples ArticleRepository::class => ArticleRepository::class,
// Vinculação de interface para implementação ArticleRepositoryInterface::class => ArticleRepository::class,
// Função factory ArticleService::class => function (ContainerInterface $c): ArticleService { return new ArticleService( $c->get(ArticleRepositoryInterface::class), $c->get(EventDispatcherInterface::class) ); },
// Instância compartilhada (singleton) 'database' => function (): XoopsDatabase { return XoopsDatabaseFactory::getDatabaseConnection(); },];Registro de Serviço
Seção intitulada “Registro de Serviço”Auto-wiring
Seção intitulada “Auto-wiring”// O container resolve automaticamente as dependências// quando type hints estão disponíveis
class ArticleController{ public function __construct( private readonly ArticleService $service, private readonly ViewRenderer $renderer ) {}}
// Container cria ArticleController com suas dependências$controller = $container->get(ArticleController::class);Registro Manual
Seção intitulada “Registro Manual”return [ ArticleService::class => [ 'class' => ArticleService::class, 'arguments' => [ ArticleRepositoryInterface::class, EventDispatcherInterface::class, ], 'shared' => true, // Singleton ],
'article.handler' => [ 'factory' => [ArticleHandlerFactory::class, 'create'], 'arguments' => ['@database'], // Referência a outro serviço ],];Injeção de Construtor
Seção intitulada “Injeção de Construtor”Abordagem Preferida
Seção intitulada “Abordagem Preferida”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('Criando artigo', ['title' => $dto->title]);
$article = Article::create($dto); $this->repository->save($article); $this->dispatcher->dispatch(new ArticleCreatedEvent($article));
return $article; }}Injeção de Método
Seção intitulada “Injeção de Método”Para Dependências Opcionais
Seção intitulada “Para Dependências Opcionais”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); }}Vinculação de Interface
Seção intitulada “Vinculação de Interface”Definir Interfaces
Seção intitulada “Definir Interfaces”interface ArticleRepositoryInterface{ public function findById(int $id): ?Article; public function save(Article $article): void; public function delete(Article $article): void;}Vincular Implementação
Seção intitulada “Vincular Implementação”return [ ArticleRepositoryInterface::class => XoopsArticleRepository::class,
// Ou com factory ArticleRepositoryInterface::class => function (ContainerInterface $c) { return new XoopsArticleRepository( $c->get('database') ); },];Testando com DI
Seção intitulada “Testando com DI”Mockagem Fácil
Seção intitulada “Mockagem Fácil”class ArticleServiceTest extends TestCase{ public function testCreateArticle(): void { // Criar mocks $repository = $this->createMock(ArticleRepositoryInterface::class); $dispatcher = $this->createMock(EventDispatcherInterface::class); $logger = $this->createMock(LoggerInterface::class);
// Injetar mocks $service = new ArticleService($repository, $dispatcher, $logger);
// Definir expectativas $repository->expects($this->once())->method('save'); $dispatcher->expects($this->once())->method('dispatch');
// Testar $dto = new CreateArticleDTO('Título', 'Conteúdo'); $article = $service->create($dto);
$this->assertInstanceOf(Article::class, $article); }}Integração Legada XOOPS
Seção intitulada “Integração Legada XOOPS”Conectando Antigo e Novo
Seção intitulada “Conectando Antigo e Novo”// Obter serviço do container em código legadofunction mymodule_get_articles(int $limit): array{ $container = \Xmf\Module\Helper::getHelper('mymodule')->getContainer(); $service = $container->get(ArticleService::class);
return $service->findRecent($limit);}Envolvendo Manipuladores Legados
Seção intitulada “Envolvendo Manipuladores Legados”return [ 'article.handler' => function () { return xoops_getModuleHandler('article', 'mymodule'); },
ArticleRepositoryInterface::class => function (ContainerInterface $c) { return new LegacyArticleRepository( $c->get('article.handler') ); },];Boas Práticas
Seção intitulada “Boas Práticas”- Injetar Interfaces - Depender de abstrações, não implementações
- Injeção de Construtor - Preferir construtor sobre injeção por setter
- Responsabilidade Única - Cada classe deve ter poucas dependências
- Evitar Consciência de Container - Serviços não devem saber sobre o container
- Configurar, Não Codificar - Usar arquivos de configuração para wiring
Documentação Relacionada
Seção intitulada “Documentação Relacionada”- ../07-XOOPS-4.0/Implementation-Guides/PSR-11-Dependency-Injection-Guide - Implementação PSR-11
- ../03-Module-Development/Patterns/Service-Layer - Padrão de serviço
- ../03-Module-Development/Best-Practices/Testing - Testando com DI
- ../07-XOOPS-4.0/XOOPS-4.0-Architecture - Visão geral de arquitetura