|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* _ __ __ _____ _____ ___ ____ _____ |
|
5
|
|
|
* | | / // // ___//_ _// || __||_ _| |
|
6
|
|
|
* | |/ // /(__ ) / / / /| || | | | |
|
7
|
|
|
* |___//_//____/ /_/ /_/ |_||_| |_| |
|
8
|
|
|
* @link http://vistart.name/ |
|
9
|
|
|
* @copyright Copyright (c) 2015 vistart |
|
10
|
|
|
* @license http://vistart.name/license/ |
|
11
|
|
|
*/ |
|
12
|
|
|
|
|
13
|
|
|
namespace vistart\Models\gii\baseUserModel; |
|
14
|
|
|
|
|
15
|
|
|
use Yii; |
|
16
|
|
|
use yii\db\ActiveQuery; |
|
17
|
|
|
use yii\db\ActiveRecord; |
|
18
|
|
|
use yii\db\Connection; |
|
19
|
|
|
use yii\db\Schema; |
|
20
|
|
|
use yii\gii\CodeFile; |
|
21
|
|
|
use yii\helpers\Inflector; |
|
22
|
|
|
use yii\base\NotSupportedException; |
|
23
|
|
|
use vistart\Models\models\BaseUserModel; |
|
24
|
|
|
|
|
25
|
|
|
/** |
|
26
|
|
|
* Description of Generator |
|
27
|
|
|
* |
|
28
|
|
|
* @author vistart <[email protected]> |
|
29
|
|
|
*/ |
|
30
|
|
|
class Generator extends \yii\gii\Generator { |
|
31
|
|
|
|
|
32
|
|
|
public $db = 'db'; |
|
33
|
|
|
public $guidAttribute = 'guid'; |
|
34
|
|
|
public $idAttribute = 'id'; |
|
35
|
|
|
public $idAttributeType = ''; |
|
36
|
|
|
public $idAttributeTypes = [ |
|
37
|
|
|
0 => 'Random Number', |
|
38
|
|
|
1 => 'Random String', |
|
39
|
|
|
2 => 'Auto Increment', |
|
40
|
|
|
]; |
|
41
|
|
|
public $passwordHashAttribute = 'pass_hash'; |
|
42
|
|
|
public $createdAtAttribute = 'create_time'; |
|
43
|
|
|
public $updatedAtAttribute = 'update_time'; |
|
44
|
|
|
public $enableIP = true; |
|
45
|
|
|
public $ipAttribute1 = 'ip_1'; |
|
46
|
|
|
public $ipAttribute2 = 'ip_2'; |
|
47
|
|
|
public $ipAttribute3 = 'ip_3'; |
|
48
|
|
|
public $ipAttribute4 = 'ip_4'; |
|
49
|
|
|
public $ipTypeAttribute = 'ip_type'; |
|
50
|
|
|
public $authKeyAttribute = 'auth_key'; |
|
51
|
|
|
public $accessTokenAttribute = 'access_token'; |
|
52
|
|
|
public $passwordResetTokenAttribute = 'password_reset_token'; |
|
53
|
|
|
public $statusAttribute = 'status'; |
|
54
|
|
|
public $sourceAttribute = 'source'; |
|
55
|
|
|
public $ns = 'app\models'; |
|
56
|
|
|
public $tableName; |
|
57
|
|
|
public $modelClass; |
|
58
|
|
|
public $baseClass = 'vistart\Models\models\BaseUserModel'; |
|
59
|
|
|
public $generatePresettingRules = false; |
|
60
|
|
|
public $generatePresettingBehaviors = false; |
|
61
|
|
|
public $generateRelations = true; |
|
62
|
|
|
public $generateLabelsFromComments = false; |
|
63
|
|
|
public $useTablePrefix = false; |
|
64
|
|
|
public $useSchemaName = true; |
|
65
|
|
|
public $generateQuery = false; |
|
66
|
|
|
public $queryNs = 'app\models'; |
|
67
|
|
|
public $queryClass; |
|
68
|
|
|
public $queryBaseClass = 'yii\db\ActiveQuery'; |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* @inheritdoc |
|
72
|
|
|
*/ |
|
73
|
|
|
public function getName() { |
|
74
|
|
|
return 'User Model Generator'; |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* @inheritdoc |
|
79
|
|
|
*/ |
|
80
|
|
|
public function getDescription() { |
|
81
|
|
|
return 'This generator generates an BaseUserModel class for the specified database table.'; |
|
82
|
|
|
} |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* @inheritdoc |
|
86
|
|
|
*/ |
|
87
|
|
|
public function rules() { |
|
88
|
|
|
return array_merge(parent::rules(), [ |
|
89
|
|
|
[['db', 'ns', 'tableName', 'guidAttribute', 'idAttribute', 'passwordHashAttribute', 'createdAtAttribute', 'updatedAtAttribute', 'ipAttribute1', 'ipAttribute2', 'ipAttribute3', 'ipAttribute4', 'ipTypeAttribute', 'authKeyAttribute', 'accessTokenAttribute', 'passwordResetTokenAttribute', 'statusAttribute', 'sourceAttribute', 'modelClass', 'baseClass', 'queryNs', 'queryClass', 'queryBaseClass'], 'filter', 'filter' => 'trim'], |
|
90
|
|
|
[['ns', 'queryNs'], 'filter', 'filter' => function($value) { |
|
91
|
|
|
return trim($value, '\\'); |
|
92
|
|
|
}], |
|
93
|
|
|
[['db', 'ns', 'tableName', 'baseClass', 'queryNs', 'queryBaseClass', 'guidAttribute', 'passwordHashAttribute', 'authKeyAttribute', 'accessTokenAttribute', 'passwordResetTokenAttribute'], 'required'], |
|
94
|
|
|
[['db', 'modelClass', 'queryClass'], 'match', 'pattern' => '/^\w+$/', 'message' => 'Only word characters are allowed.'], |
|
95
|
|
|
[['ns', 'baseClass', 'queryNs', 'queryBaseClass'], 'match', 'pattern' => '/^[\w\\\\]+$/', 'message' => 'Only word characters and backslashes are allowed.'], |
|
96
|
|
|
[['tableName'], 'match', 'pattern' => '/^(\w+\.)?([\w\*]+)$/', 'message' => 'Only word characters, and optionally an asterisk and/or a dot are allowed.'], |
|
97
|
|
|
[['db'], 'validateDb'], |
|
98
|
|
|
[['ns', 'queryNs'], 'validateNamespace'], |
|
99
|
|
|
[['tableName'], 'validateTableName'], |
|
100
|
|
|
[['modelClass'], 'validateModelClass', 'skipOnEmpty' => false], |
|
101
|
|
|
[['baseClass'], 'validateClass', 'params' => ['extends' => ActiveRecord::className()]], |
|
102
|
|
|
[['queryBaseClass'], 'validateClass', 'params' => ['extends' => ActiveQuery::className()]], |
|
103
|
|
|
[['enableIP', 'generatePresettingRules', 'generatePresettingBehaviors', 'generateRelations', 'generateLabelsFromComments', 'useTablePrefix', 'useSchemaName', 'generateQuery'], 'boolean'], |
|
104
|
|
|
[['guidAttribute', 'idAttribute', 'passwordHashAttribute', 'createdAtAttribute', 'updatedAtAttribute', 'ipAttribute1', 'ipAttribute2', 'ipAttribute3', 'ipAttribute4', 'ipTypeAttribute', 'authKeyAttribute', 'accessTokenAttribute', 'passwordResetTokenAttribute', 'statusAttribute', 'sourceAttribute'], 'validateFieldsExist'], |
|
105
|
|
|
[['enableIP', 'ipAttribute1', 'ipAttribute2', 'ipAttribute3', 'ipAttribute4', 'ipTypeAttribute'], 'validateIP'], |
|
106
|
|
|
[['enableI18N'], 'boolean'], |
|
107
|
|
|
[['messageCategory'], 'validateMessageCategory', 'skipOnEmpty' => false], |
|
108
|
|
|
]); |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
/** |
|
112
|
|
|
* @inheritdoc |
|
113
|
|
|
*/ |
|
114
|
|
|
public function attributeLabels() { |
|
115
|
|
|
return array_merge(parent::attributeLabels(), [ |
|
116
|
|
|
'ns' => 'Namespace', |
|
117
|
|
|
'db' => 'Database Connection ID', |
|
118
|
|
|
// 'guidAttribute' => 'guid', |
|
119
|
|
|
// 'idAttribute' => 'id', |
|
120
|
|
|
// 'passwordHashAttribute' => 'pass_hash', |
|
121
|
|
|
// 'createdAtAttribute' => 'create_time', |
|
122
|
|
|
// 'updatedAtAttribute' => 'update_time', |
|
123
|
|
|
// 'enableIP' => 'enableIP', |
|
124
|
|
|
// 'ipAttribute1' => 'ip_1', |
|
125
|
|
|
// 'ipAttribute2' => 'ip_2', |
|
126
|
|
|
// 'ipAttribute3' => 'ip_3', |
|
127
|
|
|
// 'ipAttribute4' => 'ip_4', |
|
128
|
|
|
// 'ipTypeAttribute' => 'ip_type', |
|
129
|
|
|
// 'authKeyAttribute' => 'auth_key', |
|
130
|
|
|
// 'accessTokenAttribute' => 'access_token', |
|
131
|
|
|
// 'passwordResetTokenAttribute' => 'password_reset_token', |
|
132
|
|
|
// 'statusAttribute' => 'status', |
|
133
|
|
|
// 'sourceAttribute' => 'source', |
|
134
|
|
|
'tableName' => 'Table Name', |
|
135
|
|
|
'modelClass' => 'Model Class', |
|
136
|
|
|
'baseClass' => 'Base Class', |
|
137
|
|
|
'generatePresettingRules' => 'Generate Presetting Rules', |
|
138
|
|
|
'generatePresettingBehaviors' => 'Generate Presetting Behaviors', |
|
139
|
|
|
'generateRelations' => 'Generate Relations', |
|
140
|
|
|
'generateLabelsFromComments' => 'Generate Labels from DB Comments', |
|
141
|
|
|
'generateQuery' => 'Generate ActiveQuery', |
|
142
|
|
|
'queryNs' => 'ActiveQuery Namespace', |
|
143
|
|
|
'queryClass' => 'ActiveQuery Class', |
|
144
|
|
|
'queryBaseClass' => 'ActiveQuery Base Class', |
|
145
|
|
|
]); |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* @inheritdoc |
|
150
|
|
|
*/ |
|
151
|
|
|
public function hints() { |
|
152
|
|
|
return array_merge(parent::hints(), [ |
|
153
|
|
|
'ns' => 'This is the namespace of the ActiveRecord class to be generated, e.g., <code>app\models</code>', |
|
154
|
|
|
'db' => 'This is the ID of the DB application component.', |
|
155
|
|
|
'tableName' => 'This is the name of the DB table that the new ActiveRecord class is associated with, e.g. <code>post</code>. |
|
156
|
|
|
The table name may consist of the DB schema part if needed, e.g. <code>public.post</code>. |
|
157
|
|
|
The table name may end with asterisk to match multiple table names, e.g. <code>tbl_*</code> |
|
158
|
|
|
will match tables who name starts with <code>tbl_</code>. In this case, multiple ActiveRecord classes |
|
159
|
|
|
will be generated, one for each matching table name; and the class names will be generated from |
|
160
|
|
|
the matching characters. For example, table <code>tbl_post</code> will generate <code>Post</code> |
|
161
|
|
|
class.', |
|
162
|
|
|
'modelClass' => 'This is the name of the ActiveRecord class to be generated. The class name should not contain |
|
163
|
|
|
the namespace part as it is specified in "Namespace". You do not need to specify the class name |
|
164
|
|
|
if "Table Name" ends with asterisk, in which case multiple ActiveRecord classes will be generated.', |
|
165
|
|
|
'baseClass' => 'This is the base class of the new ActiveRecord class. It should be a fully qualified namespaced class name.', |
|
166
|
|
|
'guidAttribute' => 'REQUIRED. GUID Attribute Name, as well as field name of user table.', |
|
167
|
|
|
'idAttribute' => 'OPTIONAL. ID Attribute Name, as well as field name of user table. Please left empty if no need for this feature.', |
|
168
|
|
|
'passwordHashAttribute' => 'REQUIRED. Password Hash Attribute Name, as well as field name of user table.', |
|
169
|
|
|
'createdAtAttribute' => 'OPTIONAL. Created At Attribute Name, as well as field name of user table. Please left empty if no need for this feature.', |
|
170
|
|
|
'updatedAtAttribute' => 'OPTIONAL. Updated At Attribute Name, as well as field name of user table. Please left empty if no need for this feature.', |
|
171
|
|
|
'enableIP' => 'Indicates whether enable the IP address feature. If true, the following five attributes are required.', |
|
172
|
|
|
'ipAttribute1' => 'IP Attribute 1 Name, as well as field name of user table.', |
|
173
|
|
|
'ipAttribute2' => 'IP Attribute 2 Name, as well as field name of user table.', |
|
174
|
|
|
'ipAttribute3' => 'IP Attribute 3 Name, as well as field name of user table.', |
|
175
|
|
|
'ipAttribute4' => 'IP Attribute 4 Name, as well as field name of user table.', |
|
176
|
|
|
'ipTypeAttribute' => 'IP Type Attribute Name, as well as field name of user table.', |
|
177
|
|
|
'authKeyAttribute' => 'REQUIRED. Auth Key Attribute Name, as well as field name of user table.', |
|
178
|
|
|
'accessTokenAttribute' => 'REQUIRED. Access Token Attribute Name, as well as field name of user table.', |
|
179
|
|
|
'passwordResetTokenAttribute' => 'OPTIONAL. Password Reset Token Attribute Name, as well as field name of user table. Please left empty if no need for this feature.', |
|
180
|
|
|
'sourceAttribute' => 'OPTIONAL. Source Attribute Name, as well as field name of user table. Please left empty if no need for this feature.', |
|
181
|
|
|
'statusAttribute' => 'OPTIONAL. Status Attribute Name, as well as field name of user table. Please left empty if no need for this feature.', |
|
182
|
|
|
'generatePresettingRules' => 'This indicates whether the generator should generate the presetting rules.', |
|
183
|
|
|
'generatePresettingBehaviors' => 'This indicates whether the generator should generate the presetting behaviors.', |
|
184
|
|
|
'generateRelations' => 'This indicates whether the generator should generate relations based on |
|
185
|
|
|
foreign key constraints it detects in the database. Note that if your database contains too many tables, |
|
186
|
|
|
you may want to uncheck this option to accelerate the code generation process.', |
|
187
|
|
|
'generateLabelsFromComments' => 'This indicates whether the generator should generate attribute labels |
|
188
|
|
|
by using the comments of the corresponding DB columns.', |
|
189
|
|
|
'useTablePrefix' => 'This indicates whether the table name returned by the generated ActiveRecord class |
|
190
|
|
|
should consider the <code>tablePrefix</code> setting of the DB connection. For example, if the |
|
191
|
|
|
table name is <code>tbl_post</code> and <code>tablePrefix=tbl_</code>, the ActiveRecord class |
|
192
|
|
|
will return the table name as <code>{{%post}}</code>.', |
|
193
|
|
|
'useSchemaName' => 'This indicates whether to include the schema name in the ActiveRecord class |
|
194
|
|
|
when it\'s auto generated. Only non default schema would be used.', |
|
195
|
|
|
'generateQuery' => 'This indicates whether to generate ActiveQuery for the ActiveRecord class.', |
|
196
|
|
|
'queryNs' => 'This is the namespace of the ActiveQuery class to be generated, e.g., <code>app\models</code>', |
|
197
|
|
|
'queryClass' => 'This is the name of the ActiveQuery class to be generated. The class name should not contain |
|
198
|
|
|
the namespace part as it is specified in "ActiveQuery Namespace". You do not need to specify the class name |
|
199
|
|
|
if "Table Name" ends with asterisk, in which case multiple ActiveQuery classes will be generated.', |
|
200
|
|
|
'queryBaseClass' => 'This is the base class of the new ActiveQuery class. It should be a fully qualified namespaced class name.', |
|
201
|
|
|
]); |
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
|
/** |
|
205
|
|
|
* @inheritdoc |
|
206
|
|
|
*/ |
|
207
|
|
|
public function autoCompleteData() { |
|
208
|
|
|
$db = $this->getDbConnection(); |
|
209
|
|
|
if ($db !== null) { |
|
210
|
|
|
return [ |
|
211
|
|
|
'tableName' => function () use ($db) { |
|
212
|
|
|
return $db->getSchema()->getTableNames(); |
|
213
|
|
|
}, |
|
214
|
|
|
]; |
|
215
|
|
|
} else { |
|
216
|
|
|
return []; |
|
217
|
|
|
} |
|
218
|
|
|
} |
|
219
|
|
|
|
|
220
|
|
|
/** |
|
221
|
|
|
* @inheritdoc |
|
222
|
|
|
*/ |
|
223
|
|
|
public function requiredTemplates() { |
|
224
|
|
|
// @todo make 'query.php' to be required before 2.1 release |
|
225
|
|
|
return ['model.php'/* , 'query.php' */]; |
|
226
|
|
|
} |
|
227
|
|
|
|
|
228
|
|
|
/** |
|
229
|
|
|
* @inheritdoc |
|
230
|
|
|
*/ |
|
231
|
|
|
public function stickyAttributes() { |
|
232
|
|
|
return array_merge(parent::stickyAttributes(), ['ns', 'db', 'baseClass', 'generatePresettingRules', 'generatePresettingBehaviors', 'generateRelations', 'generateLabelsFromComments', 'useTablePrefix', 'queryNs', 'queryBaseClass']); |
|
233
|
|
|
} |
|
234
|
|
|
|
|
235
|
|
|
public function generate() { |
|
236
|
|
|
$files = []; |
|
237
|
|
|
$relations = $this->generateRelations(); |
|
238
|
|
|
$db = $this->getDbConnection(); |
|
239
|
|
|
foreach ($this->getTableNames() as $tableName) { |
|
240
|
|
|
// model : |
|
241
|
|
|
$modelClassName = $this->generateClassName($tableName); |
|
242
|
|
|
$queryClassName = ($this->generateQuery) ? $this->generateQueryClassName($modelClassName) : false; |
|
243
|
|
|
$tableSchema = $db->getTableSchema($tableName); |
|
244
|
|
|
$params = [ |
|
245
|
|
|
'tableName' => $tableName, |
|
246
|
|
|
'className' => $modelClassName, |
|
247
|
|
|
'queryClassName' => $queryClassName, |
|
248
|
|
|
'tableSchema' => $tableSchema, |
|
249
|
|
|
'labels' => $this->generateLabels($tableSchema), |
|
250
|
|
|
'rules' => $this->generatePresettingRules, |
|
251
|
|
|
'behaviors' => $this->generatePresettingBehaviors, |
|
252
|
|
|
'relations' => isset($relations[$tableName]) ? $relations[$tableName] : [], |
|
253
|
|
|
]; |
|
254
|
|
|
$files[] = new CodeFile( |
|
255
|
|
|
Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/' . $modelClassName . '.php', $this->render('model.php', $params) |
|
256
|
|
|
); |
|
257
|
|
|
|
|
258
|
|
|
// query : |
|
259
|
|
|
if ($queryClassName) { |
|
|
|
|
|
|
260
|
|
|
$params = [ |
|
261
|
|
|
'className' => $queryClassName, |
|
262
|
|
|
'modelClassName' => $modelClassName, |
|
263
|
|
|
]; |
|
264
|
|
|
$files[] = new CodeFile( |
|
265
|
|
|
Yii::getAlias('@' . str_replace('\\', '/', $this->queryNs)) . '/' . $queryClassName . '.php', $this->render('query.php', $params) |
|
266
|
|
|
); |
|
267
|
|
|
} |
|
268
|
|
|
} |
|
269
|
|
|
return $files; |
|
270
|
|
|
} |
|
271
|
|
|
|
|
272
|
|
|
/** |
|
273
|
|
|
* Generates the attribute labels for the specified table. |
|
274
|
|
|
* @param \yii\db\TableSchema $table the table schema |
|
275
|
|
|
* @return array the generated attribute labels (name => label) |
|
276
|
|
|
*/ |
|
277
|
|
|
public function generateLabels($table) { |
|
278
|
|
|
$labels = []; |
|
279
|
|
|
foreach ($table->columns as $column) { |
|
280
|
|
|
if ($this->generateLabelsFromComments && !empty($column->comment)) { |
|
281
|
|
|
$labels[$column->name] = $column->comment; |
|
282
|
|
|
} elseif (!strcasecmp($column->name, 'id')) { |
|
283
|
|
|
$labels[$column->name] = 'ID'; |
|
284
|
|
|
} else { |
|
285
|
|
|
$label = Inflector::camel2words($column->name); |
|
286
|
|
|
if (!empty($label) && substr_compare($label, ' id', -3, 3, true) === 0) { |
|
287
|
|
|
$label = substr($label, 0, -3) . ' ID'; |
|
288
|
|
|
} |
|
289
|
|
|
$labels[$column->name] = $label; |
|
290
|
|
|
} |
|
291
|
|
|
} |
|
292
|
|
|
|
|
293
|
|
|
return $labels; |
|
294
|
|
|
} |
|
295
|
|
|
|
|
296
|
|
|
/** |
|
297
|
|
|
* Generates relations using a junction table by adding an extra viaTable(). |
|
298
|
|
|
* @param \yii\db\TableSchema the table being checked |
|
299
|
|
|
* @param array $fks obtained from the checkPivotTable() method |
|
300
|
|
|
* @param array $relations |
|
301
|
|
|
* @return array modified $relations |
|
302
|
|
|
*/ |
|
303
|
|
|
private function generateManyManyRelations($table, $fks, $relations) { |
|
304
|
|
|
$db = $this->getDbConnection(); |
|
305
|
|
|
$table0 = $fks[$table->primaryKey[0]][0]; |
|
306
|
|
|
$table1 = $fks[$table->primaryKey[1]][0]; |
|
307
|
|
|
$className0 = $this->generateClassName($table0); |
|
308
|
|
|
$className1 = $this->generateClassName($table1); |
|
309
|
|
|
$table0Schema = $db->getTableSchema($table0); |
|
310
|
|
|
$table1Schema = $db->getTableSchema($table1); |
|
311
|
|
|
|
|
312
|
|
|
$link = $this->generateRelationLink([$fks[$table->primaryKey[1]][1] => $table->primaryKey[1]]); |
|
313
|
|
|
$viaLink = $this->generateRelationLink([$table->primaryKey[0] => $fks[$table->primaryKey[0]][1]]); |
|
314
|
|
|
$relationName = $this->generateRelationName($relations, $table0Schema, $table->primaryKey[1], true); |
|
315
|
|
|
$relations[$table0Schema->fullName][$relationName] = [ |
|
316
|
|
|
"return \$this->hasMany($className1::className(), $link)->viaTable('" |
|
317
|
|
|
. $this->generateTableName($table->name) . "', $viaLink);", |
|
318
|
|
|
$className1, |
|
319
|
|
|
true, |
|
320
|
|
|
]; |
|
321
|
|
|
|
|
322
|
|
|
$link = $this->generateRelationLink([$fks[$table->primaryKey[0]][1] => $table->primaryKey[0]]); |
|
323
|
|
|
$viaLink = $this->generateRelationLink([$table->primaryKey[1] => $fks[$table->primaryKey[1]][1]]); |
|
324
|
|
|
$relationName = $this->generateRelationName($relations, $table1Schema, $table->primaryKey[0], true); |
|
325
|
|
|
$relations[$table1Schema->fullName][$relationName] = [ |
|
326
|
|
|
"return \$this->hasMany($className0::className(), $link)->viaTable('" |
|
327
|
|
|
. $this->generateTableName($table->name) . "', $viaLink);", |
|
328
|
|
|
$className0, |
|
329
|
|
|
true, |
|
330
|
|
|
]; |
|
331
|
|
|
|
|
332
|
|
|
return $relations; |
|
333
|
|
|
} |
|
334
|
|
|
|
|
335
|
|
|
/** |
|
336
|
|
|
* @return array the generated relation declarations |
|
337
|
|
|
*/ |
|
338
|
|
|
protected function generateRelations() { |
|
339
|
|
|
if (!$this->generateRelations) { |
|
340
|
|
|
return []; |
|
341
|
|
|
} |
|
342
|
|
|
|
|
343
|
|
|
$db = $this->getDbConnection(); |
|
344
|
|
|
|
|
345
|
|
|
$schema = $db->getSchema(); |
|
346
|
|
|
if ($schema->hasMethod('getSchemaNames')) { // keep BC to Yii versions < 2.0.4 |
|
347
|
|
|
try { |
|
348
|
|
|
$schemaNames = $schema->getSchemaNames(); |
|
349
|
|
|
} catch (NotSupportedException $e) { |
|
350
|
|
|
// schema names are not supported by schema |
|
351
|
|
|
} |
|
352
|
|
|
} |
|
353
|
|
|
if (!isset($schemaNames)) { |
|
354
|
|
|
if (($pos = strpos($this->tableName, '.')) !== false) { |
|
355
|
|
|
$schemaNames = [substr($this->tableName, 0, $pos)]; |
|
356
|
|
|
} else { |
|
357
|
|
|
$schemaNames = ['']; |
|
358
|
|
|
} |
|
359
|
|
|
} |
|
360
|
|
|
|
|
361
|
|
|
$relations = []; |
|
362
|
|
|
foreach ($schemaNames as $schemaName) { |
|
363
|
|
|
foreach ($db->getSchema()->getTableSchemas($schemaName) as $table) { |
|
364
|
|
|
$className = $this->generateClassName($table->fullName); |
|
365
|
|
|
foreach ($table->foreignKeys as $refs) { |
|
366
|
|
|
$refTable = $refs[0]; |
|
367
|
|
|
$refTableSchema = $db->getTableSchema($refTable); |
|
368
|
|
|
unset($refs[0]); |
|
369
|
|
|
$fks = array_keys($refs); |
|
370
|
|
|
$refClassName = $this->generateClassName($refTable); |
|
371
|
|
|
|
|
372
|
|
|
// Add relation for this table |
|
373
|
|
|
$link = $this->generateRelationLink(array_flip($refs)); |
|
374
|
|
|
$relationName = $this->generateRelationName($relations, $table, $fks[0], false); |
|
375
|
|
|
$relations[$table->fullName][$relationName] = [ |
|
376
|
|
|
"return \$this->hasOne($refClassName::className(), $link);", |
|
377
|
|
|
$refClassName, |
|
378
|
|
|
false, |
|
379
|
|
|
]; |
|
380
|
|
|
|
|
381
|
|
|
// Add relation for the referenced table |
|
382
|
|
|
$uniqueKeys = [$table->primaryKey]; |
|
383
|
|
|
try { |
|
384
|
|
|
$uniqueKeys = array_merge($uniqueKeys, $db->getSchema()->findUniqueIndexes($table)); |
|
385
|
|
|
} catch (NotSupportedException $e) { |
|
386
|
|
|
// ignore |
|
387
|
|
|
} |
|
388
|
|
|
$hasMany = true; |
|
389
|
|
|
foreach ($uniqueKeys as $uniqueKey) { |
|
390
|
|
|
if (count(array_diff(array_merge($uniqueKey, $fks), array_intersect($uniqueKey, $fks))) === 0) { |
|
391
|
|
|
$hasMany = false; |
|
392
|
|
|
break; |
|
393
|
|
|
} |
|
394
|
|
|
} |
|
395
|
|
|
$link = $this->generateRelationLink($refs); |
|
396
|
|
|
$relationName = $this->generateRelationName($relations, $refTableSchema, $className, $hasMany); |
|
397
|
|
|
$relations[$refTableSchema->fullName][$relationName] = [ |
|
398
|
|
|
"return \$this->" . ($hasMany ? 'hasMany' : 'hasOne') . "($className::className(), $link);", |
|
399
|
|
|
$className, |
|
400
|
|
|
$hasMany, |
|
401
|
|
|
]; |
|
402
|
|
|
} |
|
403
|
|
|
|
|
404
|
|
|
if (($fks = $this->checkPivotTable($table)) === false) { |
|
405
|
|
|
continue; |
|
406
|
|
|
} |
|
407
|
|
|
|
|
408
|
|
|
$relations = $this->generateManyManyRelations($table, $fks, $relations); |
|
409
|
|
|
} |
|
410
|
|
|
} |
|
411
|
|
|
|
|
412
|
|
|
return $relations; |
|
413
|
|
|
} |
|
414
|
|
|
|
|
415
|
|
|
/** |
|
416
|
|
|
* Generates the link parameter to be used in generating the relation declaration. |
|
417
|
|
|
* @param array $refs reference constraint |
|
418
|
|
|
* @return string the generated link parameter. |
|
419
|
|
|
*/ |
|
420
|
|
|
protected function generateRelationLink($refs) { |
|
421
|
|
|
$pairs = []; |
|
422
|
|
|
foreach ($refs as $a => $b) { |
|
423
|
|
|
$pairs[] = "'$a' => '$b'"; |
|
424
|
|
|
} |
|
425
|
|
|
|
|
426
|
|
|
return '[' . implode(', ', $pairs) . ']'; |
|
427
|
|
|
} |
|
428
|
|
|
|
|
429
|
|
|
protected function generateRules($table) { |
|
430
|
|
|
|
|
431
|
|
|
} |
|
432
|
|
|
|
|
433
|
|
|
/** |
|
434
|
|
|
* Checks if the given table is a junction table. |
|
435
|
|
|
* For simplicity, this method only deals with the case where the pivot contains two PK columns, |
|
436
|
|
|
* each referencing a column in a different table. |
|
437
|
|
|
* @param \yii\db\TableSchema the table being checked |
|
438
|
|
|
* @return array|boolean the relevant foreign key constraint information if the table is a junction table, |
|
439
|
|
|
* or false if the table is not a junction table. |
|
440
|
|
|
*/ |
|
441
|
|
|
protected function checkPivotTable($table) { |
|
442
|
|
|
$pk = $table->primaryKey; |
|
443
|
|
|
if (count($pk) !== 2) { |
|
444
|
|
|
return false; |
|
445
|
|
|
} |
|
446
|
|
|
$fks = []; |
|
447
|
|
|
foreach ($table->foreignKeys as $refs) { |
|
448
|
|
|
if (count($refs) === 2) { |
|
449
|
|
|
if (isset($refs[$pk[0]])) { |
|
450
|
|
|
$fks[$pk[0]] = [$refs[0], $refs[$pk[0]]]; |
|
451
|
|
|
} elseif (isset($refs[$pk[1]])) { |
|
452
|
|
|
$fks[$pk[1]] = [$refs[0], $refs[$pk[1]]]; |
|
453
|
|
|
} |
|
454
|
|
|
} |
|
455
|
|
|
} |
|
456
|
|
|
if (count($fks) === 2 && $fks[$pk[0]][0] !== $fks[$pk[1]][0]) { |
|
457
|
|
|
return $fks; |
|
458
|
|
|
} else { |
|
459
|
|
|
return false; |
|
460
|
|
|
} |
|
461
|
|
|
} |
|
462
|
|
|
|
|
463
|
|
|
/** |
|
464
|
|
|
* Generate a relation name for the specified table and a base name. |
|
465
|
|
|
* @param array $relations the relations being generated currently. |
|
466
|
|
|
* @param \yii\db\TableSchema $table the table schema |
|
467
|
|
|
* @param string $key a base name that the relation name may be generated from |
|
468
|
|
|
* @param boolean $multiple whether this is a has-many relation |
|
469
|
|
|
* @return string the relation name |
|
470
|
|
|
*/ |
|
471
|
|
|
protected function generateRelationName($relations, $table, $key, $multiple) { |
|
472
|
|
|
if (!empty($key) && substr_compare($key, 'id', -2, 2, true) === 0 && strcasecmp($key, 'id')) { |
|
473
|
|
|
$key = rtrim(substr($key, 0, -2), '_'); |
|
474
|
|
|
} |
|
475
|
|
|
if ($multiple) { |
|
476
|
|
|
$key = Inflector::pluralize($key); |
|
477
|
|
|
} |
|
478
|
|
|
$name = $rawName = Inflector::id2camel($key, '_'); |
|
479
|
|
|
$i = 0; |
|
480
|
|
|
while (isset($table->columns[lcfirst($name)])) { |
|
481
|
|
|
$name = $rawName . ($i++); |
|
482
|
|
|
} |
|
483
|
|
|
while (isset($relations[$table->fullName][$name])) { |
|
484
|
|
|
$name = $rawName . ($i++); |
|
485
|
|
|
} |
|
486
|
|
|
|
|
487
|
|
|
return $name; |
|
488
|
|
|
} |
|
489
|
|
|
|
|
490
|
|
|
public function validateFieldsExist() { |
|
491
|
|
|
$db = $this->getDbConnection(); |
|
492
|
|
|
foreach ($this->getTableNames() as $table) { |
|
493
|
|
|
$tableSchema = $db->getTableSchema($table); |
|
494
|
|
|
$fields = [ |
|
495
|
|
|
'guidAttribute', |
|
496
|
|
|
'idAttribute', |
|
497
|
|
|
'passwordHashAttribute', |
|
498
|
|
|
'createdAtAttribute', |
|
499
|
|
|
'updatedAtAttribute', |
|
500
|
|
|
'ipAttribute1', |
|
501
|
|
|
'ipAttribute2', |
|
502
|
|
|
'ipAttribute3', |
|
503
|
|
|
'ipAttribute4', |
|
504
|
|
|
'ipTypeAttribute', |
|
505
|
|
|
'authKeyAttribute', |
|
506
|
|
|
'accessTokenAttribute', |
|
507
|
|
|
'passwordResetTokenAttribute', |
|
508
|
|
|
'statusAttribute', |
|
509
|
|
|
'sourceAttribute', |
|
510
|
|
|
]; |
|
511
|
|
|
foreach ($fields as $field) { |
|
512
|
|
|
if (isset($this->$field) && $this->$field != '' && !isset($tableSchema->columns[$this->$field])) { |
|
513
|
|
|
$this->addError($field, '`' . $this->$field . '` field does not exist in `' . $table . '` table.'); |
|
514
|
|
|
continue; |
|
515
|
|
|
} |
|
516
|
|
|
} |
|
517
|
|
|
} |
|
518
|
|
|
} |
|
519
|
|
|
|
|
520
|
|
|
/** |
|
521
|
|
|
* Validates the [[db]] attribute. |
|
522
|
|
|
*/ |
|
523
|
|
|
public function validateDb() { |
|
524
|
|
|
if (!Yii::$app->has($this->db)) { |
|
525
|
|
|
$this->addError('db', 'There is no application component named "db".'); |
|
526
|
|
|
} elseif (!Yii::$app->get($this->db) instanceof Connection) { |
|
527
|
|
|
$this->addError('db', 'The "db" application component must be a DB connection instance.'); |
|
528
|
|
|
} |
|
529
|
|
|
} |
|
530
|
|
|
|
|
531
|
|
|
public function validateIP() { |
|
532
|
|
|
if (!$this->enableIP) { |
|
533
|
|
|
return; |
|
534
|
|
|
} |
|
535
|
|
|
$db = $this->getDbConnection(); |
|
|
|
|
|
|
536
|
|
|
$ipAttribute = 'ipAttribute'; |
|
537
|
|
|
for ($i = 1; $i <= 4; $i++) { |
|
538
|
|
|
$ipAttributeName = $ipAttribute . $i; |
|
539
|
|
|
if (empty($this->$ipAttributeName)) { |
|
540
|
|
|
$this->addError($ipAttributeName, "`$ipAttributeName` should not be empty."); |
|
541
|
|
|
continue; |
|
542
|
|
|
} |
|
543
|
|
|
} |
|
544
|
|
|
if (empty($this->ipTypeAttribute)) { |
|
545
|
|
|
$this->addError('ipTypeAttribute', "`ipTypeAttribute` should not be empty."); |
|
546
|
|
|
return; |
|
547
|
|
|
} |
|
548
|
|
|
} |
|
549
|
|
|
|
|
550
|
|
|
/** |
|
551
|
|
|
* Validates the [[ns]] attribute. |
|
552
|
|
|
*/ |
|
553
|
|
|
public function validateNamespace() { |
|
554
|
|
|
$this->ns = ltrim($this->ns, '\\'); |
|
555
|
|
|
$path = Yii::getAlias('@' . str_replace('\\', '/', $this->ns), false); |
|
556
|
|
|
if ($path === false) { |
|
557
|
|
|
$this->addError('ns', 'Namespace must be associated with an existing directory.'); |
|
558
|
|
|
} |
|
559
|
|
|
} |
|
560
|
|
|
|
|
561
|
|
|
/** |
|
562
|
|
|
* Validates the [[modelClass]] attribute. |
|
563
|
|
|
*/ |
|
564
|
|
|
public function validateModelClass() { |
|
565
|
|
|
if ($this->isReservedKeyword($this->modelClass)) { |
|
566
|
|
|
$this->addError('modelClass', 'Class name cannot be a reserved PHP keyword.'); |
|
567
|
|
|
} |
|
568
|
|
|
if ((empty($this->tableName) || substr_compare($this->tableName, '*', -1, 1)) && $this->modelClass == '') { |
|
569
|
|
|
$this->addError('modelClass', 'Model Class cannot be blank if table name does not end with asterisk.'); |
|
570
|
|
|
} |
|
571
|
|
|
} |
|
572
|
|
|
|
|
573
|
|
|
/** |
|
574
|
|
|
* Validates the [[tableName]] attribute. |
|
575
|
|
|
*/ |
|
576
|
|
|
public function validateTableName() { |
|
577
|
|
|
if (strpos($this->tableName, '*') !== false && substr_compare($this->tableName, '*', -1, 1)) { |
|
578
|
|
|
$this->addError('tableName', 'Asterisk is only allowed as the last character.'); |
|
579
|
|
|
|
|
580
|
|
|
return; |
|
581
|
|
|
} |
|
582
|
|
|
$tables = $this->getTableNames(); |
|
583
|
|
|
if (empty($tables)) { |
|
584
|
|
|
$this->addError('tableName', "Table '{$this->tableName}' does not exist."); |
|
585
|
|
|
} else { |
|
586
|
|
|
foreach ($tables as $table) { |
|
587
|
|
|
$class = $this->generateClassName($table); |
|
588
|
|
|
if ($this->isReservedKeyword($class)) { |
|
589
|
|
|
$this->addError('tableName', "Table '$table' will generate a class which is a reserved PHP keyword."); |
|
590
|
|
|
break; |
|
591
|
|
|
} |
|
592
|
|
|
} |
|
593
|
|
|
} |
|
594
|
|
|
} |
|
595
|
|
|
|
|
596
|
|
|
protected $tableNames; |
|
597
|
|
|
protected $classNames; |
|
598
|
|
|
|
|
599
|
|
|
/** |
|
600
|
|
|
* @return array the table names that match the pattern specified by [[tableName]]. |
|
601
|
|
|
*/ |
|
602
|
|
|
protected function getTableNames() { |
|
603
|
|
|
if ($this->tableNames !== null) { |
|
604
|
|
|
return $this->tableNames; |
|
605
|
|
|
} |
|
606
|
|
|
$db = $this->getDbConnection(); |
|
607
|
|
|
if ($db === null) { |
|
608
|
|
|
return []; |
|
609
|
|
|
} |
|
610
|
|
|
$tableNames = []; |
|
611
|
|
|
if (strpos($this->tableName, '*') !== false) { |
|
612
|
|
|
if (($pos = strrpos($this->tableName, '.')) !== false) { |
|
613
|
|
|
$schema = substr($this->tableName, 0, $pos); |
|
614
|
|
|
$pattern = '/^' . str_replace('*', '\w+', substr($this->tableName, $pos + 1)) . '$/'; |
|
615
|
|
|
} else { |
|
616
|
|
|
$schema = ''; |
|
617
|
|
|
$pattern = '/^' . str_replace('*', '\w+', $this->tableName) . '$/'; |
|
618
|
|
|
} |
|
619
|
|
|
|
|
620
|
|
|
foreach ($db->schema->getTableNames($schema) as $table) { |
|
621
|
|
|
if (preg_match($pattern, $table)) { |
|
622
|
|
|
$tableNames[] = $schema === '' ? $table : ($schema . '.' . $table); |
|
623
|
|
|
} |
|
624
|
|
|
} |
|
625
|
|
|
} elseif (($table = $db->getTableSchema($this->tableName, true)) !== null) { |
|
626
|
|
|
$tableNames[] = $this->tableName; |
|
627
|
|
|
$this->classNames[$this->tableName] = $this->modelClass; |
|
628
|
|
|
} |
|
629
|
|
|
|
|
630
|
|
|
return $this->tableNames = $tableNames; |
|
631
|
|
|
} |
|
632
|
|
|
|
|
633
|
|
|
/** |
|
634
|
|
|
* Generates the table name by considering table prefix. |
|
635
|
|
|
* If [[useTablePrefix]] is false, the table name will be returned without change. |
|
636
|
|
|
* @param string $tableName the table name (which may contain schema prefix) |
|
637
|
|
|
* @return string the generated table name |
|
638
|
|
|
*/ |
|
639
|
|
|
public function generateTableName($tableName) { |
|
640
|
|
|
if (!$this->useTablePrefix) { |
|
641
|
|
|
return $tableName; |
|
642
|
|
|
} |
|
643
|
|
|
|
|
644
|
|
|
$db = $this->getDbConnection(); |
|
645
|
|
|
if (preg_match("/^{$db->tablePrefix}(.*?)$/", $tableName, $matches)) { |
|
646
|
|
|
$tableName = '{{%' . $matches[1] . '}}'; |
|
647
|
|
|
} elseif (preg_match("/^(.*?){$db->tablePrefix}$/", $tableName, $matches)) { |
|
648
|
|
|
$tableName = '{{' . $matches[1] . '%}}'; |
|
649
|
|
|
} |
|
650
|
|
|
return $tableName; |
|
651
|
|
|
} |
|
652
|
|
|
|
|
653
|
|
|
/** |
|
654
|
|
|
* Generates a class name from the specified table name. |
|
655
|
|
|
* @param string $tableName the table name (which may contain schema prefix) |
|
656
|
|
|
* @param boolean $useSchemaName should schema name be included in the class name, if present |
|
657
|
|
|
* @return string the generated class name |
|
658
|
|
|
*/ |
|
659
|
|
|
protected function generateClassName($tableName, $useSchemaName = null) { |
|
660
|
|
|
if (isset($this->classNames[$tableName])) { |
|
661
|
|
|
return $this->classNames[$tableName]; |
|
662
|
|
|
} |
|
663
|
|
|
|
|
664
|
|
|
$schemaName = ''; |
|
665
|
|
|
$fullTableName = $tableName; |
|
666
|
|
|
if (($pos = strrpos($tableName, '.')) !== false) { |
|
667
|
|
|
if (($useSchemaName === null && $this->useSchemaName) || $useSchemaName) { |
|
668
|
|
|
$schemaName = substr($tableName, 0, $pos) . '_'; |
|
669
|
|
|
} |
|
670
|
|
|
$tableName = substr($tableName, $pos + 1); |
|
671
|
|
|
} |
|
672
|
|
|
|
|
673
|
|
|
$db = $this->getDbConnection(); |
|
674
|
|
|
$patterns = []; |
|
675
|
|
|
$patterns[] = "/^{$db->tablePrefix}(.*?)$/"; |
|
676
|
|
|
$patterns[] = "/^(.*?){$db->tablePrefix}$/"; |
|
677
|
|
|
if (strpos($this->tableName, '*') !== false) { |
|
678
|
|
|
$pattern = $this->tableName; |
|
679
|
|
|
if (($pos = strrpos($pattern, '.')) !== false) { |
|
680
|
|
|
$pattern = substr($pattern, $pos + 1); |
|
681
|
|
|
} |
|
682
|
|
|
$patterns[] = '/^' . str_replace('*', '(\w+)', $pattern) . '$/'; |
|
683
|
|
|
} |
|
684
|
|
|
$className = $tableName; |
|
685
|
|
|
foreach ($patterns as $pattern) { |
|
686
|
|
|
if (preg_match($pattern, $tableName, $matches)) { |
|
687
|
|
|
$className = $matches[1]; |
|
688
|
|
|
break; |
|
689
|
|
|
} |
|
690
|
|
|
} |
|
691
|
|
|
|
|
692
|
|
|
return $this->classNames[$fullTableName] = Inflector::id2camel($schemaName . $className, '_'); |
|
693
|
|
|
} |
|
694
|
|
|
|
|
695
|
|
|
/** |
|
696
|
|
|
* Generates a query class name from the specified model class name. |
|
697
|
|
|
* @param string $modelClassName model class name |
|
698
|
|
|
* @return string generated class name |
|
699
|
|
|
*/ |
|
700
|
|
|
protected function generateQueryClassName($modelClassName) { |
|
701
|
|
|
$queryClassName = $this->queryClass; |
|
702
|
|
|
if (empty($queryClassName) || strpos($this->tableName, '*') !== false) { |
|
703
|
|
|
$queryClassName = $modelClassName . 'Query'; |
|
704
|
|
|
} |
|
705
|
|
|
return $queryClassName; |
|
706
|
|
|
} |
|
707
|
|
|
|
|
708
|
|
|
/** |
|
709
|
|
|
* @return Connection the DB connection as specified by [[db]]. |
|
710
|
|
|
*/ |
|
711
|
|
|
protected function getDbConnection() { |
|
712
|
|
|
return Yii::$app->get($this->db, false); |
|
713
|
|
|
} |
|
714
|
|
|
|
|
715
|
|
|
/** |
|
716
|
|
|
* Checks if any of the specified columns is auto incremental. |
|
717
|
|
|
* @param \yii\db\TableSchema $table the table schema |
|
718
|
|
|
* @param array $columns columns to check for autoIncrement property |
|
719
|
|
|
* @return boolean whether any of the specified columns is auto incremental. |
|
720
|
|
|
*/ |
|
721
|
|
|
protected function isColumnAutoIncremental($table, $columns) { |
|
722
|
|
|
foreach ($columns as $column) { |
|
723
|
|
|
if (isset($table->columns[$column]) && $table->columns[$column]->autoIncrement) { |
|
724
|
|
|
return true; |
|
725
|
|
|
} |
|
726
|
|
|
} |
|
727
|
|
|
|
|
728
|
|
|
return false; |
|
729
|
|
|
} |
|
730
|
|
|
|
|
731
|
|
|
} |
|
732
|
|
|
|
In PHP, under loose comparison (like
==, or!=, orswitchconditions), values of different types might be equal.For
stringvalues, the empty string''is a special case, in particular the following results might be unexpected: