Initial commit
This commit is contained in:
37
app/Core/Auth.php
Normal file
37
app/Core/Auth.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
class Auth {
|
||||
public static function login($user) {
|
||||
session_start();
|
||||
session_regenerate_id(true); // Prevents session hijacking
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['user_name'] = $user['username'];
|
||||
}
|
||||
|
||||
public static function check() {
|
||||
if (session_status() === PHP_SESSION_NONE) session_start();
|
||||
return isset($_SESSION['user_id']);
|
||||
}
|
||||
|
||||
public static function user() {
|
||||
return $_SESSION['user_name'] ?? null;
|
||||
}
|
||||
|
||||
public static function logout() {
|
||||
session_start();
|
||||
session_destroy();
|
||||
header('Location: /login');
|
||||
exit;
|
||||
}
|
||||
public static function getBearerToken(): ?string {
|
||||
$headers = $_SERVER['Authorization'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? null;
|
||||
if (!$headers && function_exists('apache_request_headers')) {
|
||||
$req = apache_request_headers();
|
||||
$headers = $req['Authorization'] ?? $req['authorization'] ?? null;
|
||||
}
|
||||
return ($headers && preg_match('/Bearer\s(\S+)/', $headers, $matches)) ? $matches[1] : null;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
51
app/Core/Config.php
Normal file
51
app/Core/Config.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
class Config {
|
||||
private static $instance = null;
|
||||
private array $settings = [];
|
||||
|
||||
private function __construct() {
|
||||
// Map environment variables to internal keys with optional defaults
|
||||
$this->settings = [
|
||||
'db' => [
|
||||
'host' => $_ENV['DB_HOST'] ?? 'localhost',
|
||||
'name' => $_ENV['DB_NAME'] ?? 'my_app',
|
||||
'user' => $_ENV['DB_USER'] ?? 'root',
|
||||
'pass' => $_ENV['DB_PASS'] ?? '',
|
||||
],
|
||||
'app' => [
|
||||
'name' => $_ENV['APP_NAME'] ?? 'Vanilla PHP App',
|
||||
'env' => $_ENV['APP_ENV'] ?? 'production',
|
||||
'debug' => ($_ENV['APP_DEBUG'] ?? 'false') === 'true',
|
||||
'url' => $_ENV['APP_URL'] ?? 'http://localhost',
|
||||
],
|
||||
'mail' => [
|
||||
'host' => $_ENV['MAIL_HOST'] ?? 'smtp.mailtrap.io',
|
||||
'port' => $_ENV['MAIL_PORT'] ?? 2525,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public static function get(string $key, $default = null) {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
// Support dot notation (e.g., 'db.host')
|
||||
$keys = explode('.', $key);
|
||||
$value = self::$instance->settings;
|
||||
|
||||
foreach ($keys as $k) {
|
||||
if (!isset($value[$k])) {
|
||||
return $default;
|
||||
}
|
||||
$value = $value[$k];
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
87
app/Core/Controller.php
Normal file
87
app/Core/Controller.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
abstract class Controller {
|
||||
/**
|
||||
* Helper to render a view file with data
|
||||
*/
|
||||
protected function renderOld(string $view, array $data = []) {
|
||||
// Find the view file in the app/Views folder
|
||||
$viewFile = __DIR__ . "/../Views/{$view}.phtml";
|
||||
|
||||
if (!file_exists($viewFile)) {
|
||||
die("View file $view not found.");
|
||||
}
|
||||
|
||||
// Extract data array into local variables for the view
|
||||
extract($data);
|
||||
|
||||
// Start output buffering to capture the template content
|
||||
ob_start();
|
||||
include $viewFile;
|
||||
echo ob_get_clean();
|
||||
}
|
||||
protected function render(string $view, array $data = []) {
|
||||
extract($data);
|
||||
|
||||
// 1. Render the specific page view into a variable
|
||||
ob_start();
|
||||
include __DIR__ . "/../Views/{$view}.phtml";
|
||||
$content = ob_get_clean();
|
||||
|
||||
// 2. Render the main layout and pass the $content into it
|
||||
include __DIR__ . "/../Views/layouts/main.phtml";
|
||||
}
|
||||
protected function json(array $data, int $code = 200) {
|
||||
// Set header to JSON
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// Set HTTP status code (200, 201, 400, 404, etc.)
|
||||
http_response_code($code);
|
||||
|
||||
// Output the data
|
||||
echo json_encode($data);
|
||||
exit; // End execution to prevent accidental HTML output
|
||||
}
|
||||
protected function getNetwork($msisdn){
|
||||
$phone = str_replace(' ', '', $msisdn);
|
||||
$phone = str_replace('+', '', $msisdn);
|
||||
$pattern = "/^\+?(265|0)?[89]\d{8}$/i";
|
||||
|
||||
$retval = preg_match($pattern, $phone, $matches, PREG_OFFSET_CAPTURE);
|
||||
if (count($matches) < 1) {
|
||||
return false;
|
||||
}
|
||||
elseif (strlen($phone) == 9) {
|
||||
$msisdn = "265" . $phone;
|
||||
}
|
||||
elseif (strlen($phone) == 10) {
|
||||
$phone = ltrim($phone, 0);
|
||||
$msisdn = "265" . $phone;
|
||||
}
|
||||
elseif (strlen($phone) == 12) {
|
||||
$msisdn = $phone;
|
||||
}
|
||||
if (!isset($msisdn)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$prefix = substr($msisdn, 0, 4);
|
||||
|
||||
if ($prefix == '2658') {
|
||||
$network = 'tnm';
|
||||
}
|
||||
elseif ($prefix == '2659' ) {
|
||||
$network = 'airtel';
|
||||
}
|
||||
else{
|
||||
return FALSE;
|
||||
}
|
||||
return $network;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
17
app/Core/Csrf.php
Normal file
17
app/Core/Csrf.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
class Csrf {
|
||||
public static function generate() {
|
||||
Session::start();
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
return $_SESSION['csrf_token'];
|
||||
}
|
||||
|
||||
public static function verify($token) {
|
||||
Session::start();
|
||||
return hash_equals($_SESSION['csrf_token'] ?? '', $token);
|
||||
}
|
||||
}
|
||||
33
app/Core/Database.php
Normal file
33
app/Core/Database.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
use PDO;
|
||||
|
||||
class Database {
|
||||
private static $instance = null;
|
||||
private $connection;
|
||||
|
||||
private function __construct() {
|
||||
$config = require __DIR__ . '/../Config/database.php';
|
||||
|
||||
$dsn = "mysql:host={$config['host']};dbname={$config['db']};charset=utf8mb4";
|
||||
$this->connection = new PDO($dsn, $config['user'], $config['pass'], [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getInstance() {
|
||||
if (!self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance->connection;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage in a Model:
|
||||
// $db = \App\Core\Database::getInstance();
|
||||
// $stmt = $db->query("SELECT * FROM users");
|
||||
|
||||
?>
|
||||
60
app/Core/Logger.php
Normal file
60
app/Core/Logger.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
class Logger {
|
||||
private string $logFile;
|
||||
private int $maxSize = 5242880;
|
||||
private int $maxFiles = 5;
|
||||
|
||||
public function __construct(string $filename = 'app.log') {
|
||||
// Place logs in storage/logs/ at the project root
|
||||
$this->logFile = __DIR__ . "/../../storage/logs/" . $filename;
|
||||
}
|
||||
|
||||
public function log(string $message, string $level = 'INFO'): void {
|
||||
$this->checkRotation();
|
||||
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$formattedMessage = "[$timestamp] [$level] $message" . PHP_EOL;
|
||||
|
||||
file_put_contents($this->logFile, $formattedMessage, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
|
||||
private function checkRotation(): void {
|
||||
if (file_exists($this->logFile) && filesize($this->logFile) >= $this->maxSize) {
|
||||
$this->rotate();
|
||||
}
|
||||
}
|
||||
|
||||
private function rotate(): void {
|
||||
// Delete the oldest file if it exists (e.g., app.log.5)
|
||||
$oldestFile = $this->logFile . '.' . $this->maxFiles;
|
||||
if (file_exists($oldestFile)) {
|
||||
unlink($oldestFile);
|
||||
}
|
||||
|
||||
// Shift existing files up (4 becomes 5, 3 becomes 4, etc.)
|
||||
for ($i = $this->maxFiles - 1; $i >= 1; $i--) {
|
||||
$currentFile = $this->logFile . '.' . $i;
|
||||
$nextFile = $this->logFile . '.' . ($i + 1);
|
||||
if (file_exists($currentFile)) {
|
||||
rename($currentFile, $nextFile);
|
||||
}
|
||||
}
|
||||
|
||||
// Move the main log file to .1
|
||||
rename($this->logFile, $this->logFile . '.1');
|
||||
|
||||
// Create a new empty log file
|
||||
touch($this->logFile);
|
||||
chmod($this->logFile, 0664);
|
||||
}
|
||||
|
||||
// Shorthand helpers
|
||||
public function info($msg) { $this->log($msg, 'INFO'); }
|
||||
public function error($msg) { $this->log($msg, 'ERROR'); }
|
||||
}
|
||||
|
||||
?>
|
||||
33
app/Core/Model.php
Normal file
33
app/Core/Model.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
abstract class Model {
|
||||
protected static $table;
|
||||
|
||||
protected static function builder() {
|
||||
$pdo = Database::getInstance();
|
||||
return (new QueryBuilder($pdo))->table(static::$table);
|
||||
}
|
||||
|
||||
public static function all() {
|
||||
return self::builder()->get();
|
||||
}
|
||||
|
||||
public static function find($id) {
|
||||
return self::builder()->where('id', $id)->get()[0] ?? null;
|
||||
}
|
||||
public static function create(array $data) {
|
||||
return static::builder()->insert($data);
|
||||
}
|
||||
|
||||
public static function updateById($id, array $data) {
|
||||
return static::builder()->where('id', $id)->update($data);
|
||||
}
|
||||
|
||||
public static function deleteById($id) {
|
||||
return static::builder()->where('id', $id)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
71
app/Core/OldQueryBuilder.php
Normal file
71
app/Core/OldQueryBuilder.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
use PDO;
|
||||
|
||||
class OldQueryBuilder {
|
||||
protected $pdo;
|
||||
protected $table;
|
||||
protected $where = [];
|
||||
protected $params = [];
|
||||
|
||||
public function __construct(PDO $pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function table($table) {
|
||||
$this->table = $table;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function where($column, $value) {
|
||||
$this->where[] = "{$column} = :{$column}";
|
||||
$this->params[$column] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get() {
|
||||
$sql = "SELECT * FROM {$this->table}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $this->where);
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($this->params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
public function insert(array $data) {
|
||||
$columns = implode(', ', array_keys($data));
|
||||
$placeholders = ':' . implode(', :', array_keys($data));
|
||||
|
||||
$sql = "INSERT INTO {$this->table} ({$columns}) VALUES ({$placeholders})";
|
||||
return $this->pdo->prepare($sql)->execute($data);
|
||||
}
|
||||
|
||||
public function update(array $data) {
|
||||
$fields = "";
|
||||
foreach ($data as $key => $value) {
|
||||
$fields .= "{$key} = :{$key}, ";
|
||||
}
|
||||
$fields = rtrim($fields, ', ');
|
||||
|
||||
$sql = "UPDATE {$this->table} SET {$fields}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $this->where);
|
||||
}
|
||||
|
||||
// Merge update data with where parameters
|
||||
return $this->pdo->prepare($sql)->execute(array_merge($data, $this->params));
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
$sql = "DELETE FROM {$this->table}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $this->where);
|
||||
}
|
||||
|
||||
return $this->pdo->prepare($sql)->execute($this->params);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
82
app/Core/QueryBuilder copy.php
Normal file
82
app/Core/QueryBuilder copy.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
use PDO;
|
||||
|
||||
|
||||
class QueryBuilder {
|
||||
protected $pdo;
|
||||
protected $table;
|
||||
protected $where = [];
|
||||
protected $params = [];
|
||||
|
||||
public function __construct(PDO $pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function table($table) {
|
||||
$this->table = $table;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function where($column, $operator, $value = null) {
|
||||
if ($value === null) {
|
||||
$value = $operator;
|
||||
$operator = '=';
|
||||
}
|
||||
$paramName = 'where_' . str_replace('.', '_', $column) . '_' . count($this->params);
|
||||
|
||||
$this->where[] = "{$column} {$operator} :{$paramName}";
|
||||
$this->params[$paramName] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get() {
|
||||
$sql = "SELECT * FROM {$this->table}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $this->where);
|
||||
}
|
||||
|
||||
// echo "<b>SQL:</b> " . $sql . "<br><br>";
|
||||
// echo "<b>Params:</b><pre>"; print_r($this->params); echo "</pre>";
|
||||
// // die();
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($this->params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function insert(array $data) {
|
||||
$columns = implode(', ', array_keys($data));
|
||||
$placeholders = ':' . implode(', :', array_keys($data));
|
||||
|
||||
$sql = "INSERT INTO {$this->table} ({$columns}) VALUES ({$placeholders})";
|
||||
return $this->pdo->prepare($sql)->execute($data);
|
||||
}
|
||||
|
||||
public function update(array $data) {
|
||||
$fields = "";
|
||||
foreach ($data as $key => $value) {
|
||||
$fields .= "{$key} = :{$key}, ";
|
||||
}
|
||||
$fields = rtrim($fields, ', ');
|
||||
|
||||
$sql = "UPDATE {$this->table} SET {$fields}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $this->where);
|
||||
}
|
||||
return $this->pdo->prepare($sql)->execute(array_merge($data, $this->params));
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
$sql = "DELETE FROM {$this->table}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $this->where);
|
||||
}
|
||||
|
||||
return $this->pdo->prepare($sql)->execute($this->params);
|
||||
}
|
||||
}
|
||||
?>
|
||||
163
app/Core/QueryBuilder.php
Normal file
163
app/Core/QueryBuilder.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
use PDO;
|
||||
|
||||
class QueryBuilder {
|
||||
protected $pdo;
|
||||
protected $table;
|
||||
protected $where = [];
|
||||
protected $params = [];
|
||||
protected $orderBy = [];
|
||||
protected $limit = null;
|
||||
protected $selects = '*'; // Defaults to everything
|
||||
protected $joins = [];
|
||||
|
||||
public function __construct(PDO $pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function table($table) {
|
||||
$this->table = $table;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Notice the new $boolean parameter (defaults to 'AND')
|
||||
public function where($column, $operator, $value = null, $boolean = 'AND') {
|
||||
if ($value === null) {
|
||||
$value = $operator;
|
||||
$operator = '=';
|
||||
}
|
||||
|
||||
$paramName = 'where_' . str_replace('.', '_', $column) . '_' . count($this->params);
|
||||
|
||||
// If this is the first where clause, we don't need 'AND' or 'OR'
|
||||
$prefix = empty($this->where) ? '' : strtoupper($boolean) . ' ';
|
||||
|
||||
$this->where[] = "{$prefix}{$column} {$operator} :{$paramName}";
|
||||
$this->params[$paramName] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// orWhere simply calls where() but passes 'OR'
|
||||
public function orWhere($column, $operator, $value = null) {
|
||||
return $this->where($column, $operator, $value, 'OR');
|
||||
}
|
||||
|
||||
// Sort the results
|
||||
public function orderBy($column, $direction = 'ASC') {
|
||||
$direction = strtoupper($direction) === 'DESC' ? 'DESC' : 'ASC';
|
||||
$this->orderBy[] = "{$column} {$direction}";
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Limit the number of results
|
||||
public function limit(int $limit) {
|
||||
$this->limit = $limit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Fetch a single record instead of an array of arrays
|
||||
public function first() {
|
||||
$this->limit(1);
|
||||
$result = $this->get();
|
||||
return $result ? $result[0] : null;
|
||||
}
|
||||
|
||||
public function getOld() {
|
||||
$sql = "SELECT * FROM {$this->table}";
|
||||
|
||||
// Changed implode from ' AND ' to ' ' because the prefix handles it now
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' ', $this->where);
|
||||
}
|
||||
|
||||
if (!empty($this->orderBy)) {
|
||||
$sql .= " ORDER BY " . implode(', ', $this->orderBy);
|
||||
}
|
||||
|
||||
if ($this->limit !== null) {
|
||||
$sql .= " LIMIT {$this->limit}";
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($this->params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
public function get() {
|
||||
// Use the new selects property instead of hardcoded '*'
|
||||
$sql = "SELECT {$this->selects} FROM {$this->table}";
|
||||
|
||||
// Add joins immediately after the FROM clause
|
||||
if (!empty($this->joins)) {
|
||||
$sql .= " " . implode(' ', $this->joins);
|
||||
}
|
||||
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' ', $this->where);
|
||||
}
|
||||
|
||||
if (!empty($this->orderBy)) {
|
||||
$sql .= " ORDER BY " . implode(', ', $this->orderBy);
|
||||
}
|
||||
|
||||
if ($this->limit !== null) {
|
||||
$sql .= " LIMIT {$this->limit}";
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($this->params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function insert(array $data) {
|
||||
$columns = implode(', ', array_keys($data));
|
||||
$placeholders = ':' . implode(', :', array_keys($data));
|
||||
|
||||
$sql = "INSERT INTO {$this->table} ({$columns}) VALUES ({$placeholders})";
|
||||
return $this->pdo->prepare($sql)->execute($data);
|
||||
}
|
||||
|
||||
public function update(array $data) {
|
||||
$fields = "";
|
||||
foreach ($data as $key => $value) {
|
||||
$fields .= "{$key} = :{$key}, ";
|
||||
}
|
||||
$fields = rtrim($fields, ', ');
|
||||
|
||||
$sql = "UPDATE {$this->table} SET {$fields}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' ', $this->where);
|
||||
}
|
||||
|
||||
return $this->pdo->prepare($sql)->execute(array_merge($data, $this->params));
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
$sql = "DELETE FROM {$this->table}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' ', $this->where);
|
||||
}
|
||||
|
||||
return $this->pdo->prepare($sql)->execute($this->params);
|
||||
}
|
||||
// Specify exactly which columns to return
|
||||
public function select($columns) {
|
||||
// Allow passing an array ['id', 'name'] or a raw string 'id, name'
|
||||
$this->selects = is_array($columns) ? implode(', ', $columns) : $columns;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// The main join method
|
||||
public function join($table, $firstColumn, $operator, $secondColumn, $type = 'INNER') {
|
||||
$this->joins[] = "{$type} JOIN {$table} ON {$firstColumn} {$operator} {$secondColumn}";
|
||||
return $this;
|
||||
}
|
||||
|
||||
// A helpful shortcut for LEFT JOINS
|
||||
public function leftJoin($table, $firstColumn, $operator, $secondColumn) {
|
||||
return $this->join($table, $firstColumn, $operator, $secondColumn, 'LEFT');
|
||||
}
|
||||
}
|
||||
?>
|
||||
66
app/Core/Router.php
Normal file
66
app/Core/Router.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
class Router {
|
||||
protected $routes = [];
|
||||
|
||||
public function get($path, $callback) {
|
||||
$this->routes['GET'][$path] = $callback;
|
||||
}
|
||||
// Registers POST routes
|
||||
public function post($path, $callback) {
|
||||
$this->routes['POST'][$path] = $callback;
|
||||
}
|
||||
public function resolve($uri, $method) {
|
||||
// Strip query strings (e.g., /users?id=1 becomes /users)
|
||||
// In public/index.php
|
||||
|
||||
$path = parse_url($uri, PHP_URL_PATH);
|
||||
// var_dump($path);
|
||||
$callback = $this->routes[$method][$path] ?? null;
|
||||
|
||||
if (!$callback) {
|
||||
http_response_code(404);
|
||||
// echo "404 Not Found";
|
||||
$this->handleNotFound($path, $method);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle 'Controller@method' strings
|
||||
if (is_string($callback)) {
|
||||
[$controllerName, $methodName] = explode('@', $callback);
|
||||
$controllerClass = "\\App\\Controllers\\" . $controllerName;
|
||||
$controller = new $controllerClass();
|
||||
$controller->$methodName();
|
||||
}
|
||||
}
|
||||
private function handleNotFound($path, $method) {
|
||||
$logger = new Logger();
|
||||
$isDebug = ($_ENV['APP_DEBUG'] ?? 'false') === 'true';
|
||||
|
||||
// 1. Log the failure for the developer
|
||||
$logMessage = "404 Not Found | Method: $method | URI: $path";
|
||||
$logger->error($logMessage);
|
||||
|
||||
http_response_code(404);
|
||||
|
||||
// 2. If Debug is ON, show detailed info in the browser
|
||||
if ($isDebug) {
|
||||
// echo "<h1>404 Not Found (Debug Mode)</h1>";
|
||||
// echo "<p><strong>Method:</strong> $method</p>";
|
||||
// echo "<p><strong>Attempted URI:</strong> $path</p>";
|
||||
// echo "<p><strong>Defined Routes:</strong></p><pre>";
|
||||
// print_r($this->routes[$method] ?? []);
|
||||
// echo "</pre>";
|
||||
|
||||
echo json_encode([ 'message' => 'Not Found', 'method' => $method, 'path' => $path]);
|
||||
} else {
|
||||
// Simple message for production
|
||||
echo "404 Not Found";
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
22
app/Core/Session.php
Normal file
22
app/Core/Session.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
class Session {
|
||||
public static function start() {
|
||||
if (session_status() === PHP_SESSION_NONE) session_start();
|
||||
}
|
||||
|
||||
public static function setFlash($key, $message) {
|
||||
self::start();
|
||||
$_SESSION['flash'][$key] = $message;
|
||||
}
|
||||
|
||||
public static function getFlash($key) {
|
||||
self::start();
|
||||
$message = $_SESSION['flash'][$key] ?? null;
|
||||
unset($_SESSION['flash'][$key]); // Delete after retrieval
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
24
app/Core/Validator.php
Normal file
24
app/Core/Validator.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
class Validator {
|
||||
public static function validate(array $data, array $rules) {
|
||||
$errors = [];
|
||||
foreach ($rules as $field => $rule) {
|
||||
if (strpos($rule, 'required') !== false && empty($data[$field])) {
|
||||
$errors[$field] = ucfirst($field) . " is required.";
|
||||
}
|
||||
if (strpos($rule, 'email') !== false && !filter_var($data[$field], FILTER_VALIDATE_EMAIL)) {
|
||||
$errors[$field] = "Invalid email format.";
|
||||
}
|
||||
if (strpos($rule, 'min:8') !== false && strlen($data[$field] ?? '') < 8) {
|
||||
$errors[$field] = ucfirst($field) . " must be at least 8 characters.";
|
||||
}
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
Reference in New Issue
Block a user