Criando um Módulo Personalizado para Joomla

A arquitetura moderna do Joomla transformou a maneira como desenvolvemos módulos. Com a introdução de Namespaces e Containers de Injeção de Dependências (DI), o código tornou-se mais seguro, testável e organizado. Neste guia, vamos explorar o mod_hello do zero, analisando cada arquivo da sua estrutura real passo a passo.


Passo 1: A Estrutura do Projeto

Nossa base será a hierarquia de pastas padrão de um módulo moderno no Joomla. Certifique-se de ter criado a seguinte estrutura dentro da pasta mod_hello:

mod_hello/
├── language/              # Arquivos de linguagem
│   └── en-GB/             # Diretório com traduções
│       ├── mod_hello.ini  # Texto exibido pelo módulo
│       └── mod_hello.sys.ini # Metadados (nome e descrição)
├── media/                 # Recursos estáticos
│   ├── css/               # Estilos CSS do módulo
│   ├── images/            # Imagens usadas pelo módulo
│   └── js/                # Scripts JavaScript
├── services/              # Serviços para injeção de dependências
│   └── provider.php       # Configuração de serviços
├── src/                   # Arquivos principais de lógica do módulo
│   ├── Dispatcher/
│   │   └── Dispatcher.php # Controlador para tratar requisições
│   └── Helper/
│       └── HelloHelper.php # Funções auxiliares do módulo
├── tmpl/                  # Layouts e templates
│   └── default.php        # Arquivo de exibição padrão
└── mod_hello.xml          # Manifesto do módulo (configuração e estrutura)

Passo 2: O Manifesto XML mod_hello.xml

O manifesto é o documento de identidade do seu módulo. Além de listar os arquivos para a instalação, ele define o Namespace base (Joomla\Module\Hello) e os parâmetros do módulo, como a saudação ("greeting") e configurações avançadas de cache e layout.

<?xml version="1.0" encoding="UTF-8"?>
<extension type="module" client="site" method="upgrade">
	<name>mod_hello</name>
	<author>Ponto Mega</author>
	<creationDate>2024-04-01</creationDate>
	<copyright>(C) 2024 Ponto Mega</copyright>
	<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
	<authorEmail></authorEmail>
	<authorUrl>https://www.pontomega.com.br</authorUrl>
	<version>1.0.0</version>
	<description>MOD_HELLO_XML_DESCRIPTION</description>
	<namespace path="src">Joomla\Module\Hello</namespace>
	<files>
		<folder module="mod_hello">services</folder>
		<folder>src</folder>
		<folder>tmpl</folder>
	</files>
	<languages>
		<language tag="en-GB">language/en-GB/mod_hello.ini</language>
		<language tag="en-GB">language/en-GB/mod_hello.sys.ini</language>
	</languages>
	<media destination="mod_hello" folder="media">
		<folder>images</folder>
		<folder>css</folder>
		<folder>js</folder>
	</media>
	<help key="Site_Modules:_Hello" />
	<config>
		<fields name="params" addfieldprefix="Joomla\Component\Content\Administrator\Field">
			<fieldset name="basic">
				<field name="greeting" type="text" label="MOD_HELLO_GREETING_LABEL" description="MOD_HELLO_GREETING_DESC" default="Hello, World!" />
			</fieldset>

			<fieldset name="advanced">
				<field name="layout" type="modulelayout" label="JFIELD_ALT_LAYOUT_LABEL" class="form-select" validate="moduleLayout" />

				<field name="moduleclass_sfx" type="textarea" label="COM_MODULES_FIELD_MODULECLASS_SFX_LABEL" rows="3" validate="CssIdentifier" />

				<field name="cache" type="list" label="COM_MODULES_FIELD_CACHING_LABEL" default="1" filter="integer" validate="options">
					<option value="1">JGLOBAL_USE_GLOBAL</option>
					<option value="0">COM_MODULES_FIELD_VALUE_NOCACHING</option>
				</field>

				<field name="cache_time" type="number" label="COM_MODULES_FIELD_CACHE_TIME_LABEL" default="900" filter="integer" />

				<field name="cachemode" type="hidden" default="static">
					<option value="static"></option>
				</field>
			</fieldset>
		</fields>
	</config>
</extension>

Passo 3: O Provedor de Serviços services/provider.php

Este arquivo atua como o ponto de entrada (Entry Point) moderno. Ele instrui o Container DI do Joomla sobre como instanciar o seu Dispatcher e o seu Helper, dispensando o uso de require_once.

<?php

