Hello World

This commit is contained in:
William 2025-04-13 17:44:14 +02:00
commit f9162a027f
4 changed files with 493 additions and 0 deletions

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Whomst Ever
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

107
README.md Normal file
View File

@ -0,0 +1,107 @@
# Route
A lightweight routing system for handling HTTP requests.
### Features:
- Define routes for specific HTTP methods (GET, POST, PUT, DELETE, etc.).
- Apply middleware to routes or groups of routes.
- Group routes under a common prefix for better organization.
- Support for route parameters (e.g., `/user/$id`) and optional parameters (e.g., `/user/$id?`).
### Example:
```php
// Define a GET route
Route::get('/home', function () {
echo "Welcome to the homepage!";
});
// Define a middleware
Route::before(function () {
echo "Middleware executed before the route callback.";
});
// Define a POST route with middleware
Route::post('/submit', function () {
echo "Form submitted!";
}, function () {
echo "Middleware executed before the callback.";
});
// Group routes under a common prefix
Route::group('/admin', function () {
// Define a middleware for the group; it will not affect the outer routes
Route::before(function () {
echo "Admin Middleware executed.";
});
Route::get('/dashboard', function () {
echo "Admin Dashboard";
});
Route::post('/settings', function () {
echo "Admin Settings";
});
});
```
# Installation
## Composer
Composer needs to know where to locate the package. Add the repository to your project by declaring it in the `composer.json` file.
```JSON
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/WilliamAAK/Route"
}
],
"require": {
"willysoft/route": "dev-master"
}
}
```
Fetch the package by running
composer install
## Manual
Its all in a single file; include it in your project like so
```PHP
require '/YourPath/Route.php'
```
# Usage
```PHP
use WilliamAAK\Http\Route;
Route::get('/', fn() => echo 'hello world');
```
See `Route.php` and read the comments for more information
## Access directly
The simplest way to access your routes. Just put the file in your favorite folder and run it!
For example
`http://your.site/folder/index.php/your/route`
## Rewrite requests
The more sexy way of doing it.
For example
`http://your.site/your/route`
### NGINX
With PHP already configured, add this to the server block of your configuration to make requests that don't match a file on your server to be sent to your front controller and begin routing.
location / {
try_files $uri $uri/ /index.php?$query_string;
}

352
Route.php Normal file
View File

