PageRenderTime 26ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/vendor/schmunk42/yii2-giiant/src/generators/model/Generator.php

https://gitlab.com/febfeb/pelatihan-git
PHP | 422 lines | 298 code | 47 blank | 77 comment | 8 complexity | 145485dd7ded40cdc14be7cd29ae66ec MD5 | raw file
  1. <?php
  2. /**
  3. * @link http://www.phundament.com
  4. * @copyright Copyright (c) 2014 herzog kommunikation GmbH
  5. * @license http://www.phundament.com/license/
  6. */
  7. namespace schmunk42\giiant\generators\model;
  8. use Yii;
  9. use yii\gii\CodeFile;
  10. use yii\helpers\Inflector;
  11. /**
  12. * This generator will generate one or multiple ActiveRecord classes for the specified database table.
  13. *
  14. * @author Tobias Munk <schmunk@usrbin.de>
  15. * @since 0.0.1
  16. */
  17. class Generator extends \yii\gii\generators\model\Generator
  18. {
  19. /**
  20. * @var bool whether to overwrite (extended) model classes, will be always created, if file does not exist
  21. */
  22. public $generateModelClass = false;
  23. /**
  24. * @var string base-traits
  25. */
  26. public $baseTraits = null;
  27. /**
  28. * @var null string for the table prefix, which is ignored in generated class name
  29. */
  30. public $tablePrefix = null;
  31. /**
  32. * @var bool whether to use or not 2amigos/yii2-translateable-behavior
  33. */
  34. public $useTranslatableBehavior = true;
  35. /**
  36. * @var string the name of the table containing the translations. {{table}} will be replaced with the value in
  37. * "Table Name" field.
  38. */
  39. public $languageTableName = "{{table}}_lang";
  40. /**
  41. * @var string the column name where the language code is stored.
  42. */
  43. public $languageCodeColumn = "language";
  44. /**
  45. * @var string suffix to append to the base model, setting "Base" will result in a model named "PostBase"
  46. */
  47. public $baseClassSuffix = '';
  48. /**
  49. * @var array key-value pairs for mapping a table-name to class-name, eg. 'prefix_FOObar' => 'FooBar'
  50. */
  51. public $tableNameMap = [];
  52. protected $classNames2;
  53. public $singularEntities = false;
  54. /**
  55. * @inheritdoc
  56. */
  57. public function getName()
  58. {
  59. return 'Giiant Model';
  60. }
  61. /**
  62. * @inheritdoc
  63. */
  64. public function getDescription()
  65. {
  66. return 'This generator generates an ActiveRecord class and base class for the specified database table.';
  67. }
  68. /**
  69. * @inheritdoc
  70. */
  71. public function rules()
  72. {
  73. return array_merge(
  74. parent::rules(),
  75. [
  76. [['generateModelClass', 'useTranslatableBehavior'], 'boolean'],
  77. [['languageTableName', 'languageCodeColumn'], 'string'],
  78. [['tablePrefix'], 'safe'],
  79. ]
  80. );
  81. }
  82. /**
  83. * @inheritdoc
  84. */
  85. public function attributeLabels()
  86. {
  87. return array_merge(
  88. parent::attributeLabels(),
  89. [
  90. 'generateModelClass' => 'Generate Model Class',
  91. ]
  92. );
  93. }
  94. /**
  95. * @inheritdoc
  96. */
  97. public function hints()
  98. {
  99. return array_merge(
  100. parent::hints(),
  101. [
  102. 'generateModelClass' => 'This indicates whether the generator should generate the model class, this should usually be done only once. The model-base class is always generated.',
  103. 'tablePrefix' => 'Custom table prefix, eg <code>app_</code>.<br/><b>Note!</b> overrides <code>yii\db\Connection</code> prefix!',
  104. 'useTranslatableBehavior' => 'Use <code>2amigos/yii2-translateable-behavior</code> for tables with a relation to a translation table.',
  105. 'languageTableName' => 'The name of the table containing the translations. <code>{{table}}</code> will be replaced with the value in "Table Name" field.',
  106. 'languageCodeColumn' => 'The column name where the language code is stored.',
  107. ]
  108. );
  109. }
  110. /**
  111. * @inheritdoc
  112. */
  113. public function requiredTemplates()
  114. {
  115. return ['model.php', 'model-extended.php'];
  116. }
  117. /**
  118. * @inheritdoc
  119. */
  120. public function generate()
  121. {
  122. $files = [];
  123. $relations = $this->generateRelations();
  124. $db = $this->getDbConnection();
  125. foreach ($this->getTableNames() as $tableName) {
  126. list($relations, $translations) = array_values($this->extractTranslations($tableName, $relations));
  127. $className = $this->generateClassName($tableName);
  128. $queryClassName = ($this->generateQuery) ? $this->generateQueryClassName($className) : false;
  129. $tableSchema = $db->getTableSchema($tableName);
  130. $params = [
  131. 'tableName' => $tableName,
  132. 'className' => $className,
  133. 'queryClassName' => $queryClassName,
  134. 'tableSchema' => $tableSchema,
  135. 'labels' => $this->generateLabels($tableSchema),
  136. 'rules' => $this->generateRules($tableSchema),
  137. 'relations' => isset($relations[$tableName]) ? $relations[$tableName] : [],
  138. 'ns' => $this->ns,
  139. 'enum' => $this->getEnum($tableSchema->columns),
  140. ];
  141. if (!empty($translations)) {
  142. $params['translation'] = $translations;
  143. }
  144. $files[] = new CodeFile(
  145. Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/base/' . $className . $this->baseClassSuffix . '.php',
  146. $this->render('model.php', $params)
  147. );
  148. $modelClassFile = Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/' . $className . '.php';
  149. if ($this->generateModelClass || !is_file($modelClassFile)) {
  150. $files[] = new CodeFile(
  151. $modelClassFile,
  152. $this->render('model-extended.php', $params)
  153. );
  154. }
  155. if ($queryClassName) {
  156. $queryClassFile = Yii::getAlias('@' . str_replace('\\', '/', $this->queryNs)) . '/' . $queryClassName . '.php';
  157. if ($this->generateModelClass || !is_file($queryClassFile)) {
  158. $params = [
  159. 'className' => $queryClassName,
  160. 'modelClassName' => $className,
  161. ];
  162. $files[] = new CodeFile(
  163. $queryClassFile,
  164. $this->render('query.php', $params)
  165. );
  166. }
  167. }
  168. }
  169. return $files;
  170. }
  171. /**
  172. * Generates a class name from the specified table name.
  173. *
  174. * @param string $tableName the table name (which may contain schema prefix)
  175. *
  176. * @return string the generated class name
  177. */
  178. public function generateClassName($tableName, $useSchemaName = null)
  179. {
  180. #Yii::trace("Generating class name for '{$tableName}'...", __METHOD__);
  181. if (isset($this->classNames2[$tableName])) {
  182. #Yii::trace("Using '{$this->classNames2[$tableName]}' for '{$tableName}' from classNames2.", __METHOD__);
  183. return $this->classNames2[$tableName];
  184. }
  185. if (isset($this->tableNameMap[$tableName])) {
  186. Yii::trace("Converted '{$tableName}' from tableNameMap.", __METHOD__);
  187. return $this->classNames2[$tableName] = $this->tableNameMap[$tableName];
  188. }
  189. if (($pos = strrpos($tableName, '.')) !== false) {
  190. $tableName = substr($tableName, $pos + 1);
  191. }
  192. $db = $this->getDbConnection();
  193. $patterns = [];
  194. $patterns[] = "/^{$this->tablePrefix}(.*?)$/";
  195. $patterns[] = "/^(.*?){$this->tablePrefix}$/";
  196. $patterns[] = "/^{$db->tablePrefix}(.*?)$/";
  197. $patterns[] = "/^(.*?){$db->tablePrefix}$/";
  198. if (strpos($this->tableName, '*') !== false) {
  199. $pattern = $this->tableName;
  200. if (($pos = strrpos($pattern, '.')) !== false) {
  201. $pattern = substr($pattern, $pos + 1);
  202. }
  203. $patterns[] = '/^' . str_replace('*', '(\w+)', $pattern) . '$/';
  204. }
  205. $className = $tableName;
  206. foreach ($patterns as $pattern) {
  207. if (preg_match($pattern, $tableName, $matches)) {
  208. $className = $matches[1];
  209. Yii::trace("Mapping '{$tableName}' to '{$className}' from pattern '{$pattern}'.", __METHOD__);
  210. break;
  211. }
  212. }
  213. $returnName = Inflector::id2camel($className, '_');
  214. if ($this->singularEntities) $returnName = Inflector::singularize($returnName);
  215. Yii::trace("Converted '{$tableName}' to '{$returnName}'.", __METHOD__);
  216. return $this->classNames2[$tableName] = $returnName;
  217. }
  218. /**
  219. * @inheritdoc
  220. */
  221. public function generateRelationName($relations, $table, $key, $multiple)
  222. {
  223. return parent::generateRelationName($relations, $table, $key, $multiple);
  224. }
  225. protected function generateRelations()
  226. {
  227. $relations = parent::generateRelations();
  228. // inject namespace
  229. $ns = "\\{$this->ns}\\";
  230. foreach ($relations AS $model => $relInfo) {
  231. foreach ($relInfo AS $relName => $relData) {
  232. $relations[$model][$relName][0] = preg_replace(
  233. '/(has[A-Za-z0-9]+\()([a-zA-Z0-9]+::)/',
  234. '$1__NS__$2',
  235. $relations[$model][$relName][0]
  236. );
  237. $relations[$model][$relName][0] = str_replace('__NS__', $ns, $relations[$model][$relName][0]);
  238. }
  239. }
  240. return $relations;
  241. }
  242. /**
  243. * prepare ENUM field values
  244. * @param array $columns
  245. * @return array
  246. */
  247. public function getEnum($columns){
  248. $enum = [];
  249. foreach ($columns as $column) {
  250. if (!$this->isEnum($column)) {
  251. continue;
  252. }
  253. $column_camel_name = str_replace(' ', '', ucwords(implode(' ', explode('_', $column->name))));
  254. $enum[$column->name]['func_opts_name'] = 'opts' . $column_camel_name;
  255. $enum[$column->name]['func_get_label_name'] = 'get' . $column_camel_name.'ValueLabel';
  256. $enum[$column->name]['values'] = [];
  257. $enum_values = explode(',', substr($column->dbType, 4, strlen($column->dbType) - 1));
  258. foreach ($enum_values as $value) {
  259. $value = trim($value, "()'");
  260. $const_name = strtoupper($column->name . '_' . $value);
  261. $const_name = preg_replace('/\s+/','_',$const_name);
  262. $const_name = str_replace(['-','_',' '],'_',$const_name);
  263. $const_name=preg_replace('/[^A-Z0-9_]/', '', $const_name);
  264. $label = ucwords(trim(strtolower(str_replace(['-', '_'], ' ', preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $value)))));
  265. $label = preg_replace('/\s+/', ' ', $label);
  266. $enum[$column->name]['values'][] = [
  267. 'value' => $value,
  268. 'const_name' => $const_name,
  269. 'label' => $label,
  270. ];
  271. }
  272. }
  273. return $enum;
  274. }
  275. /**
  276. * validate is ENUM
  277. * @param $column table column
  278. * @return type
  279. */
  280. public function isEnum($column){
  281. return substr(strtoupper($column->dbType), 0, 4) == 'ENUM';
  282. }
  283. /**
  284. * Generates validation rules for the specified table and add enum value validation.
  285. * @param \yii\db\TableSchema $table the table schema
  286. * @return array the generated validation rules
  287. */
  288. public function generateRules($table)
  289. {
  290. $rules = [];
  291. //for enum fields create rules "in range" for all enum values
  292. $enum = $this->getEnum($table->columns);
  293. foreach($enum as $field_name => $field_details){
  294. $ea = array();
  295. foreach($field_details['values'] as $field_enum_values){
  296. $ea[] = 'self::'.$field_enum_values['const_name'];
  297. }
  298. $rules[] = "['" .$field_name . "', 'in', 'range' => [\n " . implode(",\n ",$ea) . ",\n ]\n ]";
  299. }
  300. return array_merge(parent::generateRules($table),$rules);
  301. }
  302. /**
  303. * @inheritdoc
  304. */
  305. public function getTableNames()
  306. {
  307. return parent::getTableNames();
  308. }
  309. /**
  310. * @param $relations all database's relations.
  311. * @return array associative array containing the extracted relations and the modified translations.
  312. */
  313. protected function extractTranslations($tableName, $relations)
  314. {
  315. $langTableName = str_replace("{{table}}", $tableName, $this->languageTableName);
  316. if ($this->useTranslatableBehavior and isset($relations[$langTableName], $relations[$tableName])) {
  317. $db = $this->getDbConnection();
  318. $langTableSchema = $db->getTableSchema($langTableName);
  319. $langTableColumns = $langTableSchema->getColumnNames();
  320. $langTableKeys = array_merge(
  321. $langTableSchema->primaryKey,
  322. array_map(function($fk){
  323. return array_keys($fk)[1];
  324. }, $langTableSchema->foreignKeys)
  325. );
  326. $langClassName = $this->generateClassName($langTableName);
  327. foreach ($relations[$tableName] as $relationName => $relation) {
  328. list($code, $referencedClassName) = $relation;
  329. if ($referencedClassName === $langClassName) {
  330. // found relation from model to modelLang.
  331. // collect fields which are not PK, FK nor language code
  332. $fields = [];
  333. foreach ($langTableColumns as $columnName) {
  334. if (!in_array($columnName, $langTableKeys) and strcasecmp($columnName, $this->languageCodeColumn) !== 0) {
  335. $fields[] = $columnName;
  336. }
  337. }
  338. unset($relations[$tableName][$relationName]);
  339. return [
  340. 'relations' => $relations,
  341. 'translations' => [
  342. 'fields' => $fields,
  343. 'code' => $code
  344. ]
  345. ];
  346. }
  347. }
  348. }
  349. return [
  350. 'relations' => $relations,
  351. 'translations' => []
  352. ];
  353. }
  354. }