模組開發最佳實踐
本文件彙總了開發高品質 XOOPS 模組的最佳實踐。遵循這些指南可確保可維護、安全和高效的模組。
遵循清潔架構
Section titled “遵循清潔架構”將程式碼組織成層:
src/├── Domain/ # 業務邏輯、實體├── Application/ # 使用案例、服務├── Infrastructure/ # 資料庫、外部服務└── Presentation/ # 控制器、範本每個類別應該只有一個變更原因:
// 好: 集中的類別class ArticleRepository { /* 持久性僅 */ }class ArticleValidator { /* 驗證僅 */ }class ArticleNotifier { /* 通知僅 */ }
// 差: 神祇類別class Article { public function save() { } public function validate() { } public function notify() { } public function generatePDF() { }}注入依賴,不要建立它們:
// 好public function __construct( private readonly ArticleRepositoryInterface $repository) {}
// 差public function __construct() { $this->repository = new ArticleRepository();}使用嚴格型別和型別宣告:
<?php
declare(strict_types=1);
final class ArticleService{ public function findById(int $id): ?Article { // ... }
public function create(CreateArticleDTO $dto): Article { // ... }}適當使用例外狀況:
// 擲回特定例外狀況throw new ArticleNotFoundException($id);throw new ValidationException($errors);throw new UnauthorizedException('Cannot edit this article');
// 在適當層級擷取try { $article = $service->create($dto);} catch (ValidationException $e) { return $this->renderForm($e->getErrors());} catch (UnauthorizedException $e) { return $this->redirectToLogin();}盡可能避免空值:
// 使用空物件模式public function getAuthor(): UserInterface{ return $this->author ?? new AnonymousUser();}
// 使用選用的/可能模式public function findById(int $id): ?Article{ // 明確可為空的傳回}對查詢使用 Criteria
Section titled “對查詢使用 Criteria”$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);逸出使用者輸入
Section titled “逸出使用者輸入”$sql = sprintf( "SELECT * FROM %s WHERE id = %d AND title = %s", $db->prefix('mymodule_items'), intval($id), $db->quoteString($title));$db->query('START TRANSACTION');
try { $handler->insert($article); $handler->insert($metadata); $db->query('COMMIT');} catch (\Exception $e) { $db->query('ROLLBACK'); throw $e;}始終驗證輸入
Section titled “始終驗證輸入”use Xmf\Request;
$id = Request::getInt('id', 0);$title = Request::getString('title', '');$data = Request::getArray('data', []);
// 其他驗證if (strlen($title) < 5) { throw new ValidationException('Title too short');}使用 CSRF 權杖
Section titled “使用 CSRF 權杖”// 在表單中$form->addElement(new XoopsFormHiddenToken());
// 在提交時if (!$GLOBALS['xoopsSecurity']->check()) { redirect_header('index.php', 3, 'Invalid token');}if (!$helper->isUserAdmin()) { redirect_header('index.php', 3, _NOPERM);}
if (!$permHandler->isGranted('edit', $categoryId)) { throw new UnauthorizedException();}$cache = $helper->getCache();$cacheKey = "articles_{$categoryId}_{$limit}";
$articles = $cache->read($cacheKey);if ($articles === false) { $articles = $handler->getArticles($categoryId, $limit); $cache->write($cacheKey, $articles, 3600);}// 使用索引// 新增至 sql/mysql.sql:// INDEX `idx_status_date` (`status`, `created_at`)
// 僅選擇需要的列$handler->getObjects($criteria, false, true); // asArray = true
// 使用分頁$criteria->setLimit($perPage);$criteria->setStart($offset);編寫單元測試
Section titled “編寫單元測試”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);}- Clean-Code - 清潔程式碼原則
- Code-Organization - 專案結構
- Testing - 測試指南
- ../02-Core-Concepts/Security/Security-Best-Practices - 安全指南