Boas Práticas de Desenvolvimento de Módulo
Visão Geral
Seção intitulada “Visão Geral”Este documento consolida as melhores práticas para desenvolver módulos XOOPS de alta qualidade. Seguir essas diretrizes garante módulos manteníveis, seguros e eficazes em relação ao desempenho.
Arquitetura
Seção intitulada “Arquitetura”Seguir Arquitetura Limpa
Seção intitulada “Seguir Arquitetura Limpa”Organize código em camadas:
src/├── Domain/ # Lógica de negócios, entidades├── Application/ # Casos de uso, serviços├── Infrastructure/ # Banco de dados, serviços externos└── Presentation/ # Controllers, templatesResponsabilidade Única
Seção intitulada “Responsabilidade Única”Cada classe deve ter um motivo para mudar:
// Bom: Classes focadasclass ArticleRepository { /* apenas persistência */ }class ArticleValidator { /* apenas validação */ }class ArticleNotifier { /* apenas notificações */ }
// Ruim: Classe Deusclass Article { public function save() { } public function validate() { } public function notify() { } public function generatePDF() { }}Injeção de Dependência
Seção intitulada “Injeção de Dependência”Injete dependências, não as crie:
// Bompublic function __construct( private readonly ArticleRepositoryInterface $repository) {}
// Ruimpublic function __construct() { $this->repository = new ArticleRepository();}Qualidade de Código
Seção intitulada “Qualidade de Código”Segurança de Tipo
Seção intitulada “Segurança de Tipo”Use tipos rigorosos e declarações de tipo:
<?php
declare(strict_types=1);
final class ArticleService{ public function findById(int $id): ?Article { // ... }
public function create(CreateArticleDTO $dto): Article { // ... }}Tratamento de Erros
Seção intitulada “Tratamento de Erros”Use exceções apropriadamente:
// Lançar exceções específicasthrow new ArticleNotFoundException($id);throw new ValidationException($errors);throw new UnauthorizedException('Não pode editar este artigo');
// Capturar no nível apropriadotry { $article = $service->create($dto);} catch (ValidationException $e) { return $this->renderForm($e->getErrors());} catch (UnauthorizedException $e) { return $this->redirectToLogin();}Segurança Nula
Seção intitulada “Segurança Nula”Evite nulo quando possível:
// Usar padrão de objeto nulopublic function getAuthor(): UserInterface{ return $this->author ?? new AnonymousUser();}
// Usar padrão Optional/Maybepublic function findById(int $id): ?Article{ // Retorno claramente anulável}Banco de Dados
Seção intitulada “Banco de Dados”Usar Criteria para Consultas
Seção intitulada “Usar Criteria para Consultas”$criteria = new CriteriaCompo();$criteria->add(new Criteria('status', 'published'));$criteria->add(new Criteria('category_id', $categoryId));$criteria->setSort('created_at');$criteria->setOrder('DESC');$criteria->setLimit($limit);
$items = $handler->getObjects($criteria);Escapar Entrada do Usuário
Seção intitulada “Escapar Entrada do Usuário”$sql = sprintf( "SELECT * FROM %s WHERE id = %d AND title = %s", $db->prefix('mymodule_items'), intval($id), $db->quoteString($title));Usar Transações
Seção intitulada “Usar Transações”$db->query('START TRANSACTION');
try { $handler->insert($article); $handler->insert($metadata); $db->query('COMMIT');} catch (\Exception $e) { $db->query('ROLLBACK'); throw $e;}Segurança
Seção intitulada “Segurança”Sempre Validar Entrada
Seção intitulada “Sempre Validar Entrada”use Xmf\Request;
$id = Request::getInt('id', 0);$title = Request::getString('title', '');$data = Request::getArray('data', []);
// Validação adicionalif (strlen($title) < 5) { throw new ValidationException('Título muito curto');}Usar Tokens CSRF
Seção intitulada “Usar Tokens CSRF”// Em formulário$form->addElement(new XoopsFormHiddenToken());
// Ao enviarif (!$GLOBALS['xoopsSecurity']->check()) { redirect_header('index.php', 3, 'Token inválido');}Verificar Permissões
Seção intitulada “Verificar Permissões”if (!$helper->isUserAdmin()) { redirect_header('index.php', 3, _NOPERM);}
if (!$permHandler->isGranted('edit', $categoryId)) { throw new UnauthorizedException();}Desempenho
Seção intitulada “Desempenho”Usar Cache
Seção intitulada “Usar Cache”$cache = $helper->getCache();$cacheKey = "articles_{$categoryId}_{$limit}";
$articles = $cache->read($cacheKey);if ($articles === false) { $articles = $handler->getArticles($categoryId, $limit); $cache->write($cacheKey, $articles, 3600);}Otimizar Consultas
Seção intitulada “Otimizar Consultas”// Usar índices// Adicionar a sql/mysql.sql:// INDEX `idx_status_date` (`status`, `created_at`)
// Selecionar apenas colunas necessárias$handler->getObjects($criteria, false, true); // asArray = true
// Usar paginação$criteria->setLimit($perPage);$criteria->setStart($offset);Escrever Testes Unitários
Seção intitulada “Escrever Testes Unitários”public function testCreateArticle(): void{ $repository = $this->createMock(ArticleRepositoryInterface::class); $repository->expects($this->once())->method('save');
$service = new ArticleService($repository); $dto = new CreateArticleDTO('Título', 'Conteúdo');
$article = $service->create($dto);
$this->assertInstanceOf(Article::class, $article);}Documentação Relacionada
Seção intitulada “Documentação Relacionada”- Código-Limpo - Princípios de código limpo
- Organização-de-Código - Estrutura do projeto
- Testes - Guia de testes
- ../02-Conceitos-Principais/Segurança/Boas-Práticas-de-Segurança - Guia de segurança