Generador de widgets CRUD para Yii

¿Qué tienen en común los comentarios sobre el artículo sobre Habré y las opciones adicionales al comprar un automóvil?



Desde el punto de vista del modelado de datos, ambos son entidades "anidadas" que no tienen una significación independiente aislada del objeto principal.

Yii ( php framework ) tiene Gii, un generador de código incorporado que le permite crear interfaces CRUD básicas utilizando un modelo de datos con unos pocos clics del mouse, lo que acelera significativamente el desarrollo, pero solo son aplicables a entidades independientes, como el artículo o la máquina en los ejemplos anteriores.

Sería genial poder generar algo similar para los objetos de datos "anidados", ¿verdad? Ahora, puedes, bienvenido a Kat para más detalles.

Para los más impacientes al final del artículo, se dan instrucciones para un inicio rápido.

Y para aquellos interesados ​​en el artículo, se consideran aspectos de una aplicación comercial a un dispositivo interno:

  • Business Case: Publicación por tema
    • La lista de temas en el principal
    • Lista de publicaciones relacionadas
  • Debajo del capó: un generador gii basado en CRUD
    • Gii Generator Template
    • Clase base de widget
    • Controlador de fachada integrado.
  • Inicio rápido
    • Sobre soporte y desarrollo

Business Case: Publicación por tema


Quizás comentarios sobre un habr y un mal ejemplo desde a menudo son más útiles que el artículo en sí, pero, en cualquier caso, al desarrollar una aplicación, a menudo hay situaciones en las que un determinado objeto del modelo de datos es de poco interés para el usuario como entidad independiente.

Considere una tarea empresarial simplificada: crear un sitio web para publicar mensajes agrupados por diversos temas.

El sitio debe tener las siguientes interfaces:

  1. La página principal: debería admitir varios widgets en el futuro, pero en la etapa actual de implementación solo hay uno: una lista de temas filtrada por algún criterio.
  2. Lista completa de temas: una lista completa de temas en forma de tabla;
  3. Página de tema: información sobre el tema y una lista de publicaciones publicadas en él.

Bastante estándar, ¿verdad?

Veamos el modelo de datos:



Además no hay sorpresas. Dos clases de modelos contendrán nuestra lógica de negocios:

  • La clase de tema : datos sobre el tema, validación, una lista de publicaciones en él, así como un método separado que devuelve una lista de temas filtrados por los criterios para el widget en la página principal.
  • La clase Post es solo datos y validación.

La aplicación será atendida por dos controladores:

  • SiteController : páginas estándar (sobre nosotros, contactos, etc.), autorización (no requerida por las especificaciones técnicas, pero sabemos algo) e índice: la página principal. Porque asumimos en el futuro muchos widgets diferentes, tiene sentido dejar la página principal en este controlador y no transferirla a un modelo específico para uno.
  • TopicController es un conjunto estándar de acciones: enumerar, crear, editar, ver y eliminar temas.

Potencialmente, también puede generar un PostController , para fines de administración y / o copiar y pegar piezas de código en widgets personalizados, pero deje esto fuera del alcance de este artículo.
Hasta ahora, la mayor parte del código se puede generar usando gii, lo que acelera el desarrollo y reduce los riesgos (menos código manual = menos posibilidades de cometer un error).

Quedan dos preguntas:

  1. ¿Cómo mostrar una lista filtrada de temas en la página principal?
  2. ¿Cómo mostrar una lista de publicaciones por tema?

Si puede resolverlos usando un generador automático, este será un logro sólido.

La lista de temas en el principal


La página principal que sirve la dirección del sitio / índice debe contener una lista de temas filtrados por un criterio predeterminado. Criterios de filtrado, como parte de la lógica de negocios, hemos incluido en el modelo.

Para la visualización, hay varias opciones de implementación.

El primero, sucio y rápido, es hacer todo directamente en el archivo de vista ( views / site / index.php ):

  1. Crear ActiveDataProvider ;
  2. Llénalo con datos del modelo de tema ;
  3. Visualice usando un widget estándar ListView / GridView , especificando los campos requeridos manualmente.

Puede ir un poco más allá y empaquetarlo todo en un archivo de vista separado, algo así como views / site / _topic-list-widget.php , invocando su render desde el archivo principal. Esto le dará un poco más de capacidad de administración y extensibilidad, pero aún se ve bastante sucio.

Es probable que la mayoría de nosotros creemos un widget separado de acuerdo con todas las reglas, en un espacio de nombres separado ( aplicación \ widgets o aplicación \ componentes para la plantilla básica, dependiendo de la versión que esté usando), donde encapsulan la creación de ActiveDataProvider por modelo y se muestran en un archivo separado sumisión Todo lo que queda es llamar a este widget desde la página principal. Esta solución es la más correcta desde el punto de vista de la descomposición de clases, la capacidad de administración y la extensibilidad del código.

