📜 ⬆️ ⬇️

CRUD widget generator for Yii

What do the comments on the article on Habré and additional options when buying a car?



From the point of view of data modeling, both are “nested” entities that have no independent meaning in isolation from the parent object.
')
In Yii ( php framework ), Gii is a built-in code generator that allows you to create basic CRUD interfaces on a data model with a few mouse clicks that significantly speed up development, but are applicable only to stand-alone entities, like an article or a machine in the examples above.

It would be great if you could generate something like this for “nested” data objects, right? Now - you can, welcome under the cat for details.

For the most impatient at the end of the article given instructions for a quick start.

And for those interested in the article, aspects from business application to the internal structure are considered:


Business case: posting messages by topic


Perhaps comments on Habré are a bad example, because are often more useful than the article itself, but, in any case, when developing an application, it often happens that a certain object of the data model is of little interest to the user as an independent entity.

Consider a simplified business task: make a site for posting messages grouped by various topics.

The site must have the following interfaces:

  1. The main page should support various widgets in the future, but at the current stage of implementation there is only one: a list of topics filtered by some criterion.
  2. Full list of topics - a complete list of topics in a tabular format;
  3. Topic page - information about the topic and a list of messages published in it.

Pretty standard, right?

Let's look at the data model:



No surprises too. Two classes of models will contain our business logic:


The application will be served by two controllers:


PostController can also be potentially generated - for administration purposes and / or copy-paste pieces of code into custom widgets, but we will leave this outside the scope of this article.
Until now, most of the code can be generated using gii, which speeds up development and reduces risks (less manual code = less chance of making a mistake).

Two questions remain:

  1. How to display a filtered list of topics on the main page?
  2. How to display a list of posts on the topic?

If you can solve them with the help of an automatic generator, this will be a solid achievement.

The list of topics on the main


The main page served by the site / index should contain a list of topics filtered by a predetermined criterion. The filtering criterion, as part of business logic, we included in the model.

For display there are several options for implementation.

The first is dirty and fast - everything is done directly in the presentation file ( views / site / index.php ):

  1. Create ActiveDataProvider ;
  2. Fill it with data from the Topic model;
  3. Display using the standard ListView / GridView widget, specifying the required fields manually.

You can go a little further and pack it all into a separate view file, something like views / site / _topic-list-widget.php , by calling its render from the main file. This will give a bit more manageability and expandability, but it still looks pretty messy.

Most of us will most likely create a separate widget according to all the rules, in a separate namespace ( app \ widgets or app \ components for the basic template - depending on the version you are using), where the creation of ActiveDataProvider by model and display in an independent submission. Further it will be necessary only to call this widget from the main page. This solution is the most correct in terms of class decomposition, controllability and extensibility of the code.

But is there a feeling that the code of this widget will very much repeat the TopicController code in terms of handling actionIndex () ? And so offensive to write this code manually.

It would be much better to generate this code automatically and then just call the ready widget:

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

List of posts by topic

The topic view page served by the topic / view address should contain information about the topic itself and a list of messages published in it. We receive the list of messages for the topic in the model automatically, if we have the relationships between the tables correctly configured, so that only the display question remains.

By analogy with the filtered list of topics, we have practically the same options.

The first is to do everything in the code of the view file to view the topic ( views / topic / view.php ):

  1. Create ActiveDataProvider ;
  2. Fill it with data from the model $ model-> getPosts () ;
  3. Display using the standard ListView / GridView widget, specifying the required fields manually.

The second is to isolate this code into a separate view file: views / topic / _posts-list-widget.php , just so that it does not hurt his eyes - re -using it anywhere will still not work.

The third is a full-fledged widget that will largely duplicate the code of the conditional PostController in the actionIndex () part , but written by hand.

Or generate the code automatically and call the ready widget:

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

Under the hood: CRUD based gii generator


The business task is defined, the requirements for the generated widget are outlined, let's figure out how we will generate it. Gii already has a generator for a CRUD controller. For the CRUD widget, we will need to create a new generator based on the existing one.

A couple of links to the documentation before the start - it will also be useful if you decide to write your own extension:


Obviously, all the functionality is packaged in the Yii extension, which is installed through the composer and goes into the vendor folder of your project.

