You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
449 lines
12 KiB
449 lines
12 KiB
<?php |
|
/** |
|
* @link http://www.yiiframework.com/ |
|
* @copyright Copyright (c) 2008 Yii Software LLC |
|
* @license http://www.yiiframework.com/license/ |
|
*/ |
|
|
|
namespace yii\gii; |
|
|
|
use Yii; |
|
use ReflectionClass; |
|
use yii\base\InvalidConfigException; |
|
use yii\base\Model; |
|
use yii\web\View; |
|
|
|
|
|
/** |
|
* This is the base class for all generator classes. |
|
* |
|
* A generator instance is responsible for taking user inputs, validating them, |
|
* and using them to generate the corresponding code based on a set of code template files. |
|
* |
|
* A generator class typically needs to implement the following methods: |
|
* |
|
* - [[getName()]]: returns the name of the generator |
|
* - [[getDescription()]]: returns the detailed description of the generator |
|
* - [[generate()]]: generates the code based on the current user input and the specified code template files. |
|
* This is the place where main code generation code resides. |
|
* |
|
* @property string $description The detailed description of the generator. This property is read-only. |
|
* @property string $stickyDataFile The file path that stores the sticky attribute values. This property is |
|
* read-only. |
|
* @property string $templatePath The root path of the template files that are currently being used. This |
|
* property is read-only. |
|
* |
|
* @author Qiang Xue <qiang.xue@gmail.com> |
|
* @since 2.0 |
|
*/ |
|
abstract class Generator extends Model |
|
{ |
|
/** |
|
* @var array a list of available code templates. The array keys are the template names, |
|
* and the array values are the corresponding template paths or path aliases. |
|
*/ |
|
public $templates = []; |
|
/** |
|
* @var string the name of the code template that the user has selected. |
|
* The value of this property is internally managed by this class. |
|
*/ |
|
public $template; |
|
|
|
/** |
|
* @return string name of the code generator |
|
*/ |
|
abstract public function getName(); |
|
/** |
|
* Generates the code based on the current user input and the specified code template files. |
|
* This is the main method that child classes should implement. |
|
* Please refer to [[\yii\gii\generators\controller\Generator::generate()]] as an example |
|
* on how to implement this method. |
|
* @return CodeFile[] a list of code files to be created. |
|
*/ |
|
abstract public function generate(); |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function init() |
|
{ |
|
parent::init(); |
|
if (!isset($this->templates['default'])) { |
|
$this->templates['default'] = $this->defaultTemplate(); |
|
} |
|
foreach ($this->templates as $i => $template) { |
|
$this->templates[$i] = Yii::getAlias($template); |
|
} |
|
} |
|
|
|
/** |
|
* Returns a list of code template files that are required. |
|
* Derived classes usually should override this method if they require the existence of |
|
* certain template files. |
|
* @return array list of code template files that are required. They should be file paths |
|
* relative to [[templatePath]]. |
|
*/ |
|
public function requiredTemplates() |
|
{ |
|
return []; |
|
} |
|
|
|
/** |
|
* Returns the list of sticky attributes. |
|
* A sticky attribute will remember its value and will initialize the attribute with this value |
|
* when the generator is restarted. |
|
* @return array list of sticky attributes |
|
*/ |
|
public function stickyAttributes() |
|
{ |
|
return ['template']; |
|
} |
|
|
|
/** |
|
* Returns the list of hint messages. |
|
* The array keys are the attribute names, and the array values are the corresponding hint messages. |
|
* Hint messages will be displayed to end users when they are filling the form for the generator. |
|
* @return array the list of hint messages |
|
*/ |
|
public function hints() |
|
{ |
|
return []; |
|
} |
|
|
|
/** |
|
* Returns the list of auto complete values. |
|
* The array keys are the attribute names, and the array values are the corresponding auto complete values. |
|
* Auto complete values can also be callable typed in order one want to make postponed data generation. |
|
* @return array the list of auto complete values |
|
*/ |
|
public function autoCompleteData() |
|
{ |
|
return []; |
|
} |
|
|
|
/** |
|
* Returns the message to be displayed when the newly generated code is saved successfully. |
|
* Child classes may override this method to customize the message. |
|
* @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 view file for the input form of the generator. |
|
* The default implementation will return the "form.php" file under the directory |
|
* that contains the generator class file. |
|
* @return string the view file for the input form of the generator. |
|
*/ |
|
public function formView() |
|
{ |
|
$class = new ReflectionClass($this); |
|
return dirname($class->getFileName()) . '/form.php'; |
|
} |
|
|
|
/** |
|
* Returns the root path to the default code template files. |
|
* The default implementation will return the "templates" subdirectory of the |
|
* directory containing the generator class file. |
|
* @return string the root path to the default code template files. |
|
*/ |
|
public function defaultTemplate() |
|
{ |
|
$class = new ReflectionClass($this); |
|
return dirname($class->getFileName()) . '/templates'; |
|
} |
|
|
|
/** |
|
* @return string the detailed description of the generator. |
|
*/ |
|
public function getDescription() |
|
{ |
|
return ''; |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
* |
|
* Child classes should override this method like the following so that the parent |
|
* rules are included: |
|
* |
|
* ~~~ |
|
* return array_merge(parent::rules(), [ |
|
* ...rules for the child class... |
|
* ]); |
|
* ~~~ |
|
*/ |
|
public function rules() |
|
{ |
|
return [ |
|
[['template'], 'required', 'message' => 'A code template must be selected.'], |
|
[['template'], 'validateTemplate'], |
|
]; |
|
} |
|
|
|
/** |
|
* Loads sticky attributes from an internal file and populates them into the generator. |
|
* @internal |
|
*/ |
|
public function loadStickyAttributes() |
|
{ |
|
$stickyAttributes = $this->stickyAttributes(); |
|
$attributes[] = 'template'; |
|
$path = $this->getStickyDataFile(); |
|
if (is_file($path)) { |
|
$result = json_decode(file_get_contents($path), true); |
|
if (is_array($result)) { |
|
foreach ($stickyAttributes as $name) { |
|
if (isset($result[$name])) { |
|
$this->$name = $result[$name]; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Saves sticky attributes into an internal file. |
|
* @internal |
|
*/ |
|
public function saveStickyAttributes() |
|
{ |
|
$stickyAttributes = $this->stickyAttributes(); |
|
$stickyAttributes[] = 'template'; |
|
$values = []; |
|
foreach ($stickyAttributes as $name) { |
|
$values[$name] = $this->$name; |
|
} |
|
$path = $this->getStickyDataFile(); |
|
@mkdir(dirname($path), 0755, true); |
|
file_put_contents($path, json_encode($values)); |
|
} |
|
|
|
/** |
|
* @return string the file path that stores the sticky attribute values. |
|
* @internal |
|
*/ |
|
public function getStickyDataFile() |
|
{ |
|
return Yii::$app->getRuntimePath() . '/gii-' . Yii::getVersion() . '/' . str_replace('\\', '-', get_class($this)) . '.json'; |
|
} |
|
|
|
/** |
|
* Saves the generated code into files. |
|
* @param CodeFile[] $files the code files to be saved |
|
* @param array $answers |
|
* @param string $results this parameter receives a value from this method indicating the log messages |
|
* generated while saving the code files. |
|
* @return boolean whether there is any error while saving the code files. |
|
*/ |
|
public function save($files, $answers, &$results) |
|
{ |
|
$lines = ['Generating code using template "' . $this->getTemplatePath() . '"...']; |
|
$hasError = false; |
|
foreach ($files as $file) { |
|
$relativePath = $file->getRelativePath(); |
|
if (isset($answers[$file->id]) && $file->operation !== CodeFile::OP_SKIP) { |
|
$error = $file->save(); |
|
if (is_string($error)) { |
|
$hasError = true; |
|
$lines[] = "generating $relativePath\n<span class=\"error\">$error</span>"; |
|
} else { |
|
$lines[] = $file->operation === CodeFile::OP_CREATE ? " generated $relativePath" : " overwrote $relativePath"; |
|
} |
|
} else { |
|
$lines[] = " skipped $relativePath"; |
|
} |
|
} |
|
$lines[] = "done!\n"; |
|
$results = implode("\n", $lines); |
|
|
|
return $hasError; |
|
} |
|
|
|
/** |
|
* @return string the root path of the template files that are currently being used. |
|
* @throws InvalidConfigException if [[template]] is invalid |
|
*/ |
|
public function getTemplatePath() |
|
{ |
|
if (isset($this->templates[$this->template])) { |
|
return $this->templates[$this->template]; |
|
} else { |
|
throw new InvalidConfigException("Unknown template: {$this->template}"); |
|
} |
|
} |
|
|
|
/** |
|
* Generates code using the specified code template and parameters. |
|
* Note that the code template will be used as a PHP file. |
|
* @param string $template the code template file. This must be specified as a file path |
|
* relative to [[templatePath]]. |
|
* @param array $params list of parameters to be passed to the template file. |
|
* @return string the generated code |
|
*/ |
|
public function render($template, $params = []) |
|
{ |
|
$view = new View; |
|
$params['generator'] = $this; |
|
return $view->renderFile($this->getTemplatePath() . '/' . $template, $params, $this); |
|
} |
|
|
|
/** |
|
* 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 [[requiredTemplates()]]. |
|
*/ |
|
public function validateTemplate() |
|
{ |
|
$templates = $this->templates; |
|
if (!isset($templates[$this->template])) { |
|
$this->addError('template', 'Invalid template selection.'); |
|
} else { |
|
$templatePath = $this->templates[$this->template]; |
|
foreach ($this->requiredTemplates() as $template) { |
|
if (!is_file($templatePath . '/' . $template)) { |
|
$this->addError('template', "Unable to find the required code template file '$template'."); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* An inline validator that checks if the attribute value refers to an existing class name. |
|
* If the `extends` option is specified, it will also check if the class is a child class |
|
* of the class represented by the `extends` option. |
|
* @param string $attribute the attribute being validated |
|
* @param array $params the validation options |
|
*/ |
|
public function validateClass($attribute, $params) |
|
{ |
|
$class = $this->$attribute; |
|
try { |
|
if (class_exists($class)) { |
|
if (isset($params['extends'])) { |
|
if (ltrim($class, '\\') !== ltrim($params['extends'], '\\') && !is_subclass_of($class, $params['extends'])) { |
|
$this->addError($attribute, "'$class' must extend from {$params['extends']} or its child class."); |
|
} |
|
} |
|
} else { |
|
$this->addError($attribute, "Class '$class' does not exist or has syntax error."); |
|
} |
|
} catch (\Exception $e) { |
|
$this->addError($attribute, "Class '$class' does not exist or has syntax error."); |
|
} |
|
} |
|
|
|
/** |
|
* An inline validator that checks if the attribute value refers to a valid namespaced class name. |
|
* The validator will check if the directory containing the new class file exist or not. |
|
* @param string $attribute the attribute being validated |
|
* @param array $params the validation options |
|
*/ |
|
public function validateNewClass($attribute, $params) |
|
{ |
|
$class = ltrim($this->$attribute, '\\'); |
|
if (($pos = strrpos($class, '\\')) === false) { |
|
$this->addError($attribute, "The class name must contain fully qualified namespace name."); |
|
} else { |
|
$ns = substr($class, 0, $pos); |
|
$path = Yii::getAlias('@' . str_replace('\\', '/', $ns), false); |
|
if ($path === false) { |
|
$this->addError($attribute, "The class namespace is invalid: $ns"); |
|
} elseif (!is_dir($path)) { |
|
$this->addError($attribute, "Please make sure the directory containing this class exists: $path"); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* @param string $value the attribute to be validated |
|
* @return boolean whether the value is a reserved PHP keyword. |
|
*/ |
|
public function isReservedKeyword($value) |
|
{ |
|
static $keywords = [ |
|
'__class__', |
|
'__dir__', |
|
'__file__', |
|
'__function__', |
|
'__line__', |
|
'__method__', |
|
'__namespace__', |
|
'__trait__', |
|
'abstract', |
|
'and', |
|
'array', |
|
'as', |
|
'break', |
|
'case', |
|
'catch', |
|
'callable', |
|
'cfunction', |
|
'class', |
|
'clone', |
|
'const', |
|
'continue', |
|
'declare', |
|
'default', |
|
'die', |
|
'do', |
|
'echo', |
|
'else', |
|
'elseif', |
|
'empty', |
|
'enddeclare', |
|
'endfor', |
|
'endforeach', |
|
'endif', |
|
'endswitch', |
|
'endwhile', |
|
'eval', |
|
'exception', |
|
'exit', |
|
'extends', |
|
'final', |
|
'finally', |
|
'for', |
|
'foreach', |
|
'function', |
|
'global', |
|
'goto', |
|
'if', |
|
'implements', |
|
'include', |
|
'include_once', |
|
'instanceof', |
|
'insteadof', |
|
'interface', |
|
'isset', |
|
'list', |
|
'namespace', |
|
'new', |
|
'old_function', |
|
'or', |
|
'parent', |
|
'php_user_filter', |
|
'print', |
|
'private', |
|
'protected', |
|
'public', |
|
'require', |
|
'require_once', |
|
'return', |
|
'static', |
|
'switch', |
|
'this', |
|
'throw', |
|
'trait', |
|
'try', |
|
'unset', |
|
'use', |
|
'var', |
|
'while', |
|
'xor', |
|
]; |
|
return in_array(strtolower($value), $keywords, true); |
|
} |
|
}
|
|
|