Mejores prácticas de desarrollo de módulos
Descripción general
Sección titulada «Descripción general»Este documento consolida las mejores prácticas para desarrollar módulos XOOPS de alta calidad. Seguir estas directrices garantiza módulos mantenibles, seguros y eficientes.
Arquitectura
Sección titulada «Arquitectura»Siga la arquitectura limpia
Sección titulada «Siga la arquitectura limpia»Organice el código en capas:
src/├── Domain/ # Business logic, entities├── Application/ # Use cases, services├── Infrastructure/ # Database, external services└── Presentation/ # Controllers, templatesResponsabilidad única
Sección titulada «Responsabilidad única»Cada clase debe tener una única razón para cambiar:
// Bien: Clases enfocadasclass ArticleRepository { /* persistencia únicamente */ }class ArticleValidator { /* validación únicamente */ }class ArticleNotifier { /* notificaciones únicamente */ }
// Mal: Clase diosaclass Article { public function save() { } public function validate() { } public function notify() { } public function generatePDF() { }}Inyección de dependencias
Sección titulada «Inyección de dependencias»Inyecte las dependencias, no las cree:
// Bienpublic function __construct( private readonly ArticleRepositoryInterface $repository) {}
// Malpublic function __construct() { $this->repository = new ArticleRepository();}Calidad del código
Sección titulada «Calidad del código»Seguridad de tipo
Sección titulada «Seguridad de tipo»Use tipos estrictos y declaraciones de tipo:
<?php
declare(strict_types=1);
final class ArticleService{ public function findById(int $id): ?Article { // ... }
public function create(CreateArticleDTO $dto): Article { // ... }}Manejo de errores
Sección titulada «Manejo de errores»Utilice excepciones apropiadamente:
// Lance excepciones específicasthrow new ArticleNotFoundException($id);throw new ValidationException($errors);throw new UnauthorizedException('No se puede editar este artículo');
// Capture en el nivel apropiadotry { $article = $service->create($dto);} catch (ValidationException $e) { return $this->renderForm($e->getErrors());} catch (UnauthorizedException $e) { return $this->redirectToLogin();}Seguridad nula
Sección titulada «Seguridad nula»Evite nulos donde sea posible:
// Use patrón de objeto nulopublic function getAuthor(): UserInterface{ return $this->author ?? new AnonymousUser();}
// Use patrón Opcional/Quizáspublic function findById(int $id): ?Article{ // Retorno explícitamente anulable}Base de datos
Sección titulada «Base de datos»Use Criteria para consultas
Sección titulada «Use 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);Escape de entrada del usuario
Sección titulada «Escape de entrada del usuario»$sql = sprintf( "SELECT * FROM %s WHERE id = %d AND title = %s", $db->prefix('mymodule_items'), intval($id), $db->quoteString($title));Use transacciones
Sección titulada «Use transacciones»$db->query('START TRANSACTION');
try { $handler->insert($article); $handler->insert($metadata); $db->query('COMMIT');} catch (\Exception $e) { $db->query('ROLLBACK'); throw $e;}Seguridad
Sección titulada «Seguridad»Siempre valide la entrada
Sección titulada «Siempre valide la entrada»use Xmf\Request;
$id = Request::getInt('id', 0);$title = Request::getString('title', '');$data = Request::getArray('data', []);
// Validación adicionalif (strlen($title) < 5) { throw new ValidationException('Título muy corto');}Use tokens CSRF
Sección titulada «Use tokens CSRF»// En formulario$form->addElement(new XoopsFormHiddenToken());
// En envíoif (!$GLOBALS['xoopsSecurity']->check()) { redirect_header('index.php', 3, 'Token inválido');}Verifique permisos
Sección titulada «Verifique permisos»if (!$helper->isUserAdmin()) { redirect_header('index.php', 3, _NOPERM);}
if (!$permHandler->isGranted('edit', $categoryId)) { throw new UnauthorizedException();}Rendimiento
Sección titulada «Rendimiento»Use caché
Sección titulada «Use caché»$cache = $helper->getCache();$cacheKey = "articles_{$categoryId}_{$limit}";
$articles = $cache->read($cacheKey);if ($articles === false) { $articles = $handler->getArticles($categoryId, $limit); $cache->write($cacheKey, $articles, 3600);}Optimice las consultas
Sección titulada «Optimice las consultas»// Use índices// Agregar a sql/mysql.sql:// INDEX `idx_status_date` (`status`, `created_at`)
// Seleccione solo columnas necesarias$handler->getObjects($criteria, false, true); // asArray = true
// Use paginación$criteria->setLimit($perPage);$criteria->setStart($offset);Pruebas
Sección titulada «Pruebas»Escriba pruebas unitarias
Sección titulada «Escriba pruebas unitarias»public function testCreateArticle(): void{ $repository = $this->createMock(ArticleRepositoryInterface::class); $repository->expects($this->once())->method('save');
$service = new ArticleService($repository); $dto = new CreateArticleDTO('Título', 'Contenido');
$article = $service->create($dto);
$this->assertInstanceOf(Article::class, $article);}Documentación relacionada
Sección titulada «Documentación relacionada»- Clean-Code - Principios de código limpio
- Code-Organization - Estructura del proyecto
- Testing - Guía de pruebas
- ../02-Core-Concepts/Security/Security-Best-Practices - Guía de seguridad