|
1 <?php |
|
2 /* |
|
3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
14 * |
|
15 * This software consists of voluntary contributions made by many individuals |
|
16 * and is licensed under the LGPL. For more information, see |
|
17 * <http://www.doctrine-project.org>. |
|
18 */ |
|
19 |
|
20 namespace Doctrine\DBAL\Migrations; |
|
21 |
|
22 use Doctrine\DBAL\Migrations\Configuration\Configuration, |
|
23 Doctrine\DBAL\Schema\Schema; |
|
24 |
|
25 /** |
|
26 * Class which wraps a migration version and allows execution of the |
|
27 * individual migration version up or down method. |
|
28 * |
|
29 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL |
|
30 * @link www.doctrine-project.org |
|
31 * @since 2.0 |
|
32 * @author Jonathan H. Wage <jonwage@gmail.com> |
|
33 */ |
|
34 class Version |
|
35 { |
|
36 const STATE_NONE = 0; |
|
37 const STATE_PRE = 1; |
|
38 const STATE_EXEC = 2; |
|
39 const STATE_POST = 3; |
|
40 |
|
41 /** |
|
42 * The Migrations Configuration instance for this migration |
|
43 * |
|
44 * @var Configuration |
|
45 */ |
|
46 private $configuration; |
|
47 |
|
48 /** |
|
49 * The OutputWriter object instance used for outputting information |
|
50 * |
|
51 * @var OutputWriter |
|
52 */ |
|
53 private $outputWriter; |
|
54 |
|
55 /** |
|
56 * The version in timestamp format (YYYYMMDDHHMMSS) |
|
57 * |
|
58 * @param int |
|
59 */ |
|
60 private $version; |
|
61 |
|
62 /** |
|
63 * @var AbstractSchemaManager |
|
64 */ |
|
65 private $sm; |
|
66 |
|
67 /** |
|
68 * @var AbstractPlatform |
|
69 */ |
|
70 private $platform; |
|
71 |
|
72 /** |
|
73 * The migration instance for this version |
|
74 * |
|
75 * @var AbstractMigration |
|
76 */ |
|
77 private $migration; |
|
78 |
|
79 /** |
|
80 * @var Connection |
|
81 */ |
|
82 private $connection; |
|
83 |
|
84 /** |
|
85 * @var string |
|
86 */ |
|
87 private $class; |
|
88 |
|
89 /** The array of collected SQL statements for this version */ |
|
90 private $sql = array(); |
|
91 |
|
92 /** The array of collected parameters for SQL statements for this version */ |
|
93 private $params = array(); |
|
94 |
|
95 /** The array of collected types for SQL statements for this version */ |
|
96 private $types = array(); |
|
97 |
|
98 /** The time in seconds that this migration version took to execute */ |
|
99 private $time; |
|
100 |
|
101 /** |
|
102 * @var int |
|
103 */ |
|
104 private $state = self::STATE_NONE; |
|
105 |
|
106 public function __construct(Configuration $configuration, $version, $class) |
|
107 { |
|
108 $this->configuration = $configuration; |
|
109 $this->outputWriter = $configuration->getOutputWriter(); |
|
110 $this->class = $class; |
|
111 $this->connection = $configuration->getConnection(); |
|
112 $this->sm = $this->connection->getSchemaManager(); |
|
113 $this->platform = $this->connection->getDatabasePlatform(); |
|
114 $this->migration = new $class($this); |
|
115 $this->version = $this->migration->getName() ?: $version; |
|
116 } |
|
117 |
|
118 /** |
|
119 * Returns the string version in the format YYYYMMDDHHMMSS |
|
120 * |
|
121 * @return string $version |
|
122 */ |
|
123 public function getVersion() |
|
124 { |
|
125 return $this->version; |
|
126 } |
|
127 |
|
128 /** |
|
129 * Returns the Migrations Configuration object instance |
|
130 * |
|
131 * @return Configuration $configuration |
|
132 */ |
|
133 public function getConfiguration() |
|
134 { |
|
135 return $this->configuration; |
|
136 } |
|
137 |
|
138 /** |
|
139 * Check if this version has been migrated or not. |
|
140 * |
|
141 * @param bool $bool |
|
142 * @return mixed |
|
143 */ |
|
144 public function isMigrated() |
|
145 { |
|
146 return $this->configuration->hasVersionMigrated($this); |
|
147 } |
|
148 |
|
149 public function markMigrated() |
|
150 { |
|
151 $this->configuration->createMigrationTable(); |
|
152 $this->connection->executeQuery("INSERT INTO " . $this->configuration->getMigrationsTableName() . " (version) VALUES (?)", array($this->version)); |
|
153 } |
|
154 |
|
155 public function markNotMigrated() |
|
156 { |
|
157 $this->configuration->createMigrationTable(); |
|
158 $this->connection->executeQuery("DELETE FROM " . $this->configuration->getMigrationsTableName() . " WHERE version = ?", array($this->version)); |
|
159 } |
|
160 |
|
161 /** |
|
162 * Add some SQL queries to this versions migration |
|
163 * |
|
164 * @param mixed $sql |
|
165 * @param array $params |
|
166 * @param array $types |
|
167 * @return void |
|
168 */ |
|
169 public function addSql($sql, array $params = array(), array $types = array()) |
|
170 { |
|
171 if (is_array($sql)) { |
|
172 foreach ($sql as $key => $query) { |
|
173 $this->sql[] = $query; |
|
174 if (isset($params[$key])) { |
|
175 $this->params[count($this->sql) - 1] = $params[$key]; |
|
176 $this->types[count($this->sql) - 1] = isset($types[$key]) ? $types[$key] : array(); |
|
177 } |
|
178 } |
|
179 } else { |
|
180 $this->sql[] = $sql; |
|
181 if ($params) { |
|
182 $this->params[count($this->sql) - 1] = $params; |
|
183 $this->types[count($this->sql) - 1] = $types ?: array(); |
|
184 } |
|
185 } |
|
186 } |
|
187 |
|
188 /** |
|
189 * Write a migration SQL file to the given path |
|
190 * |
|
191 * @param string $path The path to write the migration SQL file. |
|
192 * @param string $direction The direction to execute. |
|
193 * @return bool $written |
|
194 */ |
|
195 public function writeSqlFile($path, $direction = 'up') |
|
196 { |
|
197 $queries = $this->execute($direction, true); |
|
198 |
|
199 $string = sprintf("# Doctrine Migration File Generated on %s\n", date('Y-m-d H:m:s')); |
|
200 |
|
201 $string .= "\n# Version " . $this->version . "\n"; |
|
202 foreach ($queries as $query) { |
|
203 $string .= $query . ";\n"; |
|
204 } |
|
205 if (is_dir($path)) { |
|
206 $path = realpath($path); |
|
207 $path = $path . '/doctrine_migration_' . date('YmdHis') . '.sql'; |
|
208 } |
|
209 |
|
210 $this->outputWriter->write("\n".sprintf('Writing migration file to "<info>%s</info>"', $path)); |
|
211 |
|
212 return file_put_contents($path, $string); |
|
213 } |
|
214 |
|
215 /** |
|
216 * @return AbstractMigration |
|
217 */ |
|
218 public function getMigration() |
|
219 { |
|
220 return $this->migration; |
|
221 } |
|
222 |
|
223 /** |
|
224 * Execute this migration version up or down and and return the SQL. |
|
225 * |
|
226 * @param string $direction The direction to execute the migration. |
|
227 * @param string $dryRun Whether to not actually execute the migration SQL and just do a dry run. |
|
228 * @return array $sql |
|
229 * @throws Exception when migration fails |
|
230 */ |
|
231 public function execute($direction, $dryRun = false) |
|
232 { |
|
233 $this->sql = array(); |
|
234 |
|
235 $this->connection->beginTransaction(); |
|
236 |
|
237 try { |
|
238 $start = microtime(true); |
|
239 |
|
240 $this->state = self::STATE_PRE; |
|
241 $fromSchema = $this->sm->createSchema(); |
|
242 $this->migration->{'pre' . ucfirst($direction)}($fromSchema); |
|
243 |
|
244 if ($direction === 'up') { |
|
245 $this->outputWriter->write("\n" . sprintf(' <info>++</info> migrating <comment>%s</comment>', $this->version) . "\n"); |
|
246 } else { |
|
247 $this->outputWriter->write("\n" . sprintf(' <info>--</info> reverting <comment>%s</comment>', $this->version) . "\n"); |
|
248 } |
|
249 |
|
250 $this->state = self::STATE_EXEC; |
|
251 |
|
252 $toSchema = clone $fromSchema; |
|
253 $this->migration->$direction($toSchema); |
|
254 $this->addSql($fromSchema->getMigrateToSql($toSchema, $this->platform)); |
|
255 |
|
256 if ($dryRun === false) { |
|
257 if ($this->sql) { |
|
258 foreach ($this->sql as $key => $query) { |
|
259 if ( ! isset($this->params[$key])) { |
|
260 $this->outputWriter->write(' <comment>-></comment> ' . $query); |
|
261 $this->connection->executeQuery($query); |
|
262 } else { |
|
263 $this->outputWriter->write(sprintf(' <comment>-</comment> %s (with parameters)', $query)); |
|
264 $this->connection->executeQuery($query, $this->params[$key], $this->types[$key]); |
|
265 } |
|
266 } |
|
267 } else { |
|
268 $this->outputWriter->write(sprintf('<error>Migration %s was executed but did not result in any SQL statements.</error>', $this->version)); |
|
269 } |
|
270 |
|
271 if ($direction === 'up') { |
|
272 $this->markMigrated(); |
|
273 } else { |
|
274 $this->markNotMigrated(); |
|
275 } |
|
276 |
|
277 } else { |
|
278 foreach ($this->sql as $query) { |
|
279 $this->outputWriter->write(' <comment>-></comment> ' . $query); |
|
280 } |
|
281 } |
|
282 |
|
283 $this->state = self::STATE_POST; |
|
284 $this->migration->{'post' . ucfirst($direction)}($toSchema); |
|
285 |
|
286 $end = microtime(true); |
|
287 $this->time = round($end - $start, 2); |
|
288 if ($direction === 'up') { |
|
289 $this->outputWriter->write(sprintf("\n <info>++</info> migrated (%ss)", $this->time)); |
|
290 } else { |
|
291 $this->outputWriter->write(sprintf("\n <info>--</info> reverted (%ss)", $this->time)); |
|
292 } |
|
293 |
|
294 $this->connection->commit(); |
|
295 |
|
296 return $this->sql; |
|
297 } catch(SkipMigrationException $e) { |
|
298 $this->connection->rollback(); |
|
299 |
|
300 if ($dryRun == false) { |
|
301 // now mark it as migrated |
|
302 if ($direction === 'up') { |
|
303 $this->markMigrated(); |
|
304 } else { |
|
305 $this->markNotMigrated(); |
|
306 } |
|
307 } |
|
308 |
|
309 $this->outputWriter->write(sprintf("\n <info>SS</info> skipped (Reason: %s)", $e->getMessage())); |
|
310 } catch (\Exception $e) { |
|
311 |
|
312 $this->outputWriter->write(sprintf( |
|
313 '<error>Migration %s failed during %s. Error %s</error>', |
|
314 $this->version, $this->getExecutionState(), $e->getMessage() |
|
315 )); |
|
316 |
|
317 $this->connection->rollback(); |
|
318 |
|
319 $this->state = self::STATE_NONE; |
|
320 throw $e; |
|
321 } |
|
322 $this->state = self::STATE_NONE; |
|
323 } |
|
324 |
|
325 public function getExecutionState() |
|
326 { |
|
327 switch($this->state) { |
|
328 case self::STATE_PRE: |
|
329 return 'Pre-Checks'; |
|
330 case self::STATE_POST: |
|
331 return 'Post-Checks'; |
|
332 case self::STATE_EXEC: |
|
333 return 'Execution'; |
|
334 default: |
|
335 return 'No State'; |
|
336 } |
|
337 } |
|
338 |
|
339 /** |
|
340 * Returns the time this migration version took to execute |
|
341 * |
|
342 * @return integer $time The time this migration version took to execute |
|
343 */ |
|
344 public function getTime() |
|
345 { |
|
346 return $this->time; |
|
347 } |
|
348 |
|
349 public function __toString() |
|
350 { |
|
351 return $this->version; |
|
352 } |
|
353 } |