/**
 * @package     Joomla.Site
 * @subpackage  mod_hello
 *
 * @copyright   (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

\defined('_JEXEC') or die;

use Joomla\CMS\Extension\Service\Provider\HelperFactory;
use Joomla\CMS\Extension\Service\Provider\Module;
use Joomla\CMS\Extension\Service\Provider\ModuleDispatcherFactory;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;

/**
 * The article latest module service provider.
 *
 * @since  4.2.0
 */
return new class () implements ServiceProviderInterface {
    /**
     * Registers the service provider with a DI container.
     *
     * @param   Container  $container  The DI container.
     *
     * @return  void
     *
     * @since   4.2.0
     */
    public function register(Container $container)
    {
        $container->registerServiceProvider(new ModuleDispatcherFactory('\\Joomla\\Module\\Hello'));
        $container->registerServiceProvider(new HelperFactory('\\Joomla\\Module\\Hello\\Site\\Helper'));

        $container->registerServiceProvider(new Module());
    }
};

Passo 4: O Dispatcher src/Dispatcher/Dispatcher.php

O Dispatcher é o Controlador. Invocado automaticamente pelo Joomla, ele usa a interface e trait HelperFactoryAware para instanciar o Helper de forma isolada, extraindo a lista de dados e mesclando-a ao array $data disponibilizado para a View.

<?php

/**
 * @package     Joomla.Site
 * @subpackage  mod_hello
 *
 * @copyright   (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\Module\Hello\Site\Dispatcher;

use Joomla\CMS\Dispatcher\AbstractModuleDispatcher;
use Joomla\CMS\Helper\HelperFactoryAwareInterface;
use Joomla\CMS\Helper\HelperFactoryAwareTrait;

// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects

/**
 * Dispatcher class for mod_hello
 *
 * @since  4.2.0
 */
class Dispatcher extends AbstractModuleDispatcher implements HelperFactoryAwareInterface
{
    use HelperFactoryAwareTrait;

    /**
     * Returns the layout data.
     *
     * @return  array
     *
     * @since   4.2.0
     */
    protected function getLayoutData()
    {
        $data = parent::getLayoutData();

        $data['list'] = $this->getHelperFactory()->getHelper('HelloHelper')->getItems($data['params'], $this->getApplication());

        return $data;
    }
}

Passo 5: O Helper src/Helper/HelloHelper.php

O Helper assume o papel de Model (camada de dados). É aqui que o módulo conecta-se ao banco de dados do Joomla utilizando DatabaseAwareTrait para realizar uma consulta customizada e segura (neste caso, buscando módulos na tabela #__modules limitados pelo parâmetro count).

<?php
/**
 * @package     Joomla.Site
 * @subpackage  mod_hello
 *
 * @copyright   (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\Module\Hello\Site\Helper;
use Joomla\CMS\Access\Access;
use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Router\Route;
use Joomla\Component\Content\Site\Helper\RouteHelper;
use Joomla\Component\Content\Site\Model\ArticlesModel;
use Joomla\Database\DatabaseAwareInterface;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
 * Helper for mod_hello
 *
 * @since  1.6
 */
class HelloHelper implements DatabaseAwareInterface
{
    use DatabaseAwareTrait;
    /**
     * Retrieve a list of article
     *
     * @param   Registry       $params  The module parameters.
     * @param   ArticlesModel  $model   The model.
     *
     * @return  mixed
     *
     * @since   4.2.0
     */
    public function getItems(Registry $params, SiteApplication $app)
    {
        $db   = $this->getDatabase();
        $user = $app->getIdentity();
        $query = $db->getQuery(true)
            ->select('a.id, a.title')
            ->from('#__modules AS a')
            ->order('a.title ASC')
            ->setLimit($params->get('count', 5));
        $db->setQuery($query);
        $items = $db->loadObjectList();
        return $items;
    }
    public static function getList(Registry $params)
    {
        return (new self())->getItems($params, Factory::getApplication());
    }
}

Passo 6: O Layout de Exibição tmpl/default.php

O arquivo de layout mantém-se livre de lógicas complexas. Ele utiliza o Web Asset Manager para acoplar CSS e JS estáticos do módulo, e foca estritamente em renderizar a configuração de saudação (greeting) seguida pelo loop dos dados.

<?php

/**
 * @package     Joomla.Site
 * @subpackage  mod_hello
 *
 * @copyright   (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Router\Route;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $app->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('mod_hello_css', 'media/mod_hello/css/style.css');
$wa->registerAndUseScript('mod_hello_script', 'media/mod_hello/js/script.js');
if (!$list) {
    return;
}

?>
<div class="hello">
    <h3>
        <?php echo $params->get('greeting'); ?>
    </h3>
    <ul>
        <?php foreach ($list as $item) : ?>
            <li>
                <?php echo $item->title; ?>
            </li>
        <?php endforeach; ?>
    </ul>
</div>

Compartilhe este conhecimento: