Image from upshotcommerce.com
J4 - Como pode o Joomla! alterar e melhorar o seu padrão MVC
Hugo Azevedo 87

J4 - Como pode o Joomla! alterar e melhorar o seu padrão MVC

Nota: Este documento faz parte duma série de documentos que constituem um ensaio sobre o que o Joomla! é, e poderá ser no futuro. Estes documentos não representam o estado actual de desenvolvimento do Joomla!, nem são apenas "despejos de memória". Eles listam um conjunto de ideias estruturadas sobre o que o Joomla! poderá ser, e apresentam algumas soluções para lá chegar. O seu maior propósito é gerar discussão na comunidade do Joomla!.

O padrão MVC usado no Joomla! foi o que inicialmente me fez querer escrever algumas sugestões à equipa de desenvolvimento do Joomla!. Antes de mais, vamos falar sobre a abordagem do Joomla! (extensões) ao padrão MVC, e depois a estrutura de ficheiros de um componente.

Não vou entrar em grande detalhe em relação ao padrão MVC, ou qualquer outro padrão de desenvolvimento  (MVP, MVVM, PM, ...). Se mais informação sobre arquitectura de GUI ou padrões de design/desenvolvimento for necessária, penso que o site do Martin Fowler pode ser um bom ponto de partida. O único ponto que vale a pena salientar, é que cronologicamente, o padrão MVC foi criado nos finais da década de 70, e a Web foi apenas criada nos inícios da década de 90, e portanto, existe um intervalo de pelo menos 10 anos entre os dois. Acho que é justo depreender que, a primeira implementação MVC não foi criada com a Web em mente.

A principal finalidade do padrão MVC, é a separação de interesses/contextos/responsabilidades entre as várias lógicas num componente, lógica da Aplicação, lógica de Negócio, e lógica do Interface. O padrão também descreve a relação/visibilidade entre as 3 entidades (Model, View e Controller). Em MVC, o Model (Modelo) e a View (Vista) têm uma relação directa. Uma alteração no estado do Modelo, despoleta uma reacção na Vista que apresenta os dados desse mesmo Modelo. Numa Web sem estado, esta ligação não é assim tão simples. A adopção do MVC na Web originou diferentes conceitos, como por exeplo o MVC2 (MVC Model 2) que é defendido por muitos como sendo a verdadeira adopção do MVC para o ambiente Web, e outros apenas atribuem a designação diferente a um erro tipográfico (uma interpretação errada dum texto numa imagem). Mas porque esta adaptação pode adoptar diferentes formas, por vezes outros dois conceitos são referidos, em relação à implementação MVC na Web. Push MVC e Pull MVC. Estes dois últimos conceitos são bastante pertinentes enquanto discussão do padrão no Joomla, e a relação entre a Vista e o Controlador (Controller).

From www.liftigniter.com

Variáveis na Query string: task vs. view

No MVC Push, O ponto de entrada é o Controlador (um método no Controlador), que irá carregar um determinado Modelo para processar dados, para no final os enviar para a VIsta. A Vista é completamente "burra" (dumb), pois apenas é responsável pela lógica de apresentação dos dados, e nada mais.

No MVC Pull (como no Joomla!), o ponto de entrada é a Vista, e os Controladores e Modelos são criados/executados a partir desse pedido à Vista. Em teoria, não existe grandes diferenças em ambas abordagens, em relação ao mapeamento de registos, items de menu, ou routing, mas da perspectiva do programador/ambiente, o método Pull é muito menos versátil que o Push.

O método Pull assume demasiado pelo(a) programador(a) sobre a estrutura do componente, e muito do que assume está errado. Também dificulta alguma elasticidade e/ou abstracção que o/a programador(a) possa necessitar no seu componente. Algumas pessoas dizem que este é o método correcto, porque o utilizador interage (directamente) com a Vista, e não com o Controlador, mas a verdade é que, o utilizador na realidade interage com o resultado completo do componente/template. Quando o utilizador interage com o componente MVC (que está no servidor), ele(a) está na realidade a efectuar pedidos ao componente. Por outras palavras, o utilizador está a solicitar ao componente que este execute uma tarefa, mesmo que uma tarefa apenas exiba um conjunto de registos. O pedido deve ser uma responsabilidade do Controlador, e não da Vista.

No Joomla!, se quiser chamar/executar um determinado método do Controlador, é necessário usar uma a variável task na query string, mas para retornar o conteúdo do Controlador, necessitamos de usar a variável view. Em teoria, poderíamos apenas usar a variável task se usarmos o método Push, mas os menus e URLs SEF necessitam da variável view em vez disso. É um pouco confuso.

Outro aspecto confuso sobre a implementação MVC do Joomla! (Pull), é porque tem de ser o utilizador a especificar que view ou format deve ser retornada. E se o programador não quiser que o utilizador tenha acesso a um determinado formato/view? E se esse formato não estiver sequer implementado? E se o método da task alterar a view que deve ser exibida? No final, o utilizador só deverá ver/ter acesso ao que o programador quiser que ele(a) tenha acesso, e nada mais. O Controlador deverá ser quem especifica que View o utilizador recebe, e em que Formato.

Sub-Controladores, JTables, Layouts, Formatos e Helpers

O MVC do Joomla! não tem apenas Models, Views e Controllers. Também tem Sub-Controllers (sub-controladores), Tabelas (JTable), Layouts, Formatos e classes Helper, que faz com que tudo seja ainda mais complicado.

Ao explicar a um programador experiente, colega meu, como o MVC do Joomla! funciona, ele ficou perplexo. Ele desistiu de tentar perceber quando lhe expliquei que a View na realidade fazia o trabalho do Controller, e que um Layout era na realidade uma View. A reacção dele não é assim tão estranha. Eu ainda hoje não sei como e porquê, que o Joomla! chegou a esta solução (estrutura).

A PLT do Joomla! está neste momento a alterar a estrutura interna do componente. Se for necessária mais informação sobre o que se espera do J!4, eu sugiro os relatórios de desenvolvimento no canal do Youtube do Joomla!. Eles têm várias apresentações sobre o que está reservado para a próxima grande versão do Joomla!. Embora estejam a fazer um bom trabalho, ao ver os videos fico com a impressão de que não pretendem fazer grandes alterações à forma como os componentes trabalham ou como são chamados na aplicação. No entanto, uma coisa foi dita que me deixou a pensar. A camada do Model vai passar a ser plana (sem herança). Pessoalmente, acho que devem ir no sentido oposto, e tentar ter várias camadas mais especializadas.

Como simplificar o MVC do Joomla!?

Primeiro, a estrutura de ficheiros. Um componente não deverá estar separado em duas partes, Back-office e Front-office. Um componente é desenvolvido para solucionar um problema, independentemente do lugar. Muitos utilizadores do Joomla! estão a solicitar edição de conteúdos no front-end, por isso, toda a funcionalidade de edição do back-end deverá estar disponível no front-end também. Nos posts anteriores sobre o sistema de ficheiros do Joomla!, uma localização para as extensões do Joomla! foi sugerida (/usr/bin/extension_name). Todos os Controllers, Models e Views deverão estar na mesma localização.

Esta estrutura pode parecer complexa, mas na realidade não é.

A pasta bin irá ter todos os controllers, models e views do componente.

A pasta etc, terá os manifestos e ficheiros de configuração da extensão. Isto quer dizer que, ao carregar os parâmetros da extensão, o Motor não iria necessitar de se ligar a base de dados nenhuma.

Ao incluir um campo de formulário com uma extensão, localizado na pasta lib, este estaria disponível para qualquer outra extensão também. O mesmo acontece com code snippets.

Logs, sql scripts e ficheiros de tradução também estariam na mesma localização.

Controllers e Views

Já que o Controller teria toda a lógica da Aplicação (orquestração do fluxo da aplicação), todo o conteúdo deverá ser solicitado através da chamada a um método do Controller, e não duma View. Em baixo, temos um exemplo dum Controller, que foi invocado para produzir uma listagem de produtos em formato XML. O Controller é chamado com a seguinte query string:

?option=com_extension&task=coname.showproducts


<?php
// Defines the class namespace.
namespace Vendor\Extension\Controllers;

// Checks if is a direct file access.
defined('_JEXEC') or die;

final class ExtensionControllerCoName extends SomeOtherController
{
	/**
	 * @var array - Holds an array of items to display.
	 */
	protected $records = null;

	/**
	 * Collect and shows a list of records to the user.
	 * 
	 * @return void
	 */
	final public function showProducts()
	{
		// Initialize the Model.
		$model = new ExtensionModelMoName();
		$model->setState("key1", "value1");
		$model->setState("key2", "value2");
		$model->setState("key3", "value3");

		// Collect the items.
		$this->records = $model->getItems();
// Set format and View. $this->setFormat("xml"); // Defaults to html. $this->setView("ViewName"); // No default. } }

Se nenhuma variável task for especificada, então extension.extension é assumida.

Os métodos setFormat() e setView(), são métodos de classes parent do Controller, responsáveis apenas pelo processamento e inclusão de uma View. O setFormat() irá dizer ao resto da aplicação que um formato diferente será exibido, logo (se diferente de html), nenhum Template ou Módulo será executado.

O método setView() Vai apenas verificar se o ficheiro da View existe na pasta correcta. O argumento do método setView(), também poderá usar notação por pontos, representando sub-directorias e ficheiros da pasta Views. Por exemplo, setView("lists.products"); vai procurar o ficheiro products.php, dentro da sub-directoria lists na pasta Views (Views > lists > products.php). Esta estrutura pode ser definida completamente pelo programador, e não seria forçada pelo próprio sistema.

Um Controller que exibe os mesmos dados em 2 formatos distintos, usando dois formatos de ficheiros diferentes, pode parecer-se com o seguinte exemplo:


<?php
// Defines the class namespace.
namespace Vendor\Extension\Controllers;

// Checks if is a direct file access.
defined('_JEXEC') or die;

final class ExtensionControllerCoName extends SomeOtherController
{
	/**
	 * @var array - Holds an array of items to display.
	 */
	protected $records = null;

	/**
	 * Collect and shows a list of records to the user in html.
	 * 
	 * @return void
	 */
	final public function showProducts()
	{
		// Collects the data.
		$this->collectData();

		// Set the View.
		$this->setView("HtmlViewName");
	}

	/**
	 * Collect and shows a list of records to the user in xml.
	 * 
	 * @return void
	 */
	final public function showProductsInXml()
	{
		// Collects the data.
		$this->collectData();

		// Set format and View.
		$this->setFormat("xml");
		$this->setView("XmlViewName");
	}

	/**
	 * Method that collects the data.
	 * 
	 * @return void
	 */
	private function collectData()
        {
		// Initialize the Model.
		$model = new ExtensionModelMoName();
		$model->setState("key1", "value1");
		$model->setState("key2", "value2");
		$model->setState("key3", "value3");

		// Collect the items.
		$this->records = $model->getItems();
        }
}

O ficheiro da View, neste caso viewname.php, localizado em usr/bin/com_extension/views, pode ser parecido com este exemplo:


<?php

/**
 * Small description of the View.
 * 
 * @author    Author's name
 * @version   Version number
 * @since     First version
 * @copyright View's copyright
 * @license   GPL
 * 
 * List of available data (IMPORTANT for template overrides).
 * @var $this->records   List of product records. 
 * @var $whatever        ....
 */

// Checks if is a direct file access.
defined('_JEXEC') or die;

foreach ($this->records as $rec) {
?>
	<h1><?php echo $rec->name; ?></h1>
	<p><?php echo $rec->id; ?></p>

<?php 
}

As Views deixariam de precisar de classes apenas para produzirem informação, e os Layouts deixariam de existir. Views tornar-se-iam Views simples (dumb), e o único código de servidor associado com elas seria referente à lógica de apresentação/UI.

Hoje em dia, a View é exibida chamando o método display(), logo, a informação a ser exibida pode ser passada para a View de duas formas. Ou por argumentos do método display(), ou a informação pode ser guardada num atributo protegido do Controller, que estará automaticamente disponível para a View, se executada no mesmo âmbito/contexto (scope). A última opção poderá ser a melhor.

Models

Em MVC, os Models não sabem que formato ou layout é necessário, e não têm noção em que ordem os seus métodos são invocados. Eles são apenas responsáveis pela representação dos dados, e pelo seu próprio estado. Não deverá haver nenhum limite ao número de Models um Controller pode precisar de executar, de forma a conseguir a informação que pretende, e não deverá haver qualquer restrição de onde estes Models são necessários dentro do componente. Para a extensão em execução, todos os seus Models deverão estar automaticamente disponíveis e prontos a ser invocados pelo Controller (mesmo que não inicializados).

Eles também deverão ser incluídos usando a classe de importação do Joomla!, se uma extensão de terceiros pretender estender as funcionalidades da extensão a que pertencem, sem ter de especificar de forma rígida a sua localização no sistema de ficheiros (ex:. JImportModels("com_content"); ). Se uma extensão de terceiros precisar (ex:.) da lógica de negócio do com_content, sem necessitar de executar nenhuma das suas acções (tasks), ou mesmo do seu output (views), então deverá conseguir importar facilmente os seus Models. Um bom exemplo disto, poderão ser os Models da extensão de Categorias.

Herança de Classes

Parece que os Models vão ficar planos (1 nível), mas de forma a implementar princípios SOLID em OOP, eles deverão ir no sentido oposto, ficando ainda mais estratificados. Também deverão existir mais classes Model disponíveis para expansão. Classes mais especializadas. O mesmo deverá acontecer com Controllers.

Classes especializadas do tipo Controller são mais fáceis, pois deverão existir apenas duas. Uma classe com funcionalidades para uma listagem, e outra para exibição de apenas um registo, como hoje em dia. Uma JControllerAdmin e uma JControllerForm. As suas funcionalidades principais seria implementar um standard de funcionalidades comuns e transversais, dependendo do contexto (lista ou registo). No entanto, os nomes deveriam mudar.

Os Models são diferentes. Os Models poderão processar listagens de registos ou registos singulares, mas poderão também ter funções diferentes dependendo da localização desses mesmos registos. Um Model poderá processar informação duma tabela na base de dados, ou de um Webservice, ou até mesmo de um ficheiro. O programador poderia então escolher qual o Model que mais se adequa à funcionalidade que ele(a) quer implementar, logo, ele(a) poderia apenas preocupar-se com o lado do negócio na implementação.

Se um Model processar/retirar informação duma (ou mais) tabela na base de dados, também não deveria necessitar dum objecto do tipo JTable. Este próprio Model poderia assimilar essa mesma funcionalidade.

Em relação às Views, estas nem deveria ser estendidas, já que são dumb Views.

Principios OOP SOLID

De forma a implementar princípios OOP SOLID na estrutura MVC do Joomla!, a árvore estrutural dum Controller e/ou Model necessita ser expandida. Na realidade, pelo menos os dois primeiros níveis da estrutura deverão ser partilhados entre Controllers e Models. A camada/nível inicial, deverá processar informação referente ao ambiente onde o componente está a executar, e informação referente ao próprio componente (nome, localização, etc...). A segunda camada deverá processar os parâmetros/configuração do componente.

Uma possível implementação da classe na raiz da hierarquia poderá parecer-se com isto (apenas o interface é mostrado):


<?php
// Defines the class namespace.
namespace Joomla\System\Mvc;

// Checks if is a direct file access.
defined('_JEXEC') or die;

/**
 * Top MVC class definition.
 * This class should only deal with the component's environment context,
 * and should not be initialized.
 *
 * @author    Joomla
 * @copyright Joomla 2016
 * @package   Engine
 *
 * @version 1.0.0
 * @since   1.0.0
 */
abstract class MvcBase {
    protected function getNamespace();
    protected function getClassName();
    protected function getName();
    protected function getLocation();
    protected function isController();
    protected function isModel();
}

Uma possível implementação da segunda camada na hierarquia, também partilhada entre Controllers e Models poderá parecer-se assim (apenas o interface é mostrado):


<?php
// Defines the class namespace.
namespace Joomla\System\Mvc;

// Checks if is a direct file access.
defined('_JEXEC') or die;

/**
 * MVC configuration class definition.
 * This class should only deal with the component's configuration,
 * and should not be initialized.
 *
 * @author    Joomla
 * @copyright Joomla 2016
 * @package   Engine
 *
 * @version 1.0.0
 * @since   1.0.0
 */
abstract class MvcConfig extends MvcBase {
    private $configFile;
    protected function getConfig();
    protected function getParam();
    protected function setParam();
    protected function saveConfig();
}

Apenas depois da classe MvcConfig, é que a árvore estrutural dum componente MVC começa a ramificar entre os Controllers e os Models. Para cada contexto específico (especialização), uma nova classe com um propósito muito específico deverá ser criada, acabando com um conjunto de classes das quais o/a programador(a) poderá estender a sua implementação. Uma possível implementação da classe na raiz da estrutura (dedicada) ao Controller, poderá ser a seguinte.


<?php
// Defines the class namespace.
namespace Joomla\System\Mvc\Controller;

// Checks if is a direct file access.
defined('_JEXEC') or die;

/**
 * MVC Controller's base file.
 *
 * @author    Joomla
 * @copyright Joomla 2016
 * @package   Engine
 *
 * @version 1.0.0
 * @since   1.0.0
 */
abstract class MvcControllerBase extends \Joomla\System\Mvc\MvcConfig {
    private $viewName;
    private $format = "html";

    protected function getFormat();
    protected function setFormat($docFormat = "html");

    protected function getView();
    protected function setView($name);

// Not overritable display function.
// Returns the processed View file. final public function display(); // Other internal functionality wouldn't need to be visible. private function validateView(); private function createViewBuffer(); ... }

Uma possível implementação da classe na raiz da estrutura (dedicada) ao Model, poderá ser a seguinte:


<?php
// Defines the class namespace.
namespace Joomla\System\Mvc\Model;

// Checks if is a direct file access.
defined('_JEXEC') or die;

/**
 * MVC Model's base file.
 *
 * @author    Joomla
 * @copyright Joomla 2016
 * @package   Engine
 *
 * @version 1.0.0
 * @since   1.0.0
 */
abstract class MvcModelBase extends \Joomla\System\Mvc\MvcConfig {
    protected function getState($key);
    protected function setState($key, $value);
}

A estrutura completa para um componente MVC, poderá ter quantos níveis forem necessários, mas nem todos necessitarão de ser validados pelo Motor. A maioria dos mesmos, só estariam disponíveis expandindo o Motor com a Platform. A árvore final poderia parecer-se da seguinte forma:

MVC Inheritance Tree

Entre as classes na raiz dos Controllers e Models, e as suas especificações finais, deverão existir vários níveis de abstracção, cada um com a sua própria responsabilidade/funcionalidade. Isto iria manter cada classe muito pequena, e de fácil manutenção. Também poderia permitir novos níveis (com novas funcionalidades), serem inseridos em qualquer nível da árvore.

Como é obvio, o programador não iria necessitar de conhecer todos os níveis existentes na árvore, de forma a conseguir trabalhar com o Joomla!. Ele(a) só necessitaria de conhecer as classes do final da hierarquia. Se o/a programador(a) estiver a trabalhar directamente em cima do Motor, ele(a) teria diferentes classes disponíveis, do que se tivesse a trabalhar em cima da Platform (CMS).

Benefícios desta abordagem

Todo o código das extensões estaria disponível na mesma estrutura de directorias, logo, todo o código estaria disponível independentemente da aplicação a usá-lo (admin ou site).

Lançar uma nova extensão e/ou actualizar uma existente seria muito facilitado. Fazer uma cópia de segurança das extensões também.

As classes base da estrutura MVC ficariam muito mais pequenas, e muito mais especializadas. Alguém que quisesse saber mais do funcionamento do Joomla!, ou contribuir/manter o projecto, não teria de se submeter a classes com centenas de linhas de código, que encapsulam várias funcionalidades/responsabilidades.

Uma Platform em cima de um Motor poderia facilmente expandir a estrutura MVC inicial, adicionando mais camadas à àrvore.

Alguém que desenvolva uma nova extensão poderá usar uma classe específica de Controller e/ou Model de forma a melhorar a sua produtividade.

A complexidade das extensões acabaria por recair do lado da implementação do(a) programador(a), e não no entendimento do todas as partes que fazem parte do Joomla!.

Melhoria da flexibilidade para o/a programador(a) de extensões/templates.

Um conjunto de variáveis da query string muito mais reduzido. Apenas option e task.

Principios OOP SOLID OOP poderão ser adoptados pelo MVC do Joomla!

O MVC do Joomla!, ficaria muito mais simples de compreender, para alguém que venha de outras Frameworks/CMSs.

SmarterQueue - A forma mais inteligente de criar em redes sociais

SmarterQueue - A forma mais inteligente de criar em redes sociais

LRTT - Limited Resource Teacher Training

LRTT - Limited Resource Teacher Training

shareOptic - Solução de monitorização de cyber segurança

shareOptic - Solução de monitorização de cyber segurança

Blockz RAD framework web em Php

Blockz RAD framework web em Php