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:
414
app/Core/Database/Blueprint.php
Normal file
414
app/Core/Database/Blueprint.php
Normal file
@@ -0,0 +1,414 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core\Database;
|
||||
|
||||
/**
|
||||
* NovaCore Database Blueprint
|
||||
* Schema builder for migrations
|
||||
*/
|
||||
class Blueprint
|
||||
{
|
||||
private string $table;
|
||||
private array $columns = [];
|
||||
private array $indexes = [];
|
||||
private string $primaryKey = 'id';
|
||||
private array $timestamps = [];
|
||||
|
||||
public function __construct(string $table)
|
||||
{
|
||||
$this->table = $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add primary key
|
||||
*/
|
||||
public function id(string $column = 'id'): Column
|
||||
{
|
||||
$this->primaryKey = $column;
|
||||
$column = new Column('id', 'INT AUTO_INCREMENT PRIMARY KEY');
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add string column
|
||||
*/
|
||||
public function string(string $name, int $length = 255): Column
|
||||
{
|
||||
$column = new Column($name, "VARCHAR({$length})");
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add text column
|
||||
*/
|
||||
public function text(string $name): Column
|
||||
{
|
||||
$column = new Column($name, 'TEXT');
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add long text column
|
||||
*/
|
||||
public function longText(string $name): Column
|
||||
{
|
||||
$column = new Column($name, 'LONGTEXT');
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add integer column
|
||||
*/
|
||||
public function integer(string $name, int $length = null): Column
|
||||
{
|
||||
$type = $length ? "INT({$length})" : 'INT';
|
||||
$column = new Column($name, $type);
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add big integer column
|
||||
*/
|
||||
public function bigInteger(string $name): Column
|
||||
{
|
||||
$column = new Column($name, 'BIGINT');
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add small integer column
|
||||
*/
|
||||
public function smallInteger(string $name): Column
|
||||
{
|
||||
$column = new Column($name, 'SMALLINT');
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tiny integer column
|
||||
*/
|
||||
public function tinyInteger(string $name): Column
|
||||
{
|
||||
$column = new Column($name, 'TINYINT');
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add boolean column
|
||||
*/
|
||||
public function boolean(string $name): Column
|
||||
{
|
||||
$column = new Column($name, 'BOOLEAN');
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add decimal column
|
||||
*/
|
||||
public function decimal(string $name, int $precision = 8, int $scale = 2): Column
|
||||
{
|
||||
$column = new Column($name, "DECIMAL({$precision}, {$scale})");
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add float column
|
||||
*/
|
||||
public function float(string $name, int $precision = 8, int $scale = 2): Column
|
||||
{
|
||||
$column = new Column($name, "FLOAT({$precision}, {$scale})");
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add double column
|
||||
*/
|
||||
public function double(string $name, int $precision = 8, int $scale = 2): Column
|
||||
{
|
||||
$column = new Column($name, "DOUBLE({$precision}, {$scale})");
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add date column
|
||||
*/
|
||||
public function date(string $name): Column
|
||||
{
|
||||
$column = new Column($name, 'DATE');
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add datetime column
|
||||
*/
|
||||
public function datetime(string $name): Column
|
||||
{
|
||||
$column = new Column($name, 'DATETIME');
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add timestamp column
|
||||
*/
|
||||
public function timestamp(string $name): Column
|
||||
{
|
||||
$column = new Column($name, 'TIMESTAMP');
|
||||
$this->columns[] = $column;
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add timestamps
|
||||
*/
|
||||
public function timestamps(): void
|
||||
{
|
||||
$this->timestamp('created_at')->nullable();
|
||||
$this->timestamp('updated_at')->nullable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add soft deletes
|
||||
*/
|
||||
public function softDeletes(): void
|
||||
{
|
||||
$this->timestamp('deleted_at')->nullable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add index
|
||||
*/
|
||||
public function index(array $columns, string $name = null): void
|
||||
{
|
||||
if (!$name) {
|
||||
$name = $this->table . '_' . implode('_', $columns) . '_index';
|
||||
}
|
||||
|
||||
$this->indexes[] = [
|
||||
'name' => $name,
|
||||
'columns' => $columns,
|
||||
'type' => 'INDEX'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add unique index
|
||||
*/
|
||||
public function unique(array $columns, string $name = null): void
|
||||
{
|
||||
if (!$name) {
|
||||
$name = $this->table . '_' . implode('_', $columns) . '_unique';
|
||||
}
|
||||
|
||||
$this->indexes[] = [
|
||||
'name' => $name,
|
||||
'columns' => $columns,
|
||||
'type' => 'UNIQUE'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add foreign key
|
||||
*/
|
||||
public function foreign(string $column): ForeignKey
|
||||
{
|
||||
$foreignKey = new ForeignKey($column);
|
||||
$this->columns[] = $foreignKey;
|
||||
return $foreignKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SQL
|
||||
*/
|
||||
public function toSql(): string
|
||||
{
|
||||
$sql = "CREATE TABLE `{$this->table}` (";
|
||||
|
||||
$columnDefinitions = [];
|
||||
foreach ($this->columns as $column) {
|
||||
$columnDefinitions[] = $column->toSql();
|
||||
}
|
||||
|
||||
$sql .= implode(', ', $columnDefinitions);
|
||||
$sql .= ")";
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Column definition
|
||||
*/
|
||||
class Column
|
||||
{
|
||||
private string $name;
|
||||
private string $type;
|
||||
private bool $nullable = false;
|
||||
private $default = null;
|
||||
private bool $autoIncrement = false;
|
||||
private bool $primary = false;
|
||||
private bool $unique = false;
|
||||
|
||||
public function __construct(string $name, string $type)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set nullable
|
||||
*/
|
||||
public function nullable(): self
|
||||
{
|
||||
$this->nullable = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default value
|
||||
*/
|
||||
public function default($value): self
|
||||
{
|
||||
$this->default = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set auto increment
|
||||
*/
|
||||
public function autoIncrement(): self
|
||||
{
|
||||
$this->autoIncrement = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set primary key
|
||||
*/
|
||||
public function primary(): self
|
||||
{
|
||||
$this->primary = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set unique
|
||||
*/
|
||||
public function unique(): self
|
||||
{
|
||||
$this->unique = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SQL
|
||||
*/
|
||||
public function toSql(): string
|
||||
{
|
||||
$sql = "`{$this->name}` {$this->type}";
|
||||
|
||||
if ($this->autoIncrement) {
|
||||
$sql .= " AUTO_INCREMENT";
|
||||
}
|
||||
|
||||
if (!$this->nullable) {
|
||||
$sql .= " NOT NULL";
|
||||
}
|
||||
|
||||
if ($this->default !== null) {
|
||||
$default = is_string($this->default) ? "'{$this->default}'" : $this->default;
|
||||
$sql .= " DEFAULT {$default}";
|
||||
}
|
||||
|
||||
if ($this->unique) {
|
||||
$sql .= " UNIQUE";
|
||||
}
|
||||
|
||||
if ($this->primary) {
|
||||
$sql .= " PRIMARY KEY";
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Foreign key definition
|
||||
*/
|
||||
class ForeignKey
|
||||
{
|
||||
private string $column;
|
||||
private string $references;
|
||||
private string $on;
|
||||
private string $onDelete = 'RESTRICT';
|
||||
private string $onUpdate = 'RESTRICT';
|
||||
|
||||
public function __construct(string $column)
|
||||
{
|
||||
$this->column = $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set references
|
||||
*/
|
||||
public function references(string $column): self
|
||||
{
|
||||
$this->references = $column;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set on table
|
||||
*/
|
||||
public function on(string $table): self
|
||||
{
|
||||
$this->on = $table;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set on delete action
|
||||
*/
|
||||
public function onDelete(string $action): self
|
||||
{
|
||||
$this->onDelete = $action;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set on update action
|
||||
*/
|
||||
public function onUpdate(string $action): self
|
||||
{
|
||||
$this->onUpdate = $action;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SQL
|
||||
*/
|
||||
public function toSql(): string
|
||||
{
|
||||
$sql = "`{$this->column}` INT";
|
||||
|
||||
if ($this->references && $this->on) {
|
||||
$sql .= ", FOREIGN KEY (`{$this->column}`) REFERENCES `{$this->on}` (`{$this->references}`)";
|
||||
$sql .= " ON DELETE {$this->onDelete} ON UPDATE {$this->onUpdate}";
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
172
app/Core/Database/Connection.php
Normal file
172
app/Core/Database/Connection.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core\Database;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* NovaCore Database Connection
|
||||
* PDO database wrapper
|
||||
*/
|
||||
class Connection
|
||||
{
|
||||
private PDO $pdo;
|
||||
private array $config;
|
||||
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish database connection
|
||||
*/
|
||||
private function connect(): void
|
||||
{
|
||||
try {
|
||||
$dsn = $this->buildDsn();
|
||||
|
||||
if ($this->config['driver'] === 'sqlite') {
|
||||
$this->pdo = new PDO($dsn);
|
||||
} else {
|
||||
$this->pdo = new PDO(
|
||||
$dsn,
|
||||
$this->config['username'],
|
||||
$this->config['password'],
|
||||
$this->config['options'] ?? []
|
||||
);
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
throw new \Exception("Database connection failed: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build DSN string
|
||||
*/
|
||||
private function buildDsn(): string
|
||||
{
|
||||
$driver = $this->config['driver'];
|
||||
|
||||
switch ($driver) {
|
||||
case 'mysql':
|
||||
$host = $this->config['host'];
|
||||
$port = $this->config['port'] ?? null;
|
||||
$database = $this->config['database'];
|
||||
$charset = $this->config['charset'] ?? 'utf8';
|
||||
return "mysql:host={$host};port={$port};dbname={$database};charset={$charset}";
|
||||
case 'pgsql':
|
||||
$host = $this->config['host'];
|
||||
$port = $this->config['port'] ?? null;
|
||||
$database = $this->config['database'];
|
||||
return "pgsql:host={$host};port={$port};dbname={$database}";
|
||||
case 'sqlite':
|
||||
$database = $this->config['database'];
|
||||
return "sqlite:{$database}";
|
||||
default:
|
||||
throw new \Exception("Unsupported database driver: {$driver}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PDO instance
|
||||
*/
|
||||
public function getPdo(): PDO
|
||||
{
|
||||
return $this->pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute query
|
||||
*/
|
||||
public function query(string $sql, array $params = []): \PDOStatement
|
||||
{
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute query and return all results
|
||||
*/
|
||||
public function fetchAll(string $sql, array $params = []): array
|
||||
{
|
||||
$stmt = $this->query($sql, $params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute query and return single result
|
||||
*/
|
||||
public function fetch(string $sql, array $params = []): ?array
|
||||
{
|
||||
$stmt = $this->query($sql, $params);
|
||||
$result = $stmt->fetch();
|
||||
return $result ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute query and return single value
|
||||
*/
|
||||
public function fetchColumn(string $sql, array $params = [])
|
||||
{
|
||||
$stmt = $this->query($sql, $params);
|
||||
return $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute query and return affected rows
|
||||
*/
|
||||
public function execute(string $sql, array $params = []): int
|
||||
{
|
||||
$stmt = $this->query($sql, $params);
|
||||
return $stmt->rowCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin transaction
|
||||
*/
|
||||
public function beginTransaction(): bool
|
||||
{
|
||||
return $this->pdo->beginTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit transaction
|
||||
*/
|
||||
public function commit(): bool
|
||||
{
|
||||
return $this->pdo->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback transaction
|
||||
*/
|
||||
public function rollback(): bool
|
||||
{
|
||||
return $this->pdo->rollBack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last insert ID
|
||||
*/
|
||||
public function lastInsertId(): string
|
||||
{
|
||||
return $this->pdo->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if connected
|
||||
*/
|
||||
public function isConnected(): bool
|
||||
{
|
||||
try {
|
||||
$this->pdo->query('SELECT 1');
|
||||
return true;
|
||||
} catch (PDOException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
105
app/Core/Database/Migration.php
Normal file
105
app/Core/Database/Migration.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core\Database;
|
||||
|
||||
/**
|
||||
* NovaCore Database Migration
|
||||
* Base migration class
|
||||
*/
|
||||
abstract class Migration
|
||||
{
|
||||
protected Connection $connection;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->connection = $this->getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database connection
|
||||
*/
|
||||
protected function getConnection(): Connection
|
||||
{
|
||||
$config = include __DIR__ . '/../../Config/database.php';
|
||||
$connectionConfig = $config['connections'][$config['default']];
|
||||
|
||||
return new Connection($connectionConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migration
|
||||
*/
|
||||
abstract public function up(): void;
|
||||
|
||||
/**
|
||||
* Reverse the migration
|
||||
*/
|
||||
abstract public function down(): void;
|
||||
|
||||
/**
|
||||
* Create table
|
||||
*/
|
||||
protected function createTable(string $table, callable $callback): void
|
||||
{
|
||||
$blueprint = new Blueprint($table);
|
||||
$callback($blueprint);
|
||||
|
||||
$sql = $blueprint->toSql();
|
||||
$this->connection->execute($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop table
|
||||
*/
|
||||
protected function dropTable(string $table): void
|
||||
{
|
||||
$sql = "DROP TABLE IF EXISTS `{$table}`";
|
||||
$this->connection->execute($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add column
|
||||
*/
|
||||
protected function addColumn(string $table, string $column, string $type): void
|
||||
{
|
||||
$sql = "ALTER TABLE `{$table}` ADD COLUMN `{$column}` {$type}";
|
||||
$this->connection->execute($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop column
|
||||
*/
|
||||
protected function dropColumn(string $table, string $column): void
|
||||
{
|
||||
$sql = "ALTER TABLE `{$table}` DROP COLUMN `{$column}`";
|
||||
$this->connection->execute($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename column
|
||||
*/
|
||||
protected function renameColumn(string $table, string $from, string $to): void
|
||||
{
|
||||
$sql = "ALTER TABLE `{$table}` RENAME COLUMN `{$from}` TO `{$to}`";
|
||||
$this->connection->execute($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add index
|
||||
*/
|
||||
protected function addIndex(string $table, string $index, array $columns): void
|
||||
{
|
||||
$columnsStr = implode(', ', array_map(fn($col) => "`{$col}`", $columns));
|
||||
$sql = "CREATE INDEX `{$index}` ON `{$table}` ({$columnsStr})";
|
||||
$this->connection->execute($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop index
|
||||
*/
|
||||
protected function dropIndex(string $table, string $index): void
|
||||
{
|
||||
$sql = "DROP INDEX `{$index}` ON `{$table}`";
|
||||
$this->connection->execute($sql);
|
||||
}
|
||||
}
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
256
app/Core/Database/Model.php
Normal file
256
app/Core/Database/Model.php
Normal file
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core\Database;
|
||||
|
||||
/**
|
||||
* NovaCore Base Model
|
||||
* Base model class for database operations
|
||||
*/
|
||||
abstract class Model
|
||||
{
|
||||
protected Connection $connection;
|
||||
protected string $table;
|
||||
protected string $primaryKey = 'id';
|
||||
protected array $fillable = [];
|
||||
protected array $guarded = [];
|
||||
protected array $attributes = [];
|
||||
protected bool $timestamps = true;
|
||||
protected string $createdAt = 'created_at';
|
||||
protected string $updatedAt = 'updated_at';
|
||||
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
$this->attributes = $attributes;
|
||||
$this->connection = $this->getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database connection
|
||||
*/
|
||||
protected function getConnection(): Connection
|
||||
{
|
||||
$config = include __DIR__ . '/../../Config/database.php';
|
||||
$connectionConfig = $config['connections'][$config['default']];
|
||||
|
||||
return new Connection($connectionConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get query builder instance
|
||||
*/
|
||||
protected function newQuery(): QueryBuilder
|
||||
{
|
||||
return new QueryBuilder($this->connection, $this->table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find record by ID
|
||||
*/
|
||||
public static function find(int $id): ?self
|
||||
{
|
||||
$instance = new static();
|
||||
$result = $instance->newQuery()->where($instance->primaryKey, $id)->first();
|
||||
|
||||
if (!$result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new static($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all records
|
||||
*/
|
||||
public static function all(): array
|
||||
{
|
||||
$instance = new static();
|
||||
$results = $instance->newQuery()->get();
|
||||
|
||||
return array_map(function ($attributes) {
|
||||
return new static($attributes);
|
||||
}, $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new record
|
||||
*/
|
||||
public static function create(array $attributes): self
|
||||
{
|
||||
$instance = new static();
|
||||
$instance->fill($attributes);
|
||||
$instance->save();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill model attributes
|
||||
*/
|
||||
public function fill(array $attributes): self
|
||||
{
|
||||
foreach ($attributes as $key => $value) {
|
||||
if ($this->isFillable($key)) {
|
||||
$this->attributes[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if attribute is fillable
|
||||
*/
|
||||
protected function isFillable(string $key): bool
|
||||
{
|
||||
if (in_array($key, $this->guarded)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($this->fillable)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array($key, $this->fillable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save model to database
|
||||
*/
|
||||
public function save(): bool
|
||||
{
|
||||
if ($this->exists()) {
|
||||
return $this->update();
|
||||
} else {
|
||||
return $this->insert();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if model exists in database
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
return isset($this->attributes[$this->primaryKey]) && $this->attributes[$this->primaryKey] !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert new record
|
||||
*/
|
||||
protected function insert(): bool
|
||||
{
|
||||
$attributes = $this->attributes;
|
||||
|
||||
if ($this->timestamps) {
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$attributes[$this->createdAt] = $now;
|
||||
$attributes[$this->updatedAt] = $now;
|
||||
}
|
||||
|
||||
$result = $this->newQuery()->insert($attributes);
|
||||
|
||||
if ($result) {
|
||||
$this->attributes[$this->primaryKey] = $this->connection->lastInsertId();
|
||||
}
|
||||
|
||||
return $result > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing record
|
||||
*/
|
||||
protected function update(): bool
|
||||
{
|
||||
$attributes = $this->attributes;
|
||||
unset($attributes[$this->primaryKey]);
|
||||
|
||||
if ($this->timestamps) {
|
||||
$attributes[$this->updatedAt] = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$result = $this->newQuery()
|
||||
->where($this->primaryKey, $this->attributes[$this->primaryKey])
|
||||
->update($attributes);
|
||||
|
||||
return $result > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete record
|
||||
*/
|
||||
public function delete(): bool
|
||||
{
|
||||
if (!$this->exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = $this->newQuery()
|
||||
->where($this->primaryKey, $this->attributes[$this->primaryKey])
|
||||
->delete();
|
||||
|
||||
return $result > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attribute value
|
||||
*/
|
||||
public function __get(string $key)
|
||||
{
|
||||
return $this->attributes[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute value
|
||||
*/
|
||||
public function __set(string $key, $value): void
|
||||
{
|
||||
$this->attributes[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if attribute exists
|
||||
*/
|
||||
public function __isset(string $key): bool
|
||||
{
|
||||
return isset($this->attributes[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert model to array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert model to JSON
|
||||
*/
|
||||
public function toJson(): string
|
||||
{
|
||||
return json_encode($this->attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table name
|
||||
*/
|
||||
public function getTable(): string
|
||||
{
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get primary key
|
||||
*/
|
||||
public function getKeyName(): string
|
||||
{
|
||||
return $this->primaryKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get primary key value
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return $this->attributes[$this->primaryKey] ?? null;
|
||||
}
|
||||
}
|
||||
443
app/Core/Database/QueryBuilder.php
Normal file
443
app/Core/Database/QueryBuilder.php
Normal file
@@ -0,0 +1,443 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core\Database;
|
||||
|
||||
/**
|
||||
* NovaCore Query Builder
|
||||
* Simple query builder for database operations
|
||||
*/
|
||||
class QueryBuilder
|
||||
{
|
||||
private Connection $connection;
|
||||
private string $table;
|
||||
private array $select = ['*'];
|
||||
private array $where = [];
|
||||
private array $orderBy = [];
|
||||
private array $groupBy = [];
|
||||
private array $having = [];
|
||||
private ?int $limit = null;
|
||||
private ?int $offset = null;
|
||||
private array $joins = [];
|
||||
|
||||
public function __construct(Connection $connection, string $table)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->table = $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select columns
|
||||
*/
|
||||
public function select(array $columns): self
|
||||
{
|
||||
$this->select = $columns;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add where condition
|
||||
*/
|
||||
public function where(string $column, $operator, $value = null): self
|
||||
{
|
||||
if ($value === null) {
|
||||
$value = $operator;
|
||||
$operator = '=';
|
||||
}
|
||||
|
||||
$this->where[] = [
|
||||
'column' => $column,
|
||||
'operator' => $operator,
|
||||
'value' => $value,
|
||||
'boolean' => 'AND'
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add OR where condition
|
||||
*/
|
||||
public function orWhere(string $column, $operator, $value = null): self
|
||||
{
|
||||
if ($value === null) {
|
||||
$value = $operator;
|
||||
$operator = '=';
|
||||
}
|
||||
|
||||
$this->where[] = [
|
||||
'column' => $column,
|
||||
'operator' => $operator,
|
||||
'value' => $value,
|
||||
'boolean' => 'OR'
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add where in condition
|
||||
*/
|
||||
public function whereIn(string $column, array $values): self
|
||||
{
|
||||
$this->where[] = [
|
||||
'column' => $column,
|
||||
'operator' => 'IN',
|
||||
'value' => $values,
|
||||
'boolean' => 'AND'
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add where not in condition
|
||||
*/
|
||||
public function whereNotIn(string $column, array $values): self
|
||||
{
|
||||
$this->where[] = [
|
||||
'column' => $column,
|
||||
'operator' => 'NOT IN',
|
||||
'value' => $values,
|
||||
'boolean' => 'AND'
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add where null condition
|
||||
*/
|
||||
public function whereNull(string $column): self
|
||||
{
|
||||
$this->where[] = [
|
||||
'column' => $column,
|
||||
'operator' => 'IS NULL',
|
||||
'value' => null,
|
||||
'boolean' => 'AND'
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add where not null condition
|
||||
*/
|
||||
public function whereNotNull(string $column): self
|
||||
{
|
||||
$this->where[] = [
|
||||
'column' => $column,
|
||||
'operator' => 'IS NOT NULL',
|
||||
'value' => null,
|
||||
'boolean' => 'AND'
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add order by clause
|
||||
*/
|
||||
public function orderBy(string $column, string $direction = 'ASC'): self
|
||||
{
|
||||
$this->orderBy[] = [
|
||||
'column' => $column,
|
||||
'direction' => strtoupper($direction)
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add group by clause
|
||||
*/
|
||||
public function groupBy(string $column): self
|
||||
{
|
||||
$this->groupBy[] = $column;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add having clause
|
||||
*/
|
||||
public function having(string $column, $operator, $value): self
|
||||
{
|
||||
$this->having[] = [
|
||||
'column' => $column,
|
||||
'operator' => $operator,
|
||||
'value' => $value,
|
||||
'boolean' => 'AND'
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add limit clause
|
||||
*/
|
||||
public function limit(int $limit): self
|
||||
{
|
||||
$this->limit = $limit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add offset clause
|
||||
*/
|
||||
public function offset(int $offset): self
|
||||
{
|
||||
$this->offset = $offset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add join clause
|
||||
*/
|
||||
public function join(string $table, string $first, string $operator, string $second, string $type = 'INNER'): self
|
||||
{
|
||||
$this->joins[] = [
|
||||
'table' => $table,
|
||||
'first' => $first,
|
||||
'operator' => $operator,
|
||||
'second' => $second,
|
||||
'type' => $type
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add left join clause
|
||||
*/
|
||||
public function leftJoin(string $table, string $first, string $operator, string $second): self
|
||||
{
|
||||
return $this->join($table, $first, $operator, $second, 'LEFT');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add right join clause
|
||||
*/
|
||||
public function rightJoin(string $table, string $first, string $operator, string $second): self
|
||||
{
|
||||
return $this->join($table, $first, $operator, $second, 'RIGHT');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all results
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
$sql = $this->toSql();
|
||||
$params = $this->getBindings();
|
||||
|
||||
return $this->connection->fetchAll($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get first result
|
||||
*/
|
||||
public function first(): ?array
|
||||
{
|
||||
$this->limit(1);
|
||||
$sql = $this->toSql();
|
||||
$params = $this->getBindings();
|
||||
|
||||
return $this->connection->fetch($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$this->select = ['COUNT(*) as count'];
|
||||
$result = $this->first();
|
||||
return (int) $result['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert data
|
||||
*/
|
||||
public function insert(array $data): int
|
||||
{
|
||||
$columns = array_keys($data);
|
||||
$values = array_values($data);
|
||||
$placeholders = array_fill(0, count($values), '?');
|
||||
|
||||
$sql = "INSERT INTO {$this->table} (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
|
||||
|
||||
return $this->connection->execute($sql, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update data
|
||||
*/
|
||||
public function update(array $data): int
|
||||
{
|
||||
$columns = array_keys($data);
|
||||
$values = array_values($data);
|
||||
$set = [];
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$set[] = "{$column} = ?";
|
||||
}
|
||||
|
||||
$sql = "UPDATE {$this->table} SET " . implode(', ', $set);
|
||||
$params = $values;
|
||||
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . $this->buildWhereClause();
|
||||
$params = array_merge($params, $this->getWhereBindings());
|
||||
}
|
||||
|
||||
return $this->connection->execute($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete records
|
||||
*/
|
||||
public function delete(): int
|
||||
{
|
||||
$sql = "DELETE FROM {$this->table}";
|
||||
$params = [];
|
||||
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . $this->buildWhereClause();
|
||||
$params = $this->getWhereBindings();
|
||||
}
|
||||
|
||||
return $this->connection->execute($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build SQL query
|
||||
*/
|
||||
private function toSql(): string
|
||||
{
|
||||
$sql = "SELECT " . implode(', ', $this->select) . " FROM {$this->table}";
|
||||
|
||||
// Add joins
|
||||
foreach ($this->joins as $join) {
|
||||
$sql .= " {$join['type']} JOIN {$join['table']} ON {$join['first']} {$join['operator']} {$join['second']}";
|
||||
}
|
||||
|
||||
// Add where clause
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . $this->buildWhereClause();
|
||||
}
|
||||
|
||||
// Add group by clause
|
||||
if (!empty($this->groupBy)) {
|
||||
$sql .= " GROUP BY " . implode(', ', $this->groupBy);
|
||||
}
|
||||
|
||||
// Add having clause
|
||||
if (!empty($this->having)) {
|
||||
$sql .= " HAVING " . $this->buildHavingClause();
|
||||
}
|
||||
|
||||
// Add order by clause
|
||||
if (!empty($this->orderBy)) {
|
||||
$orderBy = [];
|
||||
foreach ($this->orderBy as $order) {
|
||||
$orderBy[] = "{$order['column']} {$order['direction']}";
|
||||
}
|
||||
$sql .= " ORDER BY " . implode(', ', $orderBy);
|
||||
}
|
||||
|
||||
// Add limit clause
|
||||
if ($this->limit !== null) {
|
||||
$sql .= " LIMIT {$this->limit}";
|
||||
}
|
||||
|
||||
// Add offset clause
|
||||
if ($this->offset !== null) {
|
||||
$sql .= " OFFSET {$this->offset}";
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build where clause
|
||||
*/
|
||||
private function buildWhereClause(): string
|
||||
{
|
||||
$clauses = [];
|
||||
|
||||
foreach ($this->where as $index => $condition) {
|
||||
$clause = '';
|
||||
|
||||
if ($index > 0) {
|
||||
$clause .= " {$condition['boolean']} ";
|
||||
}
|
||||
|
||||
if ($condition['operator'] === 'IN' || $condition['operator'] === 'NOT IN') {
|
||||
$placeholders = array_fill(0, count($condition['value']), '?');
|
||||
$clause .= "{$condition['column']} {$condition['operator']} (" . implode(', ', $placeholders) . ")";
|
||||
} else {
|
||||
$clause .= "{$condition['column']} {$condition['operator']} ?";
|
||||
}
|
||||
|
||||
$clauses[] = $clause;
|
||||
}
|
||||
|
||||
return implode('', $clauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build having clause
|
||||
*/
|
||||
private function buildHavingClause(): string
|
||||
{
|
||||
$clauses = [];
|
||||
|
||||
foreach ($this->having as $index => $condition) {
|
||||
$clause = '';
|
||||
|
||||
if ($index > 0) {
|
||||
$clause .= " {$condition['boolean']} ";
|
||||
}
|
||||
|
||||
$clause .= "{$condition['column']} {$condition['operator']} ?";
|
||||
$clauses[] = $clause;
|
||||
}
|
||||
|
||||
return implode('', $clauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all bindings
|
||||
*/
|
||||
private function getBindings(): array
|
||||
{
|
||||
$bindings = [];
|
||||
|
||||
// Add where bindings
|
||||
$bindings = array_merge($bindings, $this->getWhereBindings());
|
||||
|
||||
// Add having bindings
|
||||
foreach ($this->having as $condition) {
|
||||
$bindings[] = $condition['value'];
|
||||
}
|
||||
|
||||
return $bindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get where bindings
|
||||
*/
|
||||
private function getWhereBindings(): array
|
||||
{
|
||||
$bindings = [];
|
||||
|
||||
foreach ($this->where as $condition) {
|
||||
if ($condition['operator'] === 'IN' || $condition['operator'] === 'NOT IN') {
|
||||
$bindings = array_merge($bindings, $condition['value']);
|
||||
} else {
|
||||
$bindings[] = $condition['value'];
|
||||
}
|
||||
}
|
||||
|
||||
return $bindings;
|
||||
}
|
||||
}
|
||||
48
app/Core/Database/Seeder.php
Normal file
48
app/Core/Database/Seeder.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core\Database;
|
||||
|
||||
/**
|
||||
* NovaCore Database Seeder
|
||||
* Base seeder class
|
||||
*/
|
||||
abstract class Seeder
|
||||
{
|
||||
protected Connection $connection;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->connection = $this->getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database connection
|
||||
*/
|
||||
protected function getConnection(): Connection
|
||||
{
|
||||
$config = include __DIR__ . '/../../Config/database.php';
|
||||
$connectionConfig = $config['connections'][$config['default']];
|
||||
|
||||
return new Connection($connectionConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the seeder
|
||||
*/
|
||||
abstract public function run(): void;
|
||||
|
||||
/**
|
||||
* Call another seeder
|
||||
*/
|
||||
protected function call(string $seeder): void
|
||||
{
|
||||
$seederClass = "Database\\Seeders\\{$seeder}";
|
||||
|
||||
if (!class_exists($seederClass)) {
|
||||
throw new \Exception("Seeder class {$seederClass} not found");
|
||||
}
|
||||
|
||||
$seederInstance = new $seederClass();
|
||||
$seederInstance->run();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user