misuzu/src/Database/DatabaseMigrationManager.php

156 lines
4.8 KiB
PHP

<?php
namespace Misuzu\Database;
use Exception;
use PDO;
use PDOException;
final class DatabaseMigrationManager {
private $targetConnection;
private $migrationStorage;
private const MIGRATION_NAMESPACE = '\\Misuzu\\DatabaseMigrations\\%s\\%s';
private $errors = [];
private $logFunction;
public function __construct(PDO $conn, string $path) {
$this->targetConnection = $conn;
$this->migrationStorage = realpath($path);
}
private function addError(Exception $exception): void {
$this->errors[] = $exception;
$this->writeLog($exception->getMessage());
}
public function setLogger(callable $logger): void {
$this->logFunction = $logger;
}
private function writeLog(string $log): void {
if(!is_callable($this->logFunction)) {
return;
}
call_user_func($this->logFunction, $log);
}
public function getErrors(): array {
return $this->errors;
}
private function getMigrationScripts(): array {
if(!file_exists($this->migrationStorage) || !is_dir($this->migrationStorage)) {
$this->addError(new Exception('Migrations script directory does not exist.'));
return [];
}
$files = glob(rtrim($this->migrationStorage, '/\\') . '/*.php');
return $files;
}
private function createMigrationRepository(): bool {
try {
$this->targetConnection->exec('
CREATE TABLE IF NOT EXISTS `msz_migrations` (
`migration_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`migration_name` VARCHAR(255) NOT NULL,
`migration_batch` INT(11) UNSIGNED NOT NULL,
PRIMARY KEY (`migration_id`),
UNIQUE INDEX (`migration_id`)
)
');
} catch(PDOException $ex) {
$this->addError($ex);
return false;
}
return true;
}
public function migrate(): bool {
$this->writeLog('Running migrations...');
if(!$this->createMigrationRepository()) {
return false;
}
$migrationScripts = $this->getMigrationScripts();
if(count($migrationScripts) < 1) {
if(count($this->errors) > 0) {
return false;
}
$this->writeLog('Nothing to migrate!');
return true;
}
try {
$this->writeLog('Fetching completed migration...');
$fetchStatus = $this->targetConnection->prepare("
SELECT *, CONCAT(:basepath, '/', `migration_name`, '.php') as `migration_path`
FROM `msz_migrations`
");
$fetchStatus->bindValue('basepath', $this->migrationStorage);
$migrationStatus = $fetchStatus->execute() ? $fetchStatus->fetchAll() : [];
} catch(PDOException $ex) {
$this->addError($ex);
return false;
}
if(count($migrationStatus) < 1 && count($this->errors) > 0) {
return false;
}
$remainingMigrations = array_diff($migrationScripts, array_column($migrationStatus, 'migration_path'));
if(count($remainingMigrations) < 1) {
$this->writeLog('Nothing to migrate!');
return true;
}
$batchNumber = $this->targetConnection->query('
SELECT COALESCE(MAX(`migration_batch`), 0) + 1
FROM `msz_migrations`
')->fetchColumn();
$recordMigration = $this->targetConnection->prepare('
INSERT INTO `msz_migrations`
(`migration_name`, `migration_batch`)
VALUES
(:name, :batch)
');
$recordMigration->bindValue('batch', $batchNumber);
foreach($remainingMigrations as $migration) {
$filename = pathinfo($migration, PATHINFO_FILENAME);
$filenameSplit = explode('_', $filename);
$recordMigration->bindValue('name', $filename);
$migrationName = '';
if(count($filenameSplit) < 5) {
$this->addError(new Exception("Invalid migration name: '{$filename}'"));
return false;
}
for($i = 4; $i < count($filenameSplit); $i++) {
$migrationName .= ucfirst(mb_strtolower($filenameSplit[$i]));
}
include_once $migration;
$this->writeLog("Running migration '{$filename}'...");
$migrationFunction = sprintf(self::MIGRATION_NAMESPACE, $migrationName, 'migrate_up');
$migrationFunction($this->targetConnection);
$recordMigration->execute();
}
$this->writeLog('Successfully completed all migrations!');
return true;
}
}