Generator.php 18.2 KB
Newer Older
Qiang Xue committed
1 2 3 4 5 6 7 8 9
<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\gii\generators\model;

Qiang Xue committed
10
use Yii;
Qiang Xue committed
11
use yii\db\ActiveRecord;
Qiang Xue committed
12
use yii\db\Connection;
13
use yii\db\Schema;
Qiang Xue committed
14
use yii\gii\CodeFile;
Qiang Xue committed
15
use yii\helpers\Inflector;
16
use yii\base\NotSupportedException;
Qiang Xue committed
17

Qiang Xue committed
18
/**
Qiang Xue committed
19
 * This generator will generate one or multiple ActiveRecord classes for the specified database table.
Qiang Xue committed
20 21 22 23 24 25
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class Generator extends \yii\gii\Generator
{
Qiang Xue committed
26
	public $db = 'db';
Qiang Xue committed
27
	public $ns = 'app\models';
Qiang Xue committed
28 29
	public $tableName;
	public $modelClass;
Qiang Xue committed
30
	public $baseClass = 'yii\db\ActiveRecord';
Qiang Xue committed
31
	public $generateRelations = true;
Qiang Xue committed
32
	public $generateLabelsFromComments = false;
Qiang Xue committed
33 34


Qiang Xue committed
35
	/**
Qiang Xue committed
36
	 * @inheritdoc
Qiang Xue committed
37
	 */
Qiang Xue committed
38 39 40 41 42
	public function getName()
	{
		return 'Model Generator';
	}

Qiang Xue committed
43
	/**
Qiang Xue committed
44
	 * @inheritdoc
Qiang Xue committed
45
	 */
Qiang Xue committed
46 47
	public function getDescription()
	{
Qiang Xue committed
48
		return 'This generator generates an ActiveRecord class for the specified database table.';
Qiang Xue committed
49
	}
50

Qiang Xue committed
51
	/**
Qiang Xue committed
52
	 * @inheritdoc
Qiang Xue committed
53
	 */
Qiang Xue committed
54 55
	public function rules()
	{
Alexander Makarov committed
56
		return array_merge(parent::rules(), [
57 58 59 60 61 62 63 64 65 66 67
			[['db', 'ns', 'tableName', 'modelClass', 'baseClass'], 'filter', 'filter' => 'trim'],
			[['db', 'ns', 'tableName', 'baseClass'], 'required'],
			[['db', 'modelClass'], 'match', 'pattern' => '/^\w+$/', 'message' => 'Only word characters are allowed.'],
			[['ns', 'baseClass'], 'match', 'pattern' => '/^[\w\\\\]+$/', 'message' => 'Only word characters and backslashes are allowed.'],
			[['tableName'], 'match', 'pattern' => '/^(\w+\.)?([\w\*]+)$/', 'message' => 'Only word characters, and optionally an asterisk and/or a dot are allowed.'],
			[['db'], 'validateDb'],
			[['ns'], 'validateNamespace'],
			[['tableName'], 'validateTableName'],
			[['modelClass'], 'validateModelClass', 'skipOnEmpty' => false],
			[['baseClass'], 'validateClass', 'params' => ['extends' => ActiveRecord::className()]],
			[['generateRelations', 'generateLabelsFromComments'], 'boolean'],
Alexander Makarov committed
68
		]);
Qiang Xue committed
69 70
	}

Qiang Xue committed
71
	/**
Qiang Xue committed
72
	 * @inheritdoc
Qiang Xue committed
73
	 */
Qiang Xue committed
74 75
	public function attributeLabels()
	{
Alexander Makarov committed
76
		return [
Qiang Xue committed
77 78
			'ns' => 'Namespace',
			'db' => 'Database Connection ID',
Qiang Xue committed
79 80 81
			'tableName' => 'Table Name',
			'modelClass' => 'Model Class',
			'baseClass' => 'Base Class',
Qiang Xue committed
82
			'generateRelations' => 'Generate Relations',
Qiang Xue committed
83
			'generateLabelsFromComments' => 'Generate Labels from DB Comments',
Alexander Makarov committed
84
		];
Qiang Xue committed
85 86
	}

