Найкращі практики розробки модулів
Цей документ об’єднує найкращі методи розробки високоякісних модулів XOOPS. Дотримання цих вказівок гарантує надійність, надійність і продуктивність модулів.
Архітектура
Section titled “Архітектура”Слідкуйте за чистою архітектурою
Section titled “Слідкуйте за чистою архітектурою”Організуйте код у шари:
src/├── Domain/ # Business logic, entities├── Application/ # Use cases, services├── Infrastructure/ # Database, external services└── Presentation/ # Controllers, templatesЄдина відповідальність
Section titled “Єдина відповідальність”Кожен клас повинен мати одну причину для зміни:
// 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() { }}Ін’єкція залежності
Section titled “Ін’єкція залежності”Впроваджуйте залежності, не створюйте їх:
// Goodpublic function __construct( private readonly ArticleRepositoryInterface $repository) {}
// Badpublic function __construct() { $this->repository = new ArticleRepository();}Якість коду
Section titled “Якість коду”Безпека типу
Section titled “Безпека типу”Використовуйте строгі типи та оголошення типів:
<?php
declare(strict_types=1);
final class ArticleService{ public function findById(int $id): ?Article { // ... }
public function create(CreateArticleDTO $dto): Article { // ... }}Обробка помилок
Section titled “Обробка помилок”Використовуйте винятки належним чином:
// 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();}Нульова безпека
Section titled “Нульова безпека”Уникайте значення null, де це можливо:
// Use null object patternpublic function getAuthor(): UserInterface{ return $this->author ?? new AnonymousUser();}
// Use Optional/Maybe patternpublic function findById(int $id): ?Article{ // Explicitly nullable return}База даних
Section titled “База даних”Використовуйте критерії для запитів
Section titled “Використовуйте критерії для запитів”$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));Використовуйте транзакції
Section titled “Використовуйте транзакції”$db->query('START TRANSACTION');
try { $handler->insert($article); $handler->insert($metadata); $db->query('COMMIT');} catch (\Exception $e) { $db->query('ROLLBACK'); throw $e;}Безпека
Section titled “Безпека”Завжди перевіряти введені дані
Section titled “Завжди перевіряти введені дані”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');}Використовуйте токени CSRF
Section titled “Використовуйте токени CSRF”// In form$form->addElement(new XoopsFormHiddenToken());
// On submitif (!$GLOBALS['xoopsSecurity']->check()) { redirect_header('index.php', 3, 'Invalid token');}Перевірте дозволи
Section titled “Перевірте дозволи”if (!$helper->isUserAdmin()) { redirect_header('index.php', 3, _NOPERM);}
if (!$permHandler->isGranted('edit', $categoryId)) { throw new UnauthorizedException();}Продуктивність
Section titled “Продуктивність”Використовуйте кешування
Section titled “Використовуйте кешування”$cache = $helper->getCache();$cacheKey = "articles_{$categoryId}_{$limit}";
$articles = $cache->read($cacheKey);if ($articles === false) { $articles = $handler->getArticles($categoryId, $limit); $cache->write($cacheKey, $articles, 3600);}Оптимізація запитів
Section titled “Оптимізація запитів”// 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);Тестування
Section titled “Тестування”Напишіть модульні тести
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);}Пов’язана документація
Section titled “Пов’язана документація”- Clean-Code - принципи чистого коду
- Організація коду - Структура проекту
- Тестування - Посібник з тестування
- ../02-Core-Concepts/Security/Security-Best-Practices - Посібник з безпеки