¿Pero parece que el código de este widget repetirá mucho el código de TopicController en términos de manejo de actionIndex () ? Y es muy molesto escribir este código manualmente.

Sería mucho mejor generar este código automáticamente y luego simplemente llamar al widget terminado:

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

Lista de publicaciones relacionadas

La página para ver el tema servido por la dirección del tema / vista debe contener información sobre el tema en sí y una lista de mensajes publicados en él. Obtenemos la lista de mensajes para el tema en el modelo automáticamente si hemos configurado correctamente las relaciones entre las tablas, de modo que solo quede la pregunta de visualización.

Por analogía con la lista filtrada de temas, tenemos casi las mismas opciones.

El primero es hacer todo en el código del archivo de vista para ver el tema ( views / topic / view.php ):

  1. Crear ActiveDataProvider ;
  2. Llénelo con datos del modelo $ model-> getPosts () ;
  3. Visualice usando un widget estándar ListView / GridView , especificando los campos requeridos manualmente.

El segundo es aislar este código en un archivo de presentación separado: views / topic / _posts-list-widget.php , solo para no ser una monstruosidad; reutilizarlo en algún lugar todavía fallará.

El tercero es un widget completo que duplicará en gran medida el código del PostController condicional en la parte actionIndex () , pero escrito manualmente.

O genere el código automáticamente y llame al widget terminado:

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

Debajo del capó: un generador gii basado en CRUD


Se define la tarea comercial, se describen los requisitos para el widget generado, descubriremos cómo exactamente lo generaremos. Gii ya tiene un generador para el controlador CRUD. Para un widget CRUD, necesitamos crear un nuevo generador basado en el existente.

Un par de enlaces a la documentación antes de comenzar; también será útil si decide escribir su propia extensión:


Obviamente, toda la funcionalidad está empaquetada en la extensión Yii, que se instala a través del compositor y se coloca en la carpeta del proveedor de su proyecto.

La extensión consta de tres partes:

  1. El directorio templates / crud que contiene la plantilla del generador gii;
  2. Archivo Controller.php : controlador de fachada incorporado para llamadas de widget;
  3. El archivo Widget.php es la clase base para todos los widgets generados.



Gii Generator Template


La extensión debe generar código, por lo que su parte central es el generador Gii.

Inicialmente, se suponía que para implementar la extensión sería suficiente escribir su propia plantilla para el generador CRUD-Controller incorporado. Por cierto, esta es la razón por la cual el directorio se llama plantillas, no generadores. Pero resultó que el generador CRUD-Controller realiza una validación muy intensiva de los datos de entrada, lo que no permitió implementar muchos requisitos, por ejemplo, cambiar la clase de herencia. Por lo tanto, la extensión contiene un generador completo, y no solo una plantilla.

El generador gii consta de las siguientes partes (todas están dentro del directorio templates / crud):

  • El directorio predeterminado es una plantilla donde ocurre toda la magia: cada archivo en este directorio corresponderá a un archivo generado en su proyecto;
  • Archivo form.php : como puede adivinar por el nombre, este es un formulario para ingresar parámetros de generación (nombres de clase, etc.);
  • File Generator.php : una orquesta de generación que recibe datos del formulario, los valida y luego llama secuencialmente a los archivos de plantilla para crear el resultado.

Los archivos Generator.php y form.php contienen principalmente cambios cosméticos en relación con los originales del generador CRUD: nombres de archivo, validación, descripciones y mensajes de texto, etc.

Los archivos de plantilla son responsables de la vista generada y del código del widget en sí. En primer lugar, el archivo templates / crud / default / controller.php es importante, ya que es responsable de generar la clase de widget directamente, que corresponde a la clase de controlador desde el generador original.

El widget debe tener las mismas acciones que el controlador CRUD, pero se generan de forma un poco diferente. Los siguientes ejemplos muestran el resultado de la generación con comentarios:

  • actionIndex: en lugar de la salida incondicional de todos los modelos, el método acepta el parámetro $ query;

     public function actionIndex($query) { $dataProvider = new ActiveDataProvider([ 'query' => $query, ]); return $this->render('index', [ 'dataProvider' => $dataProvider, ]); } 
  • actionCreate y actionUpdate: en caso de éxito, en lugar de una redirección, simplemente devuelven el código de éxito, el controlador de fachada incorporado proporciona un procesamiento adicional;

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

  • actionDelete: admite el método GET para mostrar el widget de eliminación (un botón de forma predeterminada) y POST para realizar la acción; si tiene éxito, tampoco realiza una redirección, pero devuelve un 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'; } } 

