Utilizando Cache em suas Views para Melhorar o Desempenho de seus Módulos PrestaShop

Neste artigo falarei sobre um assunto de grande importância no desenvolvimento de módulos PrestaShop: a utilização de cache para melhorar a performance dos mesmos. A importância desse assunto é maior ainda quando falamos de módulos que são utilizados no Front-Office da loja, pois uma simples um módulo mal escrito pode comprometer seriamente o tempo de carregamento da loja. A boa notícia é que é bastante simples de se utilizar o cache interno do PrestaShop.

Como você já deve saber, todo o HTML da loja é (ou deveria ser) gerado através de alguma linguagem de template (smarty por padrão). Por se tratar de uma linguagem de template, ela deve ser compilada para a obtenção do HTML que será enviado ao navegador. Assim, se nenhum cache for utilizado, toda vez que uma página da loja for acessada, diversos arquivos de template devem ser compilados, mesmo que o resultado da compilação seja exatamente o mesmo que foi produzido na última vez que a loja foi acessada. Além de isso ser um desperdício de recursos do servidor, também faz com que o usuário espere mais tempo do que é necessário para que a página seja carregada. A situação é ainda pior quando o banco de dados precisa ser utilizado para obter os dados a serem utilizados no template.

Mas, conforme já foi dito, o sistema de cache do PrestaShop é bastante simples de ser utilizado, e você só precisará adicionar algumas poucas linhas de código ao seu módulo. Suponhamos que o seu módulo gere algum conteúdo a ser exibido no Front-Office da loja no hook displayShoppingCartFooter. Assim, o seu hook será algo como

Quando esta função for executada, se houver alguma versão do template ‘mytemplate.tpl’ em cache, o PrestaShop automaticamente utilizará essa versão, ao invés de recompilar o template. Se o nosso template não dependesse da variável $data, não precisaríamos fazer nada além disso para utilizar o sistema de cache do PrestaShop. Contudo, conforme o valor do carrinho de compras muda, o HTML produzido por esse template pode ser diferente. Isso faz com que precisemos de versões diferentes em cache desse mesmo template, que possam ser identificadas a partir do valor do carrinho de compras.

Isso pode ser feito utilizando o terceiro argumento do método Module::display(), denominado $cache_id, da seguinte forma:

Assim, o PrestaShop se encarregará de utilizar a versão em cache do template mytemplate.tpl associada ao cache $cache_id. Uma boa prática é utilizar o método Module::getCacheId(), que já gera uma string contendo as seguintes informações:

  • A loja foi acessada utilizando conexão segura ou não;
  • ID da loja acessada (no caso de multilojas);
  • ID do grupo de cliente a que o usuário que acessou a loja pertence;
  • Idioma utilizado pelo cliente no acesso à loja;
  • Moeda utilizada pelo cliente;
  • País selecionado pelo cliente.

Assim, um cache_id bastante interessante pode ser obtido como a seguir

Agora, observe que, caso o template esteja em cache e não precise ser recompilado, o objeto $data$ não será utilizado. Assim, podemos otimizar a nossa função verificando se há algum cache válido para o nosso template antes de chamar o método MyModel::getSomeDbData().

Por fim, a invalidação do cache pode ser feita através do método Module::_clearCache(). Caso desejemos limpar todos os caches associados ao nosso template sempre que sempre que qualquer objeto da classe MyModel for modificado, podemos utilizar o hook actionObjectMyModelUpdateAfter. Esse hook é invocado automaticamente pelo PrestaShop sempre que o método update() for executado com sucesso em um objeto da classe MyModel (que deve extender a classe ObjectModel do PrestaShop).

O método Module::_clearCache() também suporta o parâmetro $cache_id, para que possamos limpar o cache de apenas uma das versões do template.

Desenvolvendo um módulo com CRUD para PrestasShop

Nesse artigo, desenvolveremos um módulo para ser utilizado no painel administrativo da loja. Esse módulo permitirá possuirá as quatro operações CRUD básicas, e mostrará como isso pode ser feito reaproveitando o código do core do PrestaShop. Dessa forma, nossos formulários e a listagem das informações cadastradas possuirão um visual compatível com o padrão da plataforma. Além disso, isso deve fazer com que o módulo seja compatível com futuras versões da loja, até um ponto em que o core seja modificado significativamente.

Os seguintes tópicos serão abordados:

  1. Criação automática do banco de dados;
  2. Reparação (até um certo ponto) automática do banco de dados na reinstalação do módulo;
  3. Listagem dos dados cadastrado no banco em uma grid seguindo o padrão da plataforma;
  4. Criação do formulário de cadastro/edição;
  5. Deleção de registros utilizando ação em massa e individual.

