PageRenderTime 43ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/yii/framework/gii/generators/model/ModelCode.php

https://github.com/joshuaswarren/Mavenlink-and-Twilio-API-Mashup
PHP | 390 lines | 325 code | 39 blank | 26 comment | 66 complexity | 5159fbc53e061a94f0b503c6ee3f0095 MD5 | raw file
  1. <?php
  2. class ModelCode extends CCodeModel
  3. {
  4. public $tablePrefix;
  5. public $tableName;
  6. public $modelClass;
  7. public $modelPath='application.models';
  8. public $baseClass='CActiveRecord';
  9. public $buildRelations=true;
  10. /**
  11. * @var array list of candidate relation code. The array are indexed by AR class names and relation names.
  12. * Each element represents the code of the one relation in one AR class.
  13. */
  14. protected $relations;
  15. public function rules()
  16. {
  17. return array_merge(parent::rules(), array(
  18. array('tablePrefix, baseClass, tableName, modelClass, modelPath', 'filter', 'filter'=>'trim'),
  19. array('tableName, modelPath, baseClass', 'required'),
  20. array('tablePrefix, tableName, modelPath', 'match', 'pattern'=>'/^(\w+[\w\.]*|\*?|\w+\.\*)$/', 'message'=>'{attribute} should only contain word characters, dots, and an optional ending asterisk.'),
  21. array('tableName', 'validateTableName', 'skipOnError'=>true),
  22. array('tablePrefix, modelClass, baseClass', 'match', 'pattern'=>'/^[a-zA-Z_]\w*$/', 'message'=>'{attribute} should only contain word characters.'),
  23. array('modelPath', 'validateModelPath', 'skipOnError'=>true),
  24. array('baseClass, modelClass', 'validateReservedWord', 'skipOnError'=>true),
  25. array('baseClass', 'validateBaseClass', 'skipOnError'=>true),
  26. array('tablePrefix, modelPath, baseClass, buildRelations', 'sticky'),
  27. ));
  28. }
  29. public function attributeLabels()
  30. {
  31. return array_merge(parent::attributeLabels(), array(
  32. 'tablePrefix'=>'Table Prefix',
  33. 'tableName'=>'Table Name',
  34. 'modelPath'=>'Model Path',
  35. 'modelClass'=>'Model Class',
  36. 'baseClass'=>'Base Class',
  37. 'buildRelations'=>'Build Relations',
  38. ));
  39. }
  40. public function requiredTemplates()
  41. {
  42. return array(
  43. 'model.php',
  44. );
  45. }
  46. public function init()
  47. {
  48. if(Yii::app()->db===null)
  49. throw new CHttpException(500,'An active "db" connection is required to run this generator.');
  50. $this->tablePrefix=Yii::app()->db->tablePrefix;
  51. parent::init();
  52. }
  53. public function prepare()
  54. {
  55. if(($pos=strrpos($this->tableName,'.'))!==false)
  56. {
  57. $schema=substr($this->tableName,0,$pos);
  58. $tableName=substr($this->tableName,$pos+1);
  59. }
  60. else
  61. {
  62. $schema='';
  63. $tableName=$this->tableName;
  64. }
  65. if($tableName[strlen($tableName)-1]==='*')
  66. {
  67. $tables=Yii::app()->db->schema->getTables($schema);
  68. if($this->tablePrefix!='')
  69. {
  70. foreach($tables as $i=>$table)
  71. {
  72. if(strpos($table->name,$this->tablePrefix)!==0)
  73. unset($tables[$i]);
  74. }
  75. }
  76. }
  77. else
  78. $tables=array($this->getTableSchema($this->tableName));
  79. $this->files=array();
  80. $templatePath=$this->templatePath;
  81. $this->relations=$this->generateRelations();
  82. foreach($tables as $table)
  83. {
  84. $tableName=$this->removePrefix($table->name);
  85. $className=$this->generateClassName($table->name);
  86. $params=array(
  87. 'tableName'=>$schema==='' ? $tableName : $schema.'.'.$tableName,
  88. 'modelClass'=>$className,
  89. 'columns'=>$table->columns,
  90. 'labels'=>$this->generateLabels($table),
  91. 'rules'=>$this->generateRules($table),
  92. 'relations'=>isset($this->relations[$className]) ? $this->relations[$className] : array(),
  93. );
  94. $this->files[]=new CCodeFile(
  95. Yii::getPathOfAlias($this->modelPath).'/'.$className.'.php',
  96. $this->render($templatePath.'/model.php', $params)
  97. );
  98. }
  99. }
  100. public function validateTableName($attribute,$params)
  101. {
  102. $invalidColumns=array();
  103. if($this->tableName[strlen($this->tableName)-1]==='*')
  104. {
  105. if(($pos=strrpos($this->tableName,'.'))!==false)
  106. $schema=substr($this->tableName,0,$pos);
  107. else
  108. $schema='';
  109. $this->modelClass='';
  110. $tables=Yii::app()->db->schema->getTables($schema);
  111. foreach($tables as $table)
  112. {
  113. if($this->tablePrefix=='' || strpos($table->name,$this->tablePrefix)===0)
  114. {
  115. if(($invalidColumn=$this->checkColumns($table))!==null)
  116. $invalidColumns[]=$invalidColumn;
  117. }
  118. }
  119. }
  120. else
  121. {
  122. if(($table=$this->getTableSchema($this->tableName))===null)
  123. $this->addError('tableName',"Table '{$this->tableName}' does not exist.");
  124. if($this->modelClass==='')
  125. $this->addError('modelClass','Model Class cannot be blank.');
  126. if(!$this->hasErrors($attribute) && ($invalidColumn=$this->checkColumns($table))!==null)
  127. $invalidColumns[]=$invalidColumn;
  128. }
  129. if($invalidColumns!=array())
  130. $this->addError('tableName',"Column names that does not follow PHP variable naming convention: ".implode(', ', $invalidColumns)."." );
  131. }
  132. /*
  133. * Check that all database field names conform to PHP variable naming rules
  134. * For example mysql allows field name like "2011aa", but PHP does not allow variable like "$model->2011aa"
  135. * @param CDbTableSchema $table the table schema object
  136. * @return string the invalid table column name. Null if no error.
  137. */
  138. public function checkColumns($table)
  139. {
  140. foreach($table->columns as $column)
  141. {
  142. if(!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/',$column->name))
  143. return $table->name.'.'.$column->name;
  144. }
  145. }
  146. public function validateModelPath($attribute,$params)
  147. {
  148. if(Yii::getPathOfAlias($this->modelPath)===false)
  149. $this->addError('modelPath','Model Path must be a valid path alias.');
  150. }
  151. public function validateBaseClass($attribute,$params)
  152. {
  153. $class=@Yii::import($this->baseClass,true);
  154. if(!is_string($class) || !$this->classExists($class))
  155. $this->addError('baseClass', "Class '{$this->baseClass}' does not exist or has syntax error.");
  156. else if($class!=='CActiveRecord' && !is_subclass_of($class,'CActiveRecord'))
  157. $this->addError('baseClass', "'{$this->model}' must extend from CActiveRecord.");
  158. }
  159. public function getTableSchema($tableName)
  160. {
  161. return Yii::app()->db->getSchema()->getTable($tableName);
  162. }
  163. public function generateLabels($table)
  164. {
  165. $labels=array();
  166. foreach($table->columns as $column)
  167. {
  168. $label=ucwords(trim(strtolower(str_replace(array('-','_'),' ',preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $column->name)))));
  169. $label=preg_replace('/\s+/',' ',$label);
  170. if(strcasecmp(substr($label,-3),' id')===0)
  171. $label=substr($label,0,-3);
  172. if($label==='Id')
  173. $label='ID';
  174. $labels[$column->name]=$label;
  175. }
  176. return $labels;
  177. }
  178. public function generateRules($table)
  179. {
  180. $rules=array();
  181. $required=array();
  182. $integers=array();
  183. $numerical=array();
  184. $length=array();
  185. $safe=array();
  186. foreach($table->columns as $column)
  187. {
  188. if($column->autoIncrement)
  189. continue;
  190. $r=!$column->allowNull && $column->defaultValue===null;
  191. if($r)
  192. $required[]=$column->name;
  193. if($column->type==='integer')
  194. $integers[]=$column->name;
  195. else if($column->type==='double')
  196. $numerical[]=$column->name;
  197. else if($column->type==='string' && $column->size>0)
  198. $length[$column->size][]=$column->name;
  199. else if(!$column->isPrimaryKey && !$r)
  200. $safe[]=$column->name;
  201. }
  202. if($required!==array())
  203. $rules[]="array('".implode(', ',$required)."', 'required')";
  204. if($integers!==array())
  205. $rules[]="array('".implode(', ',$integers)."', 'numerical', 'integerOnly'=>true)";
  206. if($numerical!==array())
  207. $rules[]="array('".implode(', ',$numerical)."', 'numerical')";
  208. if($length!==array())
  209. {
  210. foreach($length as $len=>$cols)
  211. $rules[]="array('".implode(', ',$cols)."', 'length', 'max'=>$len)";
  212. }
  213. if($safe!==array())
  214. $rules[]="array('".implode(', ',$safe)."', 'safe')";
  215. return $rules;
  216. }
  217. public function getRelations($className)
  218. {
  219. return isset($this->relations[$className]) ? $this->relations[$className] : array();
  220. }
  221. protected function removePrefix($tableName,$addBrackets=true)
  222. {
  223. if($addBrackets && Yii::app()->db->tablePrefix=='')
  224. return $tableName;
  225. $prefix=$this->tablePrefix!='' ? $this->tablePrefix : Yii::app()->db->tablePrefix;
  226. if($prefix!='')
  227. {
  228. if($addBrackets && Yii::app()->db->tablePrefix!='')
  229. {
  230. $prefix=Yii::app()->db->tablePrefix;
  231. $lb='{{';
  232. $rb='}}';
  233. }
  234. else
  235. $lb=$rb='';
  236. if(($pos=strrpos($tableName,'.'))!==false)
  237. {
  238. $schema=substr($tableName,0,$pos);
  239. $name=substr($tableName,$pos+1);
  240. if(strpos($name,$prefix)===0)
  241. return $schema.'.'.$lb.substr($name,strlen($prefix)).$rb;
  242. }
  243. else if(strpos($tableName,$prefix)===0)
  244. return $lb.substr($tableName,strlen($prefix)).$rb;
  245. }
  246. return $tableName;
  247. }
  248. protected function generateRelations()
  249. {
  250. if(!$this->buildRelations)
  251. return array();
  252. $relations=array();
  253. foreach(Yii::app()->db->schema->getTables() as $table)
  254. {
  255. if($this->tablePrefix!='' && strpos($table->name,$this->tablePrefix)!==0)
  256. continue;
  257. $tableName=$table->name;
  258. if ($this->isRelationTable($table))
  259. {
  260. $pks=$table->primaryKey;
  261. $fks=$table->foreignKeys;
  262. $table0=$fks[$pks[0]][0];
  263. $table1=$fks[$pks[1]][0];
  264. $className0=$this->generateClassName($table0);
  265. $className1=$this->generateClassName($table1);
  266. $unprefixedTableName=$this->removePrefix($tableName);
  267. $relationName=$this->generateRelationName($table0, $table1, true);
  268. $relations[$className0][$relationName]="array(self::MANY_MANY, '$className1', '$unprefixedTableName($pks[0], $pks[1])')";
  269. $relationName=$this->generateRelationName($table1, $table0, true);
  270. $relations[$className1][$relationName]="array(self::MANY_MANY, '$className0', '$unprefixedTableName($pks[1], $pks[0])')";
  271. }
  272. else
  273. {
  274. $className=$this->generateClassName($tableName);
  275. foreach ($table->foreignKeys as $fkName => $fkEntry)
  276. {
  277. // Put table and key name in variables for easier reading
  278. $refTable=$fkEntry[0]; // Table name that current fk references to
  279. $refKey=$fkEntry[1]; // Key in that table being referenced
  280. $refClassName=$this->generateClassName($refTable);
  281. // Add relation for this table
  282. $relationName=$this->generateRelationName($tableName, $fkName, false);
  283. $relations[$className][$relationName]="array(self::BELONGS_TO, '$refClassName', '$fkName')";
  284. // Add relation for the referenced table
  285. $relationType=$table->primaryKey === $fkName ? 'HAS_ONE' : 'HAS_MANY';
  286. $relationName=$this->generateRelationName($refTable, $this->removePrefix($tableName,false), $relationType==='HAS_MANY');
  287. $i=1;
  288. $rawName=$relationName;
  289. while(isset($relations[$refClassName][$relationName]))
  290. $relationName=$rawName.($i++);
  291. $relations[$refClassName][$relationName]="array(self::$relationType, '$className', '$fkName')";
  292. }
  293. }
  294. }
  295. return $relations;
  296. }
  297. /**
  298. * Checks if the given table is a "many to many" pivot table.
  299. * Their PK has 2 fields, and both of those fields are also FK to other separate tables.
  300. * @param CDbTableSchema table to inspect
  301. * @return boolean true if table matches description of helpter table.
  302. */
  303. protected function isRelationTable($table)
  304. {
  305. $pk=$table->primaryKey;
  306. return (count($pk) === 2 // we want 2 columns
  307. && isset($table->foreignKeys[$pk[0]]) // pk column 1 is also a foreign key
  308. && isset($table->foreignKeys[$pk[1]]) // pk column 2 is also a foriegn key
  309. && $table->foreignKeys[$pk[0]][0] !== $table->foreignKeys[$pk[1]][0]); // and the foreign keys point different tables
  310. }
  311. protected function generateClassName($tableName)
  312. {
  313. if($this->tableName===$tableName || ($pos=strrpos($this->tableName,'.'))!==false && substr($this->tableName,$pos+1)===$tableName)
  314. return $this->modelClass;
  315. $tableName=$this->removePrefix($tableName,false);
  316. $className='';
  317. foreach(explode('_',$tableName) as $name)
  318. {
  319. if($name!=='')
  320. $className.=ucfirst($name);
  321. }
  322. return $className;
  323. }
  324. /**
  325. * Generate a name for use as a relation name (inside relations() function in a model).
  326. * @param string the name of the table to hold the relation
  327. * @param string the foreign key name
  328. * @param boolean whether the relation would contain multiple objects
  329. * @return string the relation name
  330. */
  331. protected function generateRelationName($tableName, $fkName, $multiple)
  332. {
  333. if(strcasecmp(substr($fkName,-2),'id')===0 && strcasecmp($fkName,'id'))
  334. $relationName=rtrim(substr($fkName, 0, -2),'_');
  335. else
  336. $relationName=$fkName;
  337. $relationName[0]=strtolower($relationName);
  338. if($multiple)
  339. $relationName=$this->pluralize($relationName);
  340. $names=preg_split('/_+/',$relationName,-1,PREG_SPLIT_NO_EMPTY);
  341. if(empty($names)) return $relationName; // unlikely
  342. for($name=$names[0], $i=1;$i<count($names);++$i)
  343. $name.=ucfirst($names[$i]);
  344. $rawName=$name;
  345. $table=Yii::app()->db->schema->getTable($tableName);
  346. $i=0;
  347. while(isset($table->columns[$name]))
  348. $name=$rawName.($i++);
  349. return $name;
  350. }
  351. }