@ -0,0 +1,352 @@
<?php
namespace WilliamAAK\Http;
use InvalidArgumentException;
/**
* A lightweight routing system for handling HTTP requests.
*
* ### Features:
* - Define routes for specific HTTP methods (GET, POST, PUT, DELETE, etc.).
* - Apply middleware to routes or groups of routes.
* - Group routes under a common prefix for better organization.
* - Support for route parameters (e.g., `/user/$id`) and optional parameters (e.g., `/user/$id?`).
*
* ### Example:
* ```php
* // Define a GET route
* Route::get('/home', function () {
* echo "Welcome to the homepage!";
* });
*
* // Define a middleware
* Route::before(function () {
* echo "Middleware executed before the route callback.";
* });
*
* // Define a POST route with middleware
* Route::post('/submit', function () {
* echo "Form submitted!";
* }, function () {
* echo "Middleware executed before the callback.";
* });
*
* // Group routes under a common prefix
* Route::group('/admin', function () {
* // Define a middleware for the group; it will not affect the outer routes
* Route::before(function () {
* echo "Admin Middleware executed.";
* });
* Route::get('/dashboard', function () {
* echo "Admin Dashboard";
* });
* Route::post('/settings', function () {
* echo "Admin Settings";
* });
* });
* ```
*
* @package WilliamAAK\Http
* @version 1.0.0
* @author WilliamAAK
*/
class Route
{
static string $pathPrefix = '';
static array $before = [ [ ] ];
static array $after = [ [ ] ];
/**
* Retrieves the relative path of the current HTTP request.
*
* ### Examples:
* 1. If the request URI is `/index.php/myprofile/sessions` and the script name is `/index.php`,
* this method will return:
* `/myprofile/sessions`
*
* 2. If the request URI is `/myprofile/sessions` and rewrite rules are used to hide `/index.php`,
* this method will return:
* `/myprofile/sessions`
*
* @return string The relative request path.
*/
static function getRelativeRequestPath(): string
{
static $requestUrlPath;
if (isset($requestUrlPath)) {
return $requestUrlPath;
}
$requestUrlPath = urldecode(strtok($_SERVER['REQUEST_URI'], '?'));
$scriptName = $_SERVER['SCRIPT_NAME'];
$scriptDir = dirname($scriptName) . '/';
if (str_starts_with($requestUrlPath, $scriptName)) {
$requestUrlPath = substr($requestUrlPath, strlen($scriptName));
}
if ($requestUrlPath === $scriptDir) {
$requestUrlPath = '/';
}
if ($requestUrlPath === '') {
$requestUrlPath = '/';
}
return $requestUrlPath;
}
/**
* Matches an incoming HTTP request to a defined route and executes the callback.
*
* ### Examples:
* 1. Matching a route with parameters:
* ```php
* Route::match('GET', '/user/$id', function ($id) {
* echo "User ID: $id";
* });
* ```
* - Request: GET /user/123
* - Output: "User ID: 123"
*
* 2. Matching a route with optional parameters:
* ```php
* Route::match('GET', '/user/$id?', function ($id = null) {
* echo $id ? "User ID: $id" : "No User ID";
* });
* ```
* - Request: GET /user/
* - Output: "No User ID"
*
* @param string $methods Allowed HTTP methods (e.g., "GET|POST").
* @param string $route The route path to match (e.g., "/user/$id").
* @param callable $callback The callback to execute if the route matches.
* @param callable|null $middleware Optional middleware to execute before the callback.
*
* @return void
*/
static function match(string $methods, string $route, callable $callback, ?callable $middleware = null): void
{
$allowedMethods = array_map(strtoupper(...), explode('|', $methods));
if (!in_array($_SERVER['REQUEST_METHOD'], $allowedMethods)) {
return;
}
$requestPathParts = explode('/', self::getRelativeRequestPath());
$routeParts = explode('/', self::$pathPrefix . $route);
$callbackArgs = [];
if (count($requestPathParts) !== count($routeParts)) {
return;
}
for ($i = 1; $i < count($routeParts); $i++) {
$routePart = $routeParts[$i] ?? '';
$requestPart = $requestPathParts[$i] ?? '';
if ($routePart === '') {
continue;
}
$isRouteParameter = $routePart[0] === '$';
if (!$isRouteParameter) {
if ($routePart !== $requestPart) {
return;
}
continue;
}
$isOptional = $routePart[-1] === '?';
if (!$isOptional && $requestPart === '') {
return;
}
if ($isOptional && $i !== count($routeParts) - 1) {
throw new InvalidArgumentException('Only the last route parameter can be optional.');
}
if ($requestPart !== '') {
$callbackArgs[] = htmlspecialchars($requestPart);
}
$requestPathParts[$i] = $routePart;
}
if (implode('/', $routeParts) !== implode('/', $requestPathParts)) {
return;
}
if ($middleware !== null) {
self::before($middleware);
}
foreach (self::$before as $middlewares) {
foreach ($middlewares as $middleware) {
$middleware();
}
}
$callback(...$callbackArgs);
foreach (self::$after as $middlewares) {
foreach ($middlewares as $middleware) {
$middleware();
}
}
die();
}
/**
* Shorthand for Route::match('GET', ...).
*/
static function get(string $route, callable $callback, ?callable $middleware = null): void
{self::match('get', $route, $callback, $middleware);}
/**
* Shorthand for Route::match('POST', ...).
*/
static function post(string $route, callable $callback, ?callable $middleware = null): void
{self::match('post', $route, $callback, $middleware);}
/**
* Shorthand for Route::match('PUT', ...).
*/
static function put(string $route, callable $callback, ?callable $middleware = null): void
{self::match('put', $route, $callback, $middleware);}
/**
* Shorthand for Route::match('PATCH', ...).
*/
static function patch(string $route, callable $callback, ?callable $middleware = null): void
{self::match('patch', $route, $callback, $middleware);}
/**
* Shorthand for Route::match('DELETE', ...).
*/
static function delete(string $route, callable $callback, ?callable $middleware = null): void
{self::match('delete', $route, $callback, $middleware);}
/**
* Shorthand for Route::match('OPTIONS', ...).
*/
static function options(string $route, callable $callback, ?callable $middleware = null): void
{self::match('options', $route, $callback, $middleware);}
/**
* Shorthand for Route::match('GET|POST', ...).
*/
static function form(string $route, callable $callback, ?callable $middleware = null): void
{self::match('get|post', $route, $callback, $middleware);}
/**
* Shorthand for Route::match('GET|POST|PUT|PATCH|DELETE|OPTIONS', ...).
*/
static function any(string $route, callable $callback, ?callable $middleware = null): void
{self::match('get|post|put|patch|delete|options', $route, $callback, $middleware);}
/**
* Registers middleware to be executed before the route callback.
*
* ### Example:
* ```php
* Route::before(function () {
* echo "Middleware executed before the route callback.";
* });
* ```
*
* @param callable $middleware The middleware function to execute before the route callback.
*
* @return void
*/
static function before(callable $middleware): void
{
array_push(
self::$before[array_key_last(self::$before)],
$middleware
);
}
/**
* Registers middleware to be executed after the route callback.
*
* ### Example:
* ```php
* Route::after(function () {
* echo "Middleware executed after the route callback.";
* });
* ```
*
* @param callable $middleware The middleware function to execute after the route callback.
*
* @return void
*/
static function after(callable $middleware): void
{
array_push(
self::$after[array_key_last(self::$after)],
$middleware
);
}
/**
* Group routes and middlewares with an optional common prefix.
* It is useful for organizing routes that share a common path or functionality.
*
* ### Examples:
* 1. Grouping routes under a prefix:
* ```php
* Route::group('/admin', function () {
* Route::get('/dashboard', function () {
* echo "Admin Dashboard";
* });
* });
* ```
* - Request: GET /admin/dashboard
* - Output: "Admin Dashboard"
*
* 2. Grouping middlewares and routes but this time without a prefix:
* ```php
* Route::group(callback: function () {
* Route::before(function () {
* echo "Hello routes from within or nested groups! ";
* });
* // Outputs: "Hello routes from within or nested groups! Hello from somewhere!"
* Route::get('/somewhere', function () {
* echo "Hello from somewhere!";
* });
* });
*
* // Outputs: "I am unaffected by that group middleware."
* Route::get('/', function () {
* echo "I am unaffected by that group middleware.";
* });
* ```
* @param string|null $prefix The optional path prefix for the group (e.g., "/admin").
* @param callable|null $callback The callback containing the route definitions for the group.
*
* @throws InvalidArgumentException If the callback is not provided.
*
* @return void
*/
static function group(?string $prefix = null, ?callable $callback = null): void
{
if ($callback === null) {
throw new InvalidArgumentException('You must provide a callback.');
}
if (!str_starts_with(
self::getRelativeRequestPath(),
self::$pathPrefix . $prefix ?? ''
)) return;
self::$pathPrefix = self::$pathPrefix . $prefix ?? '';
array_push(self::$before, []);
array_push(self::$after, []);
$callback();
array_pop(self::$before);
array_pop(self::$after);
self::$pathPrefix = rtrim(self::$pathPrefix, $prefix ?? '');
}
}

21
composer.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "williamaak/route",
"description": " A lightweight routing system for handling HTTP requests.",
"type": "library",
"require": {
"php": ">=8.1.0"
},
"license": "WTFPL",
"authors": [
{
"name": "WilliamAAK",
"email": "54738571+WilliamAAK@users.noreply.github.com"
}
],
"minimum-stability": "dev",
"autoload": {
"psr-4": {
"WilliamAAK\\": "./"
}
}
}