Gerador de widget CRUD para Yii

O que comentários sobre o artigo sobre Habré e opções adicionais têm em comum na compra de um carro?



Do ponto de vista da modelagem de dados, ambas são entidades "aninhadas" que não têm significado independente isoladamente do objeto pai.

No Yii ( php framework ), existe o Gii - um gerador de código interno que permite criar interfaces CRUD básicas usando um modelo de dados com apenas alguns cliques do mouse, o que acelera significativamente o desenvolvimento, mas é aplicável apenas a entidades independentes, como o artigo ou a máquina nos exemplos acima.

Seria ótimo poder gerar algo semelhante para objetos de dados "aninhados", certo? Agora - você pode, seja bem-vindo ao kat para obter detalhes.

Para os mais impacientes no final do artigo, são fornecidas instruções para um início rápido.

E para os interessados ​​no artigo, são considerados aspectos de um aplicativo de negócios para um dispositivo interno:

  • Caso de negócios: postagem por tópico
    • A lista de tópicos nos principais
    • Lista de postagens relacionadas
  • Sob o capô: um gerador de gii baseado em CRUD
    • Gii Generator Template
    • Classe base do widget
    • Controlador de fachada integrado
  • Início rápido
    • Sobre suporte e desenvolvimento

Caso de negócios: postagem por tópico


Talvez comentários sobre um habr e um mau exemplo desde geralmente são mais úteis que o próprio artigo, mas, em qualquer caso, ao desenvolver um aplicativo, geralmente há situações em que um determinado objeto do modelo de dados é de pouco interesse para o usuário como uma entidade independente.

Considere uma tarefa comercial simplificada: criar um site para publicar mensagens agrupadas por vários tópicos.

O site deve ter as seguintes interfaces:

  1. A página principal - deve suportar vários widgets no futuro, mas no estágio atual de implementação, existe apenas um: uma lista de tópicos filtrados por algum critério.
  2. Lista completa de tópicos - uma lista completa de tópicos em forma de tabela;
  3. Página de tópico - informações sobre o tópico e uma lista de postagens publicadas nele.

Bonito padrão, certo?

Vejamos o modelo de dados:



Também não há surpresas. Duas classes de modelos conterão nossa lógica de negócios:

  • A classe Topic - dados sobre o tópico, validação, uma lista de postagens nele, bem como um método separado que retorna uma lista de tópicos filtrados pelos critérios para o widget na página principal.
  • A classe Post é apenas dados e validação.

O aplicativo será atendido por dois controladores:

  • SiteController - páginas padrão (sobre nós, contatos, etc.), autorização (não exigida pelos termos de referência, mas sabemos alguma coisa) e índice - a página principal. Porque Como esperamos muitos widgets diferentes no futuro, faz sentido deixar a página principal nesse controlador e não transferi-lo para um modelo específico para um.
  • O TopicController é um conjunto padrão de ações: lista, criação, edição, exibição e exclusão de tópicos.

Potencialmente, você também pode gerar um PostController - para fins de administração e / ou copiar e colar partes de código em widgets personalizados, mas deixe isso fora do escopo deste artigo.
Até agora, a maior parte do código pode ser gerada usando o gii, o que acelera o desenvolvimento e reduz os riscos (menos código manual = menos chance de cometer um erro).

Restam duas perguntas:

  1. Como exibir uma lista filtrada de tópicos na página principal?
  2. Como exibir uma lista de postagens por tópico?

Se você puder resolvê-los usando um gerador automático - isso será uma conquista sólida.

A lista de tópicos nos principais


A página principal exibida pelo site / endereço do índice deve conter uma lista de tópicos filtrados por um critério predeterminado. Critérios de filtragem, como parte da lógica de negócios, incluímos no modelo.

Para exibição, existem várias opções de implementação.

O primeiro, sujo e rápido, é fazer tudo diretamente no arquivo view ( views / site / index.php ):

  1. Criar ActiveDataProvider ;
  2. Preencha com dados do modelo de tópico ;
  3. Exiba usando um widget ListView / GridView padrão, especificando os campos obrigatórios manualmente.

