XOOPS'deki Tasarım Desenleri
2.5.x ✅ 4.0.x ✅
Tasarım desenleri, yaygın yazılım tasarım sorunlarına yeniden kullanılabilir çözümlerdir. XOOPS, kod kalitesinin korunmasına, test edilebilirliğin geliştirilmesine ve sistem esnekliğinin geliştirilmesine yardımcı olan birçok köklü modeli kullanır.
Genel Bakış
Section titled “Genel Bakış”Bakımı yapılabilir XOOPS modülleri oluşturmak için tasarım modellerini anlamak ve doğru şekilde uygulamak çok önemlidir. Bu kılavuz, XOOPS geliştirmesinde en sık kullanılan kalıpları kapsar.
| Desen | Amaç | Yaygın Kullanım Durumları |
|---|---|---|
| MVC | Endişelerin ayrılması | module yapısı |
| Tekil | Tek örnek garantisi | database bağlantıları |
| Fabrika | Nesne oluşturma soyutlaması | İşleyiciler, database |
| Gözlemci | Etkinlik bildirimi | Ön yüklemeler, bildirimler |
| Dekoratör | Dinamik davranış uzantısı | Form öğeleri, filtreler |
| Strateji | Algoritma değişimi | Kimlik doğrulama, doğrulama |
| Adaptör | Arayüz uyumluluğu | Eski kod entegrasyonu |
| Depo | Veri erişimi soyutlaması | Veri kalıcılığı |
Model-Görünüm-Denetleyici (MVC)
Section titled “Model-Görünüm-Denetleyici (MVC)”MVC modeli, bir uygulamayı birbirine bağlı üç bileşene ayırarak kod tabanını daha organize ve test edilebilir hale getirir.
Mimarlık
Section titled “Mimarlık”flowchart TB subgraph MVC["MVC Pattern in XOOPS"] Controller["🎮 Controller<br/>(index.php, admin/index.php)"] Model["📦 Model<br/>(Handlers)"] View["🎨 View<br/>(Templates)"]
Controller --> Model Controller --> View Model <--> View end
style Controller fill:#e3f2fd,stroke:#1976d2 style Model fill:#fff3e0,stroke:#f57c00 style View fill:#e8f5e9,stroke:#388e3cModel (Veri Katmanı)
Section titled “Model (Veri Katmanı)”<?phpnamespace XoopsModules\MyModule;
class Article extends \XoopsObject{ public function __construct() { $this->initVar('article_id', XOBJ_DTYPE_INT, null, false); $this->initVar('title', XOBJ_DTYPE_TXTBOX, '', true, 255); $this->initVar('content', XOBJ_DTYPE_TXTAREA, '', true); $this->initVar('author_id', XOBJ_DTYPE_INT, 0, true); $this->initVar('status', XOBJ_DTYPE_INT, 1, false); $this->initVar('created', XOBJ_DTYPE_INT, time(), false); $this->initVar('modified', XOBJ_DTYPE_INT, time(), false); }
public function isPublished(): bool { return $this->getVar('status') === 1; }
public function getFormattedDate(): string { return formatTimestamp($this->getVar('created')); }}
class ArticleHandler extends \XoopsPersistableObjectHandler{ public function __construct(\XoopsDatabase $db) { parent::__construct($db, 'mymodule_articles', Article::class, 'article_id', 'title'); }
public function getPublishedArticles(int $limit = 10): array { $criteria = new \CriteriaCompo(); $criteria->add(new \Criteria('status', 1)); $criteria->setSort('created'); $criteria->setOrder('DESC'); $criteria->setLimit($limit);
return $this->getObjects($criteria); }}Görünüm (Sunum Katmanı)
Section titled “Görünüm (Sunum Katmanı)”{* templates/article_list.tpl *}<div class="article-list"> <h2>{$smarty.const._MD_MYMODULE_ARTICLES}</h2>
{foreach from=$articles item=article} <article class="article-item"> <h3> <a href="{$xoops_url}/modules/mymodule/article.php?id={$article.article_id}"> {$article.title|escape} </a> </h3> <p class="meta"> {$smarty.const._MD_MYMODULE_POSTED}: {$article.formatted_date} </p> <div class="content"> {$article.content|truncate:200} </div> </article> {/foreach}</div>Denetleyici (Mantık Katmanı)
Section titled “Denetleyici (Mantık Katmanı)”<?phprequire_once dirname(__DIR__, 2) . '/mainfile.php';
use XoopsModules\MyModule\Helper;
$helper = Helper::getInstance();$articleHandler = $helper->getHandler('Article');
// Get action from request$op = \Xmf\Request::getString('op', 'list');
switch ($op) { case 'view': $articleId = \Xmf\Request::getInt('id', 0); $article = $articleHandler->get($articleId);
if (!$article) { redirect_header(XOOPS_URL, 3, _MD_MYMODULE_NOT_FOUND); }
$GLOBALS['xoopsOption']['template_main'] = 'mymodule_article_view.tpl'; require_once XOOPS_ROOT_PATH . '/header.php';
$xoopsTpl->assign('article', $article->toArray()); break;
case 'list': default: $articles = $articleHandler->getPublishedArticles(10);
$GLOBALS['xoopsOption']['template_main'] = 'mymodule_article_list.tpl'; require_once XOOPS_ROOT_PATH . '/header.php';
$xoopsTpl->assign('articles', array_map(fn($a) => $a->toArray(), $articles)); break;}
require_once XOOPS_ROOT_PATH . '/footer.php';Tekli Desen
Section titled “Tekli Desen”Singleton modeli, bir sınıfın yalnızca bir örneğe sahip olmasını sağlar ve ona küresel erişim sağlar.
Ne Zaman Kullanılmalı
Section titled “Ne Zaman Kullanılmalı”- database bağlantıları
- Yapılandırma yöneticileri
- Kaydedici örnekleri
- cache yöneticileri
Uygulama
Section titled “Uygulama”<?phpnamespace XoopsModules\MyModule;
class ConfigurationManager{ private static ?self $instance = null; private array $config = [];
private function __construct() { // Load configuration $this->loadConfiguration(); }
// Prevent cloning private function __clone() {}
// Prevent unserialization public function __wakeup() { throw new \Exception("Cannot unserialize singleton"); }
public static function getInstance(): self { if (self::$instance === null) { self::$instance = new self(); }
return self::$instance; }
private function loadConfiguration(): void { $helper = Helper::getInstance(); $this->config = [ 'items_per_page' => $helper->getConfig('items_per_page', 10), 'allow_comments' => $helper->getConfig('allow_comments', true), 'date_format' => $helper->getConfig('date_format', 'Y-m-d'), ]; }
public function get(string $key, mixed $default = null): mixed { return $this->config[$key] ?? $default; }}
// Usage$config = ConfigurationManager::getInstance();$itemsPerPage = $config->get('items_per_page');XOOPS Temel Örnekler
Section titled “XOOPS Temel Örnekler”<?php// XoopsDatabaseFactory uses Singleton pattern$db = XoopsDatabaseFactory::getDatabaseConnection();
// XMF Module Helper uses Singleton$helper = \Xmf\Module\Helper::getHelper('mymodule');
// Xoops main instance$xoops = \Xoops::getInstance();Fabrika Modeli
Section titled “Fabrika Modeli”Fabrika modeli, nesneleri tam sınıflarını belirtmeden oluşturarak esnek nesne oluşturmaya olanak tanır.
Ne Zaman Kullanılmalı
Section titled “Ne Zaman Kullanılmalı”- Dinamik olarak işleyiciler oluşturma
- Farklı veritabanları için database bağlantıları
- Kimlik doğrulama sağlayıcıları
- Form öğesi oluşturma
Uygulama
Section titled “Uygulama”<?phpnamespace XoopsModules\MyModule;
interface ContentInterface{ public function render(): string;}
class ArticleContent implements ContentInterface{ private array $data;
public function __construct(array $data) { $this->data = $data; }
public function render(): string { return "<article><h2>{$this->data['title']}</h2><p>{$this->data['body']}</p></article>"; }}
class NewsContent implements ContentInterface{ private array $data;
public function __construct(array $data) { $this->data = $data; }
public function render(): string { return "<div class='news'><h3>{$this->data['headline']}</h3><p>{$this->data['summary']}</p></div>"; }}
class ContentFactory{ public static function create(string $type, array $data): ContentInterface { return match ($type) { 'article' => new ArticleContent($data), 'news' => new NewsContent($data), default => throw new \InvalidArgumentException("Unknown content type: $type"), }; }}
// Usage$article = ContentFactory::create('article', ['title' => 'Hello', 'body' => 'World']);echo $article->render();XOOPS database Fabrikası
Section titled “XOOPS database Fabrikası”<?phpclass XoopsDatabaseFactory{ public static function getDatabaseConnection() { static $instance;
if (!isset($instance)) { $dbType = XOOPS_DB_TYPE ?? 'mysql'; $className = 'XoopsDatabase' . ucfirst($dbType);
if (!class_exists($className)) { $file = XOOPS_ROOT_PATH . '/class/database/' . strtolower($dbType) . '.php'; if (file_exists($file)) { require_once $file; } }
$instance = new $className();
if (!$instance->connect()) { trigger_error('Unable to connect to database', E_USER_ERROR); } }
return $instance; }}Gözlemci Deseni
Section titled “Gözlemci Deseni”Gözlemci modeli, nesnelerin bir öznenin durumundaki değişikliklerden haberdar edilmesini sağlayarak olaya dayalı davranışı mümkün kılar.
Ne Zaman Kullanılmalı
Section titled “Ne Zaman Kullanılmalı”- Olay yönetimi
- Bildirim sistemleri
- Eklenti mimarileri
- Günlüğe kaydetme ve denetleme
Uygulama
Section titled “Uygulama”<?phpnamespace XoopsModules\MyModule;
interface ObserverInterface{ public function update(string $event, array $data): void;}
class EventDispatcher{ private array $observers = [];
public function attach(string $event, ObserverInterface $observer): void { if (!isset($this->observers[$event])) { $this->observers[$event] = []; }
$this->observers[$event][] = $observer; }
public function detach(string $event, ObserverInterface $observer): void { if (isset($this->observers[$event])) { $key = array_search($observer, $this->observers[$event], true); if ($key !== false) { unset($this->observers[$event][$key]); } } }
public function notify(string $event, array $data = []): void { if (isset($this->observers[$event])) { foreach ($this->observers[$event] as $observer) { $observer->update($event, $data); } } }}
class EmailNotifier implements ObserverInterface{ public function update(string $event, array $data): void { if ($event === 'article.published') { // Send email notification $this->sendEmail($data['article']); } }
private function sendEmail($article): void { $xoopsMailer = xoops_getMailer(); $xoopsMailer->setSubject('New Article Published: ' . $article->getVar('title')); $xoopsMailer->setBody('A new article has been published.'); $xoopsMailer->send(); }}
// Usage$dispatcher = new EventDispatcher();$dispatcher->attach('article.published', new EmailNotifier());
// When article is published$dispatcher->notify('article.published', ['article' => $article]);XOOPS Ön Yüklemeler (Observer Uygulaması)
Section titled “XOOPS Ön Yüklemeler (Observer Uygulaması)”<?phpclass MymoduleCorePreload extends XoopsPreloadItem{ public static function eventCoreIncludeCommonEnd($args) { // React to core common include completing $GLOBALS['xoopsLogger']->addExtra('MyModule', 'Initialized'); }
public static function eventCoreHeaderEnd($args) { // Add custom headers $GLOBALS['xoTheme']->addStylesheet('modules/mymodule/assets/css/custom.css'); }
public static function eventCoreFooterStart($args) { // Execute before footer renders }}Dekoratör Deseni
Section titled “Dekoratör Deseni”Dekoratör modeli, aynı sınıftaki diğer nesneleri etkilemeden nesnelere dinamik olarak davranış ekler.
Ne Zaman Kullanılmalı
Section titled “Ne Zaman Kullanılmalı”- Form öğesi özelleştirmesi
- Çıktı biçimlendirmesi
- İzin kontrolü
- Katmanları önbelleğe alma
Uygulama
Section titled “Uygulama”<?phpnamespace XoopsModules\MyModule;
interface FormElementInterface{ public function render(): string;}
class TextInput implements FormElementInterface{ private string $name; private string $value;
public function __construct(string $name, string $value = '') { $this->name = $name; $this->value = $value; }
public function render(): string { return sprintf( '<input type="text" name="%s" value="%s">', htmlspecialchars($this->name), htmlspecialchars($this->value) ); }}
abstract class FormElementDecorator implements FormElementInterface{ protected FormElementInterface $element;
public function __construct(FormElementInterface $element) { $this->element = $element; }
public function render(): string { return $this->element->render(); }}
class RequiredDecorator extends FormElementDecorator{ public function render(): string { return $this->element->render() . '<span class="required">*</span>'; }}
class LabelDecorator extends FormElementDecorator{ private string $label;
public function __construct(FormElementInterface $element, string $label) { parent::__construct($element); $this->label = $label; }
public function render(): string { return sprintf( '<label>%s</label>%s', htmlspecialchars($this->label), $this->element->render() ); }}
class HelpTextDecorator extends FormElementDecorator{ private string $helpText;
public function __construct(FormElementInterface $element, string $helpText) { parent::__construct($element); $this->helpText = $helpText; }
public function render(): string { return $this->element->render() . sprintf( '<small class="help-text">%s</small>', htmlspecialchars($this->helpText) ); }}
// Usage - decorators can be stacked$input = new TextInput('username');$input = new RequiredDecorator($input);$input = new LabelDecorator($input, 'Username');$input = new HelpTextDecorator($input, 'Enter your username');
echo $input->render();// Output: <label>Username</label><input type="text" name="username" value=""><span class="required">*</span><small class="help-text">Enter your username</small>Strateji Modeli
Section titled “Strateji Modeli”Strateji modeli bir algoritma ailesini tanımlar, her birini içine alır ve onları birbirinin yerine kullanılabilir hale getirir.
Ne Zaman Kullanılmalı
Section titled “Ne Zaman Kullanılmalı”- Çoklu kimlik doğrulama yöntemleri
- Farklı sıralama algoritmaları
- Çeşitli dışa aktarma formatları
- Esnek doğrulama kuralları
Uygulama
Section titled “Uygulama”<?phpnamespace XoopsModules\MyModule;
interface AuthStrategyInterface{ public function authenticate(string $username, string $password): bool;}
class DatabaseAuthStrategy implements AuthStrategyInterface{ public function authenticate(string $username, string $password): bool { $memberHandler = xoops_getHandler('member'); $user = $memberHandler->loginUser($username, $password);
return $user !== false; }}
class LdapAuthStrategy implements AuthStrategyInterface{ private string $ldapHost; private int $ldapPort;
public function __construct(string $host, int $port = 389) { $this->ldapHost = $host; $this->ldapPort = $port; }
public function authenticate(string $username, string $password): bool { $ldap = ldap_connect($this->ldapHost, $this->ldapPort);
if (!$ldap) { return false; }
$bind = @ldap_bind($ldap, "uid=$username,ou=users,dc=example,dc=com", $password);
ldap_close($ldap);
return $bind; }}
class AuthService{ private AuthStrategyInterface $strategy;
public function __construct(AuthStrategyInterface $strategy) { $this->strategy = $strategy; }
public function setStrategy(AuthStrategyInterface $strategy): void { $this->strategy = $strategy; }
public function login(string $username, string $password): bool { return $this->strategy->authenticate($username, $password); }}
// Usage$authService = new AuthService(new DatabaseAuthStrategy());
// Can switch strategies at runtimeif ($useLdap) { $authService->setStrategy(new LdapAuthStrategy('ldap.example.com'));}
$authenticated = $authService->login($username, $password);Depo Modeli
Section titled “Depo Modeli”Depo modeli, veri erişim mantığı ile iş mantığı arasında bir soyutlama katmanı sağlar.
Ne Zaman Kullanılmalı
Section titled “Ne Zaman Kullanılmalı”- Karmaşık veri erişim gereksinimleri
- Çoklu veri kaynakları
- Test edilebilir veri katmanları
- Etki Alanına Dayalı Tasarım
Uygulama
Section titled “Uygulama”<?phpnamespace XoopsModules\MyModule\Repository;
use XoopsModules\MyModule\Entity\Article;
interface ArticleRepositoryInterface{ public function find(int $id): ?Article; public function findBySlug(string $slug): ?Article; public function findPublished(int $limit = 10, int $offset = 0): array; public function save(Article $article): bool; public function delete(Article $article): bool;}
class ArticleRepository implements ArticleRepositoryInterface{ private \XoopsPersistableObjectHandler $handler;
public function __construct(\XoopsPersistableObjectHandler $handler) { $this->handler = $handler; }
public function find(int $id): ?Article { $obj = $this->handler->get($id); return $obj ?: null; }
public function findBySlug(string $slug): ?Article { $criteria = new \Criteria('slug', $slug); $objects = $this->handler->getObjects($criteria);
return !empty($objects) ? $objects[0] : null; }
public function findPublished(int $limit = 10, int $offset = 0): array { $criteria = new \CriteriaCompo(); $criteria->add(new \Criteria('status', 'published')); $criteria->setSort('published_at'); $criteria->setOrder('DESC'); $criteria->setLimit($limit); $criteria->setStart($offset);
return $this->handler->getObjects($criteria); }
public function save(Article $article): bool { return $this->handler->insert($article); }
public function delete(Article $article): bool { return $this->handler->delete($article); }}Bağımlılık Enjeksiyonu
Section titled “Bağımlılık Enjeksiyonu”Bağımlılık Enjeksiyonu (DI), nesnelerin dahili olarak oluşturulması yerine bağımlılıklarıyla oluşturulmasına olanak tanır.
Faydaları
Section titled “Faydaları”- Geliştirilmiş test edilebilirlik
- Gevşek bağlantı
- Esnek konfigürasyon
- Better code organization
Uygulama
Section titled “Uygulama”<?phpnamespace XoopsModules\MyModule;
class ArticleService{ private Repository\ArticleRepositoryInterface $repository; private CacheInterface $cache; private LoggerInterface $logger;
public function __construct( Repository\ArticleRepositoryInterface $repository, CacheInterface $cache, LoggerInterface $logger ) { $this->repository = $repository; $this->cache = $cache; $this->logger = $logger; }
public function getArticle(int $id): ?Entity\Article { $cacheKey = "article_{$id}";
// Try cache first if ($this->cache->has($cacheKey)) { $this->logger->debug("Article {$id} loaded from cache"); return $this->cache->get($cacheKey); }
// Load from repository $article = $this->repository->find($id);
if ($article) { $this->cache->set($cacheKey, $article, 3600); $this->logger->debug("Article {$id} loaded from database"); }
return $article; }}
// Service container setup$container = new DependencyContainer();
$container->register('db', fn() => XoopsDatabaseFactory::getDatabaseConnection());
$container->register('articleHandler', fn($c) => new ArticleHandler($c->resolve('db')));
$container->register('articleRepository', fn($c) => new Repository\ArticleRepository($c->resolve('articleHandler')));
$container->register('cache', fn() => new FileCache(XOOPS_VAR_PATH . '/caches'));
$container->register('logger', fn() => new XoopsLogger());
$container->register('articleService', fn($c) => new ArticleService( $c->resolve('articleRepository'), $c->resolve('cache'), $c->resolve('logger') ));
// Usage$articleService = $container->resolve('articleService');$article = $articleService->getArticle(1);En İyi Uygulamalar
Section titled “En İyi Uygulamalar”Desen Seçimi Yönergeleri
Section titled “Desen Seçimi Yönergeleri”- Beklenenleri değil, gerçek ihtiyaçları temel alan kalıpları seçin
- Uygulamaları basit tutun - aşırı mühendislik yapmayın
- Ekibin anlaşılması için belge modeli kullanımı
- Uygun olduğunda desenleri birleştirin (ör. Fabrika + Tekil)
- Desenleri seçerken test edilebilirliği göz önünde bulundurun
Kaçınılması Gereken Yaygın Anti-Desenler
Section titled “Kaçınılması Gereken Yaygın Anti-Desenler”| Anti-Desen | Sorun | Çözüm |
|---|---|---|
| Tanrı Nesnesi | Sınıf çok fazla şey yapıyor | Tek Sorumluluk |
| Spagetti Kodu | Açık bir yapı yok | MVC desenini kullanın |
| Kopyala-Yapıştır | Kod çoğaltma | Ortak kodu çıkarın |
| Sihirli Sayılar | Belirsiz sabitler | Adlandırılmış sabitleri kullan |
| Sıkı Kaplin | Zor test/maintain | Bağımlılık Enjeksiyonunu Kullan |
Test Modelleri
Section titled “Test Modelleri”<?php// Unit testing with dependency injectionclass ArticleServiceTest extends \PHPUnit\Framework\TestCase{ private $repository; private $cache; private $logger; private $service;
protected function setUp(): void { $this->repository = $this->createMock(ArticleRepositoryInterface::class); $this->cache = $this->createMock(CacheInterface::class); $this->logger = $this->createMock(LoggerInterface::class);
$this->service = new ArticleService( $this->repository, $this->cache, $this->logger ); }
public function testGetArticleFromCache(): void { $article = new Article(); $article->setVar('article_id', 1);
$this->cache->expects($this->once()) ->method('has') ->with('article_1') ->willReturn(true);
$this->cache->expects($this->once()) ->method('get') ->with('article_1') ->willReturn($article);
$result = $this->service->getArticle(1);
$this->assertSame($article, $result); }}İlgili Belgeler
Section titled “İlgili Belgeler”- XOOPS-Mimarlık - Genel sistem mimarisi
- database Katmanı - Veri kalıcılık modelleri
- En İyi Güvenlik Uygulamaları - Güvenli model uygulaması
#xoops #tasarım-desenleri #architecture #mvc #singleton #factory #observer