1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452:
<?php
namespace Inlm\SchemaGenerator;
use CzProject\SqlSchema;
class DiffGenerator
{
private $old;
private $new;
public function __construct(SqlSchema\Schema $old, SqlSchema\Schema $new)
{
$this->old = $old;
$this->new = $new;
}
public function getCreatedAndUpdatedTables()
{
$createdTables = $this->getCreatedTables();
$updatedTables = $this->getUpdatedTables();
return $this->sortTables(
$this->new->getTables(),
array_merge($createdTables, $updatedTables)
);
}
public function getCreatedTables()
{
$tables = array();
$oldTables = array();
foreach ($this->old->getTables() as $oldTable) {
$oldTables[$oldTable->getName()] = TRUE;
}
foreach ($this->new->getTables() as $newTable) {
$table = $newTable->getName();
if (!isset($oldTables[$table])) {
$tables[] = new Diffs\CreatedTable($newTable);
}
}
return $tables;
}
public function getRemovedTables()
{
$tables = array();
$newTables = array();
foreach ($this->new->getTables() as $newTable) {
$newTables[$newTable->getName()] = TRUE;
}
foreach ($this->old->getTables() as $oldTable) {
$table = $oldTable->getName();
if (!isset($newTables[$table])) {
$tables[] = new Diffs\RemovedTable($table);
}
}
$tables = $this->sortTables($this->old->getTables(), $tables);
return array_reverse($tables);
}
public function getUpdatedTables()
{
$tables = array();
$tablesToUpdate = array();
foreach ($this->old->getTables() as $oldTable) {
$tablesToUpdate[$oldTable->getName()] = $oldTable;
}
foreach ($this->new->getTables() as $newTable) {
$tableName = $newTable->getName();
if (isset($tablesToUpdate[$tableName])) {
$updatedTable = $this->generateUpdate($tablesToUpdate[$tableName], $newTable);
if ($updatedTable) {
$tables[] = $updatedTable;
}
}
}
return $tables;
}
private function generateUpdate(SqlSchema\Table $old, SqlSchema\Table $new)
{
$updates = array();
$this->generateColumnUpdates($updates, $old, $new);
$this->generateIndexUpdates($updates, $old, $new);
$this->generateForeignKeyUpdates($updates, $old, $new);
$this->generateOptionUpdates($updates, $old, $new);
$this->generateCommentUpdate($updates, $old, $new);
if (!empty($updates)) {
return new Diffs\UpdatedTable($new->getName(), $updates);
}
return NULL;
}
private function generateColumnUpdates(array &$updates, SqlSchema\Table $old, SqlSchema\Table $new)
{
$oldColumns = array();
$lastColumnName = NULL;
$lastUpdatedColumnName = NULL;
$oldPositions = array();
foreach ($old->getColumns() as $column) {
$columnName = $column->getName();
$oldColumns[$columnName] = $column;
$oldPositions[$columnName] = $lastColumnName;
$lastColumnName = $columnName;
}
$lastColumnName = NULL;
foreach ($new->getColumns() as $column) {
$columnName = $column->getName();
if (!isset($oldColumns[$columnName])) {
$updates[] = new Diffs\CreatedTableColumn($new->getName(), $column, $lastColumnName);
} else {
if ($this->isTableColumnUpdated($oldColumns[$columnName], $column)) {
$updates[] = new Diffs\UpdatedTableColumn($new->getName(), $column, $lastColumnName);
} elseif ($oldPositions[$columnName] !== $lastUpdatedColumnName) {
$updates[] = new Diffs\UpdatedTableColumn($new->getName(), $column, $lastColumnName, TRUE);
}
$lastUpdatedColumnName = $columnName;
}
unset($oldColumns[$columnName]);
$lastColumnName = $columnName;
}
foreach ($oldColumns as $column) {
$updates[] = new Diffs\RemovedTableColumn($new->getName(), $column->getName());
}
}
private function generateIndexUpdates(array &$updates, SqlSchema\Table $old, SqlSchema\Table $new)
{
$oldIndexes = array();
foreach ($old->getIndexes() as $index) {
$oldIndexes[$index->getName()] = $index;
}
foreach ($new->getIndexes() as $index) {
$indexName = $index->getName();
if (!isset($oldIndexes[$indexName])) {
$updates[] = new Diffs\CreatedTableIndex($new->getName(), $index);
} else {
if ($this->isTableIndexUpdated($oldIndexes[$indexName], $index)) {
$updates[] = new Diffs\UpdatedTableIndex($new->getName(), $index);
}
}
unset($oldIndexes[$indexName]);
}
foreach ($oldIndexes as $index) {
$updates[] = new Diffs\RemovedTableIndex($new->getName(), $index->getName());
}
}
private function generateForeignKeyUpdates(array &$updates, SqlSchema\Table $old, SqlSchema\Table $new)
{
$oldForeignKeys = array();
foreach ($old->getForeignKeys() as $foreignKey) {
$oldForeignKeys[$foreignKey->getName()] = $foreignKey;
}
foreach ($new->getForeignKeys() as $foreignKey) {
$foreignKeyName = $foreignKey->getName();
if (!isset($oldForeignKeys[$foreignKeyName])) {
$updates[] = new Diffs\CreatedForeignKey($new->getName(), $foreignKey);
} else {
if ($this->isForeignKeyUpdated($oldForeignKeys[$foreignKeyName], $foreignKey)) {
$updates[] = new Diffs\UpdatedForeignKey($new->getName(), $foreignKey);
}
}
unset($oldForeignKeys[$foreignKeyName]);
}
foreach ($oldForeignKeys as $foreignKey) {
$updates[] = new Diffs\RemovedForeignKey($new->getName(), $foreignKey->getName());
}
}
private function generateOptionUpdates(array &$updates, SqlSchema\Table $old, SqlSchema\Table $new)
{
$oldOptions = $old->getOptions();
foreach ($new->getOptions() as $option => $value) {
if (!array_key_exists($option, $oldOptions)) {
$updates[] = new Diffs\AddedTableOption($new->getName(), $option, $value);
} else {
if ($oldOptions[$option] !== $value) {
$updates[] = new Diffs\UpdatedTableOption($new->getName(), $option, $value);
}
}
unset($oldOptions[$option]);
}
foreach ($oldOptions as $option => $value) {
$updates[] = new Diffs\RemovedTableOption($new->getName(), $option);
}
}
private function generateCommentUpdate(array &$updates, SqlSchema\Table $old, SqlSchema\Table $new)
{
if ($old->getComment() !== $new->getComment()) {
$updates[] = new Diffs\UpdatedTableComment($new->getName(), (string) $new->getComment());
}
}
private function isTableColumnUpdated(SqlSchema\Column $old, SqlSchema\Column $new)
{
if ($old->getType() !== $new->getType()) {
return TRUE;
}
if ($old->getParameters() !== $new->getParameters()) {
return TRUE;
}
$oldOptions = $old->getOptions();
$newOptions = $new->getOptions();
ksort($oldOptions, SORT_STRING);
ksort($newOptions, SORT_STRING);
if ($oldOptions !== $newOptions) {
return TRUE;
}
if ($old->isNullable() !== $new->isNullable()) {
return TRUE;
}
if ($old->isAutoIncrement() !== $new->isAutoIncrement()) {
return TRUE;
}
if ($old->getDefaultValue() !== $new->getDefaultValue()) {
return TRUE;
}
if ($old->getComment() !== $new->getComment()) {
return TRUE;
}
return FALSE;
}
private function isTableIndexUpdated(SqlSchema\Index $old, SqlSchema\Index $new)
{
if ($old->getType() !== $new->getType()) {
return TRUE;
}
$oldColumns = $old->getColumns();
$newColumns = $new->getColumns();
if (count($oldColumns) !== count($newColumns)) {
return TRUE;
}
foreach ($newColumns as $i => $newColumn) {
if (!isset($oldColumns[$i])) {
return TRUE;
}
$oldColumn = $oldColumns[$i];
if ($oldColumn->getName() !== $newColumn->getName()) {
return TRUE;
}
if ($oldColumn->getOrder() !== $newColumn->getOrder()) {
return TRUE;
}
if ($oldColumn->getLength() !== $newColumn->getLength()) {
return TRUE;
}
}
return FALSE;
}
private function isForeignKeyUpdated(SqlSchema\ForeignKey $old, SqlSchema\ForeignKey $new)
{
if ($old->getColumns() !== $new->getColumns()) {
return TRUE;
}
if ($old->getTargetTable() !== $new->getTargetTable()) {
return TRUE;
}
if ($old->getTargetColumns() !== $new->getTargetColumns()) {
return TRUE;
}
if ($old->getOnUpdateAction() !== $new->getOnUpdateAction()) {
return TRUE;
}
if ($old->getOnDeleteAction() !== $new->getOnDeleteAction()) {
return TRUE;
}
return FALSE;
}
private function sortTables(array $allTables, array $tablesToSort)
{
$tableOrder = $this->resolveOrder($allTables);
usort($tablesToSort, function ($a, $b) use ($tableOrder) {
$orderA = $this->getTableOrder($tableOrder, $a);
$orderB = $this->getTableOrder($tableOrder, $b);
if ($orderA === $orderB) {
return 0;
}
return $orderA > $orderB ? 1 : -1;
});
return $tablesToSort;
}
private function resolveOrder(array $tables)
{
$resolver = new \Cz\Dependency;
foreach ($tables as $table) {
$sourceTable = $table->getName();
$targetTables = array();
foreach ($table->getForeignKeys() as $foreignKey) {
$targetTables[] = $foreignKey->getTargetTable();
}
$resolver->add($sourceTable, $targetTables);
}
$order = $resolver->getResolved();
return array_flip($order);
}
private function getTableOrder(array $tableOrder, $diff)
{
if (!($diff instanceof Diffs\CreatedTable) && !($diff instanceof Diffs\UpdatedTable) && !($diff instanceof Diffs\RemovedTable)) {
throw new UnsupportedException('Diff ' . get_class($diff) . ' is not supported.');
}
$name = $diff->getTableName();
return isset($tableOrder[$name]) ? $tableOrder[$name] : NULL;
}
}