<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Reorder client_id columns to be positioned directly after id column.
* Ensures consistency: id, client_id, ... other columns
*/
final class Version20251022100200 extends AbstractMigration
{
public function getDescription(): string
{
return 'Reorder client_id columns to position 2 (after id) in all tables';
}
private function columnExists(string $table, string $column): bool
{
$result = $this->connection->fetchOne(
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND COLUMN_NAME = ?",
[$table, $column]
);
return $result > 0;
}
private function getFirstColumn(string $table): ?string
{
$result = $this->connection->fetchOne(
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
ORDER BY ORDINAL_POSITION
LIMIT 1",
[$table]
);
return $result ?: null;
}
public function up(Schema $schema): void
{
// ============================================================
// Reposition client_id to be right after id column
// Using MODIFY COLUMN ... AFTER id
// ============================================================
$tables = [
// Core entities
'person',
'provider',
'speaker',
'tags',
// Newsletter & Communication
'newsletter',
'manual_newsletter',
'attachment',
// Courses & Events
'course',
'course_data',
'course_field',
'course_occurrence',
'course_occurrence_time',
'course_series',
'course_subscription',
'course_subscription_booking',
'course_type',
'course_image',
'course_text',
// Shopping & Orders
'cart',
'cart_item',
'customer_order',
'customer_order_item',
'customer_order_item_person',
'wait_item',
// Invoicing
'invoice',
'invoice_item',
'invoice_item_attendees',
'invoice_payment',
'invoice_reminder',
// Customer Management
'customer_document',
'customer_history_entry',
// System & Configuration
'client_config',
'textblocks',
'protocol_entry',
'presence',
'presence_reason',
'ckimage',
'search_index_entry',
'email_history_entry',
// Venue & Location
'venue',
'venue_room',
'venue_document',
'venue_image',
// Speaker related
'speaker_image',
'speaker_text',
// Categories
'category',
];
foreach ($tables as $table) {
// Skip search_index_entry - it has a composite primary key
if ($table === 'search_index_entry') {
// Special handling for search_index_entry: rename clientId to client_id
if ($this->columnExists($table, 'clientId')) {
$this->addSql("ALTER TABLE $table CHANGE clientId client_id INT NOT NULL FIRST");
} elseif ($this->columnExists($table, 'client_id')) {
// Already renamed, just position it first
$this->addSql("ALTER TABLE $table MODIFY client_id INT NOT NULL FIRST");
}
continue;
}
if ($this->columnExists($table, 'client_id')) {
// Check if table has 'id' column
if ($this->columnExists($table, 'id')) {
// Position after 'id' column
$this->addSql("ALTER TABLE $table MODIFY client_id INT DEFAULT NULL AFTER id");
} else {
// Skip tables without 'id' column
// This shouldn't happen in our entities
}
}
}
}
public function down(Schema $schema): void
{
// No need to reverse - column order doesn't affect functionality
// But if needed, we could reposition to original location
}
}