实现简单的 DB 迁移管理
<?phpCore_Autoloader::loadFile(COREPATH . '/vendor/SingleTableCRUD.class.php',true);/** * 迁移操作入口 * * @package pkg * */class Pkg_Gen_Table_Migration {private static $migrationTable = 'sql_table_migration';/** * @var Pkg_Gen_Table_MigrationLog */static $logger = null;/** * @return TplEngine */private static function getTplEngine(){static $tplEngine = null;if (!$tplEngine){Core_Autoloader::loadFile(COREPATH . '/vendor/TplEngine.class.php');$tplConfig = array('templateDir' => dirname(__FILE__) . '/_views','enableCache' => false,);$tplEngine = new TplEngine($tplConfig);}return $tplEngine;}private static function getMigrations($migrationDir,$tableClassPrefix){static $migrations = null;if ($migrations) return $migrations;$migrations = array();$index = 1;// 获取迁移类对象foreach (glob("{$migrationDir}/*.php") as $filename) {$id = basename($filename,'.php');$className = "{$tableClassPrefix}{$id}";// 加载迁移类到系统Core_Autoloader::loadClass($className);$obj = new $className();// 校验迁移类是否实现了Pkg_Gen_Table_MigrationElement接口if ( !($obj instanceof Pkg_Gen_Table_MigrationElement) ){throw new Core_Exception_TypeMismatch('迁移类对象','Pkg_Gen_Table_MigrationElement',$className);}$migrations[$index] = array('id' =>$id,'class' => $className ,'instance' => $obj);$index ++;}return $migrations;}private static function initMigrationTable(Core_DB $dbo){static $is = false;if (!$is){$row = $dbo->getRow( sprintf("SHOW TABLES LIKE '%s'",self::$migrationTable) );if (!empty($row)){$is = true;return;}$tb = Pkg_Gen_Table_DML::newInstance($dbo,self::$migrationTable);$tb->struct(array($tb->combindColumnParams('version','int',true,6),))->setPrimaryKey('version')->setOptions(array(Pkg_Gen_Table_DML::ENGINE => Pkg_Gen_Table_DML::ENGINE_INNODB,))->create();$tb->execute();$is = SingleTableCRUD::insert(self::$migrationTable,array('version'=>0));self::$logger->append($dbo->lastsql);}}static function ls(Core_DB $dbo,$migrationDir,$tableClassPrefix,$saveUrl){if ( !(is_readable($migrationDir) && is_dir($migrationDir)) )throw new Exception("无效的迁移类文件存放路径: {$migrationDir}");self::initMigrationTable($dbo);$migrations = self::getMigrations($migrationDir,$tableClassPrefix);// 得到当前版本号,缺省为0$curversion = (int) $dbo->getOne(sprintf('select version from %s',self::$migrationTable));self::getTplEngine()->assign('database',$dbo->getDSN('database'));self::getTplEngine()->assign('migrations',$migrations);self::getTplEngine()->assign('version',$curversion);self::getTplEngine()->assign('saveurl',$saveUrl);self::getTplEngine()->display('migrations.php');}static function change(Core_DB $dbo,$migrationDir,$tableClassPrefix,$newversion,$lastversion){if ( !(is_readable($migrationDir) && is_dir($migrationDir)) )throw new Exception("无效的迁移类文件存放路径: {$migrationDir}");self::initMigrationTable($dbo);$migrations = self::getMigrations($migrationDir,$tableClassPrefix);// 得到当前版本号,缺省为0$curversion = (int) $dbo->getOne(sprintf('select version from %s',self::$migrationTable));if ($curversion != $lastversion) throw new Exception("无效的参数 lastversion: {$lastversion}");if ($curversion == $newversion) throw new Exception("版本无需迁移操作");if ($newversion > 0){if (!isset($migrations[$newversion])) throw new Exception("无效的参数 newversion: {$newversion}");}// 开始进行版本迁移操作if ($curversion > $newversion){// 反向for($start=$curversion,$end = $newversion; $start > $end; $start --){$instance = $migrations[$start]['instance'];/* @var $instance Pkg_Gen_Table_MigrationElement */self::$logger->append($migrations[$start]['class'] . '::down()');try {$instance->down();} catch( Exception $ex){throw new Exception("反向迁移: {$curversion}到{$newversion}失败,请修正后再操作... 可参考迁移日志");}$dbo->startTrans();$is = SingleTableCRUD::incrField(self::$migrationTable,null,'version',-1);self::$logger->append($dbo->lastsql);$dbo->completeTrans($is);if (!$is) throw new Exception("反向迁移: {$curversion}到{$newversion}失败,请修正后再操作... 可参考迁移日志");}}else {// 正向for($start=$curversion + 1,$end = $newversion + 1; $start < $end; $start ++){$instance = $migrations[$start]['instance'];/* @var $instance Pkg_Gen_Table_MigrationElement */self::$logger->append($migrations[$start]['class'] . '::up()');try {$instance->up();} catch( Exception $ex){throw new Exception("正向迁移: {$curversion}到{$newversion}失败,请修正后再操作... 可参考迁移日志");}$dbo->startTrans();$is = SingleTableCRUD::incrField(self::$migrationTable,null,'version',1);self::$logger->append($dbo->lastsql);$dbo->completeTrans($is);if (!$is) throw new Exception("正向迁移: {$curversion}到{$newversion}失败,请修正后再操作... 可参考迁移日志");}}}}/** * 迁移元素接口 * * @package pkg * */interface Pkg_Gen_Table_MigrationElement {/** * 正向迁移操作 * * @return bool */function up();/** * 逆向此次迁移操作 * * @return bool */function down();/** * 迁移操作的说明 * * @return string */function description();}/** * 迁移日志类 * * @package pkg * */class Pkg_Gen_Table_MigrationLog extends Core_LogWriterAbstract {/** * 保存运行期间的日志 * * @var string */private $_log = '';/** * 日期格式 * * @var string */private $dateFormat = 'Y-m-d H:i:s';/** * 保存日志的文件名 * * @var string */private $_logFilename = '';function __construct($logDir){if ( !(is_writable($logDir) && is_dir($logDir)) ){throw new Exception("无效的迁移日志文件存放路径: {$logDir}");} $logDir = realpath($logDir); if (substr($logDir, -1) != DIRECTORY_SEPARATOR) { $logDir .= DIRECTORY_SEPARATOR; } $this->_logFilename = $logDir . 'sql_table_migration.txt';unset($logDir);$app_start_time = Core_App::ini('+app_start_time+');$sec = (int) $app_start_time;$usec = $app_start_time - $sec;$this->_startTag = sprintf("[%s %s] ======= IWP Migration Loaded =======\n",date($this->dateFormat, $sec), $usec);// 注册脚本结束时要运行的方法,将缓存的日志内容写入文件Core_Halt::getInstance()->add(array($this, '__writeLog'));}function append($msg, $title = '', $level = 'info'){if (empty($msg)) return;$this->_log .= sprintf("[%s] %s\n", date($this->dateFormat), print_r($msg, true));}/** * 将缓存的日志信息写入实际存储,并清空缓存 * 此方法由系统自动调用 * */function __writeLog(){if (empty($this->_log)) return;$app_start_time = Core_App::ini('+app_start_time+');$shutdown_time = microtime(true);$sec = (int) $shutdown_time;$usec = $shutdown_time - $sec;$elapsedTime = $shutdown_time - $app_start_time;$content = $this->_startTag . $this->_log . sprintf("[%s %s] ======= IWP Migration End (elapsed: %f seconds) =======\n\n",date($this->dateFormat, $sec), $usec, $elapsedTime);$fp = fopen($this->_logFilename, 'a');if (!$fp) { return; }flock($fp, LOCK_EX);fwrite($fp, str_replace("\r", '', $content));flock($fp, LOCK_UN);fclose($fp);}}? 1 楼 vb2005xu 2012-04-04 mysql 授权管理