* @since 2.0 */ class Generator extends Model { /** * @var string */ public $id; /** * @return string name of the code generator */ public function getName() { return 'unknown'; } public function getDescription() { return ''; } public function getUrl() { return Yii::$app->controller->createUrl('default/view', array('id' => $this->id)); } public function renderForm() { return ''; } public function renderFileList() { return ''; } 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. */ public function prepare() { } /** * Declares the model validation rules. * Child classes must override this method in the following format: *
	 * return array_merge(parent::rules(), array(
	 *     ...rules for the child class...
	 * ));
	 * 
* @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: *
	 * return array_merge(parent::attributeLabels(), array(
	 *     ...labels for the child class attributes...
	 * ));
	 * 
* @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 .= "generating {$file->relativePath}
{$file->error}
\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, "_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'; } /** * 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.'); } } }