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