The extension consists of three parts:

  1. The templates / crud directory containing the gii generator template;
  2. File Controller.php - built-in facade controller for calling widgets;
  3. The Widget.php file is the base class for all generated widgets.



Gii Generator Template


The extension must generate code, so the central part of it is the Gii generator.

Initially it was assumed that to implement the expansion it would be enough to write your own template for the built-in CRUD-Controller generator. By the way, this is why the directory is called templates, not generators. But it turned out that the CRUD-Controller generator performs a very intensive validation of the entered data, which did not allow to implement many requirements, for example, to change the class for inheritance. Therefore, the extension contains a full-fledged generator, and not just a template.

The gii generator consists of the following parts (all are inside the templates / crud directory):


The Generator.php and form.php files contain mostly cosmetic edits relative to the original ones from the CRUD generator: file names, validation, text descriptions and hints, etc.

Template files are responsible for the generated view and the widget code itself. First of all, the templates / crud / default / controller.php file is important, which is responsible for generating the widget class itself, which corresponds to the controller class from the original generator.

The widget should have the same actions (actions) as the CRUD controller, but they are generated a little differently. The examples below show the result of the generation with comments:


Finally, the presentation files contain the following basic edits:


Widget base class


When the generator finishes its work, it will create a widget class in the application namespace. The inheritance chain looks like this: widgets generated for an application are inherited from the base extension widget, the class \ ianikanov \ wce \ Widget , which, in turn, is inherited from the base widget Yii, the class \ yii \ base \ Widget .

The base class of the extension widget solves the following tasks:

  1. Defines two main fields: $ action and $ params, through which control is transferred to the widget from the calling view;
  2. Defines a number of standard parameters that can be overridden in the generated class, such as the path to the widget view files, the name and path to the facade controller (described below), and error messages;
  3. Defines standard parameters when rendering views: render and renderFile;
  4. Provides an event infrastructure similar to that of a controller so that standard filters such as AccessControl and VerbFilter work ;
  5. Defines a run method that collects all of this together.

Built-in facade controller

There are no problems with the display of data - the widgets are designed for this. But for editing, whatever one may say, you need a controller. Generate a unique controller for each widget - all its essence is lost. Using standard CRUD is not always relevant, and I don’t want to depend on the additional launch of gii. Therefore, a variant with a universal, integrated facade controller was used.

This controller is registered in the application map via the configuration file and contains only one method - actionIndex, which performs the following actions:

  1. Accepts a request from the client;
  2. Pass control to the appropriate widget class;
  3. Handles business errors resulting from the widget;
  4. Redirects back to the main application.

It is perhaps more important to indicate what this controller does NOT do:

  1. It does not check access levels - this logic belongs to specific widgets;
  2. It does not perform any input manipulations — the parameters are passed to the widget as is;
  3. It does not make any manipulations with the output, except for checking for a predetermined success code.

This approach allows us to preserve the versatility of the facade, leaving the implementation of business requirements, including security requirements, applied application code.

Fast start

The business challenge is clear, ready to start? Using an extension consists of four steps:

  1. Installation;
  2. Configuration;
  3. Generation;
  4. Application.

Installing the extension is done using composer:

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

Next, you need to make a few changes to the application configuration file.

First, add an indication to the gii generator:

 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 ], ], ], ]; } 

Second, add the built-in controller-facade to the map:

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

This completes the installation and configuration.

To generate a widget you need:

  1. Open gii;
  2. Select "CRUD Controller Widget";
  3. Fill in the form fields;
  4. View and generate code.

Further, to use the widget, it must be called by specifying action and params - in much the same way as the controller is called.

Widget view list of models:

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

Widget view one model:

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

Model creation widget (button + form wrapped in Modal):

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

Model change widget (button + form wrapped in Modal):

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

Model delete widget (button):

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

The code of the widget and all views belongs to the application and can be easily changed - everything is exactly the same as when the controller was generated.

About support and development


A few words about how the extension will be maintained and developed. I have a main job and a few pet-projects. So, this extension is a side project from my side projects, so I will develop improvements to it only for the needs of my projects.

In the best traditions of open source, the code is available on the githaba , and I will support it in terms of fixing bugs, and I will also try to do timely reviews if anyone wants to send a pull request, so if you are interested, join.

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


All Articles