Migliori Pratiche di Sviluppo dei Moduli
Panoramica
Sezione intitolata “Panoramica”Questo documento consolida le migliori pratiche per lo sviluppo di moduli XOOPS di alta qualità. Il rispetto di queste linee guida garantisce moduli mantenibili, sicuri e performanti.
Architettura
Sezione intitolata “Architettura”Seguire Clean Architecture
Sezione intitolata “Seguire Clean Architecture”Organizzare il codice in livelli:
src/├── Domain/ # Business logic, entities├── Application/ # Use cases, services├── Infrastructure/ # Database, external services└── Presentation/ # Controllers, templatesSingle Responsibility
Sezione intitolata “Single Responsibility”Ogni classe deve avere un’unica ragione per cambiare:
// Good: Focused classesclass ArticleRepository { /* persistence only */ }class ArticleValidator { /* validation only */ }class ArticleNotifier { /* notifications only */ }
// Bad: God classclass Article { public function save() { } public function validate() { } public function notify() { } public function generatePDF() { }}Dependency Injection
Sezione intitolata “Dependency Injection”Iniettare le dipendenze, non crearle:
// Goodpublic function __construct( private readonly ArticleRepositoryInterface $repository) {}
// Badpublic function __construct() { $this->repository = new ArticleRepository();}Qualità del Codice
Sezione intitolata “Qualità del Codice”Type Safety
Sezione intitolata “Type Safety”Usa strict types e type declarations:
<?php
declare(strict_types=1);
final class ArticleService{ public function findById(int $id): ?Article { // ... }
public function create(CreateArticleDTO $dto): Article { // ... }}Error Handling
Sezione intitolata “Error Handling”Usa le eccezioni appropriatamente:
// Throw specific exceptionsthrow new ArticleNotFoundException($id);throw new ValidationException($errors);throw new UnauthorizedException('Cannot edit this article');
// Catch at appropriate leveltry { $article = $service->create($dto);} catch (ValidationException $e) { return $this->renderForm($e->getErrors());} catch (UnauthorizedException $e) { return $this->redirectToLogin();}Null Safety
Sezione intitolata “Null Safety”Evita null dove possibile:
// Use null object patternpublic function getAuthor(): UserInterface{ return $this->author ?? new AnonymousUser();}
// Use Optional/Maybe patternpublic function findById(int $id): ?Article{ // Explicitly nullable return}Database
Sezione intitolata “Database”Usa Criteria per le Query
Sezione intitolata “Usa Criteria per le Query”$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 User Input
Sezione intitolata “Escape User Input”$sql = sprintf( "SELECT * FROM %s WHERE id = %d AND title = %s", $db->prefix('mymodule_items'), intval($id), $db->quoteString($title));Usa Transactions
Sezione intitolata “Usa Transactions”$db->query('START TRANSACTION');
try { $handler->insert($article); $handler->insert($metadata); $db->query('COMMIT');} catch (\Exception $e) { $db->query('ROLLBACK'); throw $e;}Sicurezza
Sezione intitolata “Sicurezza”Sempre Validare l’Input
Sezione intitolata “Sempre Validare l’Input”use Xmf\Request;
$id = Request::getInt('id', 0);$title = Request::getString('title', '');$data = Request::getArray('data', []);
// Additional validationif (strlen($title) < 5) { throw new ValidationException('Title too short');}Usa CSRF Tokens
Sezione intitolata “Usa CSRF Tokens”// In form$form->addElement(new XoopsFormHiddenToken());
// On submitif (!$GLOBALS['xoopsSecurity']->check()) { redirect_header('index.php', 3, 'Invalid token');}Controlla i Permessi
Sezione intitolata “Controlla i Permessi”if (!$helper->isUserAdmin()) { redirect_header('index.php', 3, _NOPERM);}
if (!$permHandler->isGranted('edit', $categoryId)) { throw new UnauthorizedException();}Performance
Sezione intitolata “Performance”Usa Caching
Sezione intitolata “Usa Caching”$cache = $helper->getCache();$cacheKey = "articles_{$categoryId}_{$limit}";
$articles = $cache->read($cacheKey);if ($articles === false) { $articles = $handler->getArticles($categoryId, $limit); $cache->write($cacheKey, $articles, 3600);}Ottimizza le Query
Sezione intitolata “Ottimizza le Query”// Use indexes// Add to sql/mysql.sql:// INDEX `idx_status_date` (`status`, `created_at`)
// Select only needed columns$handler->getObjects($criteria, false, true); // asArray = true
// Use pagination$criteria->setLimit($perPage);$criteria->setStart($offset);Testing
Sezione intitolata “Testing”Scrivi Unit Tests
Sezione intitolata “Scrivi Unit Tests”public function testCreateArticle(): void{ $repository = $this->createMock(ArticleRepositoryInterface::class); $repository->expects($this->once())->method('save');
$service = new ArticleService($repository); $dto = new CreateArticleDTO('Title', 'Content');
$article = $service->create($dto);
$this->assertInstanceOf(Article::class, $article);}Documentazione Correlata
Sezione intitolata “Documentazione Correlata”- Clean-Code - Principi di codice pulito
- Code-Organization - Struttura del progetto
- Testing - Guida ai test
- ../02-Core-Concepts/Security/Security-Best-Practices - Guida alla sicurezza