Finalmente, los archivos de vista contienen las siguientes ediciones básicas:

  • Todos los encabezados traducidos a h2 en lugar de h1;
  • Se eliminó el código responsable de mostrar el título de la página y de las migas de pan; el widget no debería afectar estas cosas;
  • La creación y edición de modelos se realiza mediante una ventana modal (widget Modal incorporado);
  • Se agregó una plantilla de widget de eliminación, con un gran botón rojo.

Clase base de widget


Cuando el generador termine su trabajo, creará una clase de widget en el espacio de nombres de la aplicación. La cadena de herencia tiene este aspecto: los widgets generados para la aplicación se heredan del widget de extensión base, clase \ ianikanov \ wce \ Widget , que, a su vez, se hereda del widget base Yii, clase \ yii \ base \ Widget .

La clase base del widget de extensión resuelve las siguientes tareas:

  1. Define dos campos principales: $ action y $ params, a través de los cuales se transfiere el control al widget desde la vista de llamada;
  2. Define una serie de parámetros estándar que pueden anularse en la clase generada, como la ruta a los archivos de vista del widget, el nombre y la ruta al controlador de fachada (más abajo) y los mensajes de error;
  3. Define parámetros estándar al representar vistas: render y renderFile;
  4. Proporciona una infraestructura de eventos similar a la infraestructura del controlador para que funcionen los filtros estándar como AccessControl y VerbFilter ;
  5. Define un método de ejecución que recopila todo esto juntos.

Controlador de fachada integrado.

No hay problemas con la visualización de estos datos: los widgets están destinados para este propósito. Pero para editar, de todos modos, necesitas un controlador. Genere un controlador único para cada widget: se pierde toda su esencia. Usar un CRUD estándar no siempre es relevante, y no quiero depender del lanzamiento adicional de gii. Por lo tanto, la opción se utilizó con una fachada de controlador universal integrada.

Este controlador está registrado en el mapa de la aplicación a través del archivo de configuración y contiene solo un método: actionIndex, que realiza las siguientes acciones:

  1. Acepta una solicitud de un cliente;
  2. Transfiere el control a la clase de widget correspondiente;
  3. Maneja errores de negocios como resultado del widget;
  4. Redirige a la aplicación principal.

Quizás sea más importante indicar lo que NO hace este controlador:

  1. No verifica los niveles de acceso: esta lógica pertenece a widgets específicos;
  2. No realiza ninguna manipulación de entrada: los parámetros se pasan al widget tal como está;
  3. No manipula la salida, excepto para verificar un código de éxito predefinido.

Este enfoque le permite mantener la versatilidad de la fachada, dejando la implementación de los requisitos comerciales, incluidos los requisitos de seguridad, el código de la aplicación.

Inicio rápido

El desafío comercial es claro, ¿listo para comenzar? Usar la extensión tiene cuatro pasos:

  1. Instalación;
  2. Configuración;
  3. Generación;
  4. Solicitud

La instalación de la extensión se realiza mediante el compositor:

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

A continuación, debe realizar varios cambios en el archivo de configuración de la aplicación.

Primero, agregue una referencia al generador 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 ], ], ], ]; } 

En segundo lugar, agregue el controlador de fachada integrado al mapa:

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

Esto completa la instalación y configuración.

Para generar un widget:

  1. Abrir gii;
  2. Seleccione "Widget del controlador CRUD";
  3. Rellene los campos del formulario;
  4. Ver y generar código.

Además, para usar el widget, se debe invocar especificando acción y parámetros, casi lo mismo que se llama al controlador.

Widget para ver la lista de modelos:

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

Widget para ver un modelo:

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

Widget de creación de modelos (botón + formulario envuelto en modal):

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

Widget de cambio de modelo (botón + formulario envuelto en modal):

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

Widget de eliminación de modelo (botón):

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

El código del widget y todas las vistas pertenece a la aplicación y se puede cambiar fácilmente: todo es exactamente igual que al generar el controlador.

Sobre soporte y desarrollo


Algunas palabras sobre cómo se apoyará y desarrollará la expansión. Tengo el trabajo principal y algunos de mis proyectos "secundarios" (proyectos favoritos). Entonces, esta extensión es un proyecto paralelo de mis proyectos paralelos, por lo que desarrollaré mejoras solo para las necesidades de mis proyectos.

En las mejores tradiciones de código abierto, el código está disponible en el github , y lo apoyaré en términos de corrección de errores, y también trataré de hacer revisiones oportunas si alguien quiere enviar una solicitud de extracción, por lo que quienes estén interesados, únase.

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


All Articles