<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Complete multi-tenancy migration - Simple and reliable version.
* Adds client_id to all entities and makes created/modified nullable.
*/
final class Version20251022100000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add client_id to all entities and make created/modified nullable';
}
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 foreignKeyExists(string $table, string $fkName): bool
{
$result = $this->connection->fetchOne(
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND CONSTRAINT_NAME = ?
AND CONSTRAINT_TYPE = 'FOREIGN KEY'",
[$table, $fkName]
);
return $result > 0;
}
private function indexExists(string $table, string $indexName): bool
{
$result = $this->connection->fetchOne(
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND INDEX_NAME = ?",
[$table, $indexName]
);
return $result > 0;
}
public function up(Schema $schema): void
{
// ============================================================
// Add client_id columns
// ============================================================
$tables = [
'attachment', 'cart_item', 'ckimage', 'course_image', 'course_occurrence',
'course_occurrence_time', 'course_subscription_booking', 'course_text',
'customer_document', 'customer_history_entry', 'customer_order_item',
'customer_order_item_person', 'invoice', 'invoice_item', 'invoice_item_attendees',
'invoice_payment', 'invoice_reminder', 'manual_newsletter', 'newsletter',
'person', 'presence_reason', 'protocol_entry', 'provider', 'speaker',
'speaker_image', 'speaker_text', 'tags', 'textblocks', 'venue_document',
'venue_image', 'venue_room', 'wait_item'
];
foreach ($tables as $table) {
if (!$this->columnExists($table, 'client_id')) {
$this->addSql("ALTER TABLE $table ADD client_id INT DEFAULT NULL");
}
}
// ============================================================
// Make created/modified nullable in all tables
// ============================================================
$entityTables = [
'attachment', 'cart', 'cart_item', 'category', 'ckimage', 'client_config',
'course', 'course_data', 'course_field', 'course_image', 'course_occurrence',
'course_series', 'course_subscription', 'course_subscription_booking',
'course_text', 'course_type', 'customer_document', 'customer_history_entry',
'customer_order', 'customer_order_item', 'customer_order_item_person',
'email_history_entry', 'invoice', 'invoice_item', 'invoice_payment',
'invoice_reminder', 'person', 'presence', 'presence_reason', 'protocol_entry',
'provider', 'search_index_entry', 'speaker', 'speaker_image', 'speaker_text',
'tags', 'textblocks', 'venue', 'venue_document', 'venue_image', 'venue_room', 'wait_item'
];
foreach ($entityTables as $table) {
if ($this->columnExists($table, 'created')) {
$this->addSql("ALTER TABLE $table MODIFY created DATETIME DEFAULT NULL");
}
if ($this->columnExists($table, 'modified')) {
$this->addSql("ALTER TABLE $table MODIFY modified DATETIME DEFAULT NULL");
}
}
// ============================================================
// Add Foreign Keys and Indexes
// ============================================================
$constraints = [
'attachment' => 'FK_795FD9BB19EB6921',
'cart_item' => 'FK_F0FE252719EB6921',
'ckimage' => 'FK_5A0E314919EB6921',
'course_image' => 'FK_3E47454719EB6921',
'course_occurrence' => 'FK_9A13519919EB6921',
'course_occurrence_time' => 'FK_A4E42A7319EB6921',
'course_subscription_booking' => 'FK_CSB19EB6921',
'course_text' => 'FK_9E5A7BF19EB6921',
'customer_document' => 'FK_7C3547E219EB6921',
'customer_history_entry' => 'FK_48E0B46219EB6921',
'customer_order_item' => 'FK_4D39F7F419EB6921',
'customer_order_item_person' => 'FK_9C6D81F519EB6921',
'invoice' => 'FK_9065174419EB6921',
'invoice_item' => 'FK_1DDE477B19EB6921',
'invoice_item_attendees' => 'FK_IIA19EB6921',
'invoice_payment' => 'FK_1850519419EB6921',
'invoice_reminder' => 'FK_5F1F151819EB6921',
'manual_newsletter' => 'FK_76687F0719EB6921',
'newsletter' => 'FK_7E8585C819EB6921',
'person' => 'FK_34DCD17619EB6921',
'presence_reason' => 'FK_D629391C19EB6921',
'protocol_entry' => 'FK_E545771019EB6921',
'provider' => 'FK_92C4739C19EB6921',
'speaker' => 'FK_7B85DB6119EB6921',
'speaker_image' => 'FK_F0ABBBCC19EB6921',
'speaker_text' => 'FK_189CCEEF19EB6921',
'tags' => 'FK_6FBC942619EB6921',
'textblocks' => 'FK_1261C3B919EB6921',
'venue_document' => 'FK_B9B2BD5219EB6921',
'venue_image' => 'FK_1D86098819EB6921',
'venue_room' => 'FK_C4B95C3319EB6921',
'wait_item' => 'FK_640BFE6E19EB6921',
];
foreach ($constraints as $table => $fk) {
if ($this->columnExists($table, 'client_id')) {
// Add foreign key only if it doesn't exist
if (!$this->foreignKeyExists($table, $fk)) {
$this->addSql("ALTER TABLE $table ADD CONSTRAINT $fk FOREIGN KEY (client_id) REFERENCES client (id)");
}
// Add index only if it doesn't exist
if (!$this->indexExists($table, $fk)) {
$this->addSql("CREATE INDEX $fk ON $table (client_id)");
}
}
}
// Other fixes
if ($this->columnExists('user', 'person_id')) {
$this->addSql('ALTER TABLE user DROP person_id');
}
}
public function down(Schema $schema): void
{
// Remove foreign keys and columns
$constraints = [
'attachment' => 'FK_795FD9BB19EB6921',
'cart_item' => 'FK_F0FE252719EB6921',
'ckimage' => 'FK_5A0E314919EB6921',
'course_image' => 'FK_3E47454719EB6921',
'course_occurrence' => 'FK_9A13519919EB6921',
'course_occurrence_time' => 'FK_A4E42A7319EB6921',
'course_subscription_booking' => 'FK_CSB19EB6921',
'course_text' => 'FK_9E5A7BF19EB6921',
'customer_document' => 'FK_7C3547E219EB6921',
'customer_history_entry' => 'FK_48E0B46219EB6921',
'customer_order_item' => 'FK_4D39F7F419EB6921',
'customer_order_item_person' => 'FK_9C6D81F519EB6921',
'invoice' => 'FK_9065174419EB6921',
'invoice_item' => 'FK_1DDE477B19EB6921',
'invoice_item_attendees' => 'FK_IIA19EB6921',
'invoice_payment' => 'FK_1850519419EB6921',
'invoice_reminder' => 'FK_5F1F151819EB6921',
'manual_newsletter' => 'FK_76687F0719EB6921',
'newsletter' => 'FK_7E8585C819EB6921',
'person' => 'FK_34DCD17619EB6921',
'presence_reason' => 'FK_D629391C19EB6921',
'protocol_entry' => 'FK_E545771019EB6921',
'provider' => 'FK_92C4739C19EB6921',
'speaker' => 'FK_7B85DB6119EB6921',
'speaker_image' => 'FK_F0ABBBCC19EB6921',
'speaker_text' => 'FK_189CCEEF19EB6921',
'tags' => 'FK_6FBC942619EB6921',
'textblocks' => 'FK_1261C3B919EB6921',
'venue_document' => 'FK_B9B2BD5219EB6921',
'venue_image' => 'FK_1D86098819EB6921',
'venue_room' => 'FK_C4B95C3319EB6921',
'wait_item' => 'FK_640BFE6E19EB6921',
];
foreach ($constraints as $table => $fk) {
$this->addSql("ALTER TABLE $table DROP FOREIGN KEY $fk");
$this->addSql("DROP INDEX $fk ON $table");
$this->addSql("ALTER TABLE $table DROP client_id");
}
}
}