schema = $table->copy(); } public function column(string $name, Type $type, mixed $default = null, bool $isNullable = false, bool $isUnique = false): Migration { if ($this->schema->existsColumn($name)) throw new InvalidArgumentException("Column '$name' already exists."); $this->schema->columns[$name] = [ 'name' => $name, 'type' => $type, 'default' => $default, 'isNullable' => $isNullable, 'isUnique' => $isUnique ]; $this->columnsToAdd[] = $name; return $this; } public function modify(string $name, Type $type, mixed $default = null, bool $isNullable = false, bool $isUnique = false): Migration { $this->schema->requireColumn($name); $this->schema->columns[$name] = [ 'name' => $name, 'type' => $type, 'default' => $default, 'isNullable' => $isNullable, 'isUnique' => $isUnique ]; $this->columnsToModify[] = $name; return $this; } public function delete(string $name): Migration { $this->schema->requireColumn($name); unset($this->schema->columns[$name]); $this->columnsToDrop[] = $name; return $this; } public function reference(Schema $foreignTable, Propagation $onDelete = Propagation::CASCADE, Propagation $onUpdate = Propagation::CASCADE): Migration { $name = $foreignTable->primaryKey(); if ($this->schema->existsColumn($name)) throw new InvalidArgumentException("Column '$name' already exists."); $this->schema->foreignKeys[$name] = [ 'name' => $name, 'table' => $foreignTable->name, 'onDelete' => $onDelete, 'onUpdate' => $onUpdate ]; $this->schema->columns[$name] = $foreignTable->columns[$name]; $this->foreignKeysToAdd[] = $name; return $this; } public function dereference(Schema $foreignTable): Migration { $name = $foreignTable->primaryKey(); if ($this->schema->existsReference($foreignTable)) throw new InvalidArgumentException('Foreign table is not referenced.'); unset($this->schema->foreignKeys[$name]); // Also remove the column and mark it for dropping. if (isset($this->schema->columns[$name])) $this->delete($name); $this->foreignKeysToDrop[] = $name; return $this; } public function toSql(): string { 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 = []; // Process new columns. foreach ($this->columnsToAdd as $columnName) { $col = $this->schema->columns[$columnName]; $clause = "ADD COLUMN `{$col['name']}` " . $col['type']->toString(); if (!$col['isNullable']) { $clause .= " NOT NULL"; } if ($col['isUnique']) { $clause .= " UNIQUE"; } if ($col['default'] !== null) { $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(); if (!$col['isNullable']) { $clause .= " NOT NULL"; } if ($col['isUnique']) { $clause .= " UNIQUE"; } if ($col['default'] !== null) { $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 $fkName) { $fk = $this->schema->foreignKeys[$fkName]; // Here we name the constraint “fk_{$name}”. (There are other acceptable naming schemes.) $clause = "ADD CONSTRAINT `fk_{$fk['name']}` FOREIGN KEY (`{$fk['name']}`) REFERENCES `{$fk['table']}` (`{$fk['name']}`) ON DELETE " . $fk['onDelete']->toString() . " ON UPDATE " . $fk['onUpdate']->toString(); $clauses[] = $clause; } // Process foreign keys to drop. foreach ($this->foreignKeysToDrop as $fkName) { // Again, we assume the constraint was named “fk_{$name}” $clauses[] = "DROP FOREIGN KEY `fk_{$fkName}`"; } if (empty($clauses)) { throw new InvalidArgumentException("No migration operations to perform."); } $sql = "ALTER TABLE `{$tableName}`\n" . implode(",\n", $clauses) . ";"; return esc_sql($sql); } public function drop(): null { global $wpdb; $tableName = $wpdb->prefix . $this->schema->name; $sql = "DROP TABLE IF EXISTS `{$tableName}`;"; $wpdb->query(esc_sql($sql)); return null; } public function migrate(): Schema { global $wpdb; $sql = $this->toSql(); $wpdb->query($sql); return $this->schema; } }