Initial Commit
This commit is contained in:
32
core/Application.php
Normal file
32
core/Application.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
class Application
|
||||
{
|
||||
public static Router $router;
|
||||
public static Database $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
self::$router = new Router();
|
||||
self::$db = new Database();
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
try {
|
||||
self::$router->resolve();
|
||||
} catch (\Exception $e) {
|
||||
http_response_code(500);
|
||||
if ($_ENV['APP_ENV'] === 'development') {
|
||||
echo '<h1>Error</h1>';
|
||||
echo '<pre>' . $e->getMessage() . '</pre>';
|
||||
echo '<pre>' . $e->getTraceAsString() . '</pre>';
|
||||
} else {
|
||||
echo '<h1>500 - Internal Server Error</h1>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
core/Auth.php
Normal file
59
core/Auth.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
class Auth
|
||||
{
|
||||
/**
|
||||
* Check if user is authenticated
|
||||
*/
|
||||
public static function check(): bool
|
||||
{
|
||||
return isset($_SESSION['user_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user ID
|
||||
*/
|
||||
public static function id(): ?int
|
||||
{
|
||||
return $_SESSION['user_id'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current username
|
||||
*/
|
||||
public static function username(): ?string
|
||||
{
|
||||
return $_SESSION['username'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user's full name
|
||||
*/
|
||||
public static function fullName(): ?string
|
||||
{
|
||||
return $_SESSION['full_name'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Require authentication (redirect to login if not authenticated)
|
||||
*/
|
||||
public static function require(): void
|
||||
{
|
||||
// Get current path
|
||||
$currentPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
|
||||
|
||||
// Don't redirect if already on login page or logout
|
||||
if ($currentPath === '/login' || $currentPath === '/logout') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self::check()) {
|
||||
$_SESSION['error'] = 'Please login to continue';
|
||||
header('Location: /login');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
33
core/Controller.php
Normal file
33
core/Controller.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
protected function view(string $view, array $data = []): void
|
||||
{
|
||||
extract($data);
|
||||
$viewPath = __DIR__ . "/../app/Views/$view.php";
|
||||
|
||||
if (!file_exists($viewPath)) {
|
||||
throw new \Exception("View not found: $view");
|
||||
}
|
||||
|
||||
require_once $viewPath;
|
||||
}
|
||||
|
||||
protected function json($data, int $status = 200): void
|
||||
{
|
||||
http_response_code($status);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data);
|
||||
exit;
|
||||
}
|
||||
|
||||
protected function redirect(string $path): void
|
||||
{
|
||||
header("Location: $path");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
54
core/Database.php
Normal file
54
core/Database.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
class Database
|
||||
{
|
||||
private static ?PDO $pdo = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (self::$pdo === null) {
|
||||
$this->connect();
|
||||
}
|
||||
}
|
||||
|
||||
private function connect()
|
||||
{
|
||||
$host = $_ENV['DB_HOST'];
|
||||
$port = $_ENV['DB_PORT'];
|
||||
$database = $_ENV['DB_DATABASE'];
|
||||
$username = $_ENV['DB_USERNAME'];
|
||||
$password = $_ENV['DB_PASSWORD'];
|
||||
|
||||
try {
|
||||
$dsn = "mysql:host=$host;port=$port;dbname=$database;charset=utf8mb4";
|
||||
self::$pdo = new PDO($dsn, $username, $password, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
} catch (PDOException $e) {
|
||||
die("Database connection failed: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static function getConnection(): PDO
|
||||
{
|
||||
if (self::$pdo === null) {
|
||||
new self();
|
||||
}
|
||||
return self::$pdo;
|
||||
}
|
||||
|
||||
public function query(string $sql, array $params = []): \PDOStatement
|
||||
{
|
||||
$stmt = self::$pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt;
|
||||
}
|
||||
}
|
||||
|
||||
65
core/Model.php
Normal file
65
core/Model.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
use PDO;
|
||||
|
||||
abstract class Model
|
||||
{
|
||||
protected static string $table;
|
||||
protected PDO $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = Database::getConnection();
|
||||
}
|
||||
|
||||
public function all(): array
|
||||
{
|
||||
$stmt = $this->db->query("SELECT * FROM " . static::$table . " ORDER BY id DESC");
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function find(int $id): ?array
|
||||
{
|
||||
$stmt = $this->db->prepare("SELECT * FROM " . static::$table . " WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$result = $stmt->fetch();
|
||||
return $result ?: null;
|
||||
}
|
||||
|
||||
public function create(array $data): int
|
||||
{
|
||||
$columns = implode(', ', array_keys($data));
|
||||
$placeholders = implode(', ', array_fill(0, count($data), '?'));
|
||||
|
||||
$sql = "INSERT INTO " . static::$table . " ($columns) VALUES ($placeholders)";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute(array_values($data));
|
||||
|
||||
return (int)$this->db->lastInsertId();
|
||||
}
|
||||
|
||||
public function update(int $id, array $data): bool
|
||||
{
|
||||
$set = implode(', ', array_map(fn($col) => "$col = ?", array_keys($data)));
|
||||
$sql = "UPDATE " . static::$table . " SET $set WHERE id = ?";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
return $stmt->execute([...array_values($data), $id]);
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$stmt = $this->db->prepare("DELETE FROM " . static::$table . " WHERE id = ?");
|
||||
return $stmt->execute([$id]);
|
||||
}
|
||||
|
||||
public function where(string $column, $value): array
|
||||
{
|
||||
$stmt = $this->db->prepare("SELECT * FROM " . static::$table . " WHERE $column = ?");
|
||||
$stmt->execute([$value]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
}
|
||||
|
||||
76
core/Router.php
Normal file
76
core/Router.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
class Router
|
||||
{
|
||||
protected array $routes = [];
|
||||
|
||||
public function get(string $path, $callback)
|
||||
{
|
||||
$this->routes['GET'][$path] = $callback;
|
||||
}
|
||||
|
||||
public function post(string $path, $callback)
|
||||
{
|
||||
$this->routes['POST'][$path] = $callback;
|
||||
}
|
||||
|
||||
public function resolve()
|
||||
{
|
||||
$path = $_SERVER['REQUEST_URI'] ?? '/';
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
// Remove query string
|
||||
$position = strpos($path, '?');
|
||||
if ($position !== false) {
|
||||
$path = substr($path, 0, $position);
|
||||
}
|
||||
|
||||
// Try exact match first
|
||||
$callback = $this->routes[$method][$path] ?? null;
|
||||
$params = [];
|
||||
|
||||
// If no exact match, try pattern matching for dynamic segments
|
||||
if ($callback === null) {
|
||||
foreach ($this->routes[$method] ?? [] as $route => $handler) {
|
||||
// Convert route pattern to regex
|
||||
$pattern = preg_replace('/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/', '([^/]+)', $route);
|
||||
$pattern = '#^' . $pattern . '$#';
|
||||
|
||||
if (preg_match($pattern, $path, $matches)) {
|
||||
$callback = $handler;
|
||||
|
||||
// Extract parameter names from route
|
||||
preg_match_all('/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/', $route, $paramNames);
|
||||
|
||||
// Map parameter names to values
|
||||
array_shift($matches); // Remove full match
|
||||
foreach ($paramNames[1] as $index => $name) {
|
||||
$params[$name] = $matches[$index] ?? null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($callback === null) {
|
||||
http_response_code(404);
|
||||
require_once __DIR__ . '/../app/Views/errors/404.php';
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_array($callback)) {
|
||||
$controller = new $callback[0]();
|
||||
$callback[0] = $controller;
|
||||
}
|
||||
|
||||
// Pass params to the callback
|
||||
if (!empty($params)) {
|
||||
call_user_func($callback, $params);
|
||||
} else {
|
||||
call_user_func($callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user