1 <?php
2
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
22
23 Yii::import('system.cli.commands.MigrateCommand');
24 require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'EDbMigration.php');
25
26 27 28 29 30 31 32 33 34 35
36 class EMigrateCommand extends MigrateCommand
37 {
38 39 40 41 42 43 44
45 public $module;
46
47 48 49
50 public $applicationModuleName = 'core';
51
52 53 54
55 public $moduleDelimiter = ': ';
56
57 58 59 60
61 public $migrationSubPath = 'migrations';
62
63 64 65 66 67
68 private $_modulePaths = null;
69 private $_runModulePaths = null;
70
71 72 73 74 75
76 private $_disabledModules = array();
77
78 79 80
81 public function getModulePaths()
82 {
83 if ($this->_modulePaths === null) {
84 $this->_modulePaths = array();
85 foreach(Yii::app()->modules as $module => $config) {
86 if (is_array($config)) {
87 $alias = 'application.modules.' . $module . '.' . ltrim($this->migrationSubPath, '.');
88 if (isset($config['class'])) {
89 Yii::setPathOfAlias(
90 $alias,
91 dirname(Yii::getPathOfAlias($config['class'])) . '/' .
92 str_replace('.', '/', ltrim($this->migrationSubPath, '.'))
93 );
94 } elseif (isset($config['basePath'])) {
95 Yii::setPathOfAlias(
96 $alias,
97 $config['basePath'] . '/' .
98 str_replace('.', '/', ltrim($this->migrationSubPath, '.'))
99 );
100 }
101 $this->_modulePaths[$module] = $alias;
102 $path = Yii::getPathOfAlias($alias);
103 if ($path === false || !is_dir($path)) {
104 $this->_disabledModules[] = $module;
105 }
106
107 } else {
108 $this->_modulePaths[$config] = 'application.modules.' . $config . '.' . ltrim($this->migrationSubPath, '.');
109 }
110 }
111 }
112
113 $this->_modulePaths[$this->applicationModuleName] = $this->migrationPath;
114 $path = Yii::getPathOfAlias($this->migrationPath);
115 if ($path === false || !is_dir($path)) {
116 $this->_disabledModules[] = $this->applicationModuleName;
117 }
118 return $this->_modulePaths;
119 }
120
121 122 123 124 125 126 127 128 129 130 131 132 133 134
135 public function setModulePaths($modulePaths)
136 {
137 $this->_modulePaths = $modulePaths;
138 }
139
140 141 142
143 public function getDisabledModules()
144 {
145
146 $this->getModulePaths();
147 foreach($this->_disabledModules as $module) {
148 if (!array_key_exists($module, $this->modulePaths)) {
149 unset($this->_disabledModules[$module]);
150 }
151 }
152 return array_unique($this->_disabledModules);
153 }
154
155 156 157 158 159 160 161 162 163
164 public function setDisabledModules($modules)
165 {
166 $this->_disabledModules = is_array($modules) ? $modules : array();
167 }
168
169 170 171
172 public function getEnabledModulePaths()
173 {
174 $modules = $this->getModulePaths();
175 foreach($this->getDisabledModules() as $module) {
176 unset($modules[$module]);
177 }
178 return $modules;
179 }
180
181 182 183 184 185 186 187
188 public function beforeAction($action, $params)
189 {
190 $tmpMigrationPath = $this->migrationPath;
191 $this->migrationPath = 'application';
192 if (parent::beforeAction($action, $params)) {
193 $this->migrationPath = $tmpMigrationPath;
194
195 echo "extended with EMigrateCommand by cebe <mail@cebe.cc>\n\n";
196
197
198 if ($action == 'create' && !is_null($this->module)) {
199 $this->usageError('create command can not be called with --module parameter!');
200 }
201 if (!is_null($this->module) && !is_string($this->module)) {
202 $this->usageError('parameter --module must be a comma seperated list of modules or a single module name!');
203 }
204
205
206 if (!empty($this->disabledModules)) {
207 echo "The following modules are disabled: " . implode(', ', $this->disabledModules) . "\n";
208 }
209
210
211 $modules = false;
212 if ($this->module !== null) {
213 $modules = explode(',', $this->module);
214
215
216 foreach ($modules as $module) {
217 if (in_array($module, $this->disabledModules)) {
218 echo "\nError: module '$module' is disabled!\n\n";
219 exit(1);
220 }
221 if (!isset($this->enabledModulePaths[$module])) {
222 echo "\nError: module '$module' is not available!\n\n";
223 exit(1);
224 }
225 }
226 echo "Current call is limited to module" . (count($modules)>1 ? "s" : "") . ": " . implode(', ', $modules) . "\n";
227 }
228 echo "\n";
229
230
231 foreach($this->getEnabledModulePaths() as $module => $pathAlias) {
232 if ($modules === false || in_array($module, $modules)) {
233 Yii::import($pathAlias . '.*');
234 $this->_runModulePaths[$module] = $pathAlias;
235 }
236 }
237 return true;
238 }
239 return false;
240 }
241
242 public function actionCreate($args)
243 {
244
245 if (count($args)==2) {
246 if (!isset($this->modulePaths[$args[0]])) {
247 echo "\nError: module '{$args[0]}' is not available!\n\n";
248 return 1;
249 }
250 $this->migrationPath = Yii::getPathOfAlias($this->modulePaths[$args[0]]);
251 $args = array($args[1]);
252 } else {
253 $this->migrationPath = Yii::getPathOfAlias($this->modulePaths[$this->applicationModuleName]);
254 }
255 if (!is_dir($this->migrationPath)) {
256 echo "\nError: '{$this->migrationPath}' does not exist or is not a directory!\n\n";
257 return 1;
258 }
259 return parent::actionCreate($args);
260 }
261
262 public function actionUp($args)
263 {
264 $this->_scopeAddModule = true;
265 $exitCode = parent::actionUp($args);
266 $this->_scopeAddModule = false;
267 return $exitCode;
268 }
269
270 public function actionDown($args)
271 {
272 $this->_scopeAddModule = true;
273 $exitCode = parent::actionDown($args);
274 $this->_scopeAddModule = false;
275 return $exitCode;
276 }
277
278 public function actionTo($args)
279 {
280 $this->_scopeAddModule = false;
281 $exitCode = parent::actionTo($args);
282 $this->_scopeAddModule = true;
283 return $exitCode;
284 }
285
286 public function actionMark($args)
287 {
288
289 $migrations = $this->getNewMigrations();
290
291
292 $this->_scopeAddModule = false;
293 $exitCode = parent::actionMark($args);
294 $this->_scopeAddModule = true;
295
296
297
298 $command = $this->getDbConnection()->createCommand()
299 ->select('version')
300 ->from($this->migrationTable)
301 ->where('module IS NULL');
302
303 foreach($command->queryColumn() as $version) {
304 $module = null;
305 foreach($migrations as $migration) {
306 list($module, $migration) = explode($this->moduleDelimiter, $migration);
307 if ($migration == $version) {
308 break;
309 }
310 }
311
312 $this->ensureBaseMigration($module);
313
314 $this->getDbConnection()->createCommand()->update(
315 $this->migrationTable,
316 array('module' => $module),
317 'version=:version',
318 array(':version' => $version)
319 );
320 }
321 return $exitCode;
322 }
323
324 protected function instantiateMigration($class)
325 {
326 require_once($class.'.php');
327 $migration=new $class;
328 $migration->setDbConnection($this->getDbConnection());
329 if ($migration instanceof EDbMigration) {
330
331 $migration->setCommand($this);
332 }
333 return $migration;
334 }
335
336
337 private $_scopeNewMigrations = false;
338 private $_scopeAddModule = true;
339
340 protected function getNewMigrations()
341 {
342 $this->_scopeNewMigrations = true;
343 $migrations = array();
344
345 foreach($this->_runModulePaths as $module => $path)
346 {
347 $this->migrationPath = Yii::getPathOfAlias($path);
348 foreach(parent::getNewMigrations() as $migration) {
349 if ($this->_scopeAddModule) {
350 $migrations[$migration] = $module.$this->moduleDelimiter.$migration;
351 } else {
352 $migrations[$migration] = $migration;
353 }
354 }
355 }
356 $this->_scopeNewMigrations = false;
357
358 ksort($migrations);
359 return array_values($migrations);
360 }
361
362 protected function getMigrationHistory($limit)
363 {
364
365 $db=$this->getDbConnection();
366
367
368 $db->schemaCachingDuration = 0;
369 Yii::app()->coreMessages->cacheID = false;
370
371 if ($db->schema->getTable($this->migrationTable)===null)
372 {
373 echo 'Creating migration history table "'.$this->migrationTable.'"...';
374 $db->createCommand()->createTable($this->migrationTable, array(
375 'version'=>'string NOT NULL PRIMARY KEY',
376 'apply_time'=>'integer',
377 'module'=>'VARCHAR(32)',
378 ));
379 echo "done.\n";
380 }
381
382 if ($this->_scopeNewMigrations || !$this->_scopeAddModule) {
383 $select = "version AS version_name, apply_time";
384 $params = array();
385 } else {
386 387 388 389 390 391
392 switch ($db->getDriverName())
393 {
394 case 'mysql':
395 $select = "CONCAT(module, :delimiter, version) AS version_name, apply_time";
396 break;
397 case 'mssql':
398 case 'sqlsrv':
399 case 'cubrid':
400 $select = "(module + :delimiter + version) AS version_name, apply_time";
401 break;
402 default:
403
404 $select = "(module || :delimiter || version) AS version_name, apply_time";
405 }
406 $params = array(':delimiter' => $this->moduleDelimiter);
407 }
408
409 $command = $db->createCommand()
410 ->select($select)
411 ->from($this->migrationTable)
412 ->order('version DESC')
413 ->limit($limit);
414
415 if (!is_null($this->module)) {
416 $criteria = new CDbCriteria();
417 $criteria->addInCondition('module', explode(',', $this->module));
418 $command->where = $criteria->condition;
419 $params += $criteria->params;
420 }
421
422 return CHtml::listData($command->queryAll(true, $params), 'version_name', 'apply_time');
423 }
424
425 426 427 428 429 430
431 protected function ensureBaseMigration($module)
432 {
433 $baseName = self::BASE_MIGRATION . '_' . $module;
434
435 $db = $this->getDbConnection();
436 if (!$db->createCommand()->select('version')
437 ->from($this->migrationTable)
438 ->where('module=:module AND version=:version')
439 ->queryRow(true, array(':module'=>$module, 'version'=>$baseName)))
440 {
441 $db->createCommand()->insert(
442 $this->migrationTable,
443 array(
444 'version'=>$baseName,
445 'apply_time'=>time(),
446 'module'=>$module,
447 )
448 );
449 }
450 }
451
452 protected function migrateUp($class)
453 {
454 $module = $this->applicationModuleName;
455
456 if (($pos = mb_strpos($class, $this->moduleDelimiter)) !== false) {
457 $module = mb_substr($class, 0, $pos);
458 $class = mb_substr($class, $pos + mb_strlen($this->moduleDelimiter));
459 }
460
461 $this->ensureBaseMigration($module);
462
463 if (mb_strpos($class, self::BASE_MIGRATION) === 0) {
464 return;
465 }
466 if (($ret = parent::migrateUp($class)) !== false) {
467
468 $this->getDbConnection()->createCommand()->update(
469 $this->migrationTable,
470 array('module'=>$module),
471 'version=:version',
472 array(':version' => $class)
473 );
474 }
475 return $ret;
476 }
477
478 protected function migrateDown($class)
479 {
480
481 if (($pos = mb_strpos($class, $this->moduleDelimiter)) !== false) {
482 $class = mb_substr($class, $pos + mb_strlen($this->moduleDelimiter));
483 }
484
485 if (mb_strpos($class, self::BASE_MIGRATION) !== 0) {
486 return parent::migrateDown($class);
487 }
488 }
489
490 public function getHelp()
491 {
492 return parent::getHelp() . <<<EOD
493
494 EXTENDED USAGE EXAMPLES (with modules)
495 for every action except create you can specify the modules to use
496 with the parameter --module=<modulenames>
497 where <modulenames> is a comma seperated list of module names (or a single name)
498
499 * yiic migrate create modulename create_user_table
500 Creates a new migration named 'create_user_table' in module 'modulename'.
501
502 all other commands work exactly as described above.
503
504 EOD;
505 }
506
507 protected function getTemplate()
508 {
509 if ($this->templateFile!==null) {
510 return parent::getTemplate();
511 } else {
512 return str_replace('CDbMigration', 'EDbMigration', parent::getTemplate());
513 }
514 }
515 }
516