Init
This commit is contained in:
commit
5b1f18a935
54
app/WillySoft/Http/ErrorHandler.php
Normal file
54
app/WillySoft/Http/ErrorHandler.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace WillySoft\Http;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captures errors and exceptions thrown by your application and renders them to the user
|
||||||
|
*/
|
||||||
|
abstract class ErrorHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Array of strings containing HTML error and exception messages
|
||||||
|
*/
|
||||||
|
private static array $error_messages = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start capturing output to be replaced, set error and exception handlers and register shutdown function.
|
||||||
|
*
|
||||||
|
* On capturing an error or exception the callback will be called with an array of strings containing HTML error
|
||||||
|
* messages as an argument.
|
||||||
|
*/
|
||||||
|
public static function register(callable $callback)
|
||||||
|
{
|
||||||
|
ob_start();
|
||||||
|
set_error_handler(self::error(...));
|
||||||
|
set_exception_handler(self::exception(...));
|
||||||
|
register_shutdown_function(self::shutdown(...), $callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function error($errno, $errstr, $errfile, $errline)
|
||||||
|
{
|
||||||
|
$errstr = htmlspecialchars($errstr);
|
||||||
|
self::$error_messages[] = "<b>Error[$errno]:</b> $errstr in <b>$errfile</b> at line <b>$errline</b>";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function exception($exception)
|
||||||
|
{
|
||||||
|
self::$error_messages[] = "<b>Uncaught Exception:</b> " . $exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function shutdown(callable $callback)
|
||||||
|
{
|
||||||
|
if (!self::$error_messages) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove current output to be replaced by error page
|
||||||
|
ob_end_clean();
|
||||||
|
|
||||||
|
http_response_code(500);
|
||||||
|
header_remove();
|
||||||
|
|
||||||
|
$callback(self::$error_messages);
|
||||||
|
}
|
||||||
|
}
|
81
app/WillySoft/Http/Route.php
Normal file
81
app/WillySoft/Http/Route.php
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace WillySoft\Http;
|
||||||
|
|
||||||
|
abstract class Route
|
||||||
|
{
|
||||||
|
public static array $groups = [
|
||||||
|
// default global group
|
||||||
|
[]
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function match(string $methods, string $path, callable $callback)
|
||||||
|
{
|
||||||
|
// check if request methods match
|
||||||
|
if (!in_array($_SERVER['REQUEST_METHOD'],
|
||||||
|
array_map(strtoupper(...), explode('|', $methods))
|
||||||
|
)) return;
|
||||||
|
|
||||||
|
// check if the route matches and add parameters if any
|
||||||
|
// TODO: this is just really messy man wtf
|
||||||
|
if (!isset($_SERVER['PATH_INFO'])) {
|
||||||
|
$_SERVER['PATH_INFO'] = '/';
|
||||||
|
}
|
||||||
|
$args = [];
|
||||||
|
$path_parts = explode('/', $path);
|
||||||
|
$request_path_parts = explode('/', $_SERVER['PATH_INFO']);
|
||||||
|
for ($i=0; $i < count($path_parts); $i++) {
|
||||||
|
if (empty($path_parts[$i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($path_parts[$i][0] === '$') {
|
||||||
|
if (!isset($request_path_parts[$i])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($path_parts[$i][-1] !== '?' && empty($request_path_parts[$i])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!empty($request_path_parts[$i])) {
|
||||||
|
$args[] = $request_path_parts[$i];
|
||||||
|
}
|
||||||
|
$request_path_parts[$i] = $path_parts[$i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
implode('/', $path_parts) !== implode('/', $request_path_parts)
|
||||||
|
) return;
|
||||||
|
|
||||||
|
// we have a match! run the middlewares before the callback before we die :)
|
||||||
|
foreach (self::$groups as $middlewares) {
|
||||||
|
foreach ($middlewares as $middleware) {
|
||||||
|
$middleware();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$callback(...$args);
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get(string $path, callable $callback) { self::match('get', $path, $callback); }
|
||||||
|
public static function post(string $path, callable $callback) { self::match('post', $path, $callback); }
|
||||||
|
public static function put(string $path, callable $callback) { self::match('put', $path, $callback); }
|
||||||
|
public static function patch(string $path, callable $callback) { self::match('patch', $path, $callback); }
|
||||||
|
public static function delete(string $path, callable $callback) { self::match('delete', $path, $callback); }
|
||||||
|
public static function any(string $path, callable $callback) { self::match('get|post|put|patch|delete', $path, $callback); }
|
||||||
|
|
||||||
|
public static function middleware(callable|array $middlewares)
|
||||||
|
{
|
||||||
|
if (!is_array($middlewares)) {
|
||||||
|
$middlewares = [$middlewares];
|
||||||
|
}
|
||||||
|
foreach ($middlewares as $middleware) {
|
||||||
|
self::$groups[(count(self::$groups) - 1)][] = $middleware;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function group(callable $callback)
|
||||||
|
{
|
||||||
|
self::$groups[] = [];
|
||||||
|
$callback();
|
||||||
|
array_pop(self::$groups);
|
||||||
|
}
|
||||||
|
}
|
56
public/index.php
Normal file
56
public/index.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// minimum PHP version required to continue
|
||||||
|
$php_version = '8.1';
|
||||||
|
if (version_compare(PHP_VERSION, $php_version, '<')) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo 'This app requires a minimum of PHP ' . $php_version;
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
unset($php_version);
|
||||||
|
|
||||||
|
// PSR-4 like autoloader
|
||||||
|
spl_autoload_register(
|
||||||
|
function ($class_name) {
|
||||||
|
$path = __DIR__ . '/../app/' . str_replace('\\', '/', $class_name) . '.php';
|
||||||
|
require $path;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for evaluating/including views
|
||||||
|
*/
|
||||||
|
function view(string $_view, array $_data = [])
|
||||||
|
{
|
||||||
|
$_path = __DIR__ . '/../views/' . $_view . '.php';
|
||||||
|
extract($_data);
|
||||||
|
require $_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for converting and outputting JSON
|
||||||
|
*/
|
||||||
|
function json_response(mixed $data, int $status_code = 200)
|
||||||
|
{
|
||||||
|
http_response_code($status_code);
|
||||||
|
header('Content-type: application/json');
|
||||||
|
echo json_encode($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for generating URLs
|
||||||
|
*/
|
||||||
|
function url(string $url, bool $full = false): string
|
||||||
|
{
|
||||||
|
$dir = dirname($_SERVER['SCRIPT_NAME']);
|
||||||
|
if ($dir === '/') {
|
||||||
|
$dir = '';
|
||||||
|
}
|
||||||
|
if (!$full) {
|
||||||
|
return $dir . $url;
|
||||||
|
}
|
||||||
|
return isset($_SERVER['HTTPS']) ? 'https' : 'http' . '://' . $_SERVER['HTTP_HOST'] . $dir . $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now lets get routing!
|
||||||
|
require __DIR__ . '/../routes/start.php';
|
BIN
public/static/img/stars.gif
Normal file
BIN
public/static/img/stars.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
public/static/img/welcome-to-willy.club.gif
Normal file
BIN
public/static/img/welcome-to-willy.club.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
5
public/static/style/main.css
Normal file
5
public/static/style/main.css
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
body {
|
||||||
|
background-color: black;
|
||||||
|
background-image: url('../img/stars.gif');
|
||||||
|
color: bisque;
|
||||||
|
}
|
14
routes/start.php
Normal file
14
routes/start.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use WillySoft\Http\ErrorHandler;
|
||||||
|
use WillySoft\Http\Route;
|
||||||
|
|
||||||
|
ErrorHandler::register(function($error_messages) {
|
||||||
|
view('errors/500', ['error_messages' => $error_messages]);
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::get('/', fn() => view('pages/home'));
|
||||||
|
|
||||||
|
// since no route was matched we show a page not found error
|
||||||
|
http_response_code(404);
|
||||||
|
view('errors/404');
|
12
views/errors/404.php
Normal file
12
views/errors/404.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Page not found</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Page not found</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
28
views/errors/500.php
Normal file
28
views/errors/500.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Internal Server Error 500</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 1rem;
|
||||||
|
background: tomato;
|
||||||
|
line-height: 1.75;
|
||||||
|
font-family: "Comic Sans MS", sans-serif;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background: white;
|
||||||
|
border: .1rem solid black;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<h1>Error!!1 (×﹏×)</h1>
|
||||||
|
<?php foreach($error_messages as $error_message): ?>
|
||||||
|
<div class="error"><?=$error_message?></div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</body>
|
||||||
|
</html>
|
4
views/pages/home.php
Normal file
4
views/pages/home.php
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?=view('templates/header', ['title' => 'Home'])?>
|
||||||
|
|
||||||
|
|
||||||
|
<?=view('templates/footer')?>
|
3
views/templates/footer.php
Normal file
3
views/templates/footer.php
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
16
views/templates/header.php
Normal file
16
views/templates/header.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><?=$title?></title>
|
||||||
|
<link rel="stylesheet" href="static/style/main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<a href="<?=url('/')?>">
|
||||||
|
<img src="<?=url('/static/img/welcome-to-willy.club.gif')?>" alt="Welcome to willy.club">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<main>
|
Loading…
Reference in New Issue
Block a user