# Coverage for Doctrine_Migration

Back to coverage report

1 | <?php
2 | /*
3 | * $Id: Migration.php 1080 2007-02-10 18:17:08Z jwage $
4 | *
5 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16 | *
17 | * This software consists of voluntary contributions made by many individuals
18 | * and is licensed under the LGPL. For more information, see
19 | * <http://www.phpdoctrine.org>.
20 | */
21 |
22 | /**
23 | * Doctrine_Migration
24 | *
25 | * this class represents a database view
26 | *
27 | * @package Doctrine
28 | * @subpackage Migration
29 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
30 | * @link www.phpdoctrine.org
31 | * @since 1.0
32 | * @version $Revision: 1080 $
33 | * @author Jonathan H. Wage <jwage@mac.com>
34 | */
35 | class Doctrine_Migration
36 | {
37 | protected $_changes = array('created_tables' => array(),
38 | 'renamed_tables' => array(),
39 | 'created_constraints' => array(),
40 | 'dropped_fks' => array(),
41 | 'created_fks' => array(),
42 | 'dropped_constraints' => array(),
43 | 'removed_indexes' => array(),
44 | 'dropped_tables' => array(),
45 | 'added_columns' => array(),
46 | 'renamed_columns' => array(),
47 | 'changed_columns' => array(),
48 | 'removed_columns' => array(),
49 | 'added_indexes' => array()),
50 | $_migrationTableName = 'migration_version',
51 | $_migrationClassesDirectory = array(),
52 | $_migrationClasses = array(),
53 | $_loadedMigrations = array();
54 |
55 | /**
56 | * construct
57 | *
58 | * Specify the path to the directory with the migration classes.
59 | * The classes will be loaded and the migration table will be created if it does not already exist
60 | *
61 | * @param string $directory
62 | * @return void
63 | */
64 | public function __construct($directory = null)
65 | {
66 | if ($directory != null) {
67 | $this->_migrationClassesDirectory = $directory;
68 |
69 | $this->loadMigrationClasses();
70 |
71 | $this->createMigrationTable();
72 | }
73 | }
74 |
75 | /**
76 | * getTableName
77 | *
78 | * @return void
79 | */
80 | public function getTableName()
81 | {
82 | return $this->_migrationTableName;
83 | }
84 |
85 | /**
86 | * setTableName
87 | *
88 | * @param string $tableName
89 | * @return void
90 | */
91 | public function setTableName($tableName)
92 | {
93 | $this->_migrationTableName = Doctrine_Manager::connection()
94 | ->formatter->getTableName($tableName);
95 | }
96 |
97 | /**
98 | * createMigrationTable
99 | *
100 | * Creates the migration table used to store the current version
101 | *
102 | * @return void
103 | */
104 | protected function createMigrationTable()
105 | {
106 | $conn = Doctrine_Manager::connection();
107 |
108 | try {
109 | $conn->export->createTable($this->_migrationTableName, array('version' => array('type' => 'integer', 'size' => 11)));
110 |
111 | return true;
112 | } catch(Exception $e) {
113 | return false;
114 | }
115 | }
116 |
117 |
118 | /**
119 | * loadMigrationClassesFromDirectory
120 | *
121 | * refactored out from loadMigrationClasses
122 | * $param array An array of classes
123 | * @return void
124 | */
125 | public function loadMigrationClassesFromDirectory($classes){
126 | foreach ((array) $this->_migrationClassesDirectory as $dir) {
127 | $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir),
128 | RecursiveIteratorIterator::LEAVES_ONLY);
129 |
130 | foreach ($it as $file) {
131 | $e = explode('.', $file->getFileName());
132 | if (end($e) === 'php' && strpos($file->getFileName(), '.inc') === false) {
133 | if ( ! in_array($file->getFileName(), $this->_loadedMigrations)) {
134 | require_once($file->getPathName());
135 |
136 | $requiredClass = array_diff(get_declared_classes(), $classes);
137 | $requiredClass = end($requiredClass);
138 |
139 | if ($requiredClass) {
140 | $this->_loadedMigrations[$requiredClass] = $file->getFileName();
141 | }
142 | }
143 | }
144 | }
145 | }
146 | }
147 |
148 | /**
149 | * loadMigrationClasses
150 | *
151 | * Loads the migration classes for the directory specified by the constructor
152 | *
153 | * @return void
154 | */
155 | protected function loadMigrationClasses()
156 | {
157 | if ($this->_migrationClasses) {
158 | return $this->_migrationClasses;
159 | }
160 |
161 | $classes = get_declared_classes();
162 |
163 | if ($this->_migrationClassesDirectory !== null) {
164 | $this->loadMigrationClassesFromDirectory($classes);
165 | }
166 |
167 |
168 | $parent = new ReflectionClass('Doctrine_Migration');
169 |
170 | foreach ($this->_loadedMigrations as $name => $fileName) {
171 | $class = new ReflectionClass($name);
172 |
173 | while ($class->isSubclassOf($parent)) {
174 |
175 | $class = $class->getParentClass();
176 | if ($class === false) {
177 | break;
178 | }
179 | }
180 |
181 | if ($class === false) {
182 | continue;
183 | }
184 |
185 | $e = explode('_', $fileName);
186 | $classMigrationNum = (int) $e[0];
187 |
188 | $this->_migrationClasses[$classMigrationNum] = array('className' => $name, 'fileName' => $fileName);
189 | }
190 |
191 | return $this->_migrationClasses;
192 | }
193 |
194 | /**
195 | * getMigrationClasses
196 | *
197 | * @return void
198 | */
199 | public function getMigrationClasses()
200 | {
201 | return $this->_migrationClasses;
202 | }
203 |
204 | /**
205 | * setCurrentVersion
206 | *
207 | * Sets the current version in the migration table
208 | *
209 | * @param string $number
210 | * @return void
211 | */
212 | protected function setCurrentVersion($number)
213 | {
214 | $conn = Doctrine_Manager::connection();
215 |
216 | if ($this->hasMigrated()) {
217 | $conn->exec("UPDATE " . $this->_migrationTableName . " SET version = $number");
218 | } else {
219 | $conn->exec("INSERT INTO " . $this->_migrationTableName . " (version) VALUES ($number)");
220 | }
221 | }
222 |
223 | /**
224 | * getCurrentVersion
225 | *
226 | * Get the current version of the database
227 | *
228 | * @return void
229 | */
230 | public function getCurrentVersion()
231 | {
232 | $conn = Doctrine_Manager::connection();
233 |
234 | $result = $conn->fetchColumn("SELECT version FROM " . $this->_migrationTableName);
235 |
236 | return isset($result[0]) ? $result[0]:0;
237 | }
238 |
239 | /**
240 | * hasMigrated
241 | *
242 | * Returns true/false for whether or not this database has been migrated in the past
243 | *
244 | * @return void
245 | */
246 | public function hasMigrated()
247 | {
248 | $conn = Doctrine_Manager::connection();
249 |
250 | $result = $conn->fetchColumn("SELECT version FROM " . $this->_migrationTableName);
251 |
252 | return isset($result[0]) ? true:false;
253 | }
254 |
255 | /**
256 | * getLatestVersion
257 | *
258 | * Gets the latest possible version from the loaded migration classes
259 | *
260 | * @return void
261 | */
262 | public function getLatestVersion()
263 | {
264 | $this->loadMigrationClasses();
265 |
266 | $versions = array();
267 | foreach (array_keys($this->_migrationClasses) as $classMigrationNum) {
268 | $versions[$classMigrationNum] = $classMigrationNum;
269 | }
270 |
271 | rsort($versions);
272 |
273 | return isset($versions[0]) ? $versions[0]:0;
274 | }
275 |
276 | /**
277 | * getNextVersion
278 | *
279 | * @return integer $nextVersion
280 | */
281 | public function getNextVersion()
282 | {
283 | return $this->getLatestVersion() + 1;
284 | }
285 |
286 | /**
287 | * getMigrationClass
288 | *
289 | * Get instance of migration class for $num
290 | *
291 | * @param string $num
292 | * @return void
293 | */
294 | protected function getMigrationClass($num)
295 | {
296 | foreach ($this->_migrationClasses as $classMigrationNum => $info) {
297 | $className = $info['className'];
298 |
299 | if ($classMigrationNum == $num) {
300 | return new $className();
301 | }
302 | }
303 |
304 | throw new Doctrine_Migration_Exception('Could not find migration class for migration step: '.$num);
305 | }
306 |
307 | /**
308 | * doMigrateStep
309 | *
310 | * Perform migration directory for the specified version. Loads migration classes and performs the migration then processes the changes
311 | *
312 | * @param string $direction
313 | * @param string $num
314 | * @return void
315 | */
316 | protected function doMigrateStep($direction, $num)
317 | {
318 | $migrate = $this->getMigrationClass($num);
319 |
320 | $migrate->doMigrate($direction);
321 | }
322 |
323 | /**
324 | * doMigrate
325 | *
326 | * Perform migration for a migration class. Executes the up or down method then processes the changes
327 | *
328 | * @param string $direction
329 | * @return void
330 | */
331 | protected function doMigrate($direction)
332 | {
333 | if (! method_exists($this, $direction)) {
334 | return;
335 | }
336 | $this->$direction();
337 |
338 | foreach ($this->_changes as $type => $changes) {
339 | $process = new Doctrine_Migration_Process();
340 | $funcName = 'process' . Doctrine::classify($type);
341 |
342 | if ( ! empty($changes)) {
343 | $process->$funcName($changes);
344 | }
345 | }
346 | }
347 |
348 | /**
349 | * migrate
350 | *
351 | * Perform a migration chain by specifying the $from and $to.
352 | * If you do not specify a $from or $to then it will attempt to migrate from the current version to the latest version
353 | *
354 | * @param string $from
355 | * @param string $to
356 | * @return void
357 | */
358 | public function migrate($to = null)
359 | {
360 | $from = $this->getCurrentVersion();
361 |
362 | // If nothing specified then lets assume we are migrating from the current version to the latest version
363 | if ($to === null) {
364 | $to = $this->getLatestVersion();
365 | }
366 |
367 | if ($from == $to) {
368 | throw new Doctrine_Migration_Exception('Already at version # ' . $to);
369 | }
370 |
371 | $direction = $from > $to ? 'down':'up';
372 |
373 | if ($direction === 'up') {
374 | for ($i = $from + 1; $i <= $to; $i++) {
375 | $this->doMigrateStep($direction, $i);
376 | }
377 | } else {
378 | for ($i = $from; $i > $to; $i--) {
379 | $this->doMigrateStep($direction, $i);
380 | }
381 | }
382 |
383 | $this->setCurrentVersion($to);
384 |
385 | return $to;
386 | }
387 |
388 | /**
389 | * addChange
390 | *
391 | * @param string $type
392 | * @param string $array
393 | * @return void
394 | */
395 | protected function addChange($type, array $change = array())
396 | {
397 | $this->_changes[$type][] = $change;
398 | }
399 |
400 | /**
401 | * createTable
402 | *
403 | * @param string $tableName
404 | * @param string $array
405 | * @param string $array
406 | * @return void
407 | */
408 | public function createTable($tableName, array $fields = array(), array $options = array())
409 | {
410 | $options = get_defined_vars();
411 |
412 | $this->addChange('created_tables', $options);
413 | }
414 |
415 | /**
416 | * dropTable
417 | *
418 | * @param string $tableName
419 | * @return void
420 | */
421 | public function dropTable($tableName)
422 | {
423 | $options = get_defined_vars();
424 |
425 | $this->addChange('dropped_tables', $options);
426 | }
427 |
428 | /**
429 | * renameTable
430 | *
431 | * @param string $oldTableName
432 | * @param string $newTableName
433 | * @return void
434 | */
435 | public function renameTable($oldTableName, $newTableName)
436 | {
437 | $options = get_defined_vars();
438 |
439 | $this->addChange('renamed_tables', $options);
440 | }
441 |
442 | /**
443 | * createConstraint
444 | *
445 | * @param string $tableName
446 | * @param string $constraintName
447 | * @return void
448 | */
449 | public function createConstraint($tableName, $constraintName, array $definition)
450 | {
451 | $options = get_defined_vars();
452 |
453 | $this->addChange('created_constraints', $options);
454 | }
455 |
456 | /**
457 | * dropConstraint
458 | *
459 | * @param string $tableName
460 | * @param string $constraintName
461 | * @return void
462 | */
463 | public function dropConstraint($tableName, $constraintName, $primary = false)
464 | {
465 | $options = get_defined_vars();
466 |
467 | $this->addChange('dropped_constraints', $options);
468 | }
469 |
470 | /**
471 | * createForeignKey
472 | *
473 | * @param string $tableName
474 | * @param string $constraintName
475 | * @return void
476 | */
477 | public function createForeignKey($tableName, array $definition)
478 | {
479 | $options = get_defined_vars();
480 |
481 | $this->addChange('created_fks', $options);
482 | }
483 |
484 | /**
485 | * dropForeignKey
486 | *
487 | * @param string $tableName
488 | * @param string $constraintName
489 | * @return void
490 | */
491 | public function dropForeignKey($tableName, $fkName)
492 | {
493 | $options = get_defined_vars();
494 |
495 | $this->addChange('dropped_fks', $options);
496 | }
497 |
498 | /**
499 | * addColumn
500 | *
501 | * @param string $tableName
502 | * @param string $columnName
503 | * @param string $type
504 | * @param string $array
505 | * @return void
506 | */
507 | public function addColumn($tableName, $columnName, $type, array $options = array())
508 | {
509 | $options = get_defined_vars();
510 |
511 | $this->addChange('added_columns', $options);
512 | }
513 |
514 | /**
515 | * renameColumn
516 | *
517 | * @param string $tableName
518 | * @param string $oldColumnName
519 | * @param string $newColumnName
520 | * @return void
521 | */
522 | public function renameColumn($tableName, $oldColumnName, $newColumnName)
523 | {
524 | $options = get_defined_vars();
525 |
526 | $this->addChange('renamed_columns', $options);
527 | }
528 |
529 | /**
530 | * renameColumn
531 | *
532 | * @param string $tableName
533 | * @param string $columnName
534 | * @param string $type
535 | * @param string $array
536 | * @return void
537 | */
538 | public function changeColumn($tableName, $columnName, $type, array $options = array())
539 | {
540 | $options = get_defined_vars();
541 |
542 | $this->addChange('changed_columns', $options);
543 | }
544 |
545 | /**
546 | * removeColumn
547 | *
548 | * @param string $tableName
549 | * @param string $columnName
550 | * @return void
551 | */
552 | public function removeColumn($tableName, $columnName)
553 | {
554 | $options = get_defined_vars();
555 |
556 | $this->addChange('removed_columns', $options);
557 | }
558 |
559 | /**
560 | * addIndex
561 | *
562 | * @param string $tableName
563 | * @param string $indexName
564 | * @param string $array
565 | * @return void
566 | */
567 | public function addIndex($tableName, $indexName, array $definition)
568 | {
569 | $options = get_defined_vars();
570 |
571 | $this->addChange('added_indexes', $options);
572 | }
573 |
574 | /**
575 | * removeIndex
576 | *
577 | * @param string $tableName
578 | * @param string $indexName
579 | * @return void
580 | */
581 | public function removeIndex($tableName, $indexName)
582 | {
583 | $options = get_defined_vars();
584 |
585 | $this->addChange('removed_indexes', $options);
586 | }
587 | }