Qiang Xue committed
87
	/**
Qiang Xue committed
88
	 * @inheritdoc
Qiang Xue committed
89
	 */
Qiang Xue committed
90 91
	public function hints()
	{
Alexander Makarov committed
92
		return [
Qiang Xue committed
93 94 95 96
			'ns' => 'This is the namespace of the ActiveRecord class to be generated, e.g., <code>app\models</code>',
			'db' => 'This is the ID of the DB application component.',
			'tableName' => 'This is the name of the DB table that the new ActiveRecord class is associated with, e.g. <code>tbl_post</code>.
				The table name may consist of the DB schema part if needed, e.g. <code>public.tbl_post</code>.
97
				The table name may end with asterisk to match multiple table names, e.g. <code>tbl_*</code>
Qiang Xue committed
98 99 100 101
				will match tables who name starts with <code>tbl_</code>. In this case, multiple ActiveRecord classes
				will be generated, one for each matching table name; and the class names will be generated from
				the matching characters. For example, table <code>tbl_post</code> will generate <code>Post</code>
				class.',
Qiang Xue committed
102 103
			'modelClass' => 'This is the name of the ActiveRecord class to be generated. The class name should not contain
				the namespace part as it is specified in "Namespace". You do not need to specify the class name
104
				if "Table Name" ends with asterisk, in which case multiple ActiveRecord classes will be generated.',
Qiang Xue committed
105 106 107
			'baseClass' => 'This is the base class of the new ActiveRecord class. It should be a fully qualified namespaced class name.',
			'generateRelations' => 'This indicates whether the generator should generate relations based on
				foreign key constraints it detects in the database. Note that if your database contains too many tables,
Qiang Xue committed
108
				you may want to uncheck this option to accelerate the code generation process.',
Qiang Xue committed
109 110
			'generateLabelsFromComments' => 'This indicates whether the generator should generate attribute labels
				by using the comments of the corresponding DB columns.',
Alexander Makarov committed
111
		];
Qiang Xue committed
112 113
	}

114
	/**
Qiang Xue committed
115
	 * @inheritdoc
116 117 118
	 */
	public function autoCompleteData()
	{
Qiang Xue committed
119
		$db = $this->getDbConnection();
Qiang Xue committed
120
		if ($db !== null) {
Qiang Xue committed
121 122 123 124 125 126 127 128
			return [
				'tableName' => function () use ($db) {
					return $db->getSchema()->getTableNames();
				},
			];
		} else {
			return [];
		}
129 130
	}

Qiang Xue committed
131
	/**
Qiang Xue committed
132
	 * @inheritdoc
Qiang Xue committed
133
	 */
Qiang Xue committed
134 135
	public function requiredTemplates()
	{
Alexander Makarov committed
136
		return ['model.php'];
Qiang Xue committed
137 138
	}

Qiang Xue committed
139
	/**
Qiang Xue committed
140
	 * @inheritdoc
Qiang Xue committed
141
	 */
Qiang Xue committed
142 143
	public function stickyAttributes()
	{
Alexander Makarov committed
144
		return ['ns', 'db', 'baseClass', 'generateRelations', 'generateLabelsFromComments'];
Qiang Xue committed
145 146
	}

Qiang Xue committed
147
	/**
Qiang Xue committed
148
	 * @inheritdoc
Qiang Xue committed
149
	 */
Qiang Xue committed
150 151
	public function generate()
	{
Alexander Makarov committed
152
		$files = [];
Qiang Xue committed
153 154
		$relations = $this->generateRelations();
		$db = $this->getDbConnection();
Qiang Xue committed
155 156
		foreach ($this->getTableNames() as $tableName) {
			$className = $this->generateClassName($tableName);
Qiang Xue committed
157
			$tableSchema = $db->getTableSchema($tableName);
Alexander Makarov committed
158
			$params = [
Qiang Xue committed
159
				'tableName' => $tableName,
Qiang Xue committed
160
				'className' => $className,
Qiang Xue committed
161 162
				'tableSchema' => $tableSchema,
				'labels' => $this->generateLabels($tableSchema),
163
				'rules' => $this->generateRules($tableSchema),
Alexander Makarov committed
164 165
				'relations' => isset($relations[$className]) ? $relations[$className] : [],
			];
Qiang Xue committed
166
			$files[] = new CodeFile(
Qiang Xue committed
167
				Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/' . $className . '.php',
Qiang Xue committed
168
				$this->render('model.php', $params)
Qiang Xue committed
169 170
			);
		}
Qiang Xue committed
171 172

		return $files;
Qiang Xue committed
173 174
	}

Qiang Xue committed
175 176 177 178 179
	/**
	 * Generates the attribute labels for the specified table.
	 * @param \yii\db\TableSchema $table the table schema
	 * @return array the generated attribute labels (name => label)
	 */
Qiang Xue committed
180 181
	public function generateLabels($table)
	{
Alexander Makarov committed
182
		$labels = [];
Qiang Xue committed
183
		foreach ($table->columns as $column) {
Qiang Xue committed
184
			if ($this->generateLabelsFromComments && !empty($column->comment)) {
Qiang Xue committed
185
				$labels[$column->name] = $column->comment;
Qiang Xue committed
186 187
			} elseif (!strcasecmp($column->name, 'id')) {
				$labels[$column->name] = 'ID';
Qiang Xue committed
188
			} else {
Qiang Xue committed
189
				$label = Inflector::camel2words($column->name);
Qiang Xue committed
190
				if (strcasecmp(substr($label, -3), ' id') === 0) {
Qiang Xue committed
191
					$label = substr($label, 0, -3) . ' ID';
Qiang Xue committed
192 193 194 195 196 197 198
				}
				$labels[$column->name] = $label;
			}
		}
		return $labels;
	}

199
	/**
Qiang Xue committed
200 201 202
	 * Generates validation rules for the specified table.
	 * @param \yii\db\TableSchema $table the table schema
	 * @return array the generated validation rules
203
	 */
Qiang Xue committed
204 205
	public function generateRules($table)
	{
Alexander Makarov committed
206 207
		$types = [];
		$lengths = [];
Qiang Xue committed
208 209 210 211
		foreach ($table->columns as $column) {
			if ($column->autoIncrement) {
				continue;
			}
212 213
			if (!$column->allowNull && $column->defaultValue === null) {
				$types['required'][] = $column->name;
Qiang Xue committed
214
			}
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
			switch ($column->type) {
				case Schema::TYPE_SMALLINT:
				case Schema::TYPE_INTEGER:
				case Schema::TYPE_BIGINT:
					$types['integer'][] = $column->name;
					break;
				case Schema::TYPE_BOOLEAN:
					$types['boolean'][] = $column->name;
					break;
				case Schema::TYPE_FLOAT:
				case Schema::TYPE_DECIMAL:
				case Schema::TYPE_MONEY:
					$types['number'][] = $column->name;
					break;
				case Schema::TYPE_DATE:
				case Schema::TYPE_TIME:
				case Schema::TYPE_DATETIME:
				case Schema::TYPE_TIMESTAMP:
					$types['safe'][] = $column->name;
					break;
				default: // strings
					if ($column->size > 0) {
						$lengths[$column->size][] = $column->name;
					} else {
						$types['string'][] = $column->name;
					}
Qiang Xue committed
241 242
			}
		}
Alexander Makarov committed
243
		$rules = [];
244
		foreach ($types as $type => $columns) {
KiTE committed
245
			$rules[] = "[['" . implode("', '", $columns) . "'], '$type']";
Qiang Xue committed
246
		}
247
		foreach ($lengths as $length => $columns) {
KiTE committed
248
			$rules[] = "[['" . implode("', '", $columns) . "'], 'string', 'max' => $length]";
Qiang Xue committed
249
		}
250

251 252 253 254 255
		// Unique indexes rules
		try {
			$db = $this->getDbConnection();
			$uniqueIndexes = $db->getSchema()->findUniqueIndexes($table);
			foreach ($uniqueIndexes as $indexName => $uniqueColumns) {
256 257 258 259 260 261 262 263 264 265 266 267
				// Avoid validating auto incrementable columns
				if (!$this->isUniqueColumnAutoIncrementable($table, $uniqueColumns)) {
					$attributesCount = count($uniqueColumns);

					if ($attributesCount == 1) {
						$rules[] = "[['" . $uniqueColumns[0] . "'], 'unique']";
					} elseif ($attributesCount > 1) {
						$labels = array_intersect_key($this->generateLabels($table), array_flip($uniqueColumns));
						$lastLabel = array_pop($labels);
						$columnsList = implode("', '", $uniqueColumns);
						$rules[] = "[['" . $columnsList . "'], 'unique', 'targetAttribute' => ['" . $columnsList . "'], 'message' => 'The combination of " . implode(', ', $labels) . " and " . $lastLabel . " has already been taken.']";
					}
268 269 270 271 272
				}
			}
		} catch (NotSupportedException $e) {
			// doesn't support unique indexes information...do nothing
		}
Qiang Xue committed
273 274 275
		return $rules;
	}

Qiang Xue committed
276 277 278
	/**
	 * @return array the generated relation declarations
	 */
Qiang Xue committed
279 280
	protected function generateRelations()
	{
Qiang Xue committed
281
		if (!$this->generateRelations) {
Alexander Makarov committed
282
			return [];
Qiang Xue committed
283 284
		}

Qiang Xue committed
285 286
		$db = $this->getDbConnection();

Qiang Xue committed
287 288
		if (($pos = strpos($this->tableName, '.')) !== false) {
			$schemaName = substr($this->tableName, 0, $pos);
Qiang Xue committed
289 290
		} else {
			$schemaName = '';
Qiang Xue committed
291 292
		}

Alexander Makarov committed
293
		$relations = [];
Qiang Xue committed
294
		foreach ($db->getSchema()->getTableSchemas($schemaName) as $table) {
Qiang Xue committed
295
			$tableName = $table->name;
Qiang Xue committed
296 297 298 299 300 301 302 303 304 305
			$className = $this->generateClassName($tableName);
			foreach ($table->foreignKeys as $refs) {
				$refTable = $refs[0];
				unset($refs[0]);
				$fks = array_keys($refs);
				$refClassName = $this->generateClassName($refTable);

				// Add relation for this table
				$link = $this->generateRelationLink(array_flip($refs));
				$relationName = $this->generateRelationName($relations, $className, $table, $fks[0], false);
Alexander Makarov committed
306
				$relations[$className][$relationName] = [
Gudz Taras committed
307 308
					"return \$this->hasOne($refClassName::className(), $link);",
					$refClassName,
Qiang Xue committed
309
					false,
Alexander Makarov committed
310
				];
Qiang Xue committed
311 312 313 314 315 316 317

				// Add relation for the referenced table
				$hasMany = false;
				foreach ($fks as $key) {
					if (!in_array($key, $table->primaryKey, true)) {
						$hasMany = true;
						break;
Qiang Xue committed
318 319
					}
				}
Qiang Xue committed
320 321
				$link = $this->generateRelationLink($refs);
				$relationName = $this->generateRelationName($relations, $refClassName, $refTable, $className, $hasMany);
Alexander Makarov committed
322
				$relations[$refClassName][$relationName] = [
323
					"return \$this->" . ($hasMany ? 'hasMany' : 'hasOne') . "($className::className(), $link);",
Gudz Taras committed
324
					$className,
Qiang Xue committed
325
					$hasMany,
Alexander Makarov committed
326
				];
Qiang Xue committed
327 328 329 330
			}

			if (($fks = $this->checkPivotTable($table)) === false) {
				continue;
Qiang Xue committed
331
			}
Qiang Xue committed
332 333 334 335 336
			$table0 = $fks[$table->primaryKey[0]][0];
			$table1 = $fks[$table->primaryKey[1]][0];
			$className0 = $this->generateClassName($table0);
			$className1 = $this->generateClassName($table1);

Alexander Makarov committed
337 338
			$link = $this->generateRelationLink([$fks[$table->primaryKey[1]][1] => $table->primaryKey[1]]);
			$viaLink = $this->generateRelationLink([$table->primaryKey[0] => $fks[$table->primaryKey[0]][1]]);
Qiang Xue committed
339
			$relationName = $this->generateRelationName($relations, $className0, $db->getTableSchema($table0), $table->primaryKey[1], true);
Alexander Makarov committed
340
			$relations[$className0][$relationName] = [
Gudz Taras committed
341
				"return \$this->hasMany($className1::className(), $link)->viaTable('{$table->name}', $viaLink);",
342
				$className1,
Qiang Xue committed
343
				true,
Alexander Makarov committed
344
			];
Qiang Xue committed
345

Alexander Makarov committed
346 347
			$link = $this->generateRelationLink([$fks[$table->primaryKey[0]][1] => $table->primaryKey[0]]);
			$viaLink = $this->generateRelationLink([$table->primaryKey[1] => $fks[$table->primaryKey[1]][1]]);
Qiang Xue committed
348
			$relationName = $this->generateRelationName($relations, $className1, $db->getTableSchema($table1), $table->primaryKey[0], true);
Alexander Makarov committed
349
			$relations[$className1][$relationName] = [
Gudz Taras committed
350
				"return \$this->hasMany($className0::className(), $link)->viaTable('{$table->name}', $viaLink);",
351
				$className0,
Qiang Xue committed
352
				true,
Alexander Makarov committed
353
			];
Qiang Xue committed
354 355 356 357
		}
		return $relations;
	}

