Source for file migrator.php
Documentation is available at migrator.php
* This file contains the Migrator class.
* @author Gabriel Birke <birke@d-scribe.de>
* @copyright Copyright (C) 2007 by Gabriel Birke
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
require_once 'File/Util.php';
* The Migrator class executes migration files - PHP files with
* {@link http://pear.php.net/package/MDB2 MDB2} instructions for database schema changes.
* With migrations you have a versioning scheme for database schema changes
* and you can always go back and forward to a known database schema - on all of your
* database servers (development, production and test). The currect schema version number is
* stored in a special table in your database.
* @author Gabriel Birke <birke@d-scribe.de>
const ERROR_MIGRATION_DIR_MISSING =
1;
const ERROR_DB_CONNECT_FAILED =
2;
const ERROR_DB_QUERY_FAILED =
3;
const ERROR_INVALID_FILE_NAME =
4;
const ERROR_CLASS_NOT_FOUND =
5;
const ERROR_NO_MIGRATION_FILES =
6;
* Table name where the schema versions are stored
* Directory where th migration files are stored
* Example for a valid data source: mysql://my_user:my_pass@localhost/mydb
* @link http://pear.php.net/manual/en/package.database.mdb2.intro-dsn.php
* Filenames, sorted by number prefix
* Highest number prefix in {@link $migration_files}
* Version to up/downgrade to
* PEAR_Log or similar object
* Initialize the internal variables.
* If you subclass Migrator, you must call this constructor!
* @param string $dsn Data Source Name
* @param int $target Version to up- or downgrade to (-1 for most recent version)
* @param string $migrations_dir Directory path where the the migration files are
* @param Log $logger PEAR_Log or similar object (must implement debug, error, notice and info methods)
function __construct($dsn, $target_version, $migrations_dir, $logger)
throw
new Exception( "Migrations directory '{$this->migrations_dir}'
does not exist.
",
Migrator::ERROR_MIGRATION_DIR_MISSING);
// Collect migration file names
throw new Exception( "No migration files.", Migrator::ERROR_NO_MIGRATION_FILES);
* The main function that does the migration.
* Connects to the database, reads the current schema version, decides to up-
* or downgrade, loads the matching migrations, executes them and updates
$this->log("Database ist at selected version, nothing to be done.", "info");
* Collects migration file names from {@link $migrations_dir} and stores them
* in the sorted array {@link $migration_files}.
* {@link $migration_max_version} is set to the biggest migration file prefix number.
if($file->isFile() && preg_match('/^\d+_?[a-zA-Z0-9_]/', $it->getFileName()))
* Initializes {@link $db} with an instance of a MDB2 database driver.
* The MDB2 "Manager" module is also loaded for the driver.
$this->db = MDB2::connect($this->dsn, $options);
if (PEAR::isError($this->db)) {
throw new Exception("Failed to conect to '{
$this->dsn}':
".$this->db->getMessage()."\n".$this->db->getUserinfo(), <a href="../Migrator/Migrator.html">Migrator</a>::ERROR_DB_CONNECT_FAILED);
$this->db->loadModule('Manager', null, true);
* Initializes {@link $current_version} with version number from
* {@link $schema_version_table}.
* If the table doesn't exist, initialize with 0 and create the table.
$tables = $this->db->listTables();
$this->current_version = 0;
$this->current_version = $res->fetchOne();
protected function getMigrationDirection()
$this->log("{$direction}grading from version {$this->current_version} to {
$this->target_version}.
", "info");
* Leave only files in $this->migration_files that are needen for up/downgrade.
* @param string $direction "up" or "down"
protected function filterAndSortMigrationFiles($direction)
$func_name = "_filter_{
$direction}grade";
$this->log("Calling function '
$func_name'.
");
$this->log("The following migration files are left after filtering: ".
implode(', ', $this->migration_files));
* Load each class in {@link $migration_files} and call its "up" or "down" method
* @param string $direction "up" or "down"
protected function migrate($direction)
// Load the class files and check if the class we expect exists
if(!preg_match('/(\d+)_?([a-zA-Z0-9_]+)/', $file, $matches))
// Should not happen if migrationFiles were set with collectMigrationFiles
throw new Exception("Invalid file name: $file", Migrator::ERROR_INVALID_FILE_NAME);
$migration_id = $matches[1];
$this->log("Loading File '
$fn'.
", 'debug');
if(!class_exists($class))
throw new Exception("Class '$class' not found.", Migrator::ERROR_CLASS_NOT_FOUND);
// TODO check if the class implements IMigration
$classes[$migration_id] = $class;
// If we downgrade, we must subtract one from the migration id
$id_factor = $direction=="down"?-1:0;
// Call each classes up/down method
// This second loop ensured that no database mordifications are made
// when there is a syntax error in one of the loaded classes.
foreach($classes as $migration_id => $class)
$this->log("Doing Migration '
$class'.
", 'info');
call_user_func(array($class, 'init'), $this->logger);
$return =
call_user_func(array($class, $direction), $this->db);
if(PEAR::isError($return))
throw new Exception($return->getMessage().
"\n".
$return->getUserinfo(), Migrator::ERROR_DB_QUERY_FAILED);
// Write new migration version (create version table if it doesn't exist)
* Write the new version number into {$schema_version_table}.
protected function writeNewMigrationVersion($newVersion)
$newVersion = max(0, $newVersion);
$this->log("Changing schema version to $newVersion", 'debug');
* Return the migration file number at the start of a filename.
* @param string $filename
protected function migrationFileNumber($filename)
preg_match('/^(\d+)/', $filename, $matches);
return intval($matches[1]);
* Callback function for {@link array_filter()} that returns true for migration files
* between {@link $current_version} and {@link $target_version}.
* @param string $filename
protected function _filter_upgrade($filename)
return $number <=
$this->target_version &&
$number >
$this->current_version;
* Callback function for {@link array_filter()} that returns true for migration files
* between {@link $target_version} and {@link $current_version}.
* @param string $filename
protected function _filter_downgrade($filename)
return $number >
$this->target_version &&
$number <=
$this->current_version;
* If the result given is an error, throw an exception.
* @param MDB2_Result $result
protected function checkDBResult($result)
if (PEAR::isError($result)) {
throw new Exception($result->getMessage(), Migrator::ERROR_DB_QUERY_FAILED);
function log($message, $level="debug")
$this->logger->$level($message);
Documentation generated on Sat, 09 Jun 2007 11:50:10 +0200 by phpDocumentor 1.3.2