This commit is contained in:
William 2022-11-29 12:49:29 +00:00
commit 5b1f18a935
13 changed files with 276 additions and 0 deletions

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# Very kewl website
Made by yours truly

View 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);
}
}

View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,5 @@
body {
background-color: black;
background-image: url('../img/stars.gif');
color: bisque;
}

14
routes/start.php Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,4 @@
<?=view('templates/header', ['title' => 'Home'])?>
<?=view('templates/footer')?>

View File

@ -0,0 +1,3 @@
</main>
</body>
</html>

View 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>