Open-source software for Joomla, Gitea, and web platforms. Home of MokoSuite, MokoGitea, and MokoCLI.
Tennessee
architecture/joomla-mvc-patterns.-
Joomla MVC Patterns
Joomla 5/6 MVC architecture used in MokoSuite components.
Namespace Convention
MokoConsulting\Component\{ExtensionName}\Administrator\{Layer}\{Class}
MokoConsulting\Component\{ExtensionName}\Site\{Layer}\{Class}
Example for MokoSuiteBackup:
MokoConsulting\Component\MokoSuiteBackup\Administrator\Controller\BackupController
MokoConsulting\Component\MokoSuiteBackup\Administrator\Model\BackupsModel
MokoConsulting\Component\MokoSuiteBackup\Administrator\View\Backups\HtmlView
MokoConsulting\Component\MokoSuiteBackup\Administrator\Table\BackupTable
MokoConsulting\Component\MokoSuiteBackup\Administrator\Extension\MokoSuiteBackupComponent
Component Extension Class
The main component class registers services and boots the component:
namespace MokoConsulting\Component\MokoSuiteBackup\Administrator\Extension;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\Extension\BootableExtensionInterface;
use Joomla\CMS\Component\Router\RouterServiceInterface;
final class MokoSuiteBackupComponent extends MVCComponent
implements BootableExtensionInterface, RouterServiceInterface
{
public function boot(ContainerInterface $container): void
{
// Register custom services
}
}
Controllers
namespace MokoConsulting\Component\MokoSuiteBackup\Administrator\Controller;
use Joomla\CMS\MVC\Controller\FormController;
final class ProfileController extends FormController
{
protected $text_prefix = 'COM_MOKOSUITEBACKUP_PROFILE';
public function getModel($name = 'Profile', $prefix = 'Administrator', $config = []): ProfileModel
{
return parent::getModel($name, $prefix, $config);
}
}
Models
List Model (multiple records)
namespace MokoConsulting\Component\MokoSuiteBackup\Administrator\Model;
use Joomla\CMS\MVC\Model\ListModel;
final class BackupsModel extends ListModel
{
protected function getListQuery(): DatabaseQuery
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__mokosuitebackup_records', 'a'))
->order($db->quoteName('a.created_at') . ' DESC');
// Filter by published state
$published = $this->getState('filter.published');
if (is_numeric($published)) {
$query->where($db->quoteName('a.published') . ' = :published')
->bind(':published', $published, ParameterType::INTEGER);
}
return $query;
}
protected function populateState($ordering = 'a.created_at', $direction = 'DESC'): void
{
$this->setState('filter.published', $this->getUserStateFromRequest(
$this->context . '.filter.published', 'filter_published', '', 'string'
));
parent::populateState($ordering, $direction);
}
}
Form Model (single record CRUD)
namespace MokoConsulting\Component\MokoSuiteBackup\Administrator\Model;
use Joomla\CMS\MVC\Model\AdminModel;
final class ProfileModel extends AdminModel
{
public function getForm($data = [], $loadData = true): Form|false
{
return $this->loadForm(
'com_mokosuitebackup.profile',
'profile',
['control' => 'jform', 'load_data' => $loadData]
);
}
protected function loadFormData(): mixed
{
return $this->getItem();
}
}
Views
namespace MokoConsulting\Component\MokoSuiteBackup\Administrator\View\Backups;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
final class HtmlView extends BaseHtmlView
{
protected $items;
protected $pagination;
public function display($tpl = null): void
{
$this->items = $this->get('Items');
$this->pagination = $this->get('Pagination');
$this->addToolbar();
parent::display($tpl);
}
protected function addToolbar(): void
{
ToolbarHelper::title(Text::_('COM_MOKOSUITEBACKUP_BACKUPS'), 'archive');
ToolbarHelper::deleteList('', 'backups.delete');
}
}
Tables
namespace MokoConsulting\Component\MokoSuiteBackup\Administrator\Table;
use Joomla\CMS\Table\Table;
use Joomla\Database\DatabaseDriver;
final class BackupTable extends Table
{
public function __construct(DatabaseDriver $db)
{
parent::__construct('#__mokosuitebackup_records', 'id', $db);
}
public function check(): bool
{
// Validation before save
if (empty($this->profile_id)) {
$this->setError('Profile is required');
return false;
}
return true;
}
}
Event Subscribers (Plugins)
Modern Joomla 5 plugins use SubscriberInterface:
namespace MokoConsulting\Plugin\System\MokoSuiteBackup\Extension;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
final class MokoSuiteBackup extends CMSPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'onAfterExtensionUpdate' => 'handleAutoBackup',
'onTaskOptionsList' => 'registerTaskOptions',
];
}
public function handleAutoBackup(Event $event): void
{
// Auto-backup before extension updates
}
}
Pages