Yii2 Gii Console部分 – 探究解析生成代码原理

Gii 是一个蛮不错的学习Module

console 生成model的解析:

  1. gii的config配置
* return [
 *     'bootstrap' => ['gii'],
 *     'modules' => [
 *         'gii' => ['class' => 'yii\gii\Module'],
 *     ],
 * ]

也就说,在初始化的时候需要执行Module的bootstrap方法:

public function bootstrap($app)
    {
        if ($app instanceof \yii\web\Application) {
            $app->getUrlManager()->addRules([
                $this->id => $this->id . '/default/index',
                $this->id . '/<id:\w+>' => $this->id . '/default/view',
                $this->id . '/<controller:[\w\-]+>/<action:[\w\-]+>' => $this->id . '/<controller>/<action>',
            ], false);
        } elseif ($app instanceof \yii\console\Application) {
            $app->controllerMap[$this->id] = [
                'class' => 'yii\gii\console\GenerateController',
                'generators' => array_merge($this->coreGenerators(), $this->generators),
                'module' => $this,
            ];
        }
    }

如果是console的话,就设置app的controllerMap。也就是console的Gii/访问路径:

yii\gii\console\GenerateController。

传递参数:generators:

protected function coreGenerators()
    {
        return [
            'model' => ['class' => 'yii\gii\generators\model\Generator'],
            'crud' => ['class' => 'yii\gii\generators\crud\Generator'],
            'controller' => ['class' => 'yii\gii\generators\controller\Generator'],
            'form' => ['class' => 'yii\gii\generators\form\Generator'],
            'module' => ['class' => 'yii\gii\generators\module\Generator'],
            'extension' => ['class' => 'yii\gii\generators\extension\Generator'],
        ];
    }

还有一部分就是config配置中对gii的 generators 参数的配置,也就是可以自定义gii了。

生成model的命令为:

./yii gii/model --tableName=city --modelClass=City

也就是找到 yii\gii\console\GenerateController。 去找actionModel()方法,但是这个方法不存在,那么,在actions中查找

public function actions()
   {
       $actions = [];
       foreach ($this->generators as $name => $generator) {
           $actions[$name] = [
               'class' => 'yii\gii\console\GenerateAction',
               'generator' => $generator,
           ];
       }
       return $actions;
   }

从上面可以看出来,就是通过GenerateAction这个class,根据穿入的generator的不同实例化不同的参数。这个generator参数就是上面的配置

 'model' => ['class' => 'yii\gii\generators\model\Generator'],

下面我们查看这个文件

yii\gii\console\GenerateAction

的run方法:

public function run()
    {
        echo "Running '{$this->generator->getName()}'...\n\n";

        if ($this->generator->validate()) {
            $this->generateCode();
        } else {
            $this->displayValidationErrors();
        }
    }

也就是代码:

$this->generateCode();
protected function generateCode()
    {
        $files = $this->generator->generate();
        $n = count($files);
        if ($n === 0) {
            echo "No code to be generated.\n";
            return;
        }
        echo "The following files will be generated:\n";
        $skipAll = $this->controller->interactive ? null : !$this->controller->overwrite;
        $answers = [];
        foreach ($files as $file) {
            $path = $file->getRelativePath();
            if (is_file($file->path)) {
                if (file_get_contents($file->path) === $file->content) {
                    echo '  ' . $this->controller->ansiFormat('[unchanged]', Console::FG_GREY);
                    echo $this->controller->ansiFormat(" $path\n", Console::FG_CYAN);
                    $answers[$file->id] = false;
                } else {
                    echo '    ' . $this->controller->ansiFormat('[changed]', Console::FG_RED);
                    echo $this->controller->ansiFormat(" $path\n", Console::FG_CYAN);
                    if ($skipAll !== null) {
                        $answers[$file->id] = !$skipAll;
                    } else {
                        $answer = $this->controller->select("Do you want to overwrite this file?", [
                            'y' => 'Overwrite this file.',
                            'n' => 'Skip this file.',
                            'ya' => 'Overwrite this and the rest of the changed files.',
                            'na' => 'Skip this and the rest of the changed files.',
                        ]);
                        $answers[$file->id] = $answer === 'y' || $answer === 'ya';
                        if ($answer === 'ya') {
                            $skipAll = false;
                        } elseif ($answer === 'na') {
                            $skipAll = true;
                        }
                    }
                }
            } else {
                echo '        ' . $this->controller->ansiFormat('[new]', Console::FG_GREEN);
                echo $this->controller->ansiFormat(" $path\n", Console::FG_CYAN);
                $answers[$file->id] = true;
            }
        }

        if (!array_sum($answers)) {
            $this->controller->stdout("\nNo files were chosen to be generated.\n", Console::FG_CYAN);
            return;
        }

        if (!$this->controller->confirm("\nReady to generate the selected files?", true)) {
            $this->controller->stdout("\nNo file was generated.\n", Console::FG_CYAN);
            return;
        }

        if ($this->generator->save($files, (array) $answers, $results)) {
            $this->controller->stdout("\nFiles were generated successfully!\n", Console::FG_GREEN);
        } else {
            $this->controller->stdout("\nSome errors occurred while generating the files.", Console::FG_RED);
        }
        echo preg_replace('%<span class="error">(.*?)</span>%', '\1', $results) . "\n";
    }

 

 $files = $this->generator->generate();

generator就是上面的:

 'model' => ['class' => 'yii\gii\generators\model\Generator'],

也就是这个文件的generate()方法返回的内容,就是文件内容:

