A Arquitetura MVC do Joomla! Explicada

Esta página descreve a arquitetura Model-View-Controller implementado no Joomla.

Indice do Conteúdo

  1. Introdução
  2. Vista Geral do MVC do Joomla!
  3. O Parâmetro da task (tarefa) do pedido HTTP
  4. O Padrão Post/Request/Get
  5. As Classes do MVC do Joomla!

1. Introdução

Quando o Joomla começa a processar um pedido de um utilizador, seja um GET para uma determinada página ou um POST contendo dados de formulários, uma das primeiras coisas que o Joomla faz é analisar o URL para determinar qual o componente será responsável pelo processamento desse pedido. e passar o controlo do processo para esse componente.
Isto será feito executando o ficheiro PHP de ponto de entrada para esse componente. Se o componente for chamado com_exemplo, o Joomla será executado:

No Frontend: components/com_exemplo/exemplo.php
Na Administração: administrator/components/com_exemplo/exemplo.php

Se estiver a desenvolver um componente, pode colocar todo o código do componente nestes dois ficheiros exempleo.php. No entanto, uma vantagem de seguir o padrão Joomla MVC é que pode fazer uso total das classes MVC da biblioteca Joomla, o que reduz bastante a quantidade de código que tem de escrever.

2. Vista Geral do MVC Joomla!

Código do Ponto de Entrada

A principal função do ficheiro PHP do ponto de entrada (exemplo.php para com_exemplo) é determinar qual o controlador a executar. Isto é feito com base no parâmetro task (tarefa - descrito com mais detalhe mais à frente) e envolve:

  • determinando qual a classe Controller que deve ser carregada;
  • determinando onde encontrar o código para essa classe;
  • obter uma instância dessa classe;
  • chamando o método apropriado dessa classe.
Diagrama MVC do Joomla!
Diagrama MVC Joomla!

Controller (Controlador)

O controlador é responsável por analisar o pedido do utilizador, verificando se o utilizador tem permissão para realizar aquela ação e determinar como satisfazer o pedido. Este último envolve:

  • determinar qual o modelo (ou modelos) que serão necessários para satisfazer o pedido e criar uma instância desse modelo;
  • fazendo chamadas aos métodos de modelo para fazer quaisquer atualizações necessárias na base de dados;
  • determinar qual a vista que deve ser utilizada para apresentar a página web ao utilizador e criar uma instância dessa vista ou...
  • se, em vez disso, o utilizador receber um redireccionamento para outro URL, determine esse URL de redireccionamento.

View (Vista)

A Vista especifica o que deve aparecer na página web e agrupa todos os dados necessários para gerar a resposta HTTP.
Depois de o Controlador criar a instância da Vista, chama o método setModel() da Vista e passa a instância do Modelo. Desta forma a Vista sabe qual o Modelo a utilizar e chama os métodos do modelo para obter os dados necessários para retornar ao utilizador.

Layout

A Vista não gera código HTML, mas delega isso ao Layout. O Layout contém código PHP que é executado dentro do contexto do Método (geralmente a função display()) da Vista, o que significa que se a vista contiver os dados de resposta, por exemplo, $this->items então o Layout pode aceder aos mesmos $this->items quando está a gerar o código HTML.
Separar a Vista e o Layout desta forma permite outro nível de flexibilidade, uma vez que pode configurar facilmente uma substituição de Layout para gerar os dados da Vista utilizando o seu próprio código HTML.

Model (Modelo)

O modelo encapsula os dados utilizados pelo componente. Na maioria dos casos, estes dados virão de uma base de dados, seja a base de dados Joomla! ou uma base de dados externa, mas também é possível que o modelo obtenha dados de outras fontes, como por exemplo através de uma API de serviços web correndo noutro servidor. O modelo é também responsável por atualizar a base de dados quando apropriado. O objetivo do Modelo é isolar o Controlador e a Vista dos detalhes de como os dados são obtidos ou alterados.

Se o Componente estiver a apresentar um formulário que é definido em XML utilizando a abordagem Joomla Form, o modelo trata da preparação e da configuração da instância do Formulário (Form), ficando tudo pronto para o Layout gerar campos utilizando $form->renderField() e outros.

Processamento Subsequente

A saída do componente (especificamente a saída HTML do layout) não é gerada diretamente como a resposta HTTP, mas é capturada e armazenada em buffer pelo Joomla!. Depois de o layout gerar a saída, o componente devolve o controlo à framework Joomla, que depois carrega e executa o template. O template combina a saída do componente e quaisquer módulos ativos na página atual, para que possa ser entregue ao browser como uma única página.

3. O Parâmetro da task (tarefa) do pedido HTTP

O Joomla! utiliza o parâmetro task do HTTP Request para determinar qual o controlador que deve ser utilizado. O parâmetro task pode ser enviado num HTTP GET ou HTTP POST – na verdade, o Joomla! não faz distinção entre os parâmetros GET e POST – o parâmetro task é apenas um parâmetro HTTP comum.

Nos ficheiros PHP do ponto de entrada do core do Joomla!, quase sempre surgirá algo como:

$controller = JControllerLegacy::getInstance(‘exemplo’);
$controller->execute(JFactory::getApplication()->input->get('task'));

com exemplo substituído por Contacto, Conteúdo, Módulos etc.

O método getInstance() do JControllerLegacy (ou BaseController, como é agora conhecido desde o Joomla! 3.8) é onde tudo acontece. 

  • Ele examina o parâmetro da tarefa (task);
  • com base neste parâmetro decide qual a classe de controlador a carregar e em que ficheiro espera encontrar essa classe;
  • cria uma instância desta classe de controlador (que é devolvida desta função);
  • redefine o parâmetro task para ser o método que deve ser chamado.

A linha de código seguinte:

$controller->execute(JFactory::getApplication()->input->get('task'));

chama então o método execute() da instância do controlador com o parâmetro que é agora o método a executar, e o método execute basicamente executa o método transmitido.

O parâmetro task tem o formato tipo.método, em que o tipo representa o tipo de controlador e método o método a chamar. O tipo de controlador pode estar ausente, nesse caso o método localizado no ficheiro controller.php é executado. Se o tipo de controlador estiver presente, será utilizado um dos controladores na pasta controllers. Se o parâmetro task não estiver definido, será executado o método display() localizado no ficheiro controller.php.

Abaixo as possibilidades para um componente chamado com_exemplo.

Caso 1

Tipo de task (tarefa)
TipodeControlador.metodo
Ficheiro do Controlador
/controllers/TipoControlador.php
Classe do Controlador
ExemploControllerTipodeControlador
Método do Controlador
método

Caso 2

Tipo de task (tarefa)
Items.process
Ficheiro do Controlador
/controllers/items.php
Classe do Controlador
ExemploControllersItems
Método do Controlador
process

Caso 3

Tipo de task (tarefa)
Método(Sem Controlador)
Ficheiro do Controlador
/controller.php
Classe do Controlador
ExemploControllers
Método do Controlador
método

Caso 4

Tipo de task (tarefa)
(task não definida)
Ficheiro do Controlador
/controller.php
Classe do Controlador
ExemploControllers
Método do Controlador
display

Os casos acima são válidos tanto para Frontend do site como para o lado do Administrador, só que para o Administrador tudo acontece na pasta /administrator.

4. O Padrão Post/Request/Get

Diagrama do Patrão Post-Get-Redirect do Joomla! 1
Diagrama do Patrão Post-Get-Redirect do Joomla! 1

O Joomla! segue o padrão Post/Rediret/Get, o que significa que quando um utilizador envia um formulário através de HTTP POST, então, em vez do Joomla! responder ao POST com uma página HTML, redireciona para uma outra página que o browser irá aceder com um pedido HTTP GET.

Isto é tipificado quando a área de Administrador apresenta a página de Conteúdo/Artigos, conforme mostrado no primeiro diagrama. A ação a executar é indicada pelo parâmetro task (tarefa), e tanto ela como os dados relacionados são enviados no pedido POST. Assim que a ação é executada, a próxima página web a ser apresentada é definida no Redirect HTTP.

O segundo diagrama descreve o padrão quando um administrador edita um artigo. Neste caso, existe o passo adicional de exibir o formulário de edição, que novamente o Joomla! trata enviando um Redirect HTTP em resposta ao pedido original para editar um artigo. (No ponto 3, o Joomla! (no BaseDatabaseModel) armazena um ID de edição na sessão e os controladores do componente verificam esse ID no ponto 4 para garantir que os utilizadores não podem simplesmente especificar o URL do formulário diretamente sem passar pelo ponto 3).

Os números vermelhos em círculos verdes no diagrama referem-se aos diferentes tipos de pedidos HTTP que o Joomla! está a tratar. Veremos na próxima secção que foram definidas diferentes classes Joomla MVC para atender a estes cinco casos de pedido.

5.    As Classes do MVC do Joomla!

Existem várias classes MVC na biblioteca Joomla! em …/libraries/src/MVC e esta secção tem como objetivo fornecer uma visão geral (não uma descrição completa) destas classes e explicar quando é que o componente deve utilizar cada classe.

Classes MVC Base

