WP-Database-Helper/Migration.php

163 lines
5.5 KiB
PHP
Raw Normal View History

2025-02-12 14:03:39 +00:00
<?php
namespace DatabaseHelper;
2025-02-21 18:06:16 +00:00
use DatabaseHelper\beans\Schema;
2025-02-12 14:03:39 +00:00
use DatabaseHelper\enums\Propagation;
use DatabaseHelper\enums\Type;
use InvalidArgumentException;
class Migration
{
2025-02-12 14:55:28 +00:00
protected Schema $schema;
2025-02-12 14:03:39 +00:00
protected array $columnsToAdd = [];
protected array $columnsToModify = [];
protected array $columnsToDrop = [];
protected array $foreignKeysToAdd = [];
protected array $foreignKeysToDrop = [];
public function __construct(Schema $table) {
2025-02-12 14:55:28 +00:00
$this->schema = $table->copy();
2025-02-12 14:03:39 +00:00
}
2025-02-12 15:55:03 +00:00
public function column(string $name, Type $type, mixed $default = null, bool $isNullable = false, bool $isUnique = false): Migration {
2025-02-12 14:55:28 +00:00
if ($this->schema->existsColumn($name))
throw new InvalidArgumentException("Column '$name' already exists.");
2025-02-12 15:55:03 +00:00
2025-02-12 14:55:28 +00:00
$this->schema->columns[$name] = [
2025-02-12 15:55:03 +00:00
'name' => $name,
'type' => $type,
'default' => $default,
2025-02-12 14:03:39 +00:00
'isNullable' => $isNullable,
2025-02-12 15:55:03 +00:00
'isUnique' => $isUnique
2025-02-12 14:03:39 +00:00
];
2025-02-12 15:55:03 +00:00
2025-02-12 14:03:39 +00:00
$this->columnsToAdd[] = $name;
return $this;
}
2025-02-12 14:55:28 +00:00
public function modify(string $name, Type $type, mixed $default = null, bool $isNullable = false, bool $isUnique = false): Migration {
$this->schema->requireColumn($name);
2025-02-21 18:06:16 +00:00
if (isset($this->schema->references[$name]))
2025-02-12 15:55:03 +00:00
throw new InvalidArgumentException('Referencing columns cannot be modified.');
2025-02-12 14:55:28 +00:00
$this->schema->columns[$name] = [
2025-02-12 15:55:03 +00:00
'name' => $name,
'type' => $type,
'default' => $default,
2025-02-12 14:03:39 +00:00
'isNullable' => $isNullable,
2025-02-12 15:55:03 +00:00
'isUnique' => $isUnique
2025-02-12 14:03:39 +00:00
];
2025-02-12 15:55:03 +00:00
2025-02-12 14:03:39 +00:00
$this->columnsToModify[] = $name;
return $this;
}
2025-02-12 15:55:03 +00:00
public function drop(string $name): Migration {
2025-02-12 14:55:28 +00:00
$this->schema->requireColumn($name);
unset($this->schema->columns[$name]);
2025-02-12 14:03:39 +00:00
$this->columnsToDrop[] = $name;
return $this;
}
2025-02-12 14:55:28 +00:00
public function reference(Schema $foreignTable, Propagation $onDelete = Propagation::CASCADE, Propagation $onUpdate = Propagation::CASCADE): Migration {
2025-02-12 15:55:03 +00:00
$name = $foreignTable->primaryKey();
$this->schema->requireColumn($name);
2025-02-21 18:06:16 +00:00
$this->schema->references[$name] = [
2025-02-12 15:55:03 +00:00
'name' => $name,
'table' => $foreignTable->name,
'onDelete' => $onDelete,
'onUpdate' => $onUpdate
];
$this->schema->columns[$name] = $foreignTable->columns[$name];
2025-02-12 14:03:39 +00:00
2025-02-12 15:55:03 +00:00
$this->foreignKeysToAdd[] = $name;
return $this;
2025-02-12 14:03:39 +00:00
}
2025-02-12 14:55:28 +00:00
public function dereference(Schema $foreignTable): Migration {
2025-02-12 15:55:03 +00:00
$name = $foreignTable->primaryKey();
if ($this->schema->existsReference($foreignTable))
throw new InvalidArgumentException('Foreign table is not referenced.');
2025-02-21 18:06:16 +00:00
unset($this->schema->references[$name]);
2025-02-12 15:55:03 +00:00
$this->drop($name);
2025-02-12 14:55:28 +00:00
2025-02-12 15:55:03 +00:00
$this->foreignKeysToDrop[] = $name;
return $this;
2025-02-12 14:03:39 +00:00
}
public function toSql(): string {
2025-02-12 15:55:03 +00:00
global $wpdb;
// We assume that the table name in the schema is not prefixed; add the prefix here.
$tableName = $wpdb->prefix . $this->schema->name;
$clauses = [];
2025-02-12 14:03:39 +00:00
2025-02-12 15:55:03 +00:00
// Process new columns.
foreach ($this->columnsToAdd as $columnName) {
$col = $this->schema->columns[$columnName];
$clause = "ADD COLUMN `{$col['name']}` {$col['type']->toString()}";
2025-02-12 14:03:39 +00:00
2025-02-12 15:55:03 +00:00
$clause .= !$col['isNullable'] ? " NOT NULL" : "";
$clause .= $col['isUnique'] ? " UNIQUE" : "";
if (!is_null($col['default'])) {
$default = is_string($col['default']) ? "'{$col['default']}'" : $col['default'];
$clause .= " DEFAULT $default";
}
$clauses[] = $clause;
}
// Process modified columns.
foreach ($this->columnsToModify as $columnName) {
$col = $this->schema->columns[$columnName];
$clause = "MODIFY COLUMN `{$col['name']}` {$col['type']->toString()}";
$clause .= !$col['isNullable'] ? " NOT NULL" : "";
$clause .= $col['isUnique'] ? " UNIQUE" : "";
if (!is_null($col['default'])) {
$default = is_string($col['default']) ? "'{$col['default']}'" : $col['default'];
$clause .= " DEFAULT $default";
}
$clauses[] = $clause;
}
// Process dropped columns.
foreach ($this->columnsToDrop as $columnName)
$clauses[] = "DROP COLUMN `$columnName`";
// Process foreign keys to add.
foreach ($this->foreignKeysToAdd as $key) {
2025-02-21 18:06:16 +00:00
$foreignKey = $this->schema->references[$key];
2025-02-12 15:55:03 +00:00
$clause = " ADD CONSTRAINT `fk_{$foreignKey['name']}`";
$clause .= " FOREIGN KEY (`{$foreignKey['name']}`)";
$clause .= " REFERENCES `{$foreignKey['table']}` (`{$foreignKey['name']}`)";
$clause .= " ON DELETE {$foreignKey['onDelete']->toString()}";
$clause .= " ON UPDATE {$foreignKey['onUpdate']->toString()},\n";
$clauses[] = $clause;
}
// Process foreign keys to drop.
foreach ($this->foreignKeysToDrop as $fkName)
$clauses[] = "DROP FOREIGN KEY `fk_{$fkName}`";
if (empty($clauses))
throw new InvalidArgumentException("No migration operations to perform.");
2025-02-12 14:03:39 +00:00
2025-02-12 15:55:03 +00:00
return "ALTER TABLE `$tableName`\n" . implode(",\n", $clauses) . ";";
2025-02-12 14:03:39 +00:00
}
public function migrate(): Schema {
global $wpdb;
$sql = $this->toSql();
$wpdb->query($sql);
2025-02-12 14:55:28 +00:00
return $this->schema;
2025-02-12 14:03:39 +00:00
}
}