自动化代码生成


Starting from version 1.1.2, Yii is equipped with a Web-based code generation tool called Gii. It supercedes the previous yiic shell generation tool which runs on command line. In this section, we will describe how to use Gii and how to extend Gii to increase our development productivity.

Using Gii

Gii is implemented in terms of a module and must be used within an existing Yii application. To use Gii, we first modify the application configuration as follows:

[php]
return array(
	......
	'modules'=>array(
		'gii'=>array(
			'class'=>'system.gii.GiiModule',
			'password'=>'pick up a password here',
			// 'ipFilters'=>array(...a list of IPs...),
			// 'newFileMode'=>0666,
			// 'newDirMode'=>0777,
		),
	),
);

In the above, we declare a module named gii whose class is [GiiModule]. We also specify a password for the module which we will be prompted for when accessing Gii.

By default for security reasons, Gii is configured to be accessible only on localhost. If we want to make it accessible on other trustable computers, we can configure the [GiiModule::ipFilters] property as shown in the above code.

Because Gii may generate and save new code files in the existing application, we need to make sure that the Web server process has the proper permission to do so. The above [GiiModule::newFileMode] and [GiiModule::newDirMode] properties control how the new files and directories should be generated.

Note: Gii is mainly provided as a development tool. Therefore, it should only be installed on a development machine. Because it can generate new PHP script files in the application, we should pay sufficient attention to its security measures (e.g. password, IP filters).

We can now access Gii via the URL http://hostname/path/to/index.php?r=gii. Here we assume http://hostname/path/to/index.php is the URL for accessing the existing Yii application.

If the existing Yii application uses path-format URLs (see URL management), we can access Gii via the URL http://hostname/path/to/index.php/gii. We may need to add the following URL rules to the front of the existing URL rules:

[php]
'components'=>array(
	......
	'urlManager'=>array(
		'urlFormat'=>'path',
		'rules'=>array(
			'gii'=>'gii',
			'gii/<controller:\w+>'=>'gii/<controller>',
			'gii/<controller:\w+>/<action:\w+>'=>'gii/<controller>/<action>',
			...existing rules...
		),
	),
)

Gii comes with a few default code generators. Each code generator is responsible for generating a specific type of code. For example, the controller generator generates a controller class together with a few action view scripts; the model generator generates an ActiveRecord class for the specified database table.

The basic workflow of using a generator is as follows:

  1. Enter the generator page;
  2. Fill in the fields that specify the code generation parameters. For example, to use the module generator to create a new module, you need to specify the module ID;
  3. Press the Preview button to preview the code to be generated. You will see a table showing a list of code files to be generated. You can click on any of them to preview the code;
  4. Press the Generate button to generate the code files;
  5. Review the code generation log.

Note: when generating models make sure to review and correct rules method since database structure often doesn't reflect data validation requirements.

Extending Gii

While the default code generators coming with Gii can generate very powerful code, we often want to customize them or create new ones to fit for our taste and needs. For example, we may want the generated code to be in our own favorite coding styles, or we may want to make the code to support multiple languages. All these can be done easily with Gii.

Gii can be extended in two ways: customizing the code templates of the existing code generators, and writing new code generators.

Structure of a Code Generator

A code generator is stored under a directory whose name is treated as the generator name. The directory usually consists of the following content:

model/                       the model generator root folder   ModelCode.php             the code model used to generate code   ModelGenerator.php        the code generation controller
   views/                    containing view scripts for the generator
      index.php              the default view script
   templates/                containing code template sets
      default/               the 'default' code template set
         model.php           the code template for generating model class code

Generator Search Path

Gii looks for available generators in a set of directories specified by the [GiiModule::generatorPaths] property. When customization is needed, we can configure this property in the application configuration as follows,

[php]
return array(
	'modules'=>array(
		'gii'=>array(
			'class'=>'system.gii.GiiModule',
			'generatorPaths'=>array(
				'common.gii',   // a path alias
			),
		),
	),
);

The above configuration instructs Gii to look for generators under the directory aliased as common.gii, in addition to the default locations system.gii.generators and application.gii.

It is possible to have two generators with the same name but under different search paths. In this case, the generator under the path specified earlier in [GiiModule::generatorPaths] will take precedence.

Customizing Code Templates

This is the easiest and the most common way of extending Gii. We use an example to explain how to customize code templates. Assume we want to customize the code generated by the model generator.

We first create a directory named protected/gii/model/templates/compact. Here model means that we are going to override the default model generator. And templates/compact means we will add a new code template set named compact.

We then modify our application configuration to add application.gii to [GiiModule::generatorPaths], as shown in the previous sub-section.

Now open the model code generator page. Click on the Code Template field. We should see a dropdown list which contains our newly created template directory compact. However, if we choose this template to generate the code, we will see an error. This is because we have yet to put any actual code template file in this new compact template set.

Copy the file framework/gii/generators/model/templates/default/model.php to protected/gii/model/templates/compact. If we try generating again with the compact template, we should succeed. However, the code generated is no different from the one generated by the default template set.

It is time for us to do the real customization work. Open the file protected/gii/model/templates/compact/model.php to edit it. Remember that this file will be used like a view script, which means it can contain PHP expressions and statements. Let's modify the template so that the attributeLabels() method in the generated code uses Yii::t() to translate the attribute labels:

[php]
public function attributeLabels()
{
	return array(
<?php foreach($labels as $name=>$label): ?>
			<?php echo "'$name' => Yii::t('application', '$label'),\n"; ?>
<?php endforeach; ?>
	);
}

In each code template, we can access some predefined variables, such as $labels in the above example. These variables are provided by the corresponding code generator. Different code generators may provide different set of variables in their code templates. Please read the description in the default code templates carefully.

Creating New Generators

In this sub-section, we show how to create a new generator that can generate a new widget class.

We first create a directory named protected/gii/widget. Under this directory, we will create the following files:

  • WidgetGenerator.php: contains the WidgetGenerator controller class. This is the entry point of the widget generator.
  • WidgetCode.php: contains the WidgetCode model class. This class has the main logic for code generation.
  • views/index.php: the view script showing the code generator input form.
  • templates/default/widget.php: the default code template for generating a widget class file.

Creating WidgetGenerator.php

The WidgetGenerator.php file is extremely simple. It only contains the following code:

[php]
class WidgetGenerator extends CCodeGenerator{
	public $codeModel='application.gii.widget.WidgetCode';
}

In the above code, we specify that the generator will use the model class whose path alias is application.gii.widget.WidgetCode. The WidgetGenerator class extends from [CCodeGenerator] which implements a lot of functionalities, including the controller actions needed to coordinate the code generation process.

Creating WidgetCode.php

The WidgetCode.php file contains the WidgetCode model class that has the main logic for generating a widget class based on the user input. In this example, we assume that the only input we want from the user is the widget class name. Our WidgetCode looks like the following:

[php]
class WidgetCode extends CCodeModel
{
	public $className;

	public function rules()
	{
		return array_merge(parent::rules(), array(
			array('className', 'required'),
			array('className', 'match', 'pattern'=>'/^\w+$/'),
		));
	}

	public function attributeLabels()
	{
		return array_merge(parent::attributeLabels(), array(
			'className'=>'Widget Class Name',
		));
	}

	public function prepare()
	{
		$path=Yii::getPathOfAlias('application.components.' . $this->className) . '.php';
		$code=$this->render($this->templatepath.'/widget.php');

		$this->files[]=new CCodeFile($path, $code);
	}
}

The WidgetCode class extends from [CCodeModel]. Like a normal model class, in this class we can declare rules() and attributeLabels() to validate user inputs and provide attribute labels, respectively. Note that because the base class [CCodeModel] already defines some rules and attribute labels, we should merge them with our new rules and labels here.

The prepare() method prepares the code to be generated. Its main task is to prepare a list of [CCodeFile] objects, each of which represent a code file being generated. In our example, we only need to create one [CCodeFile] object that represents the widget class file being generated. The new widget class will be generated under the protected/components directory. We call [CCodeFile::render] method to generate the actual code. This method includes the code template as a PHP script and returns the echoed content as the generated code.

Creating views/index.php

Having the controller (WidgetGenerator) and the model (WidgetCode), it is time for us to create the view views/index.php.

[php]
<h1>Widget Generator</h1>

<?php $form=$this->beginWidget('CCodeForm', array('model'=>$model)); ?>

	<div class="row">
		<?php echo $form->labelEx($model,'className'); ?>
		<?php echo $form->textField($model,'className',array('size'=>65)); ?>
		<div class="tooltip">
			Widget class name must only contain word characters.
		</div>
		<?php echo $form->error($model,'className'); ?>
	</div>

<?php $this->endWidget(); ?>

In the above, we mainly display a form using the [CCodeForm] widget. In this form, we display the field to collect the input for the className attribute in WidgetCode.

When creating the form, we can exploit two nice features provided by the [CCodeForm] widget. One is about input tooltips. The other is about sticky inputs.

If you have tried any default code generator, you will notice that when setting focus in one input field, a nice tooltip will show up next to the field. This can easily achieved here by writing next to the input field a div whose CSS class is tooltip.

For some input fields, we may want to remember their last valid values so that the user can save the trouble of re-entering them each time they use the generator to generate code. An example is the input field collecting the controller base class name default controller generator. These sticky fields are initially displayed as highlighted static text. If we click on them, they will turn into input fields to take user inputs.

In order to declare an input field to be sticky, we need to do two things.

First, we need to declare a sticky validation rule for the corresponding model attribute. For example, the default controller generator has the following rule to declare that baseClass and actions attributes are sticky:

[php]
public function rules()
{
	return array_merge(parent::rules(), array(
		......
		array('baseClass, actions', 'sticky'),
	));
}

Second, we need to add a CSS class named sticky to the container div of the input field in the view, like the following:

[php]
<div class="row sticky">
	...input field here...
</div>

Creating templates/default/widget.php

Finally, we create the code template templates/default/widget.php. As we described earlier, this is used like a view script that can contain PHP expressions and statements. In a code template, we can always access the $this variable which refers to the code model object. In our example, $this refers to the WidgetModel object. We can thus get the user-entered widget class name via $this->className.

[php]
<?php echo '<?php'; ?>

class <?php echo $this->className; ?> extends CWidget
{
	public function run()
	{

	}
}

This concludes the creation of a new code generator. We can access this code generator immediately via the URL http://hostname/path/to/index.php?r=gii/widget.