php-censor/src/B8Framework/Database/Generator.php
2016-06-23 21:18:41 +06:00

429 lines
10 KiB
PHP
Executable file

<?php
/**
* Database generator updates a database to match a set of Models.
*/
namespace b8\Database;
use b8\Database;
class Generator
{
protected $_db = null;
protected $_map = null;
protected $_tables = null;
protected $_ns = null;
protected $_path = null;
public function __construct(Database $db, $namespace, $path)
{
$this->_db = $db;
$this->_ns = $namespace;
$this->_path = $path;
$this->_map = new Map($this->_db);
$this->_tables = $this->_map->generate();
}
public function generate()
{
error_reporting(E_ERROR & E_WARNING);
$di = new \DirectoryIterator($this->_path);
$this->_todo = array(
'drop_fk' => array(),
'drop_index'=> array(),
'create' => array(),
'alter' => array(),
'add_index' => array(),
'add_fk' => array(),
);
foreach($di as $file)
{
if($file->isDot())
{
continue;
}
$fileName = explode('.', $file->getBasename());
if ($fileName[count($fileName)-1] != 'php')
{
continue;
}
$modelName = '\\' . $this->_ns . '\\Model\\Base\\' . str_replace('.php', '', $file->getFilename());
require_once($this->_path . $file->getFilename());
$model = new $modelName();
$columns = $model->columns;
$indexes = $model->indexes;
$foreignKeys = $model->foreignKeys;
$tableName = $model->getTableName();
if(!array_key_exists($tableName, $this->_tables))
{
$this->_createTable($tableName, $columns, $indexes, $foreignKeys);
continue;
}
else
{
$table = $this->_tables[$tableName];
$this->_updateColumns($tableName, $table, $columns);
$this->_updateRelationships($tableName, $table, $foreignKeys);
$this->_updateIndexes($tableName, $table, $indexes);
}
}
print 'DROP FK: ' . count($this->_todo['drop_fk']) . PHP_EOL;
print 'DROP INDEX: ' . count($this->_todo['drop_index']) . PHP_EOL;
print 'CREATE TABLE: ' . count($this->_todo['create']) . PHP_EOL;
print 'ALTER TABLE: ' . count($this->_todo['alter']) . PHP_EOL;
print 'ADD INDEX: ' . count($this->_todo['add_index']) . PHP_EOL;
print 'ADD FK: ' . count($this->_todo['add_fk']) . PHP_EOL;
$order = array_keys($this->_todo);
while($group = array_shift($order))
{
if(!isset($this->_todo[$group]) || !is_array($this->_todo[$group]) || !count($this->_todo[$group]))
{
continue;
}
foreach($this->_todo[$group] as $query)
{
try
{
//print $query . PHP_EOL;
$this->_db->query($query);
}
catch(\Exception $ex)
{
print 'FAILED TO EXECUTE: ' . $query . PHP_EOL;
print $ex->getMessage().PHP_EOL.PHP_EOL;
}
}
}
}
protected function _createTable($tbl, $cols, $idxs, $fks)
{
$defs = array();
$pks = array();
foreach($cols as $colName => $def)
{
$add = '`' . $colName . '` ' . $def['type'];
switch($def['type'])
{
case 'text':
case 'longtext':
case 'mediumtext':
case 'date':
case 'datetime':
case 'float':
$add .= '';
break;
default:
$add .= !empty($def['length']) ? '(' . $def['length'] . ')' : '';
break;
}
if(empty($def['nullable']) || !$def['nullable'])
{
$add .= ' NOT NULL ';
}
if(!empty($def['default']))
{
$add .= ' DEFAULT ' . (is_numeric($def['default']) ? $def['default'] : '\'' . $def['default'] . '\'');
}
if(!empty($def['auto_increment']) && $def['auto_increment'])
{
$add .= ' AUTO_INCREMENT ';
}
if(!empty($def['primary_key']) && $def['primary_key'])
{
$pks[] = '`' . $colName . '`';
}
$defs[] = $add;
}
if(count($pks))
{
$defs[] = 'PRIMARY KEY (' . implode(', ', $pks) . ')';
}
$stmt = 'CREATE TABLE `' . $tbl . '` (' . PHP_EOL;
$stmt .= implode(", \n", $defs);
$stmt .= PHP_EOL . ') ENGINE=InnoDB DEFAULT CHARSET=utf8';
$stmt .= PHP_EOL;
$this->_todo['create'][] = $stmt;
foreach($idxs as $name => $idx)
{
$this->_addIndex($tbl, $name, $idx);
}
foreach($fks as $name => $fk)
{
$this->_addFk($tbl, $name, $fk);
}
}
protected function _updateColumns($tableName, $table, $columns)
{
$currentColumns = $table['columns'];
while($column = array_shift($currentColumns))
{
if(!array_key_exists($column['name'], $columns))
{
$this->_todo['alter'][$tableName.'.'.$column['name']] = 'ALTER TABLE `' . $tableName . '` DROP COLUMN `' . $column['name'] . '`';
}
else
{
$model = $columns[$column['name']];
$model['nullable'] = !isset($model['nullable']) ? false : $model['nullable'];
$model['default'] = !isset($model['default']) ? false : $model['default'];
$model['auto_increment'] = !isset($model['auto_increment']) ? false : $model['auto_increment'];
$model['primary_key'] = !isset($model['primary_key']) ? false : $model['primary_key'];
$column['is_primary_key'] = !isset($column['is_primary_key']) ? false : $column['is_primary_key'];
if( $column['type'] != $model['type'] ||
($column['length'] != $model['length'] && !in_array($model['type'], array('text', 'longtext', 'mediumtext', 'date', 'datetime', 'float'))) ||
$column['null'] != $model['nullable'] ||
$column['default'] != $model['default'] ||
$column['auto'] != $model['auto_increment'])
{
$this->_updateColumn($tableName, $column['name'], $column['name'], $model);
}
}
unset($columns[$column['name']]);
}
if(count($columns))
{
foreach($columns as $name => $model)
{
// Check if we're renaming a column:
if(isset($model['rename']))
{
unset($this->_todo['alter'][$tableName.'.'.$model['rename']]);
$this->_updateColumn($tableName, $model['rename'], $name, $model);
continue;
}
// New column
$add = '`' . $name . '` ' . $model['type'];;
switch($model['type'])
{
case 'text':
case 'longtext':
case 'mediumtext':
case 'date':
case 'datetime':
case 'float':
$add .= '';
break;
default:
$add .= !empty($model['length']) ? '(' . $model['length'] . ')' : '';
break;
}
if(empty($model['nullable']) || !$model['nullable'])
{
$add .= ' NOT NULL ';
}
if(!empty($model['default']))
{
$add .= ' DEFAULT ' . (is_numeric($model['default']) ? $model['default'] : '\'' . $model['default'] . '\'');
}
if(!empty($model['auto_increment']) && $model['auto_increment'])
{
$add .= ' AUTO_INCREMENT ';
}
if(!empty($model['primary_key']) && $model['primary_key'] && !isset($table['indexes']['PRIMARY']))
{
$add .= ' PRIMARY KEY ';
}
$this->_todo['alter'][] = 'ALTER TABLE `' . $tableName . '` ADD COLUMN ' . $add;
}
}
}
protected function _updateColumn($tableName, $prevName, $newName, $model)
{
$add = '`' . $newName . '` ' . $model['type'];;
switch($model['type'])
{
case 'text':
case 'longtext':
case 'mediumtext':
case 'date':
case 'datetime':
case 'float':
$add .= '';
break;
default:
$add .= !empty($model['length']) ? '(' . $model['length'] . ')' : '';
break;
}
if(empty($model['nullable']) || !$model['nullable'])
{
$add .= ' NOT NULL ';
}
if(!empty($model['default']))
{
$add .= ' DEFAULT ' . (is_numeric($model['default']) ? $model['default'] : '\'' . $model['default'] . '\'');
}
if(!empty($model['auto_increment']) && $model['auto_increment'])
{
$add .= ' AUTO_INCREMENT ';
}
$this->_todo['alter'][] = 'ALTER TABLE `' . $tableName . '` CHANGE COLUMN `' . $prevName . '` ' . $add;
}
protected function _updateRelationships($tableName, $table, $foreignKeys)
{
$current = $table['relationships']['toOne'];
while($foreignKey = array_shift($current))
{
if(!array_key_exists($foreignKey['fk_name'], $foreignKeys))
{
$this->_dropFk($tableName, $foreignKey['fk_name']);
}
elseif( $foreignKey['from_col'] != $foreignKeys[$foreignKey['fk_name']]['local_col'] ||
$foreignKey['table'] != $foreignKeys[$foreignKey['fk_name']]['table'] ||
$foreignKey['col'] != $foreignKeys[$foreignKey['fk_name']]['col'] ||
$foreignKey['fk_update'] != $foreignKeys[$foreignKey['fk_name']]['update'] ||
$foreignKey['fk_delete'] != $foreignKeys[$foreignKey['fk_name']]['delete'])
{
$this->_alterFk($tableName, $foreignKey['fk_name'], $foreignKeys[$foreignKey['fk_name']]);
}
unset($foreignKeys[$foreignKey['fk_name']]);
}
if(count($foreignKeys))
{
foreach($foreignKeys as $name => $foreignKey)
{
// New column
$this->_addFk($tableName, $name, $foreignKey);
}
}
}
protected function _updateIndexes($tableName, $table, $indexes)
{
$current = $table['indexes'];
while($index = array_shift($current))
{
if(!array_key_exists($index['name'], $indexes))
{
$this->_dropIndex($tableName, $index['name']);
}
elseif( $index['unique'] != $indexes[$index['name']]['unique'] ||
$index['columns'] != $indexes[$index['name']]['columns'])
{
$this->_alterIndex($tableName, $index['name'], $index);
}
unset($indexes[$index['name']]);
}
if(count($indexes))
{
foreach($indexes as $name => $index)
{
if($name == 'PRIMARY')
{
continue;
}
// New index
$this->_addIndex($tableName, $name, $index);
}
}
}
protected function _addIndex($table, $name, $idx, $stage = 'add_index')
{
if($name == 'PRIMARY')
{
return;
}
$q = 'CREATE ' . (isset($idx['unique']) && $idx['unique'] ? 'UNIQUE' : '') . ' INDEX `' . $name . '` ON `' . $table . '` (' . $idx['columns'] . ')';
$this->_todo[$stage][] = $q;
}
protected function _alterIndex($table, $name, $idx, $stage = 'index')
{
$this->_dropIndex($table, $name, $stage);
$this->_addIndex($table, $name, $idx, $stage);
}
protected function _dropIndex($table, $idxName, $stage = 'drop_index')
{
if($idxName == 'PRIMARY')
{
return;
}
$q = 'DROP INDEX `' . $idxName . '` ON `' . $table . '`';
$this->_todo[$stage][] = $q;
}
protected function _addFk($table, $name, $fk)
{
$q = 'ALTER TABLE `' . $table . '` ADD CONSTRAINT `' . $name . '` FOREIGN KEY (`' . $fk['local_col'] . '`) REFERENCES `'.$fk['table'].'` (`'.$fk['col'].'`)';
if(!empty($fk['delete']))
{
$q .= ' ON DELETE ' . $fk['delete'] . ' ';
}
if(!empty($fk['update']))
{
$q .= ' ON UPDATE ' . $fk['update'] . ' ';
}
$this->_todo['add_fk'][] = $q;
}
protected function _alterFk($table, $name, $fk)
{
$this->_dropFk($table, $name);
$this->_addFk($table, $name, $fk);
}
protected function _dropFk($table, $name)
{
$q = 'ALTER TABLE `'.$table.'` DROP FOREIGN KEY `' . $name . '`';
$this->_todo['drop_fk'][] = $q;
}
}