Para os itens 1. e 2., utilizarei a classe CustomObjectModel, que desenvolvi para adicionar alguns recursos à classe ObjectModel do Prestashop.  Para que essa classe funcione bem, os models precisam ser definidos da mesma forma que os models padrão do PrestaShop. Explicarei como fazer essa definição no decorrer do artigo.

Uma vez que os models foram definidos de maneira adequada, os itens 3., 4. e 5. são feitos de maneira praticamente automática pelo próprio PrestaShop. Portanto, nosso trabalho principal será a criação dos models com as definições corretas.

O código-fonte completo do módulo pode ser encontrado neste repositório no GitHub. Para vê-lo em funcionamento, basta extrair todo o código presente no repositório para a pasta /modules/crud/. Recomendo que você siga esse artigo visualizando o código completo do módulo, modificando alguns trechos de seu código para melhor visualização do significado de cada uma das configurações feitas.

Configuração do módulo

Logo no início do arquivo crud.php, temos a linha

A propriedade $models deve conter o nome de todos os models que serão utilizados no módulo. Isso não é um requisito do PrestaShop, mas será utilizado na criação automática do banco de dados. A seguir, temos a definição dasabas que serão criadas no menu do painel administrativo da loja.

Isso fará com que seja criado um menu no painel administrativo da loja com o nome Complete CRUD , com um submenu Custom Operation, conforme a imagem abaixo. Isso também não é feito automaticamente pelo PrestaShop, mas sim com a funçao addTab() que será apresentada a seguir.

Na linha 17, adicionamos o texto a ser exibido no menu (e no submenu) das abas em cada um dos idiomas da loja. Na linha 25, usamos a mesma função addTab() para criar o submenu Custom Operation. A função removeTab(), que será mostrada abaixo, remove os itens que foram criados no menu.

Com essas funções, podemos criar o construtor de nosso módulo, para depois fazer a sua instalação.

Os parâmetros configurados no construtor são auto-explicativos. Sendo assim, seguiremos adiante para a função que instalará o módulo na loja.

Primeiramente, criamos no banco de dados as tabelas referente a cada um dos models utilizados no módulo. Caso as tabelas já existam, a instalação verifica se há alguma coluna faltando nessas tabelas. Isso é util para corrigir eventuais erros provocados por alguma atualização mal-sucedida do módulo. A seguir, instalamos o módulo na loja, e, por fim, criamos o menu do painel referente ao módulo.

Por fim, segue a função uninstall(), que removerá o menu anteriormente criado.

Eu não costumo remover nenhuma informação do banco de dados (muito menos excluir tabelas) na desinstalação do módulo, para evitar perdas acidentais de dados. Ao invés disso, você pode adicionar um botão na tela de configurações do módulo para oferecer essa opção ao usuário, avisando-o das possíveis consequências. Isso não será abordado nesse artigo,

Criação do Model

Na criação do model, muito pouco precisa ser feito. Devemos, apenas, especificar todas as colunas que devem ser criadas no banco de dados, e criar um atributo para cada uma dessas colunas na classe CrudModel. Isso é feito com o código abaixo, que está no arquivo classes/CrudModel.php.

O parâmetro primary refere-se à chave primária da tabela utilizada, e é o campo que será utilizado para identificar unicamente cada registro dessa tabela pelo PrestaShop. As constantes TYPE_INTTYPE_STRING, etc, estão definidas no arquivo ObjectModel.php, dentro da pasta classes da sua loja. validate refere-se ao nome de uma das funções da classe Validate que será utilizada para validar os dados antes de serem salvos no banco de dados. Por fim, o parâmetro db_type não é padrão do PrestaShop, mas acrescentei ele para ser utilizado pela CrudCustomObjectModel, na criação automática do banco de dados.

Criação dos controllers

controller também é bastante simples, assim como foi o model. Devemos apenas configurar quais colunas desejamos que sejam apresentadas na tabela que apresentará os dados. A tabela em si será gerada automaticamente pelo PrestaShop. Do mesmo modo, especificaremos quais os campos presentes nos formulários de cadastro/edição, e o formulário será gerado automaticamente. Não escreveremos uma linha da camada de apresentação (view), nem da parte pesada do controller. O PrestaShop se encarregará de chamar os métodos adequados da classe CrudModel quando o usuário utilizar algum filtro na tabela, ou quando ele editar algum dos registros dela, ou até mesmo quando excluir uma de suas linhas do banco de dados.

As imagens abaixo mostram as telas de listagem e de cadastro que são geradas automaticamente pelo prestashop, no tema padrão do painel administrativo. Para que elas sejam geradas, devemos criar o arquivo controllers/admin/AdminCrudModels.php. Todos os parâmetros necessários para a geração das telas serão configurados no construtor da classe. Assim, comecemos a declaração básica do controller.

Na linha 7, dizemos que esse controller requer que o bootstrap seja carregado para que as views sejam exibidas corretamente. A seguir, especificamos os parâmetros do model cujos dados desejamos manipularPor fim, devemos especificar quais campos desejamos que sejam apresentados na listagem dos dados. Isso é feito através do código abaixo.

As chaves do array acima correspondem aos nomes dos campos que foram configurados no nosso model. Os parâmetros utilizados no código acima seguem a documentação da classe HelperList, então não farei maiores comentários. Na linha 14, fazemos com que a coluna active ganhe a funcionalidade de modificar o status do objeto apenas clicando nos ícones “v” ou “x“. Além disso, a tabela ganha automaticamente as ações em massa “Ativar Aelecionados” e “Desativar Selecionados”.

Para a ação em massa de deleção, devemos adicionar o código abaixo.

Para finalizar nossa listagem, vamos adicionar os botões de ação individual, como mostra a figura abaixo.

Isso é feito facilmente com o código abaixo.

Quando o usuário clicar em Editar, ele verá o formulário abaixo, já preenchido com os dados do objeto escolhido.

Ele pode, ainda, adicionar uma nova linha à tabela com o botão de Adicionar Novo, na parte de cima da tabela. Ele verá o mesmo formulário, mas sem nenhum dado preenchido.

btn_new

Para que esse formulário seja gerado automaticamente, devemos especificar todos os campos que serão exibidos, através do código abaixo.

Essa especificação é feita conforme a documentação da classe HelperForm.

Feito isso, nosso CRUD está finalizado, e totalmente funcional. Note que, como as telas são geradas automaticamente, alguns aspectos da interface não podem ser modificados tão facilmente. Para fazer qualquer modificação não coberta nesse artigo, recomendo uma análise da classe AdminController, bem como as classes HelperFormHelperList, que são as classes base para a geração das telas.

[PrestaShop] – Realizando Requisições AJAX para URLs do Painel

Neste artigo falarei sobre um problema que encontrei quando comecei a desenvolver módulos para a plataforma Prestashop: como realizar requisições AJAX para meus controllers que herdam a classe AdminController.  Apesar de requisições AJAX não serem, em geral, algo complicado de se fazer, essa tarefa se torna um pouco mais complicada quando o destino da requisição é um controller do tipo mencionado.

Primeiramente, para que a requisição seja processada, é necessário que haja um registro na tabela tab referente ao controller para o qual desejamos enviar a requisição. Você pode criar esse registro através da função abaixo:

$tab = new Tab();
$tab->module = <nomedomodulo>;
$tab->active = 0; 
$tab->class_name = <nomedocontroller>;  //AdminCustomProducts por exemplo
$tab->id_parent = Tab::getIdFromClassName(<nomedomodulo>);

foreach (Language::getLanguages(true) as $lang)
{
    $tab->name[$lang['id_lang']] = <nomedaaba>;
}

$tab->add();

Na linha 3, se você deixar a aba como ativa, ela será exibida no menu do Prestashop. Se você estiver criando um controller apenas para se comunicar via AJAX, deixe como 0. Se você deixar active=1, uma aba aparecerá no painel administrativo do usuário com um link para esse novo controller. Na linha 9, o nome da aba é o texto que será apresentado ao usuário no link para o seu controller. Caso você use active=0, esse campo não é importante, e pode conter qualquer valor.

A segunda dificuldade é que toda URL do painel precisa conter um token de segurança válido para que a requisição chegue até o controller desejado. Para que esse token seja acessível via Javascript, eu costumo deixá-lo como atributo de algum div em meu template.

<div id='meumodulo_wrapper' data-token='{$token}'>
	...
</div>

No controller responsável por gerar o template acima, o token de segurança deve ser adicionado à variável “$token“.

$this->smarty->assign(array(
    'token' => Tools::getAdminTokenLite('nomedocontroller')
));

Assim, a requisição ajax pode ser feita da forma a seguir.

$.ajax({
    type : 'POST',
    dataType : 'json',
    url : 'ajax-tab.php',
    data : {
        //parâmetros obrigatórios
        ajax : true,
        controller : 'nomedocontroller',
        action : 'nomedometodo',
        token : $('#meumodulo_wrapper').attr('data-token'),

        //demais dados a serem utilizados pelo controller
        foo : 'bar'
    },
})
.done(function(){})
.fail(function(){});

Isso fará com que a requisição chegue até o método “ajaxProcessnomedometodo()“ do controller “nomedocontroller.php“.  Note que o nome do método é case-sensitive. Por fim, implemente esse método em seu controller.

function ajaxProcessnomedometodo()
{	
    echo json_encode(array(
        'success' => true,
        'received_data' => Tools::getValue('foo')
    ));
}