CCodeModel.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. <?php
  2. /**
  3. * CCodeModel class file.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright 2008-2013 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CCodeModel is the base class for model classes that are used to generate code.
  12. *
  13. * Each code generator should have at least one code model class that extends from this class.
  14. * The purpose of a code model is to represent user-supplied parameters and use them to
  15. * generate customized code.
  16. *
  17. * Derived classes should implement the {@link prepare} method whose main task is to
  18. * fill up the {@link files} property based on the user parameters.
  19. *
  20. * The {@link files} property should be filled with a set of {@link CCodeFile} instances,
  21. * each representing a single code file to be generated.
  22. *
  23. * CCodeModel implements the feature of "sticky attributes". A sticky attribute is an attribute
  24. * that can remember its last valid value, even if the user closes his browser window
  25. * and reopen it. To declare an attribute is sticky, simply list it in a validation rule with
  26. * the validator name being "sticky".
  27. *
  28. * @property array $templates A list of available code templates (name=>directory).
  29. * @property string $templatePath The directory that contains the template files.
  30. * @property string $stickyFile The file path that stores the sticky attribute values.
  31. *
  32. * @author Qiang Xue <qiang.xue@gmail.com>
  33. * @package system.gii
  34. * @since 1.1.2
  35. */
  36. abstract class CCodeModel extends CFormModel
  37. {
  38. const STATUS_NEW=1;
  39. const STATUS_PREVIEW=2;
  40. const STATUS_SUCCESS=3;
  41. const STATUS_ERROR=4;
  42. static $keywords=array(
  43. '__class__',
  44. '__dir__',
  45. '__file__',
  46. '__function__',
  47. '__line__',
  48. '__method__',
  49. '__namespace__',
  50. 'abstract',
  51. 'and',
  52. 'array',
  53. 'as',
  54. 'break',
  55. 'case',
  56. 'catch',
  57. 'cfunction',
  58. 'class',
  59. 'clone',
  60. 'const',
  61. 'continue',
  62. 'declare',
  63. 'default',
  64. 'die',
  65. 'do',
  66. 'echo',
  67. 'else',
  68. 'elseif',
  69. 'empty',
  70. 'enddeclare',
  71. 'endfor',
  72. 'endforeach',
  73. 'endif',
  74. 'endswitch',
  75. 'endwhile',
  76. 'eval',
  77. 'exception',
  78. 'exit',
  79. 'extends',
  80. 'final',
  81. 'final',
  82. 'for',
  83. 'foreach',
  84. 'function',
  85. 'global',
  86. 'goto',
  87. 'if',
  88. 'implements',
  89. 'include',
  90. 'include_once',
  91. 'instanceof',
  92. 'interface',
  93. 'isset',
  94. 'list',
  95. 'namespace',
  96. 'new',
  97. 'old_function',
  98. 'or',
  99. 'parent',
  100. 'php_user_filter',
  101. 'print',
  102. 'private',
  103. 'protected',
  104. 'public',
  105. 'require',
  106. 'require_once',
  107. 'return',
  108. 'static',
  109. 'switch',
  110. 'this',
  111. 'throw',
  112. 'try',
  113. 'unset',
  114. 'use',
  115. 'var',
  116. 'while',
  117. 'xor',
  118. );
  119. /**
  120. * @var array user confirmations on whether to overwrite existing code files with the newly generated ones.
  121. * The value of this property is internally managed by this class and {@link CCodeGenerator}.
  122. */
  123. public $answers;
  124. /**
  125. * @var string the name of the code template that the user has selected.
  126. * The value of this property is internally managed by this class and {@link CCodeGenerator}.
  127. */
  128. public $template;
  129. /**
  130. * @var array a list of {@link CCodeFile} objects that represent the code files to be generated.
  131. * The {@link prepare()} method is responsible to populate this property.
  132. */
  133. public $files=array();
  134. /**
  135. * @var integer the status of this model. T
  136. * The value of this property is internally managed by {@link CCodeGenerator}.
  137. */
  138. public $status=self::STATUS_NEW;
  139. private $_stickyAttributes=array();
  140. /**
  141. * Prepares the code files to be generated.
  142. * This is the main method that child classes should implement. It should contain the logic
  143. * that populates the {@link files} property with a list of code files to be generated.
  144. */
  145. abstract public function prepare();
  146. /**
  147. * Declares the model validation rules.
  148. * Child classes must override this method in the following format:
  149. * <pre>
  150. * return array_merge(parent::rules(), array(
  151. * ...rules for the child class...
  152. * ));
  153. * </pre>
  154. * @return array validation rules
  155. */
  156. public function rules()
  157. {
  158. return array(
  159. array('template', 'required'),
  160. array('template', 'validateTemplate', 'skipOnError'=>true),
  161. array('template', 'sticky'),
  162. );
  163. }
  164. /**
  165. * Validates the template selection.
  166. * This method validates whether the user selects an existing template
  167. * and the template contains all required template files as specified in {@link requiredTemplates}.
  168. * @param string $attribute the attribute to be validated
  169. * @param array $params validation parameters
  170. */
  171. public function validateTemplate($attribute,$params)
  172. {
  173. $templates=$this->templates;
  174. if(!isset($templates[$this->template]))
  175. $this->addError('template', 'Invalid template selection.');
  176. else
  177. {
  178. $templatePath=$this->templatePath;
  179. foreach($this->requiredTemplates() as $template)
  180. {
  181. if(!is_file($templatePath.'/'.$template))
  182. $this->addError('template', "Unable to find the required code template file '$template'.");
  183. }
  184. }
  185. }
  186. /**
  187. * Checks if the named class exists (in a case sensitive manner).
  188. * @param string $name class name to be checked
  189. * @return boolean whether the class exists
  190. */
  191. public function classExists($name)
  192. {
  193. return class_exists($name,false) && in_array($name, get_declared_classes());
  194. }
  195. /**
  196. * Declares the model attribute labels.
  197. * Child classes must override this method in the following format:
  198. * <pre>
  199. * return array_merge(parent::attributeLabels(), array(
  200. * ...labels for the child class attributes...
  201. * ));
  202. * </pre>
  203. * @return array the attribute labels
  204. */
  205. public function attributeLabels()
  206. {
  207. return array(
  208. 'template'=>'Code Template',
  209. );
  210. }
  211. /**
  212. * Returns a list of code templates that are required.
  213. * Derived classes usually should override this method.
  214. * @return array list of code templates that are required. They should be file paths
  215. * relative to {@link templatePath}.
  216. */
  217. public function requiredTemplates()
  218. {
  219. return array();
  220. }
  221. /**
  222. * Saves the generated code into files.
  223. */
  224. public function save()
  225. {
  226. $result=true;
  227. foreach($this->files as $file)
  228. {
  229. if($this->confirmed($file))
  230. $result=$file->save() && $result;
  231. }
  232. return $result;
  233. }
  234. /**
  235. * Returns the message to be displayed when the newly generated code is saved successfully.
  236. * Child classes should override this method if the message needs to be customized.
  237. * @return string the message to be displayed when the newly generated code is saved successfully.
  238. */
  239. public function successMessage()
  240. {
  241. return 'The code has been generated successfully.';
  242. }
  243. /**
  244. * Returns the message to be displayed when some error occurred during code file saving.
  245. * Child classes should override this method if the message needs to be customized.
  246. * @return string the message to be displayed when some error occurred during code file saving.
  247. */
  248. public function errorMessage()
  249. {
  250. return 'There was some error when generating the code. Please check the following messages.';
  251. }
  252. /**
  253. * Returns a list of available code templates (name=>directory).
  254. * This method simply returns the {@link CCodeGenerator::templates} property value.
  255. * @return array a list of available code templates (name=>directory).
  256. */
  257. public function getTemplates()
  258. {
  259. return Yii::app()->controller->templates;
  260. }
  261. /**
  262. * @return string the directory that contains the template files.
  263. * @throws CHttpException if {@link templates} is empty or template selection is invalid
  264. */
  265. public function getTemplatePath()
  266. {
  267. $templates=$this->getTemplates();
  268. if(isset($templates[$this->template]))
  269. return $templates[$this->template];
  270. elseif(empty($templates))
  271. throw new CHttpException(500,'No templates are available.');
  272. else
  273. throw new CHttpException(500,'Invalid template selection.');
  274. }
  275. /**
  276. * @param CCodeFile $file whether the code file should be saved
  277. * @return bool whether the confirmation is found in {@link answers} with appropriate {@link operation}
  278. */
  279. public function confirmed($file)
  280. {
  281. return $this->answers===null && $file->operation===CCodeFile::OP_NEW
  282. || is_array($this->answers) && isset($this->answers[md5($file->path)]);
  283. }
  284. /**
  285. * Generates the code using the specified code template file.
  286. * This method is manly used in {@link generate} to generate code.
  287. * @param string $templateFile the code template file path
  288. * @param array $_params_ a set of parameters to be extracted and made available in the code template
  289. * @throws CException is template file does not exist
  290. * @return string the generated code
  291. */
  292. public function render($templateFile,$_params_=null)
  293. {
  294. if(!is_file($templateFile))
  295. throw new CException("The template file '$templateFile' does not exist.");
  296. if(is_array($_params_))
  297. extract($_params_,EXTR_PREFIX_SAME,'params');
  298. else
  299. $params=$_params_;
  300. ob_start();
  301. ob_implicit_flush(false);
  302. require($templateFile);
  303. return ob_get_clean();
  304. }
  305. /**
  306. * @return string the code generation result log.
  307. */
  308. public function renderResults()
  309. {
  310. $output='Generating code using template "'.$this->templatePath."\"...\n";
  311. foreach($this->files as $file)
  312. {
  313. if($file->error!==null)
  314. $output.="<span class=\"error\">generating {$file->relativePath}<br/> {$file->error}</span>\n";
  315. elseif($file->operation===CCodeFile::OP_NEW && $this->confirmed($file))
  316. $output.=' generated '.$file->relativePath."\n";
  317. elseif($file->operation===CCodeFile::OP_OVERWRITE && $this->confirmed($file))
  318. $output.=' overwrote '.$file->relativePath."\n";
  319. else
  320. $output.=' skipped '.$file->relativePath."\n";
  321. }
  322. $output.="done!\n";
  323. return $output;
  324. }
  325. /**
  326. * The "sticky" validator.
  327. * This validator does not really validate the attributes.
  328. * It actually saves the attribute value in a file to make it sticky.
  329. * @param string $attribute the attribute to be validated
  330. * @param array $params the validation parameters
  331. */
  332. public function sticky($attribute,$params)
  333. {
  334. if(!$this->hasErrors())
  335. $this->_stickyAttributes[$attribute]=$this->$attribute;
  336. }
  337. /**
  338. * Loads sticky attributes from a file and populates them into the model.
  339. */
  340. public function loadStickyAttributes()
  341. {
  342. $this->_stickyAttributes=array();
  343. $path=$this->getStickyFile();
  344. if(is_file($path))
  345. {
  346. $result=@include($path);
  347. if(is_array($result))
  348. {
  349. $this->_stickyAttributes=$result;
  350. foreach($this->_stickyAttributes as $name=>$value)
  351. {
  352. if(property_exists($this,$name) || $this->canSetProperty($name))
  353. $this->$name=$value;
  354. }
  355. }
  356. }
  357. }
  358. /**
  359. * Saves sticky attributes into a file.
  360. */
  361. public function saveStickyAttributes()
  362. {
  363. $path=$this->getStickyFile();
  364. @mkdir(dirname($path),0755,true);
  365. file_put_contents($path,"<?php\nreturn ".var_export($this->_stickyAttributes,true).";\n");
  366. }
  367. /**
  368. * @return string the file path that stores the sticky attribute values.
  369. */
  370. public function getStickyFile()
  371. {
  372. return Yii::app()->runtimePath.'/gii-'.Yii::getVersion().'/'.get_class($this).'.php';
  373. }
  374. /**
  375. * Converts a word to its plural form.
  376. * Note that this is for English only!
  377. * For example, 'apple' will become 'apples', and 'child' will become 'children'.
  378. * @param string $name the word to be pluralized
  379. * @return string the pluralized word
  380. */
  381. public function pluralize($name)
  382. {
  383. $rules=array(
  384. '/(m)ove$/i' => '\1oves',
  385. '/(f)oot$/i' => '\1eet',
  386. '/(c)hild$/i' => '\1hildren',
  387. '/(h)uman$/i' => '\1umans',
  388. '/(m)an$/i' => '\1en',
  389. '/(s)taff$/i' => '\1taff',
  390. '/(t)ooth$/i' => '\1eeth',
  391. '/(p)erson$/i' => '\1eople',
  392. '/([m|l])ouse$/i' => '\1ice',
  393. '/(x|ch|ss|sh|us|as|is|os)$/i' => '\1es',
  394. '/([^aeiouy]|qu)y$/i' => '\1ies',
  395. '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
  396. '/(shea|lea|loa|thie)f$/i' => '\1ves',
  397. '/([ti])um$/i' => '\1a',
  398. '/(tomat|potat|ech|her|vet)o$/i' => '\1oes',
  399. '/(bu)s$/i' => '\1ses',
  400. '/(ax|test)is$/i' => '\1es',
  401. '/s$/' => 's',
  402. );
  403. foreach($rules as $rule=>$replacement)
  404. {
  405. if(preg_match($rule,$name))
  406. return preg_replace($rule,$replacement,$name);
  407. }
  408. return $name.'s';
  409. }
  410. /**
  411. * Converts a class name into a HTML ID.
  412. * For example, 'PostTag' will be converted as 'post-tag'.
  413. * @param string $name the string to be converted
  414. * @return string the resulting ID
  415. */
  416. public function class2id($name)
  417. {
  418. return trim(strtolower(str_replace('_','-',preg_replace('/(?<![A-Z])[A-Z]/', '-\0', $name))),'-');
  419. }
  420. /**
  421. * Converts a class name into space-separated words.
  422. * For example, 'PostTag' will be converted as 'Post Tag'.
  423. * @param string $name the string to be converted
  424. * @param boolean $ucwords whether to capitalize the first letter in each word
  425. * @return string the resulting words
  426. */
  427. public function class2name($name,$ucwords=true)
  428. {
  429. $result=trim(strtolower(str_replace('_',' ',preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $name))));
  430. return $ucwords ? ucwords($result) : $result;
  431. }
  432. /**
  433. * Converts a class name into a variable name with the first letter in lower case.
  434. * This method is provided because lcfirst() PHP function is only available for PHP 5.3+.
  435. * @param string $name the class name
  436. * @return string the variable name converted from the class name
  437. * @since 1.1.4
  438. */
  439. public function class2var($name)
  440. {
  441. $name[0]=strtolower($name[0]);
  442. return $name;
  443. }
  444. /**
  445. * Validates an attribute to make sure it is not taking a PHP reserved keyword.
  446. * @param string $attribute the attribute to be validated
  447. * @param array $params validation parameters
  448. */
  449. public function validateReservedWord($attribute,$params)
  450. {
  451. $value=$this->$attribute;
  452. if(in_array(strtolower($value),self::$keywords))
  453. $this->addError($attribute, $this->getAttributeLabel($attribute).' cannot take a reserved PHP keyword.');
  454. }
  455. }