Você pode ir um pouco mais longe e agrupar tudo em um arquivo de exibição separado, algo como views / site / _topic-list-widget.php , chamando sua renderização do arquivo principal. Isso dará um pouco mais de capacidade de gerenciamento e extensibilidade, mas ainda parece bastante sujo.

É provável que a maioria de nós crie um widget separado de acordo com todas as regras, em um espaço para nome separado ( app \ widgets ou app \ components para o modelo básico - dependendo da versão que você está usando), onde eles encapsulam a criação do ActiveDataProvider por modelo e são exibidos em um separado submissão. Tudo o que resta é chamar esse widget a partir da página principal. Esta solução é a mais correta do ponto de vista da decomposição de classe, gerenciabilidade e extensibilidade do código.

Mas parece que o código desse widget repetirá muito o código do TopicController em termos de manipulação de actionIndex () ? E é tão chato escrever esse código manualmente.

Seria muito melhor gerar esse código automaticamente e, em seguida, basta chamar o widget finalizado:

<?= \app\widgets\TopicControllerWidget::widget([ 'action' => 'index', 'params' => [ 'query' => app\models\Topic::findBySomeSpecificCriteria() ], ]) ?> 

Lista de postagens relacionadas

A página para exibir o tópico exibido pelo endereço de tópico / exibição deve conter informações sobre o próprio tópico e uma lista de mensagens publicadas nele. Obtemos a lista de mensagens para o tópico no modelo automaticamente se tivermos configurado corretamente os relacionamentos entre as tabelas, para que apenas a pergunta de exibição permaneça.

Por analogia com a lista filtrada de tópicos, temos quase as mesmas opções.

O primeiro é fazer tudo no código do arquivo de exibição para exibir o tópico ( views / topic / view.php ):

  1. Criar ActiveDataProvider ;
  2. Preencha com dados do modelo $ model-> getPosts () ;
  3. Exiba usando um widget ListView / GridView padrão, especificando os campos obrigatórios manualmente.

O segundo é isolar esse código em um arquivo de apresentação separado: views / topic / _posts-list-widget.php , apenas para não ser uma desgraça - reutilizá-lo em algum lugar ainda falhará.

O terceiro é um widget completo que duplicará amplamente o código do PostController condicional na parte actionIndex () , mas gravado manualmente.

Ou gere o código automaticamente e chame o widget finalizado:

 <?= app\widgets\PostControllerWidget::widget([ 'action' => 'index', 'params' => [ 'query' => $model->getPosts(), ], ]) ?> 

Sob o capô: um gerador de gii baseado em CRUD


A tarefa de negócios é definida, os requisitos para o widget gerado são descritos, descobriremos como exatamente o geraremos. O Gii já possui um gerador para o controlador CRUD. Para um widget CRUD, precisamos criar um novo gerador com base no existente.

Alguns links para a documentação antes de iniciar - também será útil se você decidir escrever sua própria extensão:


Obviamente, toda a funcionalidade é empacotada na extensão Yii, que é instalada através do compositor e entra na pasta do fornecedor do seu projeto.

A extensão consiste em três partes:

  1. O diretório templates / crud que contém o modelo do gerador gii;
  2. Arquivo Controller.php - controlador de fachada embutido para chamadas de widget;
  3. O arquivo Widget.php é a classe base para todos os widgets gerados.



Gii Generator Template


A extensão deve gerar código; portanto, sua parte central é o gerador Gii.

Inicialmente, assumiu-se que, para implementar a extensão, seria suficiente escrever seu próprio modelo para o gerador CRUD-Controller interno. A propósito, é por isso que o diretório é chamado de modelos, não de geradores. Porém, o gerador CRUD-Controller realiza uma validação muito intensiva dos dados de entrada, o que não permitiu implementar muitos requisitos, por exemplo, alterar a classe de herança. Portanto, a extensão contém um gerador completo e não apenas um modelo.

