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')
    ));
}

Node.js: Criando um servidor REST com o Padrão MVC utilizando o Framework Express

Olá, já faz um bom tempo que não escrevo por aqui. Estou com pouco tempo livre devido a meu projeto de mestrado. Neste post falarei sobre parte de um projeto recente que venho desenvolvendo, durante o qual precisei criar um servidor REST com Node.js.

Sou um grande fã de CakePHP, pois foi o primeiro framework para web que tive contato, e com o qual aprendi bastante. Talvez por isso, sempre que aprendo uma nova linguagem, procuro encontrar analogias com alguma parte do CakePHP que eu já conheço. Sendo assim, procurei deixar a estrutura da aplicação o mais parecida possível com o que eu utilizava com CakePHP.

obs: se você não está familiarizado com o padrão MVC, leia este artigo.

Neste projeto, a maioria dos métodos dos models utilizam o pacote Bluebird para promessas. Além disso, o banco de dados que escolhi foi o MySQL. Algumas alterações na camada demodels podem ser necessárias se você quiser utilizar outro banco de dados.

O fonte completo da aplicação pode ser baixado aqui.

Estrutura básica da aplicação

Se você já estiver acostumado com o padrão MVC, não terá dificuldades para entender a função de cada diretório da aplicação. Há um diretório para os controllers e um para osmodels, mas nenhum para as views. Optei por retornar os dados do servidor apenas no formato JSON. Caso você deseje suporte para mais formatos, pode utilizar uma camada deviews separada da dos controllers para isso.

Antes de tudo, edite o arquivo config/config.js com as configurações de acesso ao banco de dados e a porta a ser utilizada pela aplicação. Você também pode especificar a porta através da variável de ambiente PORT.

Rotas

O Framework Express já possui um sistema de rotas bastante interessante, então pouco trabalho foi preciso aqui.

Cada controller possui uma função addRestRoutes que adiciona as rotas básicas do servidor automaticamente. Essas rotas mapeiam para ações presentes na classe Controller. Cadacontroller da sua aplicação deve herdar os métodos da classe Controller.

As rotas padrões são no formato a seguir:

  • GET /blog/1: mapeia para a ação findById
  • POST /blogs/searches: mapeia para a ação search
  • POST /blogs: mapeia para a ação create
  • PUT /blog/1: mapeia para a ação edit
  • DELETE /blog/1: mapeia para a ação delete

As ações search, create e edit requerem que alguns parâmetros sejam passados no corpo da requisição.

Para a ação de busca, os parâmetros devem ser passados seguindo o formato a seguir:

Para as ações create e put, devem ser passados os dados a serem salvos no banco de dados.

Se o controller possuir alguma rota que saia deste padrão, elas podem ser adicionadas pelo método addCustomRoutes como a seguir:

Controllers

A estrutura básica para um controller é a seguir.

O arquivo acima deve se chamar CarsController.js e estar na pasta controllers.

Models

Os models precisam de algumas configurações para o correto funcionamento dos métodos da classe Model. Um model básico é apresentado a seguir.

Os principais métodos herdados da classe Model são listados abaixo.

Model.find

Realiza uma consulta ao banco de dados.

Exemplo de Uso

Parâmetros

  • conditions : array de condições no formato do pacote squel
  • limit : máximo de registros para serem retornados
  • offset : números de registros para serem pulados
  • group : cláusula SQL group by
  • recursive : nível de recursividade para buscar por registros nos models com os quais este está associado

Model.findById

Busca por um registro com determinado id. Seu único parâmetro é o id do objeto a ser buscado. Este método retorna uma promessa rejeitada se não houver registro com o id especificado.

Model.save

Cria ou atualiza um registro já existente no banco de dados.

Exemplos de Uso

Model.delete

Remove um registro do banco e dados. Seu único parâmetro é o id do registro a ser removido.