public function generate()
    {
        $files = [];
        $relations = $this->generateRelations();
        $db = $this->getDbConnection();
        foreach ($this->getTableNames() as $tableName) {
            // model :
            $modelClassName = $this->generateClassName($tableName);
            $queryClassName = ($this->generateQuery) ? $this->generateQueryClassName($modelClassName) : false;
            $tableSchema = $db->getTableSchema($tableName);
            $params = [
                'tableName' => $tableName,
                'className' => $modelClassName,
                'queryClassName' => $queryClassName,
                'tableSchema' => $tableSchema,
                'labels' => $this->generateLabels($tableSchema),
                'rules' => $this->generateRules($tableSchema),
                'relations' => isset($relations[$tableName]) ? $relations[$tableName] : [],
            ];
            $files[] = new CodeFile(
                Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/' . $modelClassName . '.php',
                $this->render('model.php', $params)
            );

            // query :
            if ($queryClassName) {
                $params = [
                    'className' => $queryClassName,
                    'modelClassName' => $modelClassName,
                ];
                $files[] = new CodeFile(
                    Yii::getAlias('@' . str_replace('\\', '/', $this->queryNs)) . '/' . $queryClassName . '.php',
                    $this->render('query.php', $params)
                );
            }
        }

        return $files;
    }

生成文件的部分:

 $this->render('model.php', $params)

public function render($template, $params = [])
    {
        $view = new View();
        $params['generator'] = $this;

        return $view->renderFile($this->getTemplatePath() . '/' . $template, $params, $this);
    }

在这里可以看到,还是用的view的renderFile来生成的:

public function renderFile($viewFile, $params = [], $context = null)
   {
       $viewFile = Yii::getAlias($viewFile);

       if ($this->theme !== null) {
           $viewFile = $this->theme->applyTo($viewFile);
       }
       if (is_file($viewFile)) {
           $viewFile = FileHelper::localize($viewFile);
       } else {
           throw new InvalidParamException("The view file does not exist: $viewFile");
       }

       $oldContext = $this->context;
       if ($context !== null) {
           $this->context = $context;
       }
       $output = '';
       $this->_viewFiles[] = $viewFile;

       if ($this->beforeRender($viewFile, $params)) {
           Yii::trace("Rendering view file: $viewFile", __METHOD__);
           $ext = pathinfo($viewFile, PATHINFO_EXTENSION);
           if (isset($this->renderers[$ext])) {
               if (is_array($this->renderers[$ext]) || is_string($this->renderers[$ext])) {
                   $this->renderers[$ext] = Yii::createObject($this->renderers[$ext]);
               }
               /* @var $renderer ViewRenderer */
               $renderer = $this->renderers[$ext];
               $output = $renderer->render($this, $viewFile, $params);
           } else {
               $output = $this->renderPhpFile($viewFile, $params);
           }
           $this->afterRender($viewFile, $params, $output);
       }

       array_pop($this->_viewFiles);
       $this->context = $oldContext;

       return $output;
   }

最终生成内容。

下面查看对应的model.php文件:

yii2-gii/generators/model/default/model.php

<?php
/**
 * This is the template for generating the model class of a specified table.
 */

/* @var $this yii\web\View */
/* @var $generator yii\gii\generators\model\Generator */
/* @var $tableName string full table name */
/* @var $className string class name */
/* @var $queryClassName string query class name */
/* @var $tableSchema yii\db\TableSchema */
/* @var $labels string[] list of attribute labels (name => label) */
/* @var $rules string[] list of validation rules */
/* @var $relations array list of relations (name => relation declaration) */

echo "<?php\n";
?>

namespace <?= $generator->ns ?>;

use Yii;

/**
 * This is the model class for table "<?= $generator->generateTableName($tableName) ?>".
 *
<?php foreach ($tableSchema->columns as $column): ?>
 * @property <?= "{$column->phpType} \${$column->name}\n" ?>
<?php endforeach; ?>
<?php if (!empty($relations)): ?>
 *
<?php foreach ($relations as $name => $relation): ?>
 * @property <?= $relation[1] . ($relation[2] ? '[]' : '') . ' $' . lcfirst($name) . "\n" ?>
<?php endforeach; ?>
<?php endif; ?>
 */
class <?= $className ?> extends <?= '\\' . ltrim($generator->baseClass, '\\') . "\n" ?>
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return '<?= $generator->generateTableName($tableName) ?>';
    }
<?php if ($generator->db !== 'db'): ?>

    /**
     * @return \yii\db\Connection the database connection used by this AR class.
     */
    public static function getDb()
    {
        return Yii::$app->get('<?= $generator->db ?>');
    }
<?php endif; ?>

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [<?= "\n            " . implode(",\n            ", $rules) . "\n        " ?>];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
<?php foreach ($labels as $name => $label): ?>
            <?= "'$name' => " . $generator->generateString($label) . ",\n" ?>
<?php endforeach; ?>
        ];
    }
<?php foreach ($relations as $name => $relation): ?>

    /**
     * @return \yii\db\ActiveQuery
     */
    public function get<?= $name ?>()
    {
        <?= $relation[0] . "\n" ?>
    }
<?php endforeach; ?>
<?php if ($queryClassName): ?>
<?php
    $queryClassFullName = ($generator->ns === $generator->queryNs) ? $queryClassName : '\\' . $generator->queryNs . '\\' . $queryClassName;
    echo "\n";
?>
    /**
     * @inheritdoc
     * @return <?= $queryClassFullName ?> the active query used by this AR class.
     */
    public static function find()
    {
        return new <?= $queryClassFullName ?>(get_called_class());
    }
<?php endif; ?>
}

也就是一个模板文件生成而来。

OK,到这里就完成了流程的查看了。

《Yii2 Gii Console部分 – 探究解析生成代码原理》有1个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注