ntentan /
yentu
| 1 | <?php |
||
| 2 | |||
| 3 | namespace yentu\commands; |
||
| 4 | |||
| 5 | use clearice\io\Io; |
||
| 6 | use yentu\ChangeLogger; |
||
| 7 | use yentu\database\ForeignKey; |
||
| 8 | use yentu\exceptions\SyntaxErrorException; |
||
| 9 | use yentu\exceptions\YentuException; |
||
| 10 | use yentu\factories\DatabaseManipulatorFactory; |
||
| 11 | use yentu\factories\DatabaseItemFactory; |
||
| 12 | use yentu\Migrations; |
||
| 13 | use yentu\Yentu; |
||
| 14 | |||
| 15 | |||
| 16 | /** |
||
| 17 | * The migrate class runs the command that creates database items. |
||
| 18 | */ |
||
| 19 | class Migrate extends Command implements Reversible |
||
| 20 | { |
||
| 21 | |||
| 22 | const FILTER_UNRUN = 'unrun'; |
||
| 23 | const FILTER_LAST_SESSION = 'lastSession'; |
||
| 24 | |||
| 25 | private $driver; |
||
| 26 | private $dryDriver; |
||
| 27 | private $lastSession; |
||
| 28 | private $currentPath; |
||
| 29 | private $rollbackCommand; |
||
| 30 | private $manipulatorFactory; |
||
| 31 | private $migrations; |
||
| 32 | private $io; |
||
| 33 | private $itemFactory; |
||
| 34 | |||
| 35 | public function __construct(Migrations $migrations, DatabaseManipulatorFactory $manipulatorFactory, Io $io, DatabaseItemFactory $itemFactory) |
||
| 36 | { |
||
| 37 | $this->manipulatorFactory = $manipulatorFactory; |
||
| 38 | $this->migrations = $migrations; |
||
| 39 | $this->io = $io; |
||
| 40 | $this->itemFactory = $itemFactory; |
||
| 41 | } |
||
| 42 | |||
| 43 | public function setupOptions($options, &$filter): void |
||
| 44 | { |
||
| 45 | if (isset($options['no-foreign-keys'])) { |
||
| 46 | $this->io->output("Ignoring all foreign key constraints ...\n"); |
||
| 47 | $this->driver->skip('ForeignKey'); |
||
| 48 | } |
||
| 49 | |||
| 50 | if (isset($options['only-foreign-keys'])) { |
||
| 51 | $this->io->output("Applying only foreign keys ...\n"); |
||
| 52 | $this->lastSession = $this->driver->getLastSession(); |
||
| 53 | $this->driver->allowOnly('ForeignKey'); |
||
| 54 | $filter = self::FILTER_LAST_SESSION; |
||
| 55 | } |
||
| 56 | |||
| 57 | if (isset($options['force-foreign-keys'])) { |
||
| 58 | $this->io->output("Applying only foreign keys and skipping on errors ...\n"); |
||
| 59 | $this->lastSession = $this->driver->getLastSession(); |
||
| 60 | $this->driver->setSkipOnErrors($options['force-foreign-keys']); |
||
| 61 | $this->driver->allowOnly('ForeignKey'); |
||
| 62 | $filter = self::FILTER_LAST_SESSION; |
||
| 63 | } |
||
| 64 | |||
| 65 | if (isset($options['default-ondelete-action'])) { |
||
| 66 | ForeignKey::$defaultOnDelete = $options['default-ondelete-action']; |
||
| 67 | } |
||
| 68 | |||
| 69 | if (isset($options['default-onupdate-action'])) { |
||
| 70 | ForeignKey::$defaultOnUpdate = $options['default-onupdate-action']; |
||
| 71 | } |
||
| 72 | } |
||
| 73 | |||
| 74 | private function announceMigration($migrations, $path): void |
||
| 75 | { |
||
| 76 | $size = count($migrations); |
||
| 77 | if ($size > 0) { |
||
| 78 | $this->io->output("Running $size migration(s) from '{$path['home']}'"); |
||
| 79 | } else { |
||
| 80 | $this->io->output("No migrations to run from '{$path['home']}'\n"); |
||
| 81 | } |
||
| 82 | } |
||
| 83 | |||
| 84 | private static function fillOptions(&$options): void |
||
| 85 | { |
||
| 86 | if (!isset($options['dump-queries'])) { |
||
| 87 | $options['dump-queries'] = false; |
||
| 88 | } |
||
| 89 | if (!isset($options['dry'])) { |
||
| 90 | $options['dry'] = false; |
||
| 91 | } |
||
| 92 | } |
||
| 93 | |||
| 94 | #[\Override] |
||
| 95 | public function run(): void |
||
| 96 | { |
||
| 97 | self::fillOptions($this->options); |
||
| 98 | |||
| 99 | $this->driver = ChangeLogger::wrap($this->manipulatorFactory->createManipulator(), $this->migrations, $this->io); |
||
| 100 | $this->driver->setDumpQueriesOnly($this->options['dump-queries']); |
||
| 101 | $this->driver->setDryRun($this->options['dry']); |
||
| 102 | $this->itemFactory->setChangeLogger($this->driver); |
||
| 103 | Yentu::setup($this->itemFactory, $this->driver->getDefaultSchema()); |
||
| 104 | |||
| 105 | $totalOperations = 0; |
||
| 106 | |||
| 107 | $filter = self::FILTER_UNRUN; |
||
| 108 | $this->setupOptions($this->options, $filter); |
||
| 109 | |||
| 110 | \yentu\Timer::start(); |
||
| 111 | $migrationPaths = $this->migrations->getAllPaths(); |
||
| 112 | |||
| 113 | foreach ($migrationPaths as $path) { |
||
| 114 | $migrations = $this->filter($this->migrations->getMigrationFiles($path['home']), $filter); |
||
| 115 | $this->announceMigration($migrations, $path); |
||
| 116 | $this->currentPath = $path; |
||
| 117 | |||
| 118 | foreach ($migrations as $migration) { |
||
| 119 | $this->driver->setVersion($migration['timestamp']); |
||
| 120 | $this->driver->setMigration($migration['migration']); |
||
| 121 | $this->io->output("\nApplying '{$migration['migration']}' migration\n"); |
||
| 122 | try { |
||
| 123 | require "{$path['home']}/{$migration['file']}"; |
||
| 124 | } catch (YentuException $e) { |
||
| 125 | throw new SyntaxErrorException($e->getMessage(), dirname($path['home']), $e->getTrace()); |
||
| 126 | } |
||
| 127 | $this->io->output("\n"); |
||
| 128 | $totalOperations += $this->driver->resetOperations(); |
||
| 129 | } |
||
| 130 | } |
||
| 131 | |||
| 132 | if ($this->driver->getChanges()) { |
||
| 133 | $elapsed = \yentu\Timer::stop(); |
||
| 134 | $this->io->output("\nMigration took " . \yentu\Timer::pretty($elapsed) . "\n"); |
||
| 135 | $this->io->output($this->driver->getChanges() . " operations performed\n"); |
||
| 136 | $this->io->output($totalOperations - $this->driver->getChanges() . " operations skipped\n"); |
||
| 137 | } |
||
| 138 | |||
| 139 | $this->driver->disconnect(); |
||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 140 | } |
||
| 141 | |||
| 142 | private function filter($migrations, $type = self::FILTER_UNRUN) |
||
| 143 | { |
||
| 144 | $filterMethod = "{$type}Filter"; |
||
| 145 | return $this->$filterMethod($migrations); |
||
| 146 | } |
||
| 147 | |||
| 148 | private function countOperations($migrationFile) |
||
| 149 | { |
||
| 150 | if ($this->dryDriver === null) { |
||
| 151 | $this->dryDriver = clone $this->driver; |
||
| 152 | $this->dryDriver->setDryRun(true); |
||
| 153 | } |
||
| 154 | $this->io->pushOutputLevel(Io::OUTPUT_LEVEL_0); |
||
| 155 | $this->itemFactory->setChangeLogger($this->dryDriver); |
||
| 156 | require "$migrationFile"; |
||
| 157 | $this->itemFactory->setChangeLogger($this->driver); |
||
| 158 | $this->io->popOutputLevel(); |
||
| 159 | $this->driver->setExpectedOperations($this->dryDriver->resetOperations()); |
||
| 160 | } |
||
| 161 | |||
| 162 | public function getCurrentPath() |
||
| 163 | { |
||
| 164 | return $this->currentPath; |
||
| 165 | } |
||
| 166 | |||
| 167 | private function unrunFilter($input) |
||
| 168 | { |
||
| 169 | $output = array(); |
||
| 170 | foreach ($input as $migration) { |
||
| 171 | $run = $this->driver->query( |
||
| 172 | "SELECT count(*) as number_run FROM yentu_history WHERE migration = ? and version = ?", |
||
| 173 | array($migration['migration'], $migration['timestamp']) |
||
| 174 | ); |
||
| 175 | |||
| 176 | if ($run[0]['number_run'] == 0) { |
||
| 177 | $output[] = $migration; |
||
| 178 | } |
||
| 179 | } |
||
| 180 | return $output; |
||
| 181 | } |
||
| 182 | |||
| 183 | private function lastSessionFilter($input) |
||
| 184 | { |
||
| 185 | $versions = $this->driver->getSessionVersions($this->lastSession); |
||
| 186 | $output = array(); |
||
| 187 | foreach ($input as $migration) { |
||
| 188 | if (array_search($migration['timestamp'], $versions) !== false) { |
||
| 189 | $output[] = $migration; |
||
| 190 | } |
||
| 191 | } |
||
| 192 | return $output; |
||
| 193 | } |
||
| 194 | |||
| 195 | public function getChanges() |
||
| 196 | { |
||
| 197 | return $this->driver->getChanges(); |
||
| 198 | } |
||
| 199 | |||
| 200 | public function getDefaultSchema() |
||
| 201 | { |
||
| 202 | return $this->driver->getDefaultSchema(); |
||
| 203 | } |
||
| 204 | |||
| 205 | public function setRollbackCommand(Rollback $rollbackCommand) |
||
| 206 | { |
||
| 207 | $this->rollbackCommand = $rollbackCommand; |
||
| 208 | } |
||
| 209 | |||
| 210 | #[\Override] |
||
| 211 | public function reverseActions() |
||
| 212 | { |
||
| 213 | if ($this->driver === null) { |
||
| 214 | return; |
||
| 215 | } |
||
| 216 | |||
| 217 | $this->io->output("Attempting to reverse all changes ... "); |
||
| 218 | if ($this->getChanges() > 0) { |
||
| 219 | $this->io->pushOutputLevel(0); |
||
| 220 | $this->rollbackCommand->run(); |
||
| 221 | $this->io->popOutputLevel(); |
||
| 222 | } |
||
| 223 | $this->io->output("OK\n"); |
||
| 224 | } |
||
| 225 | } |
||
| 226 | |||
| 227 |