table('{TABLE_NAME}', function ($table) { {UP_STATEMENTS} }); } public function down(Schema $schema): void { $schema->table('{TABLE_NAME}', function ($table) { {DOWN_STATEMENTS} }); } } PHP; public function __construct( private PdoConnection $connection ) {} /** * Generate migration file for adding recommended indexes * * @param array $recommendations */ public function generateAddIndexMigration(array $recommendations, string $tableName): string { if (empty($recommendations)) { throw new \InvalidArgumentException('No index recommendations provided'); } $className = $this->generateClassName('AddIndexesTo' . ucfirst($tableName)); $upStatements = []; $downStatements = []; foreach ($recommendations as $recommendation) { $indexName = $recommendation->getIndexName()->toString(); $columns = $recommendation->columns; $upStatements[] = $this->generateAddIndexStatement($indexName, $columns, $recommendation->indexType->value); $downStatements[] = $this->generateDropIndexStatement($indexName); } $description = 'Add optimized indexes based on query analysis'; return $this->fillTemplate( className: $className, tableName: $tableName, description: $description, upStatements: $upStatements, downStatements: $downStatements ); } /** * Generate migration file for removing unused indexes * * @param array}> $unusedIndexes */ public function generateRemoveIndexMigration(array $unusedIndexes, string $tableName): string { if (empty($unusedIndexes)) { throw new \InvalidArgumentException('No unused indexes provided'); } $className = $this->generateClassName('RemoveUnusedIndexesFrom' . ucfirst($tableName)); $upStatements = []; $downStatements = []; foreach ($unusedIndexes as $index) { $indexName = $index['index_name']; $columns = $index['columns']; $upStatements[] = $this->generateDropIndexStatement($indexName); $downStatements[] = $this->generateAddIndexStatement($indexName, $columns, 'BTREE'); } $description = 'Remove unused indexes identified by analysis'; return $this->fillTemplate( className: $className, tableName: $tableName, description: $description, upStatements: $upStatements, downStatements: $downStatements ); } /** * Generate comprehensive optimization migration (add + remove) * * @param array $toAdd * @param array}> $toRemove */ public function generateOptimizationMigration(array $toAdd, array $toRemove, string $tableName): string { $className = $this->generateClassName('OptimizeIndexesFor' . ucfirst($tableName)); $upStatements = []; $downStatements = []; // First, drop unused indexes foreach ($toRemove as $index) { $indexName = $index['index_name']; $columns = $index['columns']; $upStatements[] = $this->generateDropIndexStatement($indexName); $downStatements[] = $this->generateAddIndexStatement($indexName, $columns, 'BTREE'); } // Then, add recommended indexes foreach ($toAdd as $recommendation) { $indexName = $recommendation->getIndexName()->toString(); $columns = $recommendation->columns; $upStatements[] = $this->generateAddIndexStatement($indexName, $columns, $recommendation->indexType->value); $downStatements[] = $this->generateDropIndexStatement($indexName); } // Reverse down statements to maintain proper order $downStatements = array_reverse($downStatements); $description = 'Optimize indexes: remove unused, add recommended'; return $this->fillTemplate( className: $className, tableName: $tableName, description: $description, upStatements: $upStatements, downStatements: $downStatements ); } /** * Save migration file to disk */ public function saveMigration(string $migrationContent, ?string $customPath = null): string { $timestamp = date('YmdHis'); $className = $this->extractClassName($migrationContent); $snakeCaseName = $this->camelToSnake($className); $filename = "{$timestamp}_{$snakeCaseName}.php"; $path = $customPath ?? __DIR__ . '/../../../migrations/' . $filename; // Ensure directory exists $directory = dirname($path); if (!is_dir($directory)) { mkdir($directory, 0755, true); } file_put_contents($path, $migrationContent); return $path; } /** * Generate migration class name with timestamp */ private function generateClassName(string $baseName): string { return 'Migration' . date('YmdHis') . $baseName; } /** * Generate ADD INDEX statement */ private function generateAddIndexStatement(string $indexName, array $columns, string $type = 'BTREE'): string { $columnsStr = implode(', ', array_map(fn($col) => "'{$col}'", $columns)); return "\$table->index([{$columnsStr}], '{$indexName}', '{$type}');"; } /** * Generate DROP INDEX statement */ private function generateDropIndexStatement(string $indexName): string { return "\$table->dropIndex('{$indexName}');"; } /** * Fill migration template with actual data */ private function fillTemplate( string $className, string $tableName, string $description, array $upStatements, array $downStatements ): string { $upStatementsStr = implode("\n", array_map( fn($stmt) => " {$stmt}", $upStatements )); $downStatementsStr = implode("\n", array_map( fn($stmt) => " {$stmt}", $downStatements )); return str_replace( ['{CLASS_NAME}', '{TABLE_NAME}', '{DESCRIPTION}', '{UP_STATEMENTS}', '{DOWN_STATEMENTS}'], [$className, $tableName, $description, $upStatementsStr, $downStatementsStr], self::MIGRATION_TEMPLATE ); } /** * Extract class name from migration content */ private function extractClassName(string $content): string { if (preg_match('/final class (\w+) extends Migration/', $content, $matches)) { return $matches[1]; } throw new \RuntimeException('Could not extract class name from migration'); } /** * Convert camelCase to snake_case */ private function camelToSnake(string $input): string { return strtolower(preg_replace('/(?