CRUD widget generator for Yii

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

    From the point of view of data modeling, both of them are “nested” entities that do not have independent significance in isolation from the parent object.

    In Yii ( php framework ) there is Gii - a built-in code generator that allows you to create basic CRUD interfaces using a data model with a few clicks of the mouse, which significantly accelerate development, but are applicable only to independent entities, like the article or machine in the examples above.

    It would be great to be able to generate something similar for “nested” data objects, right? Now - you can, welcome to kat for details.

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

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

    • Business Case: Posting by Topic
      • The list of topics on the main
      • List of related posts
    • Under the hood: a gii generator based on CRUD
      • Gii Generator Template
      • Widget base class
      • Integrated facade controller
    • Fast start
      • About support and development

    Business Case: Posting by Topic

    Perhaps comments on a habr and a bad example since often are more useful than the article itself, but, in any case, when developing an application, there are often situations when a certain object of the data model is of little interest to the user as an independent entity.

    Consider a simplified business task: to create a website for publishing messages grouped by various topics.

    The site should 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 table form;
    3. Topic page - information about the topic and a list of posts published in it.

    Pretty standard, right?

    Let's look at the data model:

    Also no surprises. Two classes of models will contain our business logic:

    • Topic class - data on the topic, validation, a list of posts in it, as well as a separate method that returns a list of topics filtered by the criteria for the widget on the main page.
    • The Post class is only data and validation.

    The application will be served by two controllers:

    • SiteController - standard pages (about us, contacts, etc.), authorization (not required by the terms of reference, but we know something) and index - the main page. Because we expect many different widgets in the future, it makes sense to leave the main page in this controller, and not transfer it to a model specific to one.
    • TopicController is a standard set of actions: list, create, edit, view and delete topics.

    Potentially, you can also generate a PostController - for administration purposes and / or copy-paste pieces of code into custom widgets, but 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 to make 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 by topic?

    If you can solve them using an automatic generator - this will be a solid achievement.

    The list of topics on the main

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

    For display, there are several implementation options.

    The first, dirty and fast, is to do everything directly in the view file ( views / site / index.php ):

    1. Create ActiveDataProvider ;
    2. Fill it with data from the Topic model ;
    3. Display using a 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 , calling its render from the main file. This will give a little more manageability and extensibility, but it still looks pretty dirty.

    Most of us are likely to 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 they encapsulate the creation of ActiveDataProviderby model and display in a separate presentation. All that remains is to call this widget from the main page. This solution is the most correct from the point of view of class decomposition, manageability and extensibility of the code.

    But does it feel like the code of this widget will very much repeat the code of TopicController in terms of handling actionIndex () ? And it’s so annoying to write this code manually.

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

        'params' => [
            'query' => app\models\Topic::findBySomeSpecificCriteria()
    ]) ?>

    List of related posts

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

    By analogy with the filtered list of topics, we have almost 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 a standard ListView / GridView widget , specifying the required fields manually.

    The second is to isolate this code into a separate presentation file: views / topic / _posts-list-widget.php , just so as not to be an eyesore - reusing it somewhere will still fail.

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

    Or generate the code automatically and call the finished widget:

        'params' => [
            'query' => $model->getPosts(),
    ]) ?>

    Under the hood: a gii generator based on CRUD

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

    A couple of links to the documentation before starting - 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 composer and gets into the vendor folder of your project.

    The extension consists of three parts:

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

    Gii Generator Template

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

    Initially, it was assumed that to implement the extension 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 conducts a very intensive validation of the input data, which did not allow to implement many requirements, for example, 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 default directory is a template where all the magic happens: each file in this directory will correspond to one generated file in your project;
    • File form.php - as you might guess from the name, this is a form for entering generation parameters (class names, etc.);
    • File Generator.php - a generation orchestra that receives data from the form, validates it, and then sequentially calls the template files to create the result.

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

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

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

    • actionIndex - instead of unconditional output of all models, the method accepts the $ query parameter;

      public function actionIndex($query)
          $dataProvider = new ActiveDataProvider([
              'query' => $query,
          return $this->render('index', [
              'dataProvider' => $dataProvider,
    • actionCreate and actionUpdate - in case of success, instead of a redirect, they simply return the success code, further processing is provided by the built-in facade controller;

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

    • actionDelete - supports the GET method for displaying the delete widget (by default - one button) and POST for performing the action; if successful, it also does not perform a redirect, but returns a code.

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

    Finally, view files contain the following basic edits:

    • All headers translated to h2 instead of h1;
    • Removed the code responsible for displaying the title of the page and for breadcrumbs - the widget should not affect these things;
    • Creating and editing models is done using a modal window (built-in Modal widget);
    • Added delete widget template - with one big red button.

    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 the application are inherited from the base extension widget, class \ ianikanov \ wce \ Widget , which, in turn, is inherited from the base Yii widget, 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 (about it below) and error messages;
    3. Defines standard parameters when rendering views: render and renderFile;
    4. Provides an event infrastructure similar to the controller infrastructure so that standard filters such as AccessControl and VerbFilter work ;
    5. Defines a run method that collects all this together.

    Integrated facade controller

    There are no problems with displaying these data - widgets are for this purpose intended. But for editing, anyway, you need a controller. Generate a unique controller for each widget - its whole essence is lost. Using a standard CRUD is not always relevant, and I do not want to depend on the additional launch of gii. Therefore, the option was used with a universal, integrated controller-facade.

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

    1. Accepts a request from a client;
    2. Transfers control to the corresponding widget class;
    3. Handles business errors as a result of the widget;
    4. Redirects back to the main application.

    Perhaps it’s more important to indicate what this controller does NOT:

    1. It does not check access levels - this logic belongs to specific widgets;
    2. It does not perform any input manipulation - the parameters are passed to the widget as it is;
    3. It does not manipulate the output except to check for a predefined success code.

    This approach allows you to maintain the versatility of the facade, leaving the implementation of business requirements, including security requirements, the application application code.

    Fast start

    The business challenge is clear, ready to start? Using the extension has 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 several changes to the application configuration file.

    First, add a reference to the gii generator:

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

    Secondly, add the integrated facade controller to the map:

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

    This completes the installation and configuration.

    To generate a widget:

    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 - almost the same as the controller is called.

    Widget for viewing the list of models:

        'params' => [
            'query' => $otherModel->getPosts(),
    ]) ?>

    Widget for viewing one model:

     'view', 'params' => ['id' => $post_id]]) ?>

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

     'create']) ?>

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

     'update', 'params'=>['id' => $post_id]]) ?>

    Model removal widget (button):

     '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 generating the controller.

    About support and development

    A few words about how the expansion will be supported and developed. I have the main work and some of my “side” projects (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 github , 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 who are interested, join in.

    Also popular now: