query("select max(version) version from `{$table_name}`"); self::$current_version = $query->fetch()['version']; $query->closeCursor(); } return self::$current_version; } final public static function get_initial_version(bool $force = false){ if(is_null(self::$initial_version) || $force){ if(is_null(self::$pdo)){ throw new \Exception("Migration is not bound to a data source"); } $table_name = self::version_table(); $query = self::$pdo->query("select min(version) version from `{$table_name}`"); self::$initial_version = $query->fetch()['version']; $query->closeCursor(); } return self::$initial_version; } final public static function get_last_install_date(bool $force = false){ if(is_null(self::$last_date) || $force){ if(is_null(self::$pdo)){ throw new \Exception("Migration is not bound to a data source"); } $table_name = self::version_table(); $query = self::$pdo->query("select max(date) date from `{$table_name}`"); self::$last_date = $query->fetch()['date']; $query->closeCursor(); } return self::$last_date; } final public static function get_initial_install_date(bool $force = false){ if(is_null(self::$initial_date) || $force){ if(is_null(self::$pdo)){ throw new \Exception("Migration is not bound to a data source"); } $table_name = self::version_table(); $query = self::$pdo->query("select min(date) date from `{$table_name}`"); self::$initial_date = $query->fetch()['date']; $query->closeCursor(); } return self::$initial_date; } final public static function installed(){ $pdo = self::$pdo; $count = $pdo->exec( "select 1 ". "from `".self::version_table()."` ". "where version = {$pdo->quote(static::version())}" ); if($count === false){ throw $pdo->getError(); } return $count; } final public static function migrations(){ $migrations = array_filter(get_declared_classes(), function($class){ return 0 === strpos($class, "Migration\\"); }); sort($migrations); return $migrations; } final public static function migrate(string $direction, int $amount = 1){ if($amount < 1){ throw new \Exception("Migration amount must be a positive integer"); } $pdo = self::$pdo; $table = self::$pdo->table(self::version_table()); $table ->column('version', 'varchar(100)') ->column('date', 'datetime', DefaultValue::named('current_timestamp')) ->primaryKey('version') ->index('date_idx', ['date']) ->commit(); switch($direction){ case self::MIGRATE_UP: foreach(self::migrations() as $migration){ if(!$migration::installed()){ $pdo->transaction(function($pdo) use($migration, $table){ $migration::up($pdo); $migration::change($pdo, self::MIGRATE_UP); }); $table->insert(['version'=>$migration::version()]); if(!$migration::installed()){ throw new \Exception("Migration {$migration::version()} {$direction} failed"); } if(--$amount == 0){ break; } } } break; case self::MIGRATE_DOWN: foreach(array_reverse(self::migrations()) as $migration){ if($migration::installed()){ $pdo->transaction(function($pdo) use($migration, $table){ $migration::change($pdo, self::MIGRATE_DOWN); $migration::down($pdo); }); $table->delete(['version'=>$migration::version()]); if($migration::installed()){ throw new \Exception("Migration {$migration::version()} {$direction} failed"); } if(--$amount == 0){ break; } } } break; default: throw new \Exception("Invalid migration direction '{$direction}'"); } self::get_initial_version(true); self::get_initial_install_date(true); self::get_current_version(true); self::get_last_install_date(true); } final public static function migrate_all(string $direction){ self::migrate($direction, count(self::migrations())); } final public static function bind(string $dsn){ self::$pdo = PDO::from($dsn); } final public static function release(){ self::$pdo = null; } } ?>