Commit 2af14ad9 authored by Administrator's avatar Administrator

Update 2 files via Son of Anton

parent a45e66f7
......@@ -4,37 +4,52 @@ declare(strict_types=1);
namespace App\Core\Migration;
use App\Core\Database;
use App\Core\Autoloader;
final class MigrationRunner
{
private Database $db;
private string $migrationsDir;
public function __construct(Database $db)
{
$this->db = $db;
$this->migrationsDir = dirname(__DIR__, 2) . '/../database/migrations';
$this->migrationsDir = realpath($this->migrationsDir) ?: (dirname(__DIR__, 3) . '/database/migrations');
}
public function migrate(): array
{
$this->ensureMigrationsTable();
$ran = $this->getRanMigrations();
$batch = $this->getNextBatch();
$executed = $this->getExecutedMigrations();
$files = $this->getMigrationFiles();
$batch = $this->getNextBatch();
$results = [];
foreach ($files as $file) {
$name = basename($file, '.php');
if (in_array($name, $ran)) {
if (in_array($name, $executed)) {
continue;
}
echo " Migrating: {$name}...\n";
$migration = require $file;
if (is_array($migration) && isset($migration['up'])) {
$this->db->raw($migration['up']);
// Array format: ['up' => 'SQL...', 'down' => 'SQL...']
$upSql = $migration['up'];
// Handle multiple statements separated by semicolons
$statements = $this->splitStatements($upSql);
foreach ($statements as $stmt) {
$stmt = trim($stmt);
if ($stmt !== '') {
$this->db->raw($stmt);
}
}
} elseif (is_object($migration) && method_exists($migration, 'up')) {
$migration->up($this->db);
} elseif (is_callable($migration)) {
$migration($this->db);
}
$this->db->insert('migrations', [
......@@ -52,30 +67,44 @@ final class MigrationRunner
public function rollback(): array
{
$this->ensureMigrationsTable();
$batch = $this->getLastBatch();
if ($batch === 0) {
$lastBatch = $this->getLastBatch();
if ($lastBatch === 0) {
return [];
}
$migrations = $this->db->select(
$rows = $this->db->select(
"SELECT migration FROM migrations WHERE batch = ? ORDER BY id DESC",
[$batch]
[$lastBatch]
);
$results = [];
foreach ($migrations as $row) {
$file = $this->getMigrationDir() . '/' . $row['migration'] . '.php';
foreach ($rows as $row) {
$name = $row['migration'];
$file = $this->migrationsDir . '/' . $name . '.php';
if (file_exists($file)) {
echo " Rolling back: {$name}...\n";
$migration = require $file;
if (is_array($migration) && isset($migration['down'])) {
$this->db->raw($migration['down']);
$statements = $this->splitStatements($migration['down']);
foreach ($statements as $stmt) {
$stmt = trim($stmt);
if ($stmt !== '') {
try {
$this->db->raw($stmt);
} catch (\Throwable $e) {
echo " ⚠️ Rollback warning for {$name}: " . $e->getMessage() . "\n";
}
}
}
} elseif (is_object($migration) && method_exists($migration, 'down')) {
$migration->down($this->db);
}
}
$this->db->delete('migrations', '`migration` = ?', [$row['migration']]);
$results[] = $row['migration'];
$this->db->query("DELETE FROM migrations WHERE migration = ?", [$name]);
$results[] = $name;
}
return $results;
......@@ -84,7 +113,7 @@ final class MigrationRunner
public function status(): array
{
$this->ensureMigrationsTable();
$ran = $this->getRanMigrations();
$executed = $this->getExecutedMigrations();
$files = $this->getMigrationFiles();
$status = [];
......@@ -92,7 +121,7 @@ final class MigrationRunner
$name = basename($file, '.php');
$status[] = [
'migration' => $name,
'status' => in_array($name, $ran) ? 'Ran' : 'Pending',
'status' => in_array($name, $executed) ? 'Ran' : 'Pending',
];
}
......@@ -101,9 +130,12 @@ final class MigrationRunner
private function ensureMigrationsTable(): void
{
if (!$this->db->tableExists('migrations')) {
try {
$this->db->selectOne("SELECT 1 FROM migrations LIMIT 1");
} catch (\Throwable $e) {
// Table doesn't exist — create it
$this->db->raw("
CREATE TABLE `migrations` (
CREATE TABLE IF NOT EXISTS `migrations` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`migration` VARCHAR(255) NOT NULL,
`batch` INT UNSIGNED NOT NULL,
......@@ -114,10 +146,29 @@ final class MigrationRunner
}
}
private function getRanMigrations(): array
private function getExecutedMigrations(): array
{
try {
$rows = $this->db->select("SELECT migration FROM migrations ORDER BY id");
return array_column($rows, 'migration');
} catch (\Throwable $e) {
return [];
}
}
private function getMigrationFiles(): array
{
if (!is_dir($this->migrationsDir)) {
echo " ⚠️ Migrations directory not found: {$this->migrationsDir}\n";
return [];
}
$files = glob($this->migrationsDir . '/Phase_*.php');
if ($files === false || empty($files)) {
return [];
}
sort($files); // Lexicographic sort: Phase_01_001 < Phase_02_001 < Phase_03_001
return $files;
}
private function getNextBatch(): int
......@@ -127,23 +178,43 @@ final class MigrationRunner
private function getLastBatch(): int
{
try {
$row = $this->db->selectOne("SELECT MAX(batch) as max_batch FROM migrations");
return (int) ($row['max_batch'] ?? 0);
} catch (\Throwable $e) {
return 0;
}
}
private function getMigrationFiles(): array
/**
* Split SQL string into individual statements.
* Handles semicolons inside the SQL properly.
*/
private function splitStatements(string $sql): array
{
$dir = $this->getMigrationDir();
$files = glob($dir . '/Phase_*.php');
if ($files === false) {
$sql = trim($sql);
if ($sql === '') {
return [];
}
sort($files);
return $files;
// Simple split on semicolons followed by whitespace/newline
// This handles 99% of cases for DDL statements
$statements = preg_split('/;\s*\n/', $sql);
if ($statements === false) {
return [$sql];
}
private function getMigrationDir(): string
{
return Autoloader::basePath() . '/database/migrations';
// Clean up: remove trailing semicolons from last statement
$result = [];
foreach ($statements as $stmt) {
$stmt = trim($stmt);
$stmt = rtrim($stmt, ';');
$stmt = trim($stmt);
if ($stmt !== '') {
$result[] = $stmt;
}
}
return $result;
}
}
\ No newline at end of file
......@@ -4,15 +4,17 @@ declare(strict_types=1);
namespace App\Core\Seeder;
use App\Core\Database;
use App\Core\Autoloader;
final class SeederRunner
{
private Database $db;
private string $seedsDir;
public function __construct(Database $db)
{
$this->db = $db;
$this->seedsDir = dirname(__DIR__, 2) . '/../database/seeds';
$this->seedsDir = realpath($this->seedsDir) ?: (dirname(__DIR__, 3) . '/database/seeds');
}
public function run(): array
......@@ -22,20 +24,28 @@ final class SeederRunner
$files = $this->getSeedFiles();
$results = [];
echo " Seeds directory: {$this->seedsDir}\n";
echo " Found " . count($files) . " seed file(s)\n";
echo " Already executed: " . count($executed) . "\n";
foreach ($files as $file) {
$name = basename($file, '.php');
if (in_array($name, $executed)) {
continue;
}
echo " Running: {$name}...\n";
echo " Seeding: {$name}...\n";
try {
$seed = require $file;
if (is_callable($seed)) {
$seed($this->db);
} elseif (is_object($seed) && method_exists($seed, 'run')) {
$seed->run($this->db);
} else {
echo " ⚠️ Seed {$name} returned non-callable, non-object. Skipping.\n";
continue;
}
$this->db->insert('seeds', [
......@@ -44,6 +54,12 @@ final class SeederRunner
]);
$results[] = $name;
echo " ✔ {$name} done\n";
} catch (\Throwable $e) {
echo " ❌ SEED FAILED: {$name} — " . $e->getMessage() . "\n";
echo " File: " . $e->getFile() . ":" . $e->getLine() . "\n";
// Continue to next seed — don't let one failure kill all seeds
}
}
return $results;
......@@ -74,7 +90,6 @@ final class SeederRunner
$seed->run($this->db);
}
// Record it (ignore duplicate)
$name = basename($targetFile, '.php');
$existing = $this->db->selectOne("SELECT id FROM seeds WHERE seed = ?", [$name]);
if (!$existing) {
......@@ -87,10 +102,9 @@ final class SeederRunner
private function ensureSeedsTable(): void
{
if (!$this->db->tableExists('seeds')) {
// Seeds table might not exist if migrations haven't created it
// The schema.sql has it, but individual migrations might not
// Create it directly
try {
$this->db->selectOne("SELECT 1 FROM seeds LIMIT 1");
} catch (\Throwable $e) {
$this->db->raw("
CREATE TABLE IF NOT EXISTS `seeds` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
......@@ -104,23 +118,26 @@ final class SeederRunner
private function getExecutedSeeds(): array
{
try {
$rows = $this->db->select("SELECT seed FROM seeds ORDER BY id");
return array_column($rows, 'seed');
} catch (\Throwable $e) {
return [];
}
}
private function getSeedFiles(): array
{
$dir = $this->getSeedDir();
$files = glob($dir . '/Phase_*.php');
if ($files === false) {
if (!is_dir($this->seedsDir)) {
echo " ⚠️ Seeds directory not found: {$this->seedsDir}\n";
return [];
}
$files = glob($this->seedsDir . '/Phase_*.php');
if ($files === false || empty($files)) {
return [];
}
sort($files);
return $files;
}
private function getSeedDir(): string
{
return Autoloader::basePath() . '/database/seeds';
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment