execute('CREATE TABLE ...'); } } EOF; private IDbStatement $checkStmt; private IDbStatement $insertStmt; public function __construct( private IDbConnection $conn, private string $tableName = self::DEFAULT_TABLE, ) {} public function createTrackingTable(): void { // this is not ok but it works for now, there should probably be a generic type bag alias thing $nameType = $this->conn instanceof SQLiteConnection ? 'TEXT' : 'VARCHAR(255)'; $this->conn->execute(sprintf(self::CREATE_TRACK_TABLE, $this->tableName, $nameType)); $this->conn->execute(sprintf(self::CREATE_TRACK_INDEX, $this->tableName)); } public function destroyTrackingTable(): void { $this->conn->execute(sprintf(self::DESTROY_TRACK_TABLE, $this->tableName)); } public function prepareStatements(): void { $this->checkStmt = $this->conn->prepare(sprintf(self::CHECK_STMT, $this->tableName)); $this->insertStmt = $this->conn->prepare(sprintf(self::INSERT_STMT, $this->tableName)); } public function init(): void { $this->createTrackingTable(); $this->prepareStatements(); } public function checkMigration(string $name): bool { $this->checkStmt->reset(); $this->checkStmt->addParameter(1, $name, DbType::STRING); $this->checkStmt->execute(); $result = $this->checkStmt->getResult(); return $result->next() && !$result->isNull(0); } public function completeMigration(string $name, ?DateTimeInterface $dateTime = null): void { $dateTime = ($dateTime ?? DateTime::utcNow())->format(DateTimeInterface::ATOM); $this->insertStmt->reset(); $this->insertStmt->addParameter(1, $name, DbType::STRING); $this->insertStmt->addParameter(2, $dateTime, DbType::STRING); $this->insertStmt->execute(); } public function template(string $name): string { return sprintf(self::TEMPLATE, $name); } public function createFileName(string $name, ?DateTimeInterface $dateTime = null): string { $dateTime ??= DateTime::utcNow(); if(empty($name)) throw new InvalidArgumentException('$name may not be empty.'); $name = str_replace(' ', '_', strtolower($name)); if(!preg_match('#^([a-z_]+)$#', $name)) throw new InvalidArgumentException('$name may only contain alphabetical, spaces and _ characters.'); return $dateTime->format('Y_m_d_His_') . trim($name, '_'); } public function createClassName(string $name, ?DateTimeInterface $dateTime = null): string { $dateTime ??= DateTime::utcNow(); if(empty($name)) throw new InvalidArgumentException('$name may not be empty.'); $name = str_replace(' ', '_', strtolower($name)); if(!preg_match('#^([a-z_]+)$#', $name)) throw new InvalidArgumentException('$name may only contain alphabetical, spaces and _ characters.'); $parts = explode('_', trim($name, '_')); $name = ''; foreach($parts as $part) $name .= ucfirst($part); return $name . $dateTime->format('_Ymd_His'); } public function createNames(string $baseName, ?DateTimeInterface $dateTime = null): object { $dateTime ??= DateTime::utcNow(); $names = new stdClass; $names->name = $this->createFileName($baseName, $dateTime); $names->className = $this->createClassName($baseName, $dateTime); return $names; } public function processMigrations(IDbMigrationRepo $migrations): array { $migrations = $migrations->getMigrations(); $completed = []; foreach($migrations as $migration) { $name = $migration->getName(); if($this->checkMigration($name)) continue; $migration->migrate($this->conn); $this->completeMigration($name); $completed[] = $name; } return $completed; } }