<?php
Core_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);
}
}
?