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
![]() |
|||
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 |