feat: Complete Woles Framework v1.0 with enterprise-grade UI
- Add comprehensive error handling system with custom error pages - Implement professional enterprise-style design with Tailwind CSS - Create modular HMVC architecture with clean separation of concerns - Add security features: CSRF protection, XSS filtering, Argon2ID hashing - Include CLI tools for development workflow - Add error reporting dashboard with system monitoring - Implement responsive design with consistent slate color scheme - Replace all emoji icons with professional SVG icons - Add comprehensive test suite with PHPUnit - Include database migrations and seeders - Add proper exception handling with fallback pages - Implement template engine with custom syntax support - Add helper functions and facades for clean code - Include proper logging and debugging capabilities
This commit is contained in:
249
app/Core/Database/Migrator.php
Normal file
249
app/Core/Database/Migrator.php
Normal file
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core\Database;
|
||||
|
||||
/**
|
||||
* NovaCore Database Migrator
|
||||
* Handle database migrations
|
||||
*/
|
||||
class Migrator
|
||||
{
|
||||
private Connection $connection;
|
||||
private string $migrationsPath;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->connection = $this->getConnection();
|
||||
$this->migrationsPath = __DIR__ . '/../../database/migrations';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database connection
|
||||
*/
|
||||
protected function getConnection(): Connection
|
||||
{
|
||||
$config = include __DIR__ . '/../../Config/database.php';
|
||||
$connectionConfig = $config['connections'][$config['default']];
|
||||
|
||||
return new Connection($connectionConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all pending migrations
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$this->createMigrationsTable();
|
||||
|
||||
$migrations = $this->getPendingMigrations();
|
||||
|
||||
foreach ($migrations as $migration) {
|
||||
$this->runMigration($migration);
|
||||
}
|
||||
|
||||
echo "Migrations completed successfully!\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create migrations table
|
||||
*/
|
||||
private function createMigrationsTable(): void
|
||||
{
|
||||
$sql = "CREATE TABLE IF NOT EXISTS migrations (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
migration VARCHAR(255) NOT NULL,
|
||||
batch INT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)";
|
||||
|
||||
$this->connection->execute($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pending migrations
|
||||
*/
|
||||
private function getPendingMigrations(): array
|
||||
{
|
||||
$migrationFiles = glob($this->migrationsPath . '/*.php');
|
||||
$migratedFiles = $this->getMigratedFiles();
|
||||
|
||||
$pending = [];
|
||||
foreach ($migrationFiles as $file) {
|
||||
$filename = basename($file);
|
||||
if (!in_array($filename, $migratedFiles)) {
|
||||
$pending[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
sort($pending);
|
||||
return $pending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get already migrated files
|
||||
*/
|
||||
private function getMigratedFiles(): array
|
||||
{
|
||||
$sql = "SELECT migration FROM migrations ORDER BY id";
|
||||
$results = $this->connection->fetchAll($sql);
|
||||
|
||||
return array_column($results, 'migration');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run single migration
|
||||
*/
|
||||
private function runMigration(string $file): void
|
||||
{
|
||||
$filename = basename($file);
|
||||
$className = $this->getMigrationClassName($filename);
|
||||
|
||||
require_once $file;
|
||||
|
||||
if (!class_exists($className)) {
|
||||
throw new \Exception("Migration class {$className} not found in {$file}");
|
||||
}
|
||||
|
||||
$migration = new $className();
|
||||
$migration->up();
|
||||
|
||||
// Record migration
|
||||
$this->recordMigration($filename);
|
||||
|
||||
echo "✓ {$filename}\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get migration class name
|
||||
*/
|
||||
private function getMigrationClassName(string $filename): string
|
||||
{
|
||||
$name = pathinfo($filename, PATHINFO_FILENAME);
|
||||
$parts = explode('_', $name);
|
||||
|
||||
// Remove timestamp
|
||||
array_shift($parts);
|
||||
|
||||
// Convert to PascalCase
|
||||
$className = '';
|
||||
foreach ($parts as $part) {
|
||||
$className .= ucfirst($part);
|
||||
}
|
||||
|
||||
return $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record migration
|
||||
*/
|
||||
private function recordMigration(string $filename): void
|
||||
{
|
||||
$batch = $this->getNextBatchNumber();
|
||||
|
||||
$sql = "INSERT INTO migrations (migration, batch) VALUES (?, ?)";
|
||||
$this->connection->execute($sql, [$filename, $batch]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next batch number
|
||||
*/
|
||||
private function getNextBatchNumber(): int
|
||||
{
|
||||
$sql = "SELECT MAX(batch) as max_batch FROM migrations";
|
||||
$result = $this->connection->fetch($sql);
|
||||
|
||||
return ($result['max_batch'] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback last batch
|
||||
*/
|
||||
public function rollback(): void
|
||||
{
|
||||
$lastBatch = $this->getLastBatchNumber();
|
||||
|
||||
if (!$lastBatch) {
|
||||
echo "No migrations to rollback.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$migrations = $this->getMigrationsByBatch($lastBatch);
|
||||
|
||||
foreach (array_reverse($migrations) as $migration) {
|
||||
$this->rollbackMigration($migration);
|
||||
}
|
||||
|
||||
echo "Rollback completed successfully!\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last batch number
|
||||
*/
|
||||
private function getLastBatchNumber(): ?int
|
||||
{
|
||||
$sql = "SELECT MAX(batch) as max_batch FROM migrations";
|
||||
$result = $this->connection->fetch($sql);
|
||||
|
||||
return $result['max_batch'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get migrations by batch
|
||||
*/
|
||||
private function getMigrationsByBatch(int $batch): array
|
||||
{
|
||||
$sql = "SELECT migration FROM migrations WHERE batch = ? ORDER BY id DESC";
|
||||
$results = $this->connection->fetchAll($sql, [$batch]);
|
||||
|
||||
return array_column($results, 'migration');
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback single migration
|
||||
*/
|
||||
private function rollbackMigration(string $filename): void
|
||||
{
|
||||
$file = $this->migrationsPath . '/' . $filename;
|
||||
|
||||
if (!file_exists($file)) {
|
||||
echo "Warning: Migration file {$filename} not found.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$className = $this->getMigrationClassName($filename);
|
||||
|
||||
require_once $file;
|
||||
|
||||
if (!class_exists($className)) {
|
||||
echo "Warning: Migration class {$className} not found.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$migration = new $className();
|
||||
$migration->down();
|
||||
|
||||
// Remove migration record
|
||||
$sql = "DELETE FROM migrations WHERE migration = ?";
|
||||
$this->connection->execute($sql, [$filename]);
|
||||
|
||||
echo "✓ Rolled back {$filename}\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get migration status
|
||||
*/
|
||||
public function status(): void
|
||||
{
|
||||
$migrationFiles = glob($this->migrationsPath . '/*.php');
|
||||
$migratedFiles = $this->getMigratedFiles();
|
||||
|
||||
echo "Migration Status:\n";
|
||||
echo "================\n";
|
||||
|
||||
foreach ($migrationFiles as $file) {
|
||||
$filename = basename($file);
|
||||
$status = in_array($filename, $migratedFiles) ? '✓' : '✗';
|
||||
echo "{$status} {$filename}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user