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
+6 -9
View File
@@ -1,16 +1,13 @@
<?php <?php
return [ return [
'GET' => [ 'GET' => [
'/' => 'home.php', '/' => 'home.php',
'/about' => 'about.php', '/about' => 'about.php',
'/sqlite' => 'sqlite.php',
'/api/test' => 'api/example.php'
], ],
/* Some Examples
'POST' => [ '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
+2 -2
View File
@@ -1,4 +1,4 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
// Add the helpers globally require_once ROOT.'/src/helpers.php';
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); <?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 function escape_string(string $value): string
{ {
return htmlspecialchars( return htmlspecialchars(
@@ -8,3 +12,76 @@ function escape_string(string $value): string
'UTF-8' '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
+38 -40
View File
@@ -1,6 +1,6 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Parse request URI // Parse Request URI
// Examples: // Examples:
// / => / // / => /
// /about => /about // /about => /about
@@ -18,47 +18,22 @@ $uri = rtrim($uri, '/');
$uri = $uri ?: '/'; $uri = $uri ?: '/';
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Request method // Request Method
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
$method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET'); $method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Load route configuration // Load Routes
// config/routes.php:
// return [
// 'GET' => [
// '/' => 'home.php',
// '/about' => 'about.php',
// ],
// ];
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
$routes = require ROOT . '/config/routes.php'; $routes = get_config('routes');
if (!is_array($routes)) {
error_log('Router: routes.php must return an array');
http_response_code(500);
exit;
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Find matching route // Find Route
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
$route = $routes[$method][$uri] ?? null; $route = $routes[$method][$uri] ?? null;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Base directory containing all page files // Route Not Found
// 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
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
if ($route === null) { if ($route === null) {
http_response_code(404); http_response_code(404);
@@ -66,33 +41,56 @@ 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: // realpath() converts:
// home.php // home.php
// ../home.php // ../home.php
// ../../etc/passwd // ../../etc/passwd
// into an absolute canonical path. // into an absolute canonical path.
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
$real = realpath($base . '/' . $route); $real = realpath($base . '/' . $file);
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Path traversal protection // Path Traversal Protection
// Only allow files that resolve inside:
// src/views/pages/
// //
// This blocks attempts such as: // Ensures the resolved file stays inside the expected base directory.
// Examples blocked:
// ../../etc/passwd // ../../etc/passwd
// ../../../secret.php // ../../../secret.php
// even if they somehow end up in the route table.
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
if ($real === false || !str_starts_with($real, $base . DIRECTORY_SEPARATOR)) { if ($real === false || !str_starts_with( $real, $base . DIRECTORY_SEPARATOR)) {
error_log("Router: invalid route target [$route]"); error_log("Router: invalid route target [$route]");
http_response_code(403); http_response_code(403);
exit; exit;
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Dispatch request // Dispatch
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
require $real; 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.