Praktik Terbaik Pengembangan module
Ikhtisar
Section titled “Ikhtisar”Dokumen ini menggabungkan praktik terbaik untuk mengembangkan module XOOPS berkualitas tinggi. Mengikuti pedoman ini memastikan module dapat dipelihara, aman, dan berkinerja baik.
Arsitektur
Section titled “Arsitektur”Ikuti Arsitektur Bersih
Section titled “Ikuti Arsitektur Bersih”Atur kode menjadi beberapa lapisan:
src/├── Domain/ # Business logic, entities├── Application/ # Use cases, services├── Infrastructure/ # Database, external services└── Presentation/ # Controllers, templatesTanggung Jawab Tunggal
Section titled “Tanggung Jawab Tunggal”Setiap kelas harus memiliki satu alasan untuk berubah:
// 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() { }}Injeksi Ketergantungan
Section titled “Injeksi Ketergantungan”Suntikkan dependensi, jangan buat:
// Goodpublic function __construct( private readonly ArticleRepositoryInterface $repository) {}
// Badpublic function __construct() { $this->repository = new ArticleRepository();}Kualitas Kode
Section titled “Kualitas Kode”Jenis Keamanan
Section titled “Jenis Keamanan”Gunakan tipe dan deklarasi tipe yang ketat:
<?php
declare(strict_types=1);
final class ArticleService{ public function findById(int $id): ?Article { // ... }
public function create(CreateArticleDTO $dto): Article { // ... }}Penanganan Kesalahan
Section titled “Penanganan Kesalahan”Gunakan pengecualian dengan tepat:
// 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();}Keamanan Nol
Section titled “Keamanan Nol”Hindari null jika memungkinkan:
// Use null object patternpublic function getAuthor(): UserInterface{ return $this->author ?? new AnonymousUser();}
// Use Optional/Maybe patternpublic function findById(int $id): ?Article{ // Explicitly nullable return}Basis Data
Section titled “Basis Data”Gunakan Kriteria untuk Kueri
Section titled “Gunakan Kriteria untuk Kueri”$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);Keluar dari Masukan Pengguna
Section titled “Keluar dari Masukan Pengguna”$sql = sprintf( "SELECT * FROM %s WHERE id = %d AND title = %s", $db->prefix('mymodule_items'), intval($id), $db->quoteString($title));Gunakan Transaksi
Section titled “Gunakan Transaksi”$db->query('START TRANSACTION');
try { $handler->insert($article); $handler->insert($metadata); $db->query('COMMIT');} catch (\Exception $e) { $db->query('ROLLBACK'); throw $e;}Keamanan
Section titled “Keamanan”Selalu Validasi Input
Section titled “Selalu Validasi 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');}Gunakan Token CSRF
Section titled “Gunakan Token CSRF”// In form$form->addElement(new XoopsFormHiddenToken());
// On submitif (!$GLOBALS['xoopsSecurity']->check()) { redirect_header('index.php', 3, 'Invalid token');}Periksa Izin
Section titled “Periksa Izin”if (!$helper->isUserAdmin()) { redirect_header('index.php', 3, _NOPERM);}
if (!$permHandler->isGranted('edit', $categoryId)) { throw new UnauthorizedException();}Kinerja
Section titled “Kinerja”Gunakan Caching
Section titled “Gunakan 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);}Optimalkan Kueri
Section titled “Optimalkan Kueri”// 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);Pengujian
Section titled “Pengujian”Tulis Tes Unit
Section titled “Tulis Tes Unit”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);}Dokumentasi Terkait
Section titled “Dokumentasi Terkait”- Kode Bersih - Prinsip kode bersih
- Kode-Organisasi - Struktur proyek
- Pengujian - Panduan pengujian
- ../02-Core-Concepts/Security/Security-Best-Practices - Panduan keamanan