O gerador gii consiste nas seguintes partes (todas estão dentro do diretório templates / crud):

  • O diretório padrão é um modelo em que toda a mágica acontece: cada arquivo neste diretório corresponderá a um arquivo gerado no seu projeto;
  • Arquivo form.php - como você pode imaginar pelo nome, este é um formulário para inserir parâmetros de geração (nomes de classes, etc.);
  • File Generator.php - uma orquestra de geração que recebe dados do formulário, os valida e depois chama seqüencialmente os arquivos de modelo para criar o resultado.

Os arquivos Generator.php e form.php contêm principalmente alterações cosméticas em relação às originais do gerador CRUD: nomes de arquivos, validação, descrições e textos de avisos, etc.

Os arquivos de modelo são responsáveis ​​pela visualização gerada e pelo próprio código do widget. Primeiro, o arquivo templates / crud / default / controller.php é importante, responsável por gerar a classe widget diretamente, que corresponde à classe controller do gerador original.

O widget deve ter as mesmas ações que o controlador CRUD, mas elas são geradas de maneira um pouco diferente. Os exemplos abaixo mostram o resultado da geração com comentários:

  • actionIndex - em vez da saída incondicional de todos os modelos, o método aceita o parâmetro $ query;

     public function actionIndex($query) { $dataProvider = new ActiveDataProvider([ 'query' => $query, ]); return $this->render('index', [ 'dataProvider' => $dataProvider, ]); } 
  • actionCreate e actionUpdate - em caso de êxito, em vez de um redirecionamento, eles simplesmente retornam o código de sucesso, o processamento adicional é fornecido pelo controlador de fachada interno;

     public function actionCreate() { $model = new Post(); if ($model->load(Yii::$app->request->post()) && $model->save()) { return 'success'; } return $this->render('create', [ 'model' => $model, ]); } 

  • actionDelete - suporta o método GET para exibir o widget de exclusão (por padrão - um botão) e o POST para executar a ação; se for bem-sucedido, ele também não executa um redirecionamento, mas retorna um código.

     public function actionDelete($id) { $model = $this->findModel($id); if (Yii::$app->request->method == 'GET') { return $this->render('delete', [ 'model' => $model, ]); } else { $model->delete(); return 'success'; } } 

Por fim, os arquivos de exibição contêm as seguintes edições básicas:

  • Todos os cabeçalhos traduzidos para h2 em vez de h1;
  • Removido o código responsável pela exibição do título da página e pela trilha de navegação - o widget não deve afetar essas coisas;
  • A criação e edição de modelos é feita usando uma janela modal (widget Modal embutido);
  • Adicionado modelo de widget de exclusão - com um grande botão vermelho.

Classe base do widget


Quando o gerador terminar seu trabalho, ele criará uma classe de widget no espaço para nome do aplicativo. A cadeia de herança é assim: os widgets gerados para o aplicativo são herdados do widget de extensão base, classe \ ianikanov \ wce \ Widget , que, por sua vez, é herdado do widget Yii base, classe \ yii \ base \ Widget .

A classe base do widget de extensão resolve as seguintes tarefas:

  1. Define dois campos principais: $ action e $ params, através dos quais o controle é transferido para o widget a partir da visualização de chamada;
  2. Define vários parâmetros padrão que podem ser substituídos na classe gerada, como o caminho para os arquivos de exibição do widget, o nome e o caminho para o controlador de fachada (sobre ele abaixo) e mensagens de erro;
  3. Define os parâmetros padrão ao renderizar visualizações: render e renderFile;
  4. Fornece uma infraestrutura de eventos semelhante à infraestrutura do controlador, para que filtros padrão como AccessControl e VerbFilter funcionem ;
  5. Define um método de execução que coleta tudo isso junto.

Controlador de fachada integrado

Não há problemas com a exibição desses dados - os widgets são para esse fim. Mas para editar, de qualquer maneira, você precisa de um controlador. Gere um controlador exclusivo para cada widget - toda a sua essência está perdida. O uso de um CRUD padrão nem sempre é relevante, e eu não quero depender do lançamento adicional do gii. Portanto, a opção foi usada com uma fachada de controlador universal integrada.

