Initial setup of basics for every project.

This commit is contained in:
2026-05-29 10:13:48 +02:00
parent bbbe90458a
commit c2a57b8445
21 changed files with 201 additions and 53 deletions
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
+6
View File
@@ -0,0 +1,6 @@
<?php
return [
'enabled' => true,
'path' => ROOT . '/storage/database.sqlite'
];
Regular → Executable
+4 -7
View File
@@ -1,16 +1,13 @@
<?php
return [
'GET' => [
'/' => 'home.php',
'/about' => 'about.php',
'/sqlite' => 'sqlite.php',
'/api/test' => 'api/example.php'
],
/* Some Examples
'POST' => [
'/contact' => 'contact.php',
'/api/test' => 'api/example.php'
],
'DELETE' => [
'/api/todo' => 'delete_todo.php',
],
*/
];
Regular → Executable
View File
+6
View File
@@ -0,0 +1,6 @@
body {
font-family: system-ui, sans-serif;
max-width:960px;
margin:0 auto;
padding:2rem;
}
+2
View File
@@ -0,0 +1,2 @@
'use strict';
console.log('miniPHP loaded');
+4
View File
@@ -0,0 +1,4 @@
<?php
header('Content-Type: application/json');
echo json_encode(['success'=>true,'message'=>'API working']);
Regular → Executable
+1 -1
View File
@@ -1,4 +1,4 @@
<?php declare(strict_types=1);
// Add the helpers globally
require_once ROOT.'/src/helpers.php';
require_once ROOT.'/src/database.php';
Regular → Executable
+19
View File
@@ -0,0 +1,19 @@
<?php declare(strict_types=1);
function db(): PDO
{
static $pdo=null;
if ($pdo instanceof PDO) { return $pdo; }
$config = get_config('database');
if (!($config['enabled']??false)) {
throw new RuntimeException('Database is disabled.');
}
$pdo = new PDO('sqlite:'.$config['path']);
$pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);
return $pdo;
}
Regular → Executable
+77
View File
@@ -1,5 +1,9 @@
<?php declare(strict_types=1);
// -----------------------------------------------------------------------------
// Escape user-controlled output before rendering HTML.
// Prevents XSS by converting special characters into HTML entities.
// -----------------------------------------------------------------------------
function escape_string(string $value): string
{
return htmlspecialchars(
@@ -8,3 +12,76 @@ function escape_string(string $value): string
'UTF-8'
);
}
// -----------------------------------------------------------------------------
// Dump a variable and terminate execution.
// Useful when debugging requests, database results, configuration, etc.
// -----------------------------------------------------------------------------
function debugx(mixed $value): never
{
echo '<pre>';
print_r($value);
echo '</pre>';
exit;
}
// -----------------------------------------------------------------------------
// Dump a variable without terminating execution.
// -----------------------------------------------------------------------------
function debug(mixed $value): void
{
echo '<pre>';
print_r($value);
echo '</pre>';
}
// -----------------------------------------------------------------------------
// Load a configuration file from /config.
// Config files are cached for the lifetime of the request to avoid
// repeatedly requiring the same file.
// -----------------------------------------------------------------------------
function get_config(string $file): array
{
static $cache = [];
if (isset($cache[$file])) {
return $cache[$file];
}
$path = ROOT . '/config/' . $file . '.php';
if (!is_file($path)) {
throw new RuntimeException("Config file not found: {$file}");
}
$config = require $path;
if (!is_array($config)) {
throw new RuntimeException("Config file must return an array: {$file}");
}
return $cache[$file] = $config;
}
// -----------------------------------------------------------------------------
// Render a reusable partial view.
// Variables passed via $data become available inside the partial.
// partial('head', [ 'title' => 'Home', ]);
// Loads: src/views/partials/head.php
// -----------------------------------------------------------------------------
function partial(string $name, array $data = []): void
{
$file = ROOT . '/src/views/partials/' . $name . '.php';
if (!is_file($file)) {
throw new RuntimeException(
"Partial not found: {$name}"
);
}
// Extract provided variables into the local scope while preventing
// accidental overwriting of existing variables.
extract($data, EXTR_SKIP);
require $file;
}
Regular → Executable
+37 -39
View File
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);
// -----------------------------------------------------------------------------
// Parse request URI
// Parse Request URI
// Examples:
// / => /
// /about => /about
@@ -18,47 +18,22 @@ $uri = rtrim($uri, '/');
$uri = $uri ?: '/';
// -----------------------------------------------------------------------------
// Request method
// Request Method
// -----------------------------------------------------------------------------
$method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
// -----------------------------------------------------------------------------
// Load route configuration
// config/routes.php:
// return [
// 'GET' => [
// '/' => 'home.php',
// '/about' => 'about.php',
// ],
// ];
// Load Routes
// -----------------------------------------------------------------------------
$routes = require ROOT . '/config/routes.php';
if (!is_array($routes)) {
error_log('Router: routes.php must return an array');
http_response_code(500);
exit;
}
$routes = get_config('routes');
// -----------------------------------------------------------------------------
// Find matching route
// Find Route
// -----------------------------------------------------------------------------
$route = $routes[$method][$uri] ?? null;
// -----------------------------------------------------------------------------
// Base directory containing all page files
// Every routed file MUST live inside this directory.
// -----------------------------------------------------------------------------
$base = realpath(ROOT . '/src/views/pages');
if ($base === false) {
error_log('Router: pages directory not found');
http_response_code(500);
exit;
}
// -----------------------------------------------------------------------------
// Route not found
// Route Not Found
// -----------------------------------------------------------------------------
if ($route === null) {
http_response_code(404);
@@ -66,24 +41,47 @@ if ($route === null) {
}
// -----------------------------------------------------------------------------
// Resolve requested file
// Determine Base Directory
//
// Routes beginning with:
// api/
// are loaded from:
// src/api/
// Everything else is loaded from:
// src/views/pages/
// -----------------------------------------------------------------------------
if (str_starts_with($route, 'api/')) {
$base = realpath(ROOT . '/src/api');
$file = substr($route, 4);
} else {
$base = realpath(ROOT . '/src/views/pages');
$file = $route;
}
if ($base === false) {
error_log('Router: base directory not found');
http_response_code(500);
exit;
}
// -----------------------------------------------------------------------------
// Resolve File
// realpath() converts:
// home.php
// ../home.php
// ../../etc/passwd
// into an absolute canonical path.
// -----------------------------------------------------------------------------
$real = realpath($base . '/' . $route);
$real = realpath($base . '/' . $file);
// -----------------------------------------------------------------------------
// Path traversal protection
// Only allow files that resolve inside:
// src/views/pages/
// Path Traversal Protection
//
// This blocks attempts such as:
// Ensures the resolved file stays inside the expected base directory.
// Examples blocked:
// ../../etc/passwd
// ../../../secret.php
// even if they somehow end up in the route table.
// -----------------------------------------------------------------------------
if ($real === false || !str_starts_with( $real, $base . DIRECTORY_SEPARATOR)) {
@@ -93,6 +91,6 @@ if ($real === false || !str_starts_with($real, $base . DIRECTORY_SEPARATOR)) {
}
// -----------------------------------------------------------------------------
// Dispatch request
// Dispatch
// -----------------------------------------------------------------------------
require $real;
Regular → Executable
+1
View File
@@ -0,0 +1 @@
<p>404</p>
Regular → Executable
+6 -1
View File
@@ -1 +1,6 @@
<p>about</p>
<?php partial('head', [ 'title' => 'About', ]); ?>
<h1>About</h1>
<p>random stuff</p>
<?php partial('footer'); ?>
Regular → Executable
+7 -1
View File
@@ -1 +1,7 @@
<p>home</p>
<?php partial('head', [ 'title' => 'Home', ]); ?>
<h1>Home</h1>
<p>Hello world.</p>
<?php partial('footer'); ?>
+15
View File
@@ -0,0 +1,15 @@
<?php
$db = db();
$db->exec("
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
)
");
$db->exec(" INSERT INTO users (name) VALUES ('Jason') ");
$users = $db ->query('SELECT * FROM users') ->fetchAll();
debugx($users);
+3
View File
@@ -0,0 +1,3 @@
<script src="/public/js/app.js"></script>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title><?php echo escape_string($title ?? 'miniPHP') ?></title>
<link rel="stylesheet" href="/public/css/app.css">
</head>
<body>
View File
Binary file not shown.