Files
michaelschiemer/src/Framework/Database/Migration/Services/MigrationDatabaseManager.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

117 lines
4.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Database\Migration\Services;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\ValueObjects\MigrationTableConfig;
use App\Framework\Database\Platform\DatabasePlatform;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\DateTime\Clock;
final readonly class MigrationDatabaseManager
{
public function __construct(
private ConnectionInterface $connection,
private DatabasePlatform $platform,
private Clock $clock,
private MigrationTableConfig $tableConfig
) {
}
public function ensureMigrationsTable(): void
{
if ($this->tableExists($this->tableConfig->tableName)) {
return;
}
$sql = $this->createMigrationsTableSQL($this->tableConfig->tableName);
// PostgreSQL doesn't support multiple statements in prepared statements
// Split by semicolon and execute each statement separately
if ($this->platform->getName() === 'PostgreSQL') {
$statements = array_filter(
array_map('trim', explode(';', $sql)),
fn($stmt) => !empty($stmt)
);
foreach ($statements as $statement) {
$this->connection->execute(SqlQuery::create($statement));
}
} else {
$this->connection->execute(SqlQuery::create($sql));
}
}
public function recordMigrationExecution(Migration $migration, string $version): void
{
$now = $this->clock->now()->format('Y-m-d H:i:s');
$sql = "INSERT INTO {$this->tableConfig->tableName} (version, description, executed_at) VALUES (?, ?, ?)";
$this->connection->execute(SqlQuery::create($sql, [$version, $migration->getDescription(), $now]));
}
public function recordMigrationRollback(string $version): void
{
$sql = "DELETE FROM {$this->tableConfig->tableName} WHERE version = ?";
$this->connection->execute(SqlQuery::create($sql, [$version]));
}
public function getAppliedVersions(): array
{
$this->ensureMigrationsTable();
$sql = "SELECT version FROM {$this->tableConfig->tableName} ORDER BY executed_at ASC";
return $this->connection->queryColumn(SqlQuery::create($sql));
}
public function tableExists(string $tableName): bool
{
try {
$sql = $this->platform->getTableExistsSQL($tableName);
$result = $this->connection->queryScalar(SqlQuery::create($sql));
return (bool) $result;
} catch (\Throwable $e) {
return false;
}
}
private function createMigrationsTableSQL(string $tableName): string
{
return match ($this->platform->getName()) {
'mysql' => "CREATE TABLE {$tableName} (
id INT AUTO_INCREMENT PRIMARY KEY,
version VARCHAR(255) NOT NULL UNIQUE,
description TEXT NOT NULL,
executed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_version (version),
INDEX idx_executed_at (executed_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
'PostgreSQL' => "CREATE TABLE {$tableName} (
id SERIAL PRIMARY KEY,
version VARCHAR(255) NOT NULL UNIQUE,
description TEXT NOT NULL,
executed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_{$tableName}_version ON {$tableName} (version);
CREATE INDEX idx_{$tableName}_executed_at ON {$tableName} (executed_at);",
'sqlite' => "CREATE TABLE {$tableName} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
version TEXT NOT NULL UNIQUE,
description TEXT NOT NULL,
executed_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_{$tableName}_version ON {$tableName} (version);
CREATE INDEX idx_{$tableName}_executed_at ON {$tableName} (executed_at);",
default => throw new \RuntimeException("Unsupported database platform: {$this->platform->getName()}")
};
}
}