358
	/**
Qiang Xue committed
359 360 361
	 * Generates the link parameter to be used in generating the relation declaration.
	 * @param array $refs reference constraint
	 * @return string the generated link parameter.
362
	 */
Qiang Xue committed
363
	protected function generateRelationLink($refs)
Qiang Xue committed
364
	{
Alexander Makarov committed
365
		$pairs = [];
Qiang Xue committed
366 367 368
		foreach ($refs as $a => $b) {
			$pairs[] = "'$a' => '$b'";
		}
Alexander Makarov committed
369
		return '[' . implode(', ', $pairs) . ']';
Qiang Xue committed
370 371 372
	}

	/**
Qiang Xue committed
373 374 375 376 377 378
	 * Checks if the given table is a pivot table.
	 * For simplicity, this method only deals with the case where the pivot contains two PK columns,
	 * each referencing a column in a different table.
	 * @param \yii\db\TableSchema the table being checked
	 * @return array|boolean the relevant foreign key constraint information if the table is a pivot table,
	 * or false if the table is not a pivot table.
Qiang Xue committed
379
	 */
Qiang Xue committed
380
	protected function checkPivotTable($table)
Qiang Xue committed
381
	{
Qiang Xue committed
382 383 384 385
		$pk = $table->primaryKey;
		if (count($pk) !== 2) {
			return false;
		}
Alexander Makarov committed
386
		$fks = [];
Qiang Xue committed
387 388 389
		foreach ($table->foreignKeys as $refs) {
			if (count($refs) === 2) {
				if (isset($refs[$pk[0]])) {
Alexander Makarov committed
390
					$fks[$pk[0]] = [$refs[0], $refs[$pk[0]]];
Qiang Xue committed
391
				} elseif (isset($refs[$pk[1]])) {
Alexander Makarov committed
392
					$fks[$pk[1]] = [$refs[0], $refs[$pk[1]]];
Qiang Xue committed
393 394 395 396 397
				}
			}
		}
		if (count($fks) === 2 && $fks[$pk[0]][0] !== $fks[$pk[1]][0]) {
			return $fks;
Qiang Xue committed
398
		} else {
Qiang Xue committed
399
			return false;
Qiang Xue committed
400
		}
Qiang Xue committed
401
	}
Qiang Xue committed
402

