484 lines
13 KiB
PHP
484 lines
13 KiB
PHP
|
<?php
|
||
|
/**
|
||
|
* CCodeModel class file.
|
||
|
*
|
||
|
* @author Qiang Xue <qiang.xue@gmail.com>
|
||
|
* @link http://www.yiiframework.com/
|
||
|
* @copyright 2008-2013 Yii Software LLC
|
||
|
* @license http://www.yiiframework.com/license/
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* CCodeModel is the base class for model classes that are used to generate code.
|
||
|
*
|
||
|
* Each code generator should have at least one code model class that extends from this class.
|
||
|
* The purpose of a code model is to represent user-supplied parameters and use them to
|
||
|
* generate customized code.
|
||
|
*
|
||
|
* Derived classes should implement the {@link prepare} method whose main task is to
|
||
|
* fill up the {@link files} property based on the user parameters.
|
||
|
*
|
||
|
* The {@link files} property should be filled with a set of {@link CCodeFile} instances,
|
||
|
* each representing a single code file to be generated.
|
||
|
*
|
||
|
* CCodeModel implements the feature of "sticky attributes". A sticky attribute is an attribute
|
||
|
* that can remember its last valid value, even if the user closes his browser window
|
||
|
* and reopen it. To declare an attribute is sticky, simply list it in a validation rule with
|
||
|
* the validator name being "sticky".
|
||
|
*
|
||
|
* @property array $templates A list of available code templates (name=>directory).
|
||
|
* @property string $templatePath The directory that contains the template files.
|
||
|
* @property string $stickyFile The file path that stores the sticky attribute values.
|
||
|
*
|
||
|
* @author Qiang Xue <qiang.xue@gmail.com>
|
||
|
* @package system.gii
|
||
|
* @since 1.1.2
|
||
|
*/
|
||
|
abstract class CCodeModel extends CFormModel
|
||
|
{
|
||
|
const STATUS_NEW=1;
|
||
|
const STATUS_PREVIEW=2;
|
||
|
const STATUS_SUCCESS=3;
|
||
|
const STATUS_ERROR=4;
|
||
|
|
||
|
static $keywords=array(
|
||
|
'__class__',
|
||
|
'__dir__',
|
||
|
'__file__',
|
||
|
'__function__',
|
||
|
'__line__',
|
||
|
'__method__',
|
||
|
'__namespace__',
|
||
|
'abstract',
|
||
|
'and',
|
||
|
'array',
|
||
|
'as',
|
||
|
'break',
|
||
|
'case',
|
||
|
'catch',
|
||
|
'cfunction',
|
||
|
'class',
|
||
|
'clone',
|
||
|
'const',
|
||
|
'continue',
|
||
|
'declare',
|
||
|
'default',
|
||
|
'die',
|
||
|
'do',
|
||
|
'echo',
|
||
|
'else',
|
||
|
'elseif',
|
||
|
'empty',
|
||
|
'enddeclare',
|
||
|
'endfor',
|
||
|
'endforeach',
|
||
|
'endif',
|
||
|
'endswitch',
|
||
|
'endwhile',
|
||
|
'eval',
|
||
|
'exception',
|
||
|
'exit',
|
||
|
'extends',
|
||
|
'final',
|
||
|
'final',
|
||
|
'for',
|
||
|
'foreach',
|
||
|
'function',
|
||
|
'global',
|
||
|
'goto',
|
||
|
'if',
|
||
|
'implements',
|
||
|
'include',
|
||
|
'include_once',
|
||
|
'instanceof',
|
||
|
'interface',
|
||
|
'isset',
|
||
|
'list',
|
||
|
'namespace',
|
||
|
'new',
|
||
|
'old_function',
|
||
|
'or',
|
||
|
'parent',
|
||
|
'php_user_filter',
|
||
|
'print',
|
||
|
'private',
|
||
|
'protected',
|
||
|
'public',
|
||
|
'require',
|
||
|
'require_once',
|
||
|
'return',
|
||
|
'static',
|
||
|
'switch',
|
||
|
'this',
|
||
|
'throw',
|
||
|
'try',
|
||
|
'unset',
|
||
|
'use',
|
||
|
'var',
|
||
|
'while',
|
||
|
'xor',
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* @var array user confirmations on whether to overwrite existing code files with the newly generated ones.
|
||
|
* The value of this property is internally managed by this class and {@link CCodeGenerator}.
|
||
|
*/
|
||
|
public $answers;
|
||
|
/**
|
||
|
* @var string the name of the code template that the user has selected.
|
||
|
* The value of this property is internally managed by this class and {@link CCodeGenerator}.
|
||
|
*/
|
||
|
public $template;
|
||
|
/**
|
||
|
* @var array a list of {@link CCodeFile} objects that represent the code files to be generated.
|
||
|
* The {@link prepare()} method is responsible to populate this property.
|
||
|
*/
|
||
|
public $files=array();
|
||
|
/**
|
||
|
* @var integer the status of this model. T
|
||
|
* The value of this property is internally managed by {@link CCodeGenerator}.
|
||
|
*/
|
||
|
public $status=self::STATUS_NEW;
|
||
|
|
||
|
private $_stickyAttributes=array();
|
||
|
|
||
|
/**
|
||
|
* Prepares the code files to be generated.
|
||
|
* This is the main method that child classes should implement. It should contain the logic
|
||
|
* that populates the {@link files} property with a list of code files to be generated.
|
||
|
*/
|
||
|
abstract public function prepare();
|
||
|
|
||
|
/**
|
||
|
* Declares the model validation rules.
|
||
|
* Child classes must override this method in the following format:
|
||
|
* <pre>
|
||
|
* return array_merge(parent::rules(), array(
|
||
|
* ...rules for the child class...
|
||
|
* ));
|
||
|
* </pre>
|
||
|
* @return array validation rules
|
||
|
*/
|
||
|
public function rules()
|
||
|
{
|
||
|
return array(
|
||
|
array('template', 'required'),
|
||
|
array('template', 'validateTemplate', 'skipOnError'=>true),
|
||
|
array('template', 'sticky'),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Validates the template selection.
|
||
|
* This method validates whether the user selects an existing template
|
||
|
* and the template contains all required template files as specified in {@link requiredTemplates}.
|
||
|
* @param string $attribute the attribute to be validated
|
||
|
* @param array $params validation parameters
|
||
|
*/
|
||
|
public function validateTemplate($attribute,$params)
|
||
|
{
|
||
|
$templates=$this->templates;
|
||
|
if(!isset($templates[$this->template]))
|
||
|
$this->addError('template', 'Invalid template selection.');
|
||
|
else
|
||
|
{
|
||
|
$templatePath=$this->templatePath;
|
||
|
foreach($this->requiredTemplates() as $template)
|
||
|
{
|
||
|
if(!is_file($templatePath.'/'.$template))
|
||
|
$this->addError('template', "Unable to find the required code template file '$template'.");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks if the named class exists (in a case sensitive manner).
|
||
|
* @param string $name class name to be checked
|
||
|
* @return boolean whether the class exists
|
||
|
*/
|
||
|
public function classExists($name)
|
||
|
{
|
||
|
return class_exists($name,false) && in_array($name, get_declared_classes());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Declares the model attribute labels.
|
||
|
* Child classes must override this method in the following format:
|
||
|
* <pre>
|
||
|
* return array_merge(parent::attributeLabels(), array(
|
||
|
* ...labels for the child class attributes...
|
||
|
* ));
|
||
|
* </pre>
|
||
|
* @return array the attribute labels
|
||
|
*/
|
||
|
public function attributeLabels()
|
||
|
{
|
||
|
return array(
|
||
|
'template'=>'Code Template',
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a list of code templates that are required.
|
||
|
* Derived classes usually should override this method.
|
||
|
* @return array list of code templates that are required. They should be file paths
|
||
|
* relative to {@link templatePath}.
|
||
|
*/
|
||
|
public function requiredTemplates()
|
||
|
{
|
||
|
return array();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Saves the generated code into files.
|
||
|
*/
|
||
|
public function save()
|
||
|
{
|
||
|
$result=true;
|
||
|
foreach($this->files as $file)
|
||
|
{
|
||
|
if($this->confirmed($file))
|
||
|
$result=$file->save() && $result;
|
||
|
}
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the message to be displayed when the newly generated code is saved successfully.
|
||
|
* Child classes should override this method if the message needs to be customized.
|
||
|
* @return string the message to be displayed when the newly generated code is saved successfully.
|
||
|
*/
|
||
|
public function successMessage()
|
||
|
{
|
||
|
return 'The code has been generated successfully.';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the message to be displayed when some error occurred during code file saving.
|
||
|
* Child classes should override this method if the message needs to be customized.
|
||
|
* @return string the message to be displayed when some error occurred during code file saving.
|
||
|
*/
|
||
|
public function errorMessage()
|
||
|
{
|
||
|
return 'There was some error when generating the code. Please check the following messages.';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a list of available code templates (name=>directory).
|
||
|
* This method simply returns the {@link CCodeGenerator::templates} property value.
|
||
|
* @return array a list of available code templates (name=>directory).
|
||
|
*/
|
||
|
public function getTemplates()
|
||
|
{
|
||
|
return Yii::app()->controller->templates;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string the directory that contains the template files.
|
||
|
* @throws CHttpException if {@link templates} is empty or template selection is invalid
|
||
|
*/
|
||
|
public function getTemplatePath()
|
||
|
{
|
||
|
$templates=$this->getTemplates();
|
||
|
if(isset($templates[$this->template]))
|
||
|
return $templates[$this->template];
|
||
|
elseif(empty($templates))
|
||
|
throw new CHttpException(500,'No templates are available.');
|
||
|
else
|
||
|
throw new CHttpException(500,'Invalid template selection.');
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param CCodeFile $file whether the code file should be saved
|
||
|
* @return bool whether the confirmation is found in {@link answers} with appropriate {@link operation}
|
||
|
*/
|
||
|
public function confirmed($file)
|
||
|
{
|
||
|
return $this->answers===null && $file->operation===CCodeFile::OP_NEW
|
||
|
|| is_array($this->answers) && isset($this->answers[md5($file->path)]);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generates the code using the specified code template file.
|
||
|
* This method is manly used in {@link generate} to generate code.
|
||
|
* @param string $templateFile the code template file path
|
||
|
* @param array $_params_ a set of parameters to be extracted and made available in the code template
|
||
|
* @throws CException is template file does not exist
|
||
|
* @return string the generated code
|
||
|
*/
|
||
|
public function render($templateFile,$_params_=null)
|
||
|
{
|
||
|
if(!is_file($templateFile))
|
||
|
throw new CException("The template file '$templateFile' does not exist.");
|
||
|
|
||
|
if(is_array($_params_))
|
||
|
extract($_params_,EXTR_PREFIX_SAME,'params');
|
||
|
else
|
||
|
$params=$_params_;
|
||
|
ob_start();
|
||
|
ob_implicit_flush(false);
|
||
|
require($templateFile);
|
||
|
return ob_get_clean();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string the code generation result log.
|
||
|
*/
|
||
|
public function renderResults()
|
||
|
{
|
||
|
$output='Generating code using template "'.$this->templatePath."\"...\n";
|
||
|
foreach($this->files as $file)
|
||
|
{
|
||
|
if($file->error!==null)
|
||
|
$output.="<span class=\"error\">generating {$file->relativePath}<br/> {$file->error}</span>\n";
|
||
|
elseif($file->operation===CCodeFile::OP_NEW && $this->confirmed($file))
|
||
|
$output.=' generated '.$file->relativePath."\n";
|
||
|
elseif($file->operation===CCodeFile::OP_OVERWRITE && $this->confirmed($file))
|
||
|
$output.=' overwrote '.$file->relativePath."\n";
|
||
|
else
|
||
|
$output.=' skipped '.$file->relativePath."\n";
|
||
|
}
|
||
|
$output.="done!\n";
|
||
|
return $output;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The "sticky" validator.
|
||
|
* This validator does not really validate the attributes.
|
||
|
* It actually saves the attribute value in a file to make it sticky.
|
||
|
* @param string $attribute the attribute to be validated
|
||
|
* @param array $params the validation parameters
|
||
|
*/
|
||
|
public function sticky($attribute,$params)
|
||
|
{
|
||
|
if(!$this->hasErrors())
|
||
|
$this->_stickyAttributes[$attribute]=$this->$attribute;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Loads sticky attributes from a file and populates them into the model.
|
||
|
*/
|
||
|
public function loadStickyAttributes()
|
||
|
{
|
||
|
$this->_stickyAttributes=array();
|
||
|
$path=$this->getStickyFile();
|
||
|
if(is_file($path))
|
||
|
{
|
||
|
$result=@include($path);
|
||
|
if(is_array($result))
|
||
|
{
|
||
|
$this->_stickyAttributes=$result;
|
||
|
foreach($this->_stickyAttributes as $name=>$value)
|
||
|
{
|
||
|
if(property_exists($this,$name) || $this->canSetProperty($name))
|
||
|
$this->$name=$value;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Saves sticky attributes into a file.
|
||
|
*/
|
||
|
public function saveStickyAttributes()
|
||
|
{
|
||
|
$path=$this->getStickyFile();
|
||
|
@mkdir(dirname($path),0755,true);
|
||
|
file_put_contents($path,"<?php\nreturn ".var_export($this->_stickyAttributes,true).";\n");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string the file path that stores the sticky attribute values.
|
||
|
*/
|
||
|
public function getStickyFile()
|
||
|
{
|
||
|
return Yii::app()->runtimePath.'/gii-'.Yii::getVersion().'/'.get_class($this).'.php';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts a word to its plural form.
|
||
|
* Note that this is for English only!
|
||
|
* For example, 'apple' will become 'apples', and 'child' will become 'children'.
|
||
|
* @param string $name the word to be pluralized
|
||
|
* @return string the pluralized word
|
||
|
*/
|
||
|
public function pluralize($name)
|
||
|
{
|
||
|
$rules=array(
|
||
|
'/(m)ove$/i' => '\1oves',
|
||
|
'/(f)oot$/i' => '\1eet',
|
||
|
'/(c)hild$/i' => '\1hildren',
|
||
|
'/(h)uman$/i' => '\1umans',
|
||
|
'/(m)an$/i' => '\1en',
|
||
|
'/(s)taff$/i' => '\1taff',
|
||
|
'/(t)ooth$/i' => '\1eeth',
|
||
|
'/(p)erson$/i' => '\1eople',
|
||
|
'/([m|l])ouse$/i' => '\1ice',
|
||
|
'/(x|ch|ss|sh|us|as|is|os)$/i' => '\1es',
|
||
|
'/([^aeiouy]|qu)y$/i' => '\1ies',
|
||
|
'/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
|
||
|
'/(shea|lea|loa|thie)f$/i' => '\1ves',
|
||
|
'/([ti])um$/i' => '\1a',
|
||
|
'/(tomat|potat|ech|her|vet)o$/i' => '\1oes',
|
||
|
'/(bu)s$/i' => '\1ses',
|
||
|
'/(ax|test)is$/i' => '\1es',
|
||
|
'/s$/' => 's',
|
||
|
);
|
||
|
foreach($rules as $rule=>$replacement)
|
||
|
{
|
||
|
if(preg_match($rule,$name))
|
||
|
return preg_replace($rule,$replacement,$name);
|
||
|
}
|
||
|
return $name.'s';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts a class name into a HTML ID.
|
||
|
* For example, 'PostTag' will be converted as 'post-tag'.
|
||
|
* @param string $name the string to be converted
|
||
|
* @return string the resulting ID
|
||
|
*/
|
||
|
public function class2id($name)
|
||
|
{
|
||
|
return trim(strtolower(str_replace('_','-',preg_replace('/(?<![A-Z])[A-Z]/', '-\0', $name))),'-');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts a class name into space-separated words.
|
||
|
* For example, 'PostTag' will be converted as 'Post Tag'.
|
||
|
* @param string $name the string to be converted
|
||
|
* @param boolean $ucwords whether to capitalize the first letter in each word
|
||
|
* @return string the resulting words
|
||
|
*/
|
||
|
public function class2name($name,$ucwords=true)
|
||
|
{
|
||
|
$result=trim(strtolower(str_replace('_',' ',preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $name))));
|
||
|
return $ucwords ? ucwords($result) : $result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts a class name into a variable name with the first letter in lower case.
|
||
|
* This method is provided because lcfirst() PHP function is only available for PHP 5.3+.
|
||
|
* @param string $name the class name
|
||
|
* @return string the variable name converted from the class name
|
||
|
* @since 1.1.4
|
||
|
*/
|
||
|
public function class2var($name)
|
||
|
{
|
||
|
$name[0]=strtolower($name[0]);
|
||
|
return $name;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Validates an attribute to make sure it is not taking a PHP reserved keyword.
|
||
|
* @param string $attribute the attribute to be validated
|
||
|
* @param array $params validation parameters
|
||
|
*/
|
||
|
public function validateReservedWord($attribute,$params)
|
||
|
{
|
||
|
$value=$this->$attribute;
|
||
|
if(in_array(strtolower($value),self::$keywords))
|
||
|
$this->addError($attribute, $this->getAttributeLabel($attribute).' cannot take a reserved PHP keyword.');
|
||
|
}
|
||
|
}
|