As 3 classes base MVC são a classe BaseController (anteriormente denominada JControllerLegacy), HtmlView (anteriormente denominada JViewLegacy) e BaseDatabaseModel (anteriormente denominada JModelLegacy). As funcionalidades que a BaseController inclui:

  • os métodos getInstance() e execute() descritos acima, que em conjunto encontram o controlador do componente e executam o método apropriado, todos baseados no valor do parâmetro task.
  • funções para encontrar a classe de vista (view) apropriada a utilizar (com base na configuração do parâmetro de view no pedido HTTP) e onde procurar o ficheiro PHP que deve conter esta classe.
  • executar o ficheiro PHP da classe e criar uma instância da vista (view)
  • utilizar o método BaseDatabaseModel::getInstance() para obter uma instância do modelo (model) que está associada à vista (view) que está a ser utilizada
  • o método por defeito display(), que obtém instâncias das classes de vista e modelo incluindo fornecer à instância da vista uma ligação para a instância do modelo) e, em seguida, chama o método display() da classe da vista.

A classe HtmlView inclui:

  • o método display(), que executa o ficheiro de layout
  • código para encontrar o ficheiro de layout, tendo em conta uma substituição de layout que pode ter sido colocada na estrutura de pastas do template
  • código para definir a instância do modelo (model) e, posteriormente, recuperá-la
  • um método get() multiusos que quando chamado como get("algo") o converte numa chamada no modelo $model->getSomething().

A Classe BaseDatabaseModel contém:

  • o método estático getInstance() que encontra o ficheiro PHP contendo o código do modelo, executa-o e devolve uma instância da classe do modelo.
  • código para obter uma instância da classe Table que irá manipular a interface para a tabela de base de dados subjacente para este componente.
  • código base para criar um "estado" (state) do modelo. Esta funcionalidade é útil se o componente e/ou vários módulos apresentados na página web utilizarem todos os mesmos dados. Neste caso, podem partilhar a mesma instância do modelo e o "estado" (state) do modelo atua como um contentor para partilhar os valores dos itens entre o componente e os módulos.

Em geral, a utilização de classes base é uma boa opção se o seu componente estiver a apresentar um único item numa página do site.

Classes de Vista (View) de Alto Nível

Existem 3 classes de Vista de alto nível, todas herdadas diretamente da classe base HtmlView. O nome da classe (e também o nome do ficheiro PHP) dá uma boa indicação de como pode ser utilizada:

  • CategoryView – para exibir uma categoria e os seus filhos
  • CategoriesView – para exibir todas as categorias num determinado nível na hierarquia de categorias e o número de itens associados a cada categoria
  • CategoryFeedView – para gerar um feed de categorias.

A utilização das classes CategoryView ou CategoriesView pode ser útil se estiver a seguir o paradigma de como o com_content exibe esta informação no site, mas provavelmente não serão tão úteis.
A classe CategoryFeedView ajudará se estiver a fornecer um feed.

Classes de Controlador de Alto Nível

Existem 2 classes de controlador de alto nível, cada uma herdada da classe BaseController.
A classe AdminController contém métodos que tratam dos tipos de operações que podem ser executadas em vários itens, por exemplo:

  • Apagar
  • Verificar (checking-in)
  • Alterar o estado de publicação
  • Alterar a ordem dos registos

No entanto, é de notar que não suporta as operações ativadas pelo Botão em Lote (Batch button), por exemplo. a página Conteúdo/Artigos. O código chama normalmente o método do modelo relacionado para efetuar a operação, define a mensagem com base no sucesso da operação do modelo e configura o redireccionamento de volta para a mesma página. Por este motivo é muito útil no caso 2 apresentado nos diagramas acima no padrão Post/Request/Get.

O nome AdminController sugere que este controlador deve ser utilizado apenas nas funcionalidades da área de Administrador, no entanto, este não é o caso. É apropriado utilizá-lo também no Frontend do site.

O FormController contém métodos associados à edição de um item individual

  • lidar com um pedido para editar um item - o que envolve verificar se o utilizador tem permissão para editar o item e se ainda não foi verificado, e se estas verificações forem aprovadas, o item passará a verificado (se este componente tiver ativado o check-out) e é emitido um redireccionamento para exibir o formulário de edição;
  • lidar com um pedido para adicionar um novo item – o que envolve verificar se o utilizador tem permissão para editar o item e resulta num redirecionamento para exibir um formulário de edição em branco
  • lidar com a opção Guardar de um item que está a ser editado ou criado a partir de um novo – o código verifica se o utilizador tem permissão para executar a operação e chama o método do modelo apropriado para guardar o item novo/editado.
  • lidar com o cancelamento de uma edição, redirecionando o utilizador de volta para a página apropriada (e fazendo o check-in do registo, se necessário).
  • lidar com as operações iniciadas através do botão Em Lote

O FormController é, portanto, adequado para os casos 3 e 5 apresentados nos diagramas acima no padrão Post/Request/Get.

Classes Modelo de Alto Nível

O diagrama mostra a árvore de herança dos modelos MVC da biblioteca Joomla.

A classe ItemModel é quase igual à classe BaseDatabaseModel. Apenas possui um método a mais, o método getStoreId() que é relevante quando se tem um componente e/ou vários módulos a partilhar o mesmo modelo e se pretende distinguir entre conjuntos de dados relevantes para cada um.

Juntamente com o método getStoreId(), o ListModel possui capacidade relacionada com a obtenção de um conjunto de registos para exibição numa página web, incluindo suporte para paginação. Note que a capacidade de paginação pode ainda ser ligeiramente diferente entre Frontend e Backend – consulte este problema. O ListModel é útil para suportar o caso 1 nos diagramas acima.

A classe FormModel inclui suporte para formulários Joomla!, tanto para configurar o formulário para que possa ser apresentado, como para que os dados do formulário enviados no POST possam ser validados. Além disso, possui métodos para a implementação de checkin e checkout de registos de base de dados. Portanto, é adequado para lidar com os casos 3 e 4 nos diagramas acima.

A classe AdminModel estende a classe FormModel, pelo que tem toda a capacidade de lidar com formulários, mas adicionalmente tem métodos para lidar com atualizações de base de dados – incluindo a capacidade de adicionar, atualizar e eliminar registos – bem como suporte para lidar com operações em lote. Portanto, é adequado para lidar com os casos 2 e 5 nos diagramas acima. Tal como acontece com a classe AdminController, este modelo não é apenas apropriado para utilizar na área de administrador, mas também pode ser utilizado no Frontend do seu site.

No entanto, apesar de a classe FormModel e a AdminModel suportarem casos diferentes no fluxo associado à edição de um registo, na prática é comum utilizar a  mesma classe de modelo para todas as diferentes etapas do fluxo. Todos os componentes principais do Joomla! utilizam o mesmo modelo nestas etapas e, portanto, todos estendem a AdminModel em vez da FormModel.

Algo a ter em conta se estiver a utilizar o mesmo modelo no fluxo "editar item" é que o código do seu modelo está a executar duas finalidades:

  • preparando dados para serem mostrados numa página web
  • preparar um formulário, seja para exibição numa página web ou para validação de dados POST

Quando utiliza o modelo porque está a lidar com um POST (ou seja, casos 3 e 5 no diagrama), qualquer esforço despendido na preparação dos dados para uma página web será desperdiçado. (Na verdade, na chamada do método getModel() do FormController, o parâmetro $config é definido como array('ignore_request' => true) por defeito, o que resulta na não execução do método populateState do modelo, talvez para poupar este esforço desperdiçado.

Resumo

Como foi descrito o Joomla!:

  • utiliza o parâmetro task para definir ações, resultando normalmente em algum tipo de atualização da base de dados e terminando sempre com um redireccionamento
  • não define um parâmetro de tarefa quando uma página Web está para ser apresentada
  • possui funcionalidades ricas em classes de controladores e de modelo de Alto Nível que podem simplificar bastante o seu código (todas as quais podem ser utilizadas tanto no front-end como no back-end).

A escolha de quais as classes de controlador e modelo a estender é mais fácil no Backend, pois basta seguir o padrão dos componentes principais do Joomla!.

Para o Frontend, aqui está um guia aproximado:

Simplesmente apresentar um registo ou conjunto de registos, sem fornecer a capacidade de alterar nada:

  • O controlador estende a clase BaseController
  • o modelo estende BaseDatabaseModel ou (especialmente se estiver a partilhar um modelo entre um componente e módulos) ItemModel se for um único registo, ListModel se existirem vários registos.

Visualização de um formulário com vários registos (mas o formulário não está definido num ficheiro XML), incluindo a capacidade de selecionar vários registos e aplicar-lhes algum tipo de operação (por exemplo, eliminar, publicar):

  • controlador estende a classe BaseController
  • o modelo estende a classe ListModel – exceto se estiver a utilizar o mesmo modelo para visualizar o formulário e tratar das atualizações; nesse caso, utilize a classe AdminModel.

Manipulando os POSTs HTTP do formulário em subcontroladores:

  • controlador estende a classe AdminController
    o modelo estende a AdminModel

Visualização de um formulário com um único registo, onde o formulário é definido num ficheiro XML, e permite ao utilizador editá-lo, ou um registo em branco e permitindo ao utilizador criar um registo:

  • Controlador estende BaseController
  • Modelo estende FormModel – exceto se estiver a utilizar o mesmo modelo para exibir o formulário e tratar das atualizações (como é normalmente o caso), nesse caso utilize a classe AdminModel.

Manipulando os POSTs HTTP do formulário em subcontroladores:

  • controlador estende a classe FormController
  • o modelo estende a classe AdminModel