Qiang Xue committed
403 404 405 406 407 408 409 410 411 412 413 414 415
	/**
	 * Generate a relation name for the specified table and a base name.
	 * @param array $relations the relations being generated currently.
	 * @param string $className the class name that will contain the relation declarations
	 * @param \yii\db\TableSchema $table the table schema
	 * @param string $key a base name that the relation name may be generated from
	 * @param boolean $multiple whether this is a has-many relation
	 * @return string the relation name
	 */
	protected function generateRelationName($relations, $className, $table, $key, $multiple)
	{
		if (strcasecmp(substr($key, -2), 'id') === 0 && strcasecmp($key, 'id')) {
			$key = rtrim(substr($key, 0, -2), '_');
Qiang Xue committed
416
		}
Qiang Xue committed
417 418
		if ($multiple) {
			$key = Inflector::pluralize($key);
Qiang Xue committed
419
		}
Qiang Xue committed
420
		$name = $rawName = Inflector::id2camel($key, '_');
Qiang Xue committed
421
		$i = 0;
422
		while (isset($table->columns[lcfirst($name)])) {
Qiang Xue committed
423 424
			$name = $rawName . ($i++);
		}
425
		while (isset($relations[$className][lcfirst($name)])) {
Qiang Xue committed
426 427
			$name = $rawName . ($i++);
		}
Qiang Xue committed
428 429 430 431

		return $name;
	}

Qiang Xue committed
432 433 434
	/**
	 * Validates the [[db]] attribute.
	 */
Qiang Xue committed
435
	public function validateDb()
436
	{
Qiang Xue committed
437 438 439 440
		if (Yii::$app->hasComponent($this->db) === false) {
			$this->addError('db', 'There is no application component named "db".');
		} elseif (!Yii::$app->getComponent($this->db) instanceof Connection) {
			$this->addError('db', 'The "db" application component must be a DB connection instance.');
Qiang Xue committed
441 442 443
		}
	}

Qiang Xue committed
444 445 446
	/**
	 * Validates the [[ns]] attribute.
	 */
Qiang Xue committed
447 448
	public function validateNamespace()
	{
Qiang Xue committed
449 450
		$this->ns = ltrim($this->ns, '\\');
		$path = Yii::getAlias('@' . str_replace('\\', '/', $this->ns), false);
Qiang Xue committed
451 452 453
		if ($path === false) {
			$this->addError('ns', 'Namespace must be associated with an existing directory.');
		}
Qiang Xue committed
454 455
	}

Qiang Xue committed
456 457 458
	/**
	 * Validates the [[modelClass]] attribute.
	 */
Qiang Xue committed
459 460 461
	public function validateModelClass()
	{
		if ($this->isReservedKeyword($this->modelClass)) {
Qiang Xue committed
462
			$this->addError('modelClass', 'Class name cannot be a reserved PHP keyword.');
Qiang Xue committed
463
		}
464 465
		if (substr($this->tableName, -1) !== '*' && $this->modelClass == '') {
			$this->addError('modelClass', 'Model Class cannot be blank if table name does not end with asterisk.');
Qiang Xue committed
466 467 468
		}
	}

Qiang Xue committed
469 470 471
	/**
	 * Validates the [[tableName]] attribute.
	 */
Qiang Xue committed
472 473
	public function validateTableName()
	{
474 475
		if (($pos = strpos($this->tableName, '*')) !== false && substr($this->tableName, -1) !== '*') {
			$this->addError('tableName', 'Asterisk is only allowed as the last character.');
Qiang Xue committed
476 477
			return;
		}
Qiang Xue committed
478 479
		$tables = $this->getTableNames();
		if (empty($tables)) {
Qiang Xue committed
480
			$this->addError('tableName', "Table '{$this->tableName}' does not exist.");
Qiang Xue committed
481 482 483 484
		} else {
			foreach ($tables as $table) {
				$class = $this->generateClassName($table);
				if ($this->isReservedKeyword($class)) {
Qiang Xue committed
485
					$this->addError('tableName', "Table '$table' will generate a class which is a reserved PHP keyword.");
Qiang Xue committed
486 487 488 489 490
					break;
				}
			}
		}
	}
Qiang Xue committed
491

492 493 494
	private $_tableNames;
	private $_classNames;

Qiang Xue committed
495 496 497
	/**
	 * @return array the table names that match the pattern specified by [[tableName]].
	 */
Qiang Xue committed
498 499
	protected function getTableNames()
	{
500 501 502
		if ($this->_tableNames !== null) {
			return $this->_tableNames;
		}
Qiang Xue committed
503
		$db = $this->getDbConnection();
504
		if ($db === null) {
Alexander Makarov committed
505
			return [];
506
		}
Alexander Makarov committed
507
		$tableNames = [];
Qiang Xue committed
508
		if (strpos($this->tableName, '*') !== false) {
Qiang Xue committed
509 510
			if (($pos = strrpos($this->tableName, '.')) !== false) {
				$schema = substr($this->tableName, 0, $pos);
Qiang Xue committed
511
				$pattern = '/^' . str_replace('*', '\w+', substr($this->tableName, $pos + 1)) . '$/';
Qiang Xue committed
512 513
			} else {
				$schema = '';
Qiang Xue committed
514
				$pattern = '/^' . str_replace('*', '\w+', $this->tableName) . '$/';
Qiang Xue committed
515 516
			}

Qiang Xue committed
517 518 519
			foreach ($db->schema->getTableNames($schema) as $table) {
				if (preg_match($pattern, $table)) {
					$tableNames[] = $schema === '' ? $table : ($schema . '.' . $table);
Qiang Xue committed
520 521
				}
			}
Qiang Xue committed
522 523
		} elseif (($table = $db->getTableSchema($this->tableName, true)) !== null) {
			$tableNames[] = $this->tableName;
524 525 526 527 528
			$this->_classNames[$this->tableName] = $this->modelClass;
		}
		return $this->_tableNames = $tableNames;
	}

Qiang Xue committed
529 530 531 532 533
	/**
	 * Generates a class name from the specified table name.
	 * @param string $tableName the table name (which may contain schema prefix)
	 * @return string the generated class name
	 */
534 535 536 537 538 539 540 541 542 543 544
	protected function generateClassName($tableName)
	{
		if (isset($this->_classNames[$tableName])) {
			return $this->_classNames[$tableName];
		}

		if (($pos = strrpos($tableName, '.')) !== false) {
			$tableName = substr($tableName, $pos + 1);
		}

		$db = $this->getDbConnection();
Alexander Makarov committed
545
		$patterns = [];
546 547
		$patterns[] = "/^{$db->tablePrefix}(.*?)$/";
		$patterns[] = "/^(.*?){$db->tablePrefix}$/";
548 549 550 551 552 553 554 555 556 557 558
		if (strpos($this->tableName, '*') !== false) {
			$pattern = $this->tableName;
			if (($pos = strrpos($pattern, '.')) !== false) {
				$pattern = substr($pattern, $pos + 1);
			}
			$patterns[] = '/^' . str_replace('*', '(\w+)', $pattern) . '$/';
		}
		$className = $tableName;
		foreach ($patterns as $pattern) {
			if (preg_match($pattern, $tableName, $matches)) {
				$className = $matches[1];
559
				break;
560
			}
Qiang Xue committed
561
		}
562
		return $this->_classNames[$tableName] = Inflector::id2camel($className, '_');
563
	}
Qiang Xue committed
564 565 566 567 568 569 570 571

	/**
	 * @return Connection the DB connection as specified by [[db]].
	 */
	protected function getDbConnection()
	{
		return Yii::$app->{$this->db};
	}
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587

	/**
	 * Checks if any of the specified columns of an unique index is auto incrementable.
	 * @param \yii\db\TableSchema $table the table schema
	 * @param array $columns columns to check for autoIncrement property
	 * @return boolean whether any of the specified columns is auto incrementable.
	 */
	protected function isUniqueColumnAutoIncrementable($table, $columns)
	{
		foreach ($columns as $column) {
			if ($table->columns[$column]->autoIncrement) {
				return true;
			}
		}
		return false;
	}
Qiang Xue committed
588
}