diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/config/app.php b/config/app.php old mode 100644 new mode 100755 diff --git a/config/database.php b/config/database.php old mode 100644 new mode 100755 index e69de29..9257e2c --- a/config/database.php +++ b/config/database.php @@ -0,0 +1,6 @@ + true, + 'path' => ROOT . '/storage/database.sqlite' +]; diff --git a/config/routes.php b/config/routes.php old mode 100644 new mode 100755 index 8dde48c..f9e1b06 --- a/config/routes.php +++ b/config/routes.php @@ -1,16 +1,13 @@ [ - '/' => 'home.php', - '/about' => 'about.php', + '/' => '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', - ], - */ ]; diff --git a/index.php b/index.php old mode 100644 new mode 100755 diff --git a/public/css/app.css b/public/css/app.css new file mode 100755 index 0000000..0d400a4 --- /dev/null +++ b/public/css/app.css @@ -0,0 +1,6 @@ +body { + font-family: system-ui, sans-serif; + max-width:960px; + margin:0 auto; + padding:2rem; +} diff --git a/public/js/app.js b/public/js/app.js new file mode 100755 index 0000000..9012239 --- /dev/null +++ b/public/js/app.js @@ -0,0 +1,2 @@ +'use strict'; +console.log('miniPHP loaded'); diff --git a/src/api/example.php b/src/api/example.php new file mode 100755 index 0000000..83241a7 --- /dev/null +++ b/src/api/example.php @@ -0,0 +1,4 @@ +true,'message'=>'API working']); diff --git a/src/bootstrap.php b/src/bootstrap.php old mode 100644 new mode 100755 index 0d459d9..3608da7 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -1,4 +1,4 @@ setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION); + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC); + + return $pdo; +} diff --git a/src/helpers.php b/src/helpers.php old mode 100644 new mode 100755 index f8dd042..d745d00 --- a/src/helpers.php +++ b/src/helpers.php @@ -1,5 +1,9 @@ '; + print_r($value); + echo ''; + exit; +} + +// ----------------------------------------------------------------------------- +// Dump a variable without terminating execution. +// ----------------------------------------------------------------------------- +function debug(mixed $value): void +{ + echo '
';
+	print_r($value);
+	echo '
'; +} + +// ----------------------------------------------------------------------------- +// 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; +} diff --git a/src/router.php b/src/router.php old mode 100644 new mode 100755 index 5023fa1..93d49c4 --- a/src/router.php +++ b/src/router.php @@ -1,6 +1,6 @@ / // /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,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: // 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)) { +if ($real === false || !str_starts_with( $real, $base . DIRECTORY_SEPARATOR)) { error_log("Router: invalid route target [$route]"); http_response_code(403); exit; } // ----------------------------------------------------------------------------- -// Dispatch request +// Dispatch // ----------------------------------------------------------------------------- require $real; diff --git a/src/views/pages/404.php b/src/views/pages/404.php old mode 100644 new mode 100755 index e69de29..d415ffc --- a/src/views/pages/404.php +++ b/src/views/pages/404.php @@ -0,0 +1 @@ +

404

diff --git a/src/views/pages/about.php b/src/views/pages/about.php old mode 100644 new mode 100755 index 86c62fe..2b3a95a --- a/src/views/pages/about.php +++ b/src/views/pages/about.php @@ -1 +1,6 @@ -

about

+ 'About', ]); ?> + +

About

+

random stuff

+ + diff --git a/src/views/pages/home.php b/src/views/pages/home.php old mode 100644 new mode 100755 index 0a88878..d66fb9f --- a/src/views/pages/home.php +++ b/src/views/pages/home.php @@ -1 +1,7 @@ -

home

+ 'Home', ]); ?> + +

Home

+ +

Hello world.

+ + diff --git a/src/views/pages/sqlite.php b/src/views/pages/sqlite.php new file mode 100644 index 0000000..d3fb401 --- /dev/null +++ b/src/views/pages/sqlite.php @@ -0,0 +1,15 @@ +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); diff --git a/src/views/partials/footer.php b/src/views/partials/footer.php new file mode 100755 index 0000000..a6ef893 --- /dev/null +++ b/src/views/partials/footer.php @@ -0,0 +1,3 @@ + + + diff --git a/src/views/partials/head.php b/src/views/partials/head.php new file mode 100755 index 0000000..62363c3 --- /dev/null +++ b/src/views/partials/head.php @@ -0,0 +1,9 @@ + + + + + + <?php echo escape_string($title ?? 'miniPHP') ?> + + + diff --git a/storage/.gitkeep b/storage/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/storage/database.sqlite b/storage/database.sqlite new file mode 100644 index 0000000..4994547 Binary files /dev/null and b/storage/database.sqlite differ