Add MySQL schema dump for phpMyAdmin
This commit is contained in:
93
app/Core/Config.php
Normal file
93
app/Core/Config.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
class Config
|
||||
{
|
||||
private static ?self $instance = null;
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
private array $values = [];
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
$this->loadEnv();
|
||||
$this->loadConfigFiles();
|
||||
}
|
||||
|
||||
public static function getInstance(): self
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function get(string $key, $default = null)
|
||||
{
|
||||
$segments = explode('.', $key);
|
||||
$value = $this->values;
|
||||
|
||||
foreach ($segments as $segment) {
|
||||
if (!is_array($value) || !array_key_exists($segment, $value)) {
|
||||
return $default;
|
||||
}
|
||||
$value = $value[$segment];
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $config
|
||||
*/
|
||||
public function set(array $config): void
|
||||
{
|
||||
$this->values = array_replace_recursive($this->values, $config);
|
||||
}
|
||||
|
||||
private function loadEnv(): void
|
||||
{
|
||||
$envPath = base_path('.env');
|
||||
if (!file_exists($envPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
if ($lines === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (str_starts_with(trim($line), '#')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$name, $value] = array_map('trim', explode('=', $line, 2) + [1 => '']);
|
||||
$value = trim($value, "\"' ");
|
||||
$_ENV[$name] = $value;
|
||||
putenv("{$name}={$value}");
|
||||
}
|
||||
}
|
||||
|
||||
private function loadConfigFiles(): void
|
||||
{
|
||||
$configDir = base_path('config');
|
||||
if (!is_dir($configDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$files = glob($configDir . '/*.php');
|
||||
if ($files === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($files as $file) {
|
||||
$key = basename($file, '.php');
|
||||
/** @var array<string, mixed> $data */
|
||||
$data = require $file;
|
||||
$this->values[$key] = $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
app/Core/Controller.php
Normal file
17
app/Core/Controller.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
class Controller
|
||||
{
|
||||
protected function view(string $template, array $data = []): Response
|
||||
{
|
||||
$content = View::render($template, $data);
|
||||
return new Response($content);
|
||||
}
|
||||
|
||||
protected function redirect(string $url): Response
|
||||
{
|
||||
return new Response('', 302, ['Location' => $url]);
|
||||
}
|
||||
}
|
||||
44
app/Core/Database.php
Normal file
44
app/Core/Database.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
class Database
|
||||
{
|
||||
private static ?PDO $connection = null;
|
||||
|
||||
public static function connection(): PDO
|
||||
{
|
||||
if (self::$connection === null) {
|
||||
$config = config('database');
|
||||
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',
|
||||
$config['host'],
|
||||
$config['port'],
|
||||
$config['database']
|
||||
);
|
||||
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
|
||||
try {
|
||||
self::$connection = new PDO(
|
||||
$dsn,
|
||||
$config['username'],
|
||||
$config['password'],
|
||||
$options
|
||||
);
|
||||
} catch (PDOException $exception) {
|
||||
throw new \RuntimeException('Database connection failed: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return self::$connection;
|
||||
}
|
||||
}
|
||||
39
app/Core/Response.php
Normal file
39
app/Core/Response.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
class Response
|
||||
{
|
||||
private string $body;
|
||||
private int $status;
|
||||
/** @var array<string, string> */
|
||||
private array $headers;
|
||||
|
||||
/**
|
||||
* @param array<string, string> $headers
|
||||
*/
|
||||
public function __construct(string $body = '', int $status = 200, array $headers = [])
|
||||
{
|
||||
$this->body = $body;
|
||||
$this->status = $status;
|
||||
$this->headers = $headers;
|
||||
}
|
||||
|
||||
public function getBody(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function getStatus(): int
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
}
|
||||
97
app/Core/Router.php
Normal file
97
app/Core/Router.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
class Router
|
||||
{
|
||||
/** @var array<string, array<int, array{path:string,action:callable,middleware:array}>> */
|
||||
private array $routes = [];
|
||||
|
||||
/** @var array<int, array<int, class-string>> */
|
||||
private array $middlewareStack = [];
|
||||
|
||||
public function get(string $path, callable $action, array $middleware = []): void
|
||||
{
|
||||
$this->addRoute('GET', $path, $action, $middleware);
|
||||
}
|
||||
|
||||
public function post(string $path, callable $action, array $middleware = []): void
|
||||
{
|
||||
$this->addRoute('POST', $path, $action, $middleware);
|
||||
}
|
||||
|
||||
public function put(string $path, callable $action, array $middleware = []): void
|
||||
{
|
||||
$this->addRoute('PUT', $path, $action, $middleware);
|
||||
}
|
||||
|
||||
public function delete(string $path, callable $action, array $middleware = []): void
|
||||
{
|
||||
$this->addRoute('DELETE', $path, $action, $middleware);
|
||||
}
|
||||
|
||||
public function group(array $attributes, \Closure $callback): void
|
||||
{
|
||||
$groupMiddleware = $attributes['middleware'] ?? [];
|
||||
$this->middlewareStack[] = $groupMiddleware;
|
||||
$callback($this);
|
||||
array_pop($this->middlewareStack);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, class-string> $middleware
|
||||
*/
|
||||
private function addRoute(string $method, string $path, callable $action, array $middleware = []): void
|
||||
{
|
||||
$stackedMiddleware = [];
|
||||
foreach ($this->middlewareStack as $group) {
|
||||
$stackedMiddleware = array_merge($stackedMiddleware, $group);
|
||||
}
|
||||
$combinedMiddleware = array_merge($stackedMiddleware, $middleware);
|
||||
|
||||
$this->routes[$method][] = [
|
||||
'path' => $path,
|
||||
'action' => $action,
|
||||
'middleware' => $combinedMiddleware,
|
||||
];
|
||||
}
|
||||
|
||||
public function dispatch(string $method, string $uri): Response
|
||||
{
|
||||
$uri = parse_url($uri, PHP_URL_PATH) ?? '/';
|
||||
$routes = $this->routes[$method] ?? [];
|
||||
|
||||
foreach ($routes as $route) {
|
||||
$pattern = '#^' . preg_replace('#\{(.*?)\}#', '(?P<$1>[^/]+)', $route['path']) . '$#';
|
||||
if (preg_match($pattern, $uri, $matches)) {
|
||||
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
|
||||
$handler = $this->applyMiddleware($route['action'], $route['middleware']);
|
||||
$result = $handler($params);
|
||||
|
||||
if ($result instanceof Response) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return new Response((string) $result);
|
||||
}
|
||||
}
|
||||
|
||||
return new Response('Not Found', 404);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $action
|
||||
* @param array<int, class-string> $middleware
|
||||
*/
|
||||
private function applyMiddleware(callable $action, array $middleware): callable
|
||||
{
|
||||
return array_reduce(
|
||||
array_reverse($middleware),
|
||||
function ($next, $middlewareClass) {
|
||||
$middlewareInstance = new $middlewareClass();
|
||||
return fn(array $params) => $middlewareInstance->handle($params, $next);
|
||||
},
|
||||
$action
|
||||
);
|
||||
}
|
||||
}
|
||||
19
app/Core/View.php
Normal file
19
app/Core/View.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
class View
|
||||
{
|
||||
public static function render(string $template, array $data = []): string
|
||||
{
|
||||
$path = base_path('resources/views/' . $template . '.php');
|
||||
if (!file_exists($path)) {
|
||||
throw new \RuntimeException('View not found: ' . $template);
|
||||
}
|
||||
|
||||
extract($data, EXTR_SKIP);
|
||||
ob_start();
|
||||
include $path;
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user