Este controlador é registrado no mapa do aplicativo através do arquivo de configuração e contém apenas um método - actionIndex, que executa as seguintes ações:

  1. Aceita uma solicitação de um cliente;
  2. Transfere o controle para a classe de widget correspondente;
  3. Lida com erros de negócios como resultado do widget;
  4. Redireciona de volta para o aplicativo principal.

Talvez seja mais importante indicar o que esse controlador NÃO faz:

  1. Ele não verifica os níveis de acesso - essa lógica pertence a widgets específicos;
  2. Ele não realiza nenhuma manipulação de entrada - os parâmetros são passados ​​para o widget como estão;
  3. Ele não manipula a saída, exceto para verificar um código de sucesso predefinido.

Essa abordagem permite manter a versatilidade da fachada, deixando a implementação de requisitos de negócios, incluindo requisitos de segurança, o código do aplicativo.

Início rápido

O desafio do negócio é claro, pronto para começar? O uso da extensão tem quatro etapas:

  1. Instalação;
  2. Configuração;
  3. Geração;
  4. Aplicação.

A instalação da extensão é feita usando o compositor:

 php composer.phar require --prefer-dist ianikanov/yii2-wce "dev-master" 

Em seguida, você precisa fazer várias alterações no arquivo de configuração do aplicativo.

Primeiro, adicione uma referência ao gerador gii:

 if (YII_ENV_DEV) { $config['modules']['gii'] = [ 'class' => 'yii\gii\Module', 'allowedIPs' => ['127.0.0.1', '::1', '192.168.0.*', '192.168.178.20'], 'generators' => [ //here 'widgetCrud' => [ 'class' => '\ianikanov\wce\templates\crud\Generator', 'templates' => [ 'WCE' => '@vendor/ianikanov/yii2-wce/templates/crud/default', // template name ], ], ], ]; } 

Em segundo lugar, adicione o controlador de fachada integrado ao mapa:

 $config = [ ... 'controllerMap' => [ 'wce-embed' => '\ianikanov\wce\Controller', ], ... ]; 

Isso completa a instalação e configuração.

Para gerar um widget:

  1. Gii aberto;
  2. Selecione "CRUD Controller Widget";
  3. Preencha os campos do formulário;
  4. Ver e gerar código.

Além disso, para usar o widget, ele deve ser chamado especificando ação e parâmetros - quase o mesmo que o controlador é chamado.

Widget para visualizar a lista de modelos:

 <?= app\widgets\PostControllerWidget::widget([ 'action' => 'index', 'params' => [ 'query' => $otherModel->getPosts(), ], ]) ?> 

Widget para visualizar um modelo:

 <?= app\widgets\PostControllerWidget::widget(['action' => 'view', 'params' => ['id' => $post_id]]) ?> 

Widget de criação de modelo (botão + formulário envolto em Modal):

 <?= app\widgets\PostControllerWidget::widget(['action' => 'create']) ?> 

Widget de alteração de modelo (botão + formulário incluído no Modal):

 <?= app\widgets\PostControllerWidget::widget(['action' => 'update', 'params'=>['id' => $post_id]]) ?> 

Widget de remoção de modelo (botão):

 <?= app\widgets\PostControllerWidget::widget(['action' => 'delete', 'params'=>['id' => $post_id]]) ?> 

O código do widget e todas as visualizações pertencem ao aplicativo e podem ser facilmente alterados - tudo é exatamente o mesmo que ao gerar o controlador.

Sobre suporte e desenvolvimento


Algumas palavras sobre como a expansão será suportada e desenvolvida. Eu tenho o trabalho principal e alguns dos meus projetos “paralelos” (pet-projects). Portanto, essa extensão é um projeto paralelo dos meus projetos paralelos, portanto, desenvolverei melhorias apenas para as necessidades dos meus projetos.

Nas melhores tradições de código aberto, o código está disponível no github , e eu o apoiarei em termos de correção de bugs, e também tentarei fazer revisões oportunas se alguém quiser enviar uma solicitação de recebimento, para que quem estiver interessado, participe.

Source: https://habr.com/ru/post/pt450696/


All Articles