From 89be921fd01cdd5c4ca27a53612b6b0e925792fe Mon Sep 17 00:00:00 2001 From: Dasun Date: Sat, 26 Oct 2024 16:01:54 +0530 Subject: [PATCH] Migrations implemented (make, run, rollback) --- Core/src/Database/Database.php | 39 +++++++ Core/src/Database/Migration.php | 9 ++ Core/src/Database/Schema.php | 52 +++++++++ cli | 201 +++++++++++++++++++++----------- composer.json | 3 +- 5 files changed, 235 insertions(+), 69 deletions(-) create mode 100644 Core/src/Database/Database.php create mode 100644 Core/src/Database/Migration.php create mode 100644 Core/src/Database/Schema.php diff --git a/Core/src/Database/Database.php b/Core/src/Database/Database.php new file mode 100644 index 0000000..6ca0eae --- /dev/null +++ b/Core/src/Database/Database.php @@ -0,0 +1,39 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } catch (PDOException $e) { + die("Database connection failed: " . $e->getMessage()); + } + } + return self::$connection; + } + + public static function execute($sql): void + { + $connection = self::connect(); + $connection->exec($sql); + } +} \ No newline at end of file diff --git a/Core/src/Database/Migration.php b/Core/src/Database/Migration.php new file mode 100644 index 0000000..b674941 --- /dev/null +++ b/Core/src/Database/Migration.php @@ -0,0 +1,9 @@ +tableName = $tableName; + $callback($schema); + $schema->executeCreate(); + } + + public static function drop($tableName): void + { + $sql = "DROP TABLE IF EXISTS `$tableName`;"; + Database::execute($sql); + } + + public function id(): void + { + $this->columns[] = "`{$this->primaryKey}` INT AUTO_INCREMENT PRIMARY KEY"; + } + + public function string($name, $length = 255): void + { + $this->columns[] = "`$name` VARCHAR($length)"; + } + + public function integer($name): void + { + $this->columns[] = "`$name` INT"; + } + + public function timestamps(): void + { + $this->columns[] = "`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP"; + $this->columns[] = "`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"; + } + + protected function executeCreate(): void + { + $columnsSql = implode(", ", $this->columns); + $sql = "CREATE TABLE IF NOT EXISTS `{$this->tableName}` ({$columnsSql});"; + Database::execute($sql); + } +} diff --git a/cli b/cli index 2aebf38..952944e 100644 --- a/cli +++ b/cli @@ -2,14 +2,21 @@ namespace ZenithPHP; -use ZenithPHP\Core\Controller\Controller; -use ZenithPHP\Core\Model\Model; +use ZenithPHP\Core\Database\Migration; +use ZenithPHP\Core\Database\Schema; +use ZenithPHP\Core\Database\Database; +use ZenithPHP\Core\Http\InitEnv; + +// Autoload necessary classes and environment configurations +require_once 'vendor/autoload.php'; +require_once 'Core/src/Database/Migration.php'; +require_once 'Core/src/Database/Schema.php'; class CLI { protected array $arguments; - public function __construct($argv) + public function __construct(array $argv) { $this->arguments = $argv; $this->handle(); @@ -19,99 +26,157 @@ class CLI { $command = $this->arguments[1] ?? null; - switch ($command) { - case 'make:controller': - $this->makeController(); - break; - case 'make:model': - $this->makeModel(); - break; - case 'run': - $this->runProject(); - break; - default: - echo "Invalid command. Use 'make:controller ', 'make:model ', or 'run'.\n"; - break; + $commands = [ + 'make:controller' => 'makeController', + 'make:model' => 'makeModel', + 'make:migration' => 'makeMigration', + 'migrate' => 'runMigrations', + 'migrate:rollback' => 'rollbackMigrations', + 'migrate:fresh' => 'migrateFresh', + 'run' => 'runProject', + ]; + + if (array_key_exists($command, $commands)) { + $this->{$commands[$command]}(); + } else { + echo "Invalid command. Use 'make:controller', 'make:model', 'make:migration', 'migrate', 'migrate:rollback', or 'run'.\n"; } } protected function makeController() { - $controllerName = $this->arguments[2] ?? null; - if (!$controllerName) { - echo "Enter the name of the controller: "; - $controllerName = trim(fgets(STDIN)); - } - - $controllerTemplate = "arguments[2] ?? $this->prompt("Enter the name of the controller: "); $path = "App/Controllers/{$controllerName}.php"; - if (!file_exists($path)) { - file_put_contents($path, $controllerTemplate); - echo "Controller '{$controllerName}' created successfully.\n"; - } else { + + if (file_exists($path)) { echo "Controller '{$controllerName}' already exists.\n"; + return; } + + $template = "arguments[2] ?? null; - if (!$modelName) { - echo "Enter the name of the model: "; - $modelName = trim(fgets(STDIN)); + $modelName = $this->arguments[2] ?? $this->prompt("Enter the name of the model: "); + $path = "App/Models/{$modelName}.php"; + + if (file_exists($path)) { + echo "Model '{$modelName}' already exists.\n"; + return; } - $modelTemplate = "arguments[2] ?? $this->prompt("Enter the name of the migration: "); + $timestamp = date('Y_m_d_His'); + $file = "Migrations/_{$timestamp}_{$migrationName}.php"; + + if (!file_exists('Migrations')) mkdir('Migrations', 0755, true); + + $template = "id();\n // Add columns\n });\n }\n\n public function down()\n {\n Schema::drop('{$migrationName}');\n }\n}\n"; + + file_put_contents($file, $template); + echo "Migration '{$migrationName}' created successfully at '{$file}'.\n"; + } + + protected function runMigrations() + { +// $this->loadEnv(); + $migrationFiles = glob('Migrations/*.php'); + + if (empty($migrationFiles)) { + echo "No migration files found.\n"; + return; + } + + foreach ($migrationFiles as $file) { + require_once $file; + $className = 'ZenithPHP\Migrations\\' . basename($file, '.php'); + + if (class_exists($className)) { + (new $className)->up(); + echo "Migration '{$className}' executed successfully.\n"; + } else { + echo "Class '{$className}' not found in '{$file}'.\n"; + } } } - protected function runProject() + protected function rollbackMigrations() { - $port = 8000; - $publicPath = __DIR__ . '/Public'; - $routerPath = "$publicPath/router.php"; + $this->loadEnv(); + $migrationFiles = array_reverse(glob('Migrations/*.php')); - // Check if the Public directory exists - if (!is_dir($publicPath)) { - echo "Error: The Public directory does not exist at the expected path: $publicPath\n"; + if (empty($migrationFiles)) { + echo "No migration files found.\n"; return; } - echo "Starting server on http://localhost:$port\n"; - chdir($publicPath); // Change to the Public directory - - // Create the router script - $router = <<down(); + echo "Rolled back: " . basename($file) . "\n"; + } else { + echo "Class '{$className}' not found in '{$file}'.\n"; + } + } + } + + protected function migrateFresh() + { + // \ZenithPHP\Core\Http\InitEnv::load(); + $this->loadEnv(); + $this->dropAllTables(); + $this->runMigrations(); + } + + protected function dropAllTables() + { + $db = Database::connect(); + $tables = $db->query("SHOW TABLES")->fetchAll(\PDO::FETCH_COLUMN); + + foreach ($tables as $table) { + $db->exec("DROP TABLE IF EXISTS `{$table}`"); + echo "Dropped table: {$table}\n"; } } - require 'index.php'; - PHP; - file_put_contents($routerPath, $router); + protected function runProject() + { + $port = 8000; + $publicPath = __DIR__ . '/Public'; + $routerPath = "$publicPath/router.php"; + + echo "Starting server on http://localhost:$port\n"; + chdir($publicPath); - // Register shutdown function to delete the router.php - register_shutdown_function(function() use ($routerPath) { - @unlink($routerPath); - echo "Temporary router file deleted.\n"; - }); + file_put_contents($routerPath, " unlink($routerPath) && print("Temporary router file deleted.\n")); + + passthru("php -S localhost:$port router.php"); + } + + private function loadEnv() + { + InitEnv::load(); + } + + private function prompt(string $message): string + { + echo $message; + return trim(fgets(STDIN)); } } diff --git a/composer.json b/composer.json index 7d1de3e..dfb4b66 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ "ZenithPHP\\App\\Controllers\\": "App/Controllers/", "ZenithPHP\\Model\\": "App/Models/", "ZenithPHP\\Core\\": "Core/src/", - "ZenithPHP\\Config\\": "Config/" + "ZenithPHP\\Config\\": "Config/", + "ZenithPHP\\Migrations\\": "Migrations/" } } } \ No newline at end of file