Overview

Packages

  • Db
    • Adapter
    • Migration

Classes

  • Horde_Db_Migration_Base
  • Horde_Db_Migration_Exception
  • Horde_Db_Migration_Migrator
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Copyright 2007 Maintainable Software, LLC
  4:  * Copyright 2006-2012 Horde LLC (http://www.horde.org/)
  5:  *
  6:  * @author     Mike Naberezny <mike@maintainable.com>
  7:  * @author     Derek DeVries <derek@maintainable.com>
  8:  * @author     Chuck Hagenbuch <chuck@horde.org>
  9:  * @license    http://www.horde.org/licenses/bsd
 10:  * @category   Horde
 11:  * @package    Db
 12:  * @subpackage Migration
 13:  */
 14: 
 15: /**
 16:  * @author     Mike Naberezny <mike@maintainable.com>
 17:  * @author     Derek DeVries <derek@maintainable.com>
 18:  * @author     Chuck Hagenbuch <chuck@horde.org>
 19:  * @license    http://www.horde.org/licenses/bsd
 20:  * @category   Horde
 21:  * @package    Db
 22:  * @subpackage Migration
 23:  */
 24: class Horde_Db_Migration_Migrator
 25: {
 26:     /**
 27:      * @var string
 28:      */
 29:     protected $_direction = null;
 30: 
 31:     /**
 32:      * @var string
 33:      */
 34:     protected $_migrationsPath = null;
 35: 
 36:     /**
 37:      * @var integer
 38:      */
 39:     protected $_targetVersion = null;
 40: 
 41:     /**
 42:      * @var string
 43:      */
 44:     protected $_schemaTableName = 'schema_info';
 45: 
 46:     /**
 47:      * Constructor.
 48:      *
 49:      * @param Horde_Db_Adapter $connection  A DB connection object.
 50:      * @param Horde_Log_Logger $logger      A logger object.
 51:      * @param array $options                Additional options for the migrator:
 52:      *                                      - migrationsPath: directory with the
 53:      *                                        migration files.
 54:      *                                      - schemaTableName: table for storing
 55:      *                                        the schema version.
 56:      *
 57:      * @throws Horde_Db_Migration_Exception
 58:      */
 59:     public function __construct(Horde_Db_Adapter $connection,
 60:                                 Horde_Log_Logger $logger = null,
 61:                                 array $options = array())
 62:     {
 63:         if (!$connection->supportsMigrations()) {
 64:             throw new Horde_Db_Migration_Exception('This database does not yet support migrations');
 65:         }
 66: 
 67:         $this->_connection = $connection;
 68:         $this->_logger = $logger ? $logger : new Horde_Support_Stub();
 69:         $this->_inflector = new Horde_Support_Inflector();
 70:         if (isset($options['migrationsPath'])) {
 71:             $this->_migrationsPath = $options['migrationsPath'];
 72:         }
 73:         if (isset($options['schemaTableName'])) {
 74:             $this->_schemaTableName = $options['schemaTableName'];
 75:         }
 76: 
 77:         $this->_initializeSchemaInformation();
 78:     }
 79: 
 80:     /**
 81:      * @param string $targetVersion
 82:      */
 83:     public function migrate($targetVersion = null)
 84:     {
 85:         $currentVersion = $this->getCurrentVersion();
 86: 
 87:         if ($targetVersion == null || $currentVersion < $targetVersion) {
 88:             $this->up($targetVersion);
 89:         } elseif ($currentVersion > $targetVersion) {
 90:             // migrate down
 91:             $this->down($targetVersion);
 92:         }
 93:     }
 94: 
 95:     /**
 96:      * @param string $targetVersion
 97:      */
 98:     public function up($targetVersion = null)
 99:     {
100:         $this->_targetVersion = $targetVersion;
101:         $this->_direction = 'up';
102:         $this->_doMigrate();
103:     }
104: 
105:     /**
106:      * @param string $targetVersion
107:      */
108:     public function down($targetVersion = null)
109:     {
110:         $this->_targetVersion = $targetVersion;
111:         $this->_direction = 'down';
112:         $this->_doMigrate();
113:     }
114: 
115:     /**
116:      * @return integer
117:      */
118:     public function getCurrentVersion()
119:     {
120:         return in_array($this->_schemaTableName, $this->_connection->tables())
121:             ? $this->_connection->selectValue('SELECT version FROM ' . $this->_schemaTableName)
122:             : 0;
123:     }
124: 
125:     /**
126:      * @return integer
127:      */
128:     public function getTargetVersion()
129:     {
130:         $migrations = array();
131:         foreach ($this->_getMigrationFiles() as $migrationFile) {
132:             list($version, $name) = $this->_getMigrationVersionAndName($migrationFile);
133:             $this->_assertUniqueMigrationVersion($migrations, $version);
134:             $migrations[$version] = $name;
135:         }
136: 
137:         // Sort by version.
138:         uksort($migrations, 'strnatcmp');
139: 
140:         return key(array_reverse($migrations, true));
141:     }
142: 
143:     /**
144:      * @param string $migrationsPath  Path to migration files.
145:      */
146:     public function setMigrationsPath($migrationsPath)
147:     {
148:         $this->_migrationsPath = $migrationsPath;
149:     }
150: 
151:     /**
152:      * @param Horde_Log_Logger $logger
153:      */
154:     public function setLogger(Horde_Log_Logger $logger)
155:     {
156:         $this->_logger = $logger;
157:     }
158: 
159:     /**
160:      * @param Horde_Support_Inflector $inflector
161:      */
162:     public function setInflector(Horde_Support_Inflector $inflector)
163:     {
164:         $this->_inflector = $inflector;
165:     }
166: 
167:     /**
168:      * Performs the migration.
169:      */
170:     protected function _doMigrate()
171:     {
172:         foreach ($this->_getMigrationClasses() as $migration) {
173:             if ($this->_hasReachedTargetVersion($migration->version)) {
174:                 $this->_logger->info('Reached target version: ' . $this->_targetVersion);
175:                 return;
176:             }
177:             if ($this->_isIrrelevantMigration($migration->version)) {
178:                 continue;
179:             }
180: 
181:             $this->_logger->info('Migrating ' . ($this->_direction == 'up' ? 'to ' : 'from ') . get_class($migration) . ' (' . $migration->version . ')');
182:             $migration->migrate($this->_direction);
183:             $this->_setSchemaVersion($migration->version);
184:         }
185:     }
186: 
187:     /**
188:      * @return array
189:      */
190:     protected function _getMigrationClasses()
191:     {
192:         $migrations = array();
193:         foreach ($this->_getMigrationFiles() as $migrationFile) {
194:             require_once $migrationFile;
195:             list($version, $name) = $this->_getMigrationVersionAndName($migrationFile);
196:             $this->_assertUniqueMigrationVersion($migrations, $version);
197:             $migrations[$version] = $this->_getMigrationClass($name, $version);
198:         }
199: 
200:         // Sort by version.
201:         uksort($migrations, 'strnatcmp');
202:         $sorted = array_values($migrations);
203: 
204:         return $this->_isDown() ? array_reverse($sorted) : $sorted;
205:     }
206: 
207:     /**
208:      * @param array   $migrations
209:      * @param integer $version
210:      *
211:      * @throws Horde_Db_Migration_Exception
212:      */
213:     protected function _assertUniqueMigrationVersion($migrations, $version)
214:     {
215:         if (isset($migrations[$version])) {
216:             throw new Horde_Db_Migration_Exception('Multiple migrations have the version number ' . $version);
217:         }
218:     }
219: 
220:     /**
221:      * Returns the list of migration files.
222:      *
223:      * @return array
224:      */
225:     protected function _getMigrationFiles()
226:     {
227:         return array_keys(
228:             iterator_to_array(
229:                 new RegexIterator(
230:                     new RecursiveIteratorIterator(
231:                         new RecursiveDirectoryIterator(
232:                             $this->_migrationsPath
233:                         )
234:                     ),
235:                     '/' . preg_quote(DIRECTORY_SEPARATOR, '/') . '\d+_.*\.php$/',
236:                     RecursiveRegexIterator::MATCH,
237:                     RegexIterator::USE_KEY
238:                 )
239:             )
240:         );
241:     }
242: 
243:     /**
244:      * Actually returns object, and not class.
245:      *
246:      * @param string  $migrationName
247:      * @param integer $version
248:      *
249:      * @return  Horde_Db_Migration_Base
250:      */
251:     protected function _getMigrationClass($migrationName, $version)
252:     {
253:         $className = $this->_inflector->camelize($migrationName);
254:         $class = new $className($this->_connection, $version);
255:         $class->setLogger($this->_logger);
256: 
257:         return $class;
258:     }
259: 
260:     /**
261:      * @param string $migrationFile
262:      *
263:      * @return array  ($version, $name)
264:      */
265:     protected function _getMigrationVersionAndName($migrationFile)
266:     {
267:         preg_match_all('/([0-9]+)_([_a-z0-9]*).php/', $migrationFile, $matches);
268:         return array($matches[1][0], $matches[2][0]);
269:     }
270: 
271:     /**
272:      * @TODO
273:      */
274:     protected function _initializeSchemaInformation()
275:     {
276:         if (in_array($this->_schemaTableName, $this->_connection->tables())) {
277:             return;
278:         }
279:         $schemaTable = $this->_connection->createTable($this->_schemaTableName, array('autoincrementKey' => false));
280:         $schemaTable->column('version', 'integer');
281:         $schemaTable->end();
282:         $this->_connection->insert('INSERT INTO ' . $this->_schemaTableName . ' (version) VALUES (0)');
283:     }
284: 
285:     /**
286:      * @param integer $version
287:      */
288:     protected function _setSchemaVersion($version)
289:     {
290:         $version = $this->_isDown() ? $version - 1 : $version;
291:         if ($version) {
292:             $sql = 'UPDATE ' . $this->_schemaTableName . ' SET version = ' . (int)$version;
293:             $this->_connection->update($sql);
294:         } else {
295:             $this->_connection->dropTable($this->_schemaTableName);
296:         }
297:     }
298: 
299:     /**
300:      * @return boolean
301:      */
302:     protected function _isUp()
303:     {
304:         return $this->_direction == 'up';
305:     }
306: 
307:     /**
308:      * @return boolean
309:      */
310:     protected function _isDown()
311:     {
312:         return $this->_direction == 'down';
313:     }
314: 
315:     /**
316:      * @return boolean
317:      */
318:     protected function _hasReachedTargetVersion($version)
319:     {
320:         if ($this->_targetVersion === null) {
321:             return false;
322:         }
323: 
324:         return ($this->_isUp()   && $version - 1 >= $this->_targetVersion) ||
325:                ($this->_isDown() && $version     <= $this->_targetVersion);
326:     }
327: 
328:     /**
329:      * @param integer $version
330:      *
331:      * @return  boolean
332:      */
333:     protected function _isIrrelevantMigration($version)
334:     {
335:         return ($this->_isUp()   && $version <= self::getCurrentVersion()) ||
336:                ($this->_isDown() && $version >  self::getCurrentVersion());
337:     }
338: }
339: 
API documentation generated by ApiGen