Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save mRoca/2c2c2e5ba1d17a7f795f4aee64e1e10a to your computer and use it in GitHub Desktop.
Save mRoca/2c2c2e5ba1d17a7f795f4aee64e1e10a to your computer and use it in GitHub Desktop.
Laravel - Create a migration adding an index for all foreign keys
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use Doctrine\DBAL\Schema\Index;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Schema;
final class CreateMissingForeignKeysIndexesTestCommand extends Command
{
/** @inheritdoc */
protected $signature = 'db:create-missing-indexes-migration {--dry-run}';
/** @inheritdoc */
protected $description = 'Creates a migration adding an index for each foreign key without index';
private string $migrationUpBody = '';
private string $migrationDownBody = '';
/**
* Execute the console command.
*/
public function handle(): int
{
$schemaManager = Schema::getConnection()->getDoctrineSchemaManager();
$tables = $schemaManager->listTables();
$foreignKeysWithIndex = 0;
$foreignKeysWithUniqueIndex = 0;
$foreignKeysWithoutIndex = 0;
foreach ($tables as $table) {
$tableName = $table->getName();
$foreignKeys = $schemaManager->listTableForeignKeys($tableName);
foreach ($foreignKeys as $foreignKey) {
$localColumns = $foreignKey->getLocalColumns();
/** @var Index[] $matchingIndexes */
$matchingIndexes = array_values(array_filter($table->getIndexes(), function (Index $index) use ($localColumns) {
// Foreign keys are considered as an index
// for SQL Server, indexes are clustered (primary keys), or nonclustered
// TOOD Update this line for other Databases
if (!$index->isPrimary() && !$index->hasFlag('nonclustered')) {
return false;
}
return $index->spansColumns($localColumns);
}));
if (!empty($matchingIndexes)) {
if ($matchingIndexes[0]->isUnique()) {
++$foreignKeysWithUniqueIndex;
} else {
++$foreignKeysWithIndex;
}
$firstIndexName = $matchingIndexes[0]->getName();
$this->output->success(
"Table [$tableName] already has index on [" . implode(',', $localColumns) . "] : $firstIndexName"
);
continue;
}
++$foreignKeysWithoutIndex;
$this->output->warning("Table [$tableName] needs an index on [" . implode(',', $localColumns) . "]");
$this->storeIndexToCreate($tableName, $localColumns);
}
}
$this->output->info(
"$foreignKeysWithUniqueIndex foreign keys with UNIQUE index, $foreignKeysWithIndex foreign keys with index, $foreignKeysWithoutIndex WITHOUT INDEXES"
);
if ($this->option('dry-run')) {
$this->info('Dry run, skipping migration creation');
return self::SUCCESS;
}
$fileName = $this->createMigration();
$this->output->comment("File $fileName created with success");
return self::SUCCESS;
}
private function storeIndexToCreate(string $tableName, array $columns): void
{
$columnsName = implode(', ', array_map(static fn(string $col) => "'$col'", $columns));
$this->migrationUpBody .= <<<EOL
Schema::table('$tableName', function (Blueprint \$table): void {
\$table->index([$columnsName]);
});
EOL;
$this->migrationDownBody .= <<<EOL
Schema::table('$tableName', function (Blueprint \$table): void {
\$table->dropIndex([$columnsName]);
});
EOL;
}
private function createMigration(): string
{
$curDate = date('Y_m_d_His');
$filename = "database/migrations/${curDate}_add_missing_foreign_keys_indexes.php";
$content = <<<EOL
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
return new class() extends Migration {
public function up(): void
{
$this->migrationUpBody
}
public function down(): void
{
$this->migrationDownBody
}
};
EOL;
file_put_contents($filename, $content);
return $filename;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment