Removed old router code.

This commit is contained in:
flash 2024-03-30 16:24:34 +00:00
parent 9d5b050b89
commit 9b57fbb42c
13 changed files with 90 additions and 941 deletions

View file

@ -1 +1 @@
0.2403.282326
0.2403.301624

View file

@ -1,290 +0,0 @@
<?php
// HttpFx.php
// Created: 2022-02-15
// Updated: 2023-11-20
namespace Index\Http;
use stdClass;
use Exception;
use JsonSerializable;
use Index\Environment;
use Index\IO\Stream;
use Index\Http\Content\BencodedContent;
use Index\Http\Content\JsonContent;
use Index\Http\Content\StreamContent;
use Index\Http\Content\StringContent;
use Index\Routing\IRouter;
use Index\Routing\IRouteHandler;
use Index\Routing\Router;
use Index\Routing\RoutePathNotFoundException;
use Index\Routing\RouteMethodNotSupportedException;
use Index\Serialisation\IBencodeSerialisable;
class HttpFx implements IRouter {
private Router $router;
private array $objectHandlers = [];
private array $errorHandlers = [];
private $defaultErrorHandler; // callable
public function __construct(?Router $router = null) {
self::restoreDefaultErrorHandler();
$this->router = $router ?? new Router;
$this->addObjectHandler(
fn(object $object) => $object instanceof Stream,
function(HttpResponseBuilder $responseBuilder, object $object) {
if(!$responseBuilder->hasContentType())
$responseBuilder->setTypeStream();
$responseBuilder->setContent(new StreamContent($object));
}
);
$this->addObjectHandler(
fn(object $object) => $object instanceof JsonSerializable || $object instanceof stdClass,
function(HttpResponseBuilder $responseBuilder, object $object) {
if(!$responseBuilder->hasContentType())
$responseBuilder->setTypeJson();
$responseBuilder->setContent(new JsonContent($object));
}
);
$this->addObjectHandler(
fn(object $object) => $object instanceof IBencodeSerialisable,
function(HttpResponseBuilder $responseBuilder, object $object) {
if(!$responseBuilder->hasContentType())
$responseBuilder->setTypePlain();
$responseBuilder->setContent(new BencodedContent($object));
}
);
}
public function getRouter(): Router {
return $this->router;
}
public function addObjectHandler(callable $match, callable $handler): void {
$this->objectHandlers[] = [$match, $handler];
}
public function addErrorHandler(int $code, callable $handler): void {
$this->errorHandlers[$code] = $handler;
}
public function setDefaultErrorHandler(callable $handler): void {
$this->defaultErrorHandler = $handler;
}
public function restoreDefaultErrorHandler(): void {
$this->defaultErrorHandler = [self::class, 'defaultErrorHandler'];
}
public function dispatch(?HttpRequest $request = null, array $args = []): void {
$request ??= HttpRequest::fromRequest();
$responseBuilder = new HttpResponseBuilder;
$handlers = null;
try {
$handlers = $this->router->resolve($request->getMethod(), $request->getPath(), array_merge([
$responseBuilder, $request,
], $args));
} catch(RoutePathNotFoundException $ex) {
$statusCode = 404;
} catch(RouteMethodNotSupportedException $ex) {
$statusCode = 405;
} catch(Exception $ex) {
if(Environment::isDebug())
throw $ex;
}
if($handlers === null) {
$this->errorPage($responseBuilder, $request, $statusCode ?? 500);
} else {
$result = $handlers->run();
if(is_int($result)) {
if(!$responseBuilder->hasStatusCode() && $result >= 100 && $result < 600) {
$this->errorPage($responseBuilder, $request, $result);
} elseif(!$responseBuilder->hasContent()) {
$responseBuilder->setContent(new StringContent((string)$result));
}
} elseif(!$responseBuilder->hasContent()) {
if(is_array($result)) {
if(!$responseBuilder->hasContentType())
$responseBuilder->setTypeJson();
$responseBuilder->setContent(new JsonContent($result));
} elseif(is_object($result)) {
foreach($this->objectHandlers as $info)
if($info[0]($result)) {
$info[1]($responseBuilder, $result);
break;
}
}
if(!$responseBuilder->hasContent() && $result !== null) {
$result = (string)$result;
$responseBuilder->setContent(new StringContent($result));
if(!$responseBuilder->hasContentType()) {
if(strtolower(substr($result, 0, 14)) === '<!doctype html')
$responseBuilder->setTypeHTML('utf-8');
else {
$charset = strtolower(mb_preferred_mime_name(mb_detect_encoding($result)));
if(strtolower(substr($result, 0, 5)) === '<?xml')
$responseBuilder->setTypeXML($charset);
else
$responseBuilder->setTypePlain($charset);
}
}
}
}
}
self::output($responseBuilder->toResponse());
}
public static function defaultErrorHandler(
HttpResponseBuilder $responseBuilder,
HttpRequest $request,
int $code,
string $message
): void {
$responseBuilder->setTypeHTML();
$responseBuilder->setContent(new StringContent(sprintf(
'<!doctype html><html><head><meta charset="%3$s"/><title>%1$03d %2$s</title></head><body><center><h1>%1$03d %2$s</h1></center><hr/><center>Index</center></body></html>',
$code,
$message,
strtolower(mb_preferred_mime_name(mb_internal_encoding()))
)));
}
public function errorPage(
HttpResponseBuilder $responseBuilder,
HttpRequest $request,
int $statusCode
): void {
$responseBuilder->setStatusCode($statusCode);
$responseBuilder->clearStatusText();
if(!$responseBuilder->hasContent())
($this->errorHandlers[$statusCode] ?? $this->defaultErrorHandler)(
$responseBuilder,
$request,
$responseBuilder->getStatusCode(),
$responseBuilder->getStatusText()
);
}
public static function output(HttpResponse $response): void {
$version = $response->getHttpVersion();
header(sprintf(
'HTTP/%d.%d %03d %s',
$version->getMajor(),
$version->getMinor(),
$response->getStatusCode(),
$response->getStatusText()
));
$headers = $response->getHeaders();
foreach($headers as $header) {
$name = (string)$header->getName();
$lines = $header->getLines();
foreach($lines as $line)
header(sprintf('%s: %s', $name, (string)$line));
}
if($response->hasContent())
echo (string)$response->getContent();
}
/**
* Apply middleware functions to a path.
*
* @param string $path Path to apply the middleware to.
* @param callable $handler Middleware function.
*/
public function use(string $path, callable $handler): void {
$this->router->use($path, $handler);
}
/**
* Adds a new route.
*
* @param string $method Request method.
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function add(string $method, string $path, callable $handler): void {
$this->router->add($method, $path, $handler);
}
/**
* Adds a new GET route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function get(string $path, callable $handler): void {
$this->router->add('get', $path, $handler);
}
/**
* Adds a new POST route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function post(string $path, callable $handler): void {
$this->router->add('post', $path, $handler);
}
/**
* Adds a new DELETE route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function delete(string $path, callable $handler): void {
$this->router->add('delete', $path, $handler);
}
/**
* Adds a new PATCH route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function patch(string $path, callable $handler): void {
$this->router->add('patch', $path, $handler);
}
/**
* Adds a new PUT route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function put(string $path, callable $handler): void {
$this->router->add('put', $path, $handler);
}
/**
* Adds a new OPTIONS route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function options(string $path, callable $handler): void {
$this->router->add('options', $path, $handler);
}
/**
* Registers routes in an IRouteHandler implementation.
*
* @param IRouteHandler $handler Routes handler.
*/
public function register(IRouteHandler $handler): void {
$handler->registerRoutes($this);
}
}

View file

@ -1,18 +0,0 @@
<?php
// IRouteHandler.php
// Created: 2023-09-06
// Updated: 2023-09-07
namespace Index\Routing;
/**
* Provides the interface for IRouter::register().
*/
interface IRouteHandler {
/**
* Registers routes on a given IRouter instance.
*
* @param IRouter $router Target router.
*/
public function registerRoutes(IRouter $router): void;
}

View file

@ -1,80 +0,0 @@
<?php
// IRouter.php
// Created: 2023-01-06
// Updated: 2023-09-11
namespace Index\Routing;
interface IRouter {
/**
* Apply middleware functions to a path.
*
* @param string $path Path to apply the middleware to.
* @param callable $handler Middleware function.
*/
public function use(string $path, callable $handler): void;
/**
* Adds a new route.
*
* @param string $method Request method.
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function add(string $method, string $path, callable $handler): void;
/**
* Adds a new GET route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function get(string $path, callable $handler): void;
/**
* Adds a new POST route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function post(string $path, callable $handler): void;
/**
* Adds a new DELETE route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function delete(string $path, callable $handler): void;
/**
* Adds a new PATCH route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function patch(string $path, callable $handler): void;
/**
* Adds a new PUT route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function put(string $path, callable $handler): void;
/**
* Adds a new OPTIONS route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function options(string $path, callable $handler): void;
/**
* Registers routes in an IRouteHandler implementation.
*
* @param IRouteHandler $handler Routes handler.
*/
public function register(IRouteHandler $handler): void;
}

View file

@ -1,86 +0,0 @@
<?php
// Route.php
// Created: 2023-09-07
// Updated: 2023-09-08
namespace Index\Routing;
use Attribute;
use ReflectionObject;
/**
* Provides an attribute for marking methods in a class as routes.
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Route {
private ?string $method;
private string $path;
/**
* Creates a Route attribute.
*
* @param string $pathOrMethod Method name if this is registering a route, path if this is registering middleware.
* @param ?string $path Path if this registering a route, null if this is registering middleware.
*/
public function __construct(string $pathOrMethod, ?string $path = null) {
if($path === null) {
$this->method = null;
$this->path = $pathOrMethod;
} else {
$this->method = $pathOrMethod;
$this->path = $path;
}
}
/**
* Returns the target method name.
*
* @return ?string
*/
public function getMethod(): ?string {
return $this->method;
}
/**
* Whether this route should be used as middleware.
*
* @return bool
*/
public function isMiddleware(): bool {
return $this->method === null;
}
/**
* Returns the target path.
*
* @return string
*/
public function getPath(): string {
return $this->path;
}
/**
* Reads attributes from methods in a IRouteHandler instance and registers them to a given IRouter instance.
*
* @param IRouter $router Router instance.
* @param IRouteHandler $handler Handler instance.
*/
public static function handleAttributes(IRouter $router, IRouteHandler $handler): void {
$objectInfo = new ReflectionObject($handler);
$methodInfos = $objectInfo->getMethods();
foreach($methodInfos as $methodInfo) {
$attrInfos = $methodInfo->getAttributes(Route::class);
foreach($attrInfos as $attrInfo) {
$routeInfo = $attrInfo->newInstance();
$closure = $methodInfo->getClosure($methodInfo->isStatic() ? null : $handler);
if($routeInfo->isMiddleware())
$router->use($routeInfo->getPath(), $closure);
else
$router->add($routeInfo->getMethod(), $routeInfo->getPath(), $closure);
}
}
}
}

View file

@ -1,72 +0,0 @@
<?php
// RouteCallable.php
// Created: 2022-01-20
// Updated: 2022-02-02
namespace Index\Routing;
/**
* Stack of callables and arguments for route responses.
*/
class RouteCallable {
private array $callables;
private array $args;
/**
* @internal
*/
public function __construct(array $callables, array $args) {
$this->callables = $callables;
$this->args = $args;
}
/**
* Callables in order that they should be executed.
*
* @return array Sequential list of callables.
*/
public function getCallables(): array {
return $this->callables;
}
/**
* Arguments to be sent to each callable.
*
* @return array Sequential argument list for the callables.
*/
public function getArguments(): array {
return $this->args;
}
/**
* Runs all callables and returns their returns as an array.
*
* @return array Results from the callables.
*/
public function runAll(): array {
$results = [];
foreach($this->callables as $callable) {
$result = $callable(...$this->args);
if($result !== null)
$results[] = $result;
}
return $results;
}
/**
* Runs all callables unless one returns something.
*
* @return mixed Result from the returning callable.
*/
public function run(): mixed {
foreach($this->callables as $callable) {
$result = $callable(...$this->args);
if($result !== null)
return $result;
}
return null;
}
}

View file

@ -1,14 +0,0 @@
<?php
// RouteHandler.php
// Created: 2023-09-07
// Updated: 2023-09-07
namespace Index\Routing;
/**
* Provides an abstract class version of IRouteHandler that already includes the trait as well,
* letting you only have to use one use statement rather than two!
*/
abstract class RouteHandler implements IRouteHandler {
use RouteHandlerTrait;
}

View file

@ -1,16 +0,0 @@
<?php
// RouteHandlerTrait.php
// Created: 2023-09-07
// Updated: 2023-09-07
namespace Index\Routing;
/**
* Provides an implementation of IRouteHandler::registerRoutes that uses the attributes.
* For more advanced use, everything can be use'd separately and Route::handleAttributes called manually.
*/
trait RouteHandlerTrait {
public function registerRoutes(IRouter $router): void {
Route::handleAttributes($router, $this);
}
}

View file

@ -1,109 +0,0 @@
<?php
// RouteInfo.php
// Created: 2022-01-20
// Updated: 2023-09-11
namespace Index\Routing;
use InvalidArgumentException;
use UnexpectedValueException;
/**
* Represents a node in the router structure.
*
* @internal
*/
class RouteInfo {
private array $children = [];
private array $methods = [];
private array $middlewares = [];
private ?RouteInfo $dynamicChild = null;
private function getChild(string $path, ?string &$next = null): RouteInfo {
$parts = explode('/', $path, 2);
$name = '_' . $parts[0];
if(str_starts_with($parts[0], ':'))
$child = $this->dynamicChild ?? ($this->dynamicChild = new RouteInfo);
else
$child = $this->children[$name] ?? ($this->children[$name] = new RouteInfo);
$next = $parts[1] ?? '';
return $child;
}
/**
* @internal
*/
public function addMiddleware(string $path, callable $handler): void {
if($path === '') {
$this->middlewares[] = $handler;
return;
}
$this->getChild($path, $next)->addMiddleware($next, $handler);
}
/**
* @internal
*/
public function addMethod(string $method, string $path, callable $handler): void {
if($path === '') {
$this->methods[strtolower($method)] = $handler;
return;
}
$this->getChild($path, $next)->addMethod($method, $next, $handler);
}
/**
* @internal
*/
public function resolve(string $method, string $path, array $args = [], array $callables = []): RouteCallable {
if($path === '') {
$method = strtolower($method);
$handlers = [];
if(isset($this->methods[$method])) {
$handlers[] = $this->methods[$method];
} else {
if($method !== 'options') {
if(empty($this->methods))
throw new RoutePathNotFoundException;
throw new RouteMethodNotSupportedException;
}
}
return new RouteCallable(
array_merge($callables, $this->middlewares, $handlers),
$args
);
}
$parts = explode('/', $path, 2);
$name = $parts[0];
$mName = '_' . $name;
foreach($this->children as $cName => $cObj)
if($cName === $mName) {
$child = $cObj;
break;
}
if(!isset($child)) {
$args[] = $name;
$child = $this->dynamicChild;
}
if($child === null)
throw new RoutePathNotFoundException;
return $child->resolve(
$method,
$parts[1] ?? '',
$args,
array_merge($callables, $this->middlewares)
);
}
}

View file

@ -1,13 +0,0 @@
<?php
// RouteMethodNotSupportedException.php
// Created: 2022-01-20
// Updated: 2022-01-20
namespace Index\Routing;
use RuntimeException;
/**
* Exception thrown when an unsupported request method is invoked. (HTTP 405)
*/
class RouteMethodNotSupportedException extends RuntimeException {}

View file

@ -1,13 +0,0 @@
<?php
// RoutePathNotFoundException.php
// Created: 2022-01-20
// Updated: 2022-01-20
namespace Index\Routing;
use RuntimeException;
/**
* Exception thrown when a path does not resolve to a route. (HTTP 404)
*/
class RoutePathNotFoundException extends RuntimeException {}

View file

@ -1,131 +0,0 @@
<?php
// Router.php
// Created: 2022-01-18
// Updated: 2023-09-11
namespace Index\Routing;
use InvalidArgumentException;
/**
* Provides an application router.
*/
class Router implements IRouter {
private RouteInfo $route;
/**
* Constructs a Router object.
*/
public function __construct() {
$this->route = new RouteInfo;
}
/**
* Resolves a request method and uri.
*
* @param string $method Request method.
* @param string $path Request path.
* @param array $args Arguments to be passed on to the callables.
* @return RouteCallable A collection of callables representing the route.
*/
public function resolve(string $method, string $path, array $args = []): RouteCallable {
$method = strtolower($method);
if($method === 'head')
$method = 'get';
return $this->route->resolve($method, $path, $args);
}
/**
* Apply middleware functions to a path.
*
* @param string $path Path to apply the middleware to.
* @param callable $handler Middleware function.
*/
public function use(string $path, callable $handler): void {
$this->route->addMiddleware($path, $handler);
}
/**
* Adds a new route.
*
* @param string $method Request method.
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function add(string $method, string $path, callable $handler): void {
if(empty($method))
throw new InvalidArgumentException('$method may not be empty.');
$this->route->addMethod($method, $path, $handler);
}
/**
* Adds a new GET route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function get(string $path, callable $handler): void {
$this->add('get', $path, $handler);
}
/**
* Adds a new POST route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function post(string $path, callable $handler): void {
$this->add('post', $path, $handler);
}
/**
* Adds a new DELETE route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function delete(string $path, callable $handler): void {
$this->add('delete', $path, $handler);
}
/**
* Adds a new PATCH route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function patch(string $path, callable $handler): void {
$this->add('patch', $path, $handler);
}
/**
* Adds a new PUT route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function put(string $path, callable $handler): void {
$this->add('put', $path, $handler);
}
/**
* Adds a new OPTIONS route.
*
* @param string $path Request path.
* @param callable $handler Request handler.
*/
public function options(string $path, callable $handler): void {
$this->add('options', $path, $handler);
}
/**
* Registers routes in an IRouteHandler implementation.
*
* @param IRouteHandler $handler Routes handler.
*/
public function register(IRouteHandler $handler): void {
$handler->registerRoutes($this);
}
}

View file

@ -1,144 +1,115 @@
<?php
// RouterTest.php
// Created: 2022-01-20
// Updated: 2023-09-11
// Updated: 2024-03-30
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use Index\Routing\Route;
use Index\Routing\RouteHandler;
use Index\Routing\Router;
use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,HttpPut,HttpRouter,RouteHandler};
/**
* @covers Router
* This test isn't super representative of the current functionality
* it mostly just does the same tests that were done against the previous implementation
*
* @covers HttpRouter
*/
final class RouterTest extends TestCase {
public function testRouter(): void {
$router1 = new Router;
$router1 = new HttpRouter;
$router1->get('/', function() {
return 'get';
});
$router1->post('/', function() {
return 'post';
});
$router1->delete('/', function() {
return 'delete';
});
$router1->patch('/', function() {
return 'patch';
});
$router1->put('/', function() {
return 'put';
});
$router1->add('custom', '/', function() {
return 'wacky';
});
$router1->get('/', fn() => 'get');
$router1->post('/', fn() => 'post');
$router1->delete('/', fn() => 'delete');
$router1->patch('/', fn() => 'patch');
$router1->put('/', fn() => 'put');
$router1->add('custom', '/', fn() => 'wacky');
$this->assertEquals(['get'], $router1->resolve('GET', '/')->runAll());
$this->assertEquals(['wacky'], $router1->resolve('CUSTOM', '/')->runAll());
$this->assertEquals('get', $router1->resolve('GET', '/')->dispatch([]));
$this->assertEquals('wacky', $router1->resolve('CUSTOM', '/')->dispatch([]));
$router1->use('/', function() {
return 'warioware';
});
$router1->use('/deep', function() {
return 'deep';
});
$router1->use('/', function() { /* this one intentionally does nothing */ });
$this->assertEquals(['warioware', 'post'], $router1->resolve('POST', '/')->runAll());
// registration order should matter
$router1->use('/deep', fn() => 'deep');
$router1->use('/user/:user/below', function(string $user) {
return 'warioware below ' . $user;
});
$postRoot = $router1->resolve('POST', '/');
$this->assertNull($postRoot->runMiddleware([]));
$this->assertEquals('post', $postRoot->dispatch([]));
$router1->get('/user/static', function() {
return 'the static one';
});
$router1->get('/user/static/below', function() {
return 'below the static one';
});
$router1->get('/user/:user', function(string $user) {
return $user;
});
$router1->get('/user/:user/below', function(string $user) {
return 'below ' . $user;
});
$this->assertEquals('deep', $router1->resolve('GET', '/deep/nothing')->runMiddleware([]));
$this->assertEquals(
['warioware', 'below the static one'],
$router1->resolve('GET', '/user/static/below')->runAll()
);
$this->assertEquals(
['warioware', 'warioware below flashwave', 'below flashwave'],
$router1->resolve('GET', '/user/flashwave/below')->runAll()
);
$router1->use('/user/([A-Za-z0-9]+)/below', fn(string $user) => 'warioware below ' . $user);
$router2 = new Router;
$router2->use('/', function() {
return 'meow';
});
$router2->get('/rules', function() {
return 'rules page';
});
$router2->get('/contact', function() {
return 'contact page';
});
$router2->get('/25252', function() {
return 'numeric test';
});
$router1->get('/user/static', fn() => 'the static one');
$router1->get('/user/static/below', fn() => 'below the static one');
$router1->get('/user/([A-Za-z0-9]+)', fn(string $user) => $user);
$router1->get('/user/([A-Za-z0-9]+)/below', fn(string $user) => 'below ' . $user);
$this->assertEquals(['meow', 'rules page'], $router2->resolve('GET', '/rules')->runAll());
$this->assertEquals(['meow', 'numeric test'], $router2->resolve('GET', '/25252')->runAll());
$this->assertEquals('below the static one', $router1->resolve('GET', '/user/static/below')->dispatch([]));
$router3 = new Router;
$router3->get('/static', function() {
return 'wrong';
});
$router3->get('/static/0', function() {
return 'correct';
});
$router3->get('/variable', function() {
return 'wrong';
});
$router3->get('/variable/:num', function(string $num) {
return $num === '0' ? 'correct' : 'VERY wrong';
});
$getWariowareBelowFlashwave = $router1->resolve('GET', '/user/flashwave/below');
$this->assertEquals('warioware below flashwave', $getWariowareBelowFlashwave->runMiddleware([]));
$this->assertEquals('below flashwave', $getWariowareBelowFlashwave->dispatch([]));
$this->assertEquals('correct', $router3->resolve('GET', '/static/0')->run());
$this->assertEquals('correct', $router3->resolve('GET', '/variable/0')->run());
$router2 = new HttpRouter;
$router2->use('/', fn() => 'meow');
$router2->get('/rules', fn() => 'rules page');
$router2->get('/contact', fn() => 'contact page');
$router2->get('/25252', fn() => 'numeric test');
$getRules = $router2->resolve('GET', '/rules');
$this->assertEquals('meow', $getRules->runMiddleware([]));
$this->assertEquals('rules page', $getRules->dispatch([]));
$get25252 = $router2->resolve('GET', '/25252');
$this->assertEquals('meow', $get25252->runMiddleware([]));
$this->assertEquals('numeric test', $get25252->dispatch([]));
$router3 = $router1->scopeTo('/scoped');
$router3->get('/static', fn() => 'wrong');
$router1->get('/scoped/static/0', fn() => 'correct');
$router3->get('/variable', fn() => 'wrong');
$router3->get('/variable/([0-9]+)', fn(string $num) => $num === '0' ? 'correct' : 'VERY wrong');
$router3->get('/variable/([a-z]+)', fn(string $char) => $char === 'a' ? 'correct' : 'VERY wrong');
$this->assertEquals('correct', $router3->resolve('GET', '/static/0')->dispatch([]));
$this->assertEquals('correct', $router1->resolve('GET', '/scoped/variable/0')->dispatch([]));
$this->assertEquals('correct', $router3->resolve('GET', '/variable/a')->dispatch([]));
}
public function testAttribute(): void {
$router = new Router;
$router = new HttpRouter;
$handler = new class extends RouteHandler {
#[Route('GET', '/')]
#[HttpGet('/')]
public function getIndex() {
return 'index';
}
#[Route('POST', '/avatar')]
#[HttpPost('/avatar')]
public function postAvatar() {
return 'avatar';
}
#[Route('PUT', '/static')]
#[HttpPut('/static')]
public static function putStatic() {
return 'static';
}
#[Route('GET', '/meow')]
#[Route('POST', '/meow')]
#[HttpGet('/meow')]
#[HttpPost('/meow')]
public function multiple() {
return 'meow';
}
#[Route('/mw')]
#[HttpMiddleware('/mw')]
public function useMw() {
return 'this intercepts';
}
#[Route('GET', '/mw')]
#[HttpGet('/mw')]
public function getMw() {
return 'this is intercepted';
}
@ -149,11 +120,31 @@ final class RouterTest extends TestCase {
};
$router->register($handler);
$this->assertEquals('index', $router->resolve('GET', '/')->run());
$this->assertEquals('avatar', $router->resolve('POST', '/avatar')->run());
$this->assertEquals('static', $router->resolve('PUT', '/static')->run());
$this->assertEquals('meow', $router->resolve('GET', '/meow')->run());
$this->assertEquals('meow', $router->resolve('POST', '/meow')->run());
$this->assertEquals('this intercepts', $router->resolve('GET', '/mw')->run());
$this->assertFalse($router->resolve('GET', '/soap')->hasHandler());
$patchAvatar = $router->resolve('PATCH', '/avatar');
$this->assertFalse($patchAvatar->hasHandler());
$this->assertTrue($patchAvatar->hasOtherMethods());
$this->assertEquals(['POST'], $patchAvatar->getSupportedMethods());
$this->assertEquals('index', $router->resolve('GET', '/')->dispatch([]));
$this->assertEquals('avatar', $router->resolve('POST', '/avatar')->dispatch([]));
$this->assertEquals('static', $router->resolve('PUT', '/static')->dispatch([]));
$this->assertEquals('meow', $router->resolve('GET', '/meow')->dispatch([]));
$this->assertEquals('meow', $router->resolve('POST', '/meow')->dispatch([]));
// stopping on middleware is the dispatcher's job
$getMw = $router->resolve('GET', '/mw');
$this->assertEquals('this intercepts', $getMw->runMiddleware([]));
$this->assertEquals('this is intercepted', $getMw->dispatch([]));
$scoped = $router->scopeTo('/scoped');
$scoped->register($handler);
$this->assertEquals('index', $scoped->resolve('GET', '/')->dispatch([]));
$this->assertEquals('avatar', $router->resolve('POST', '/scoped/avatar')->dispatch([]));
$this->assertEquals('static', $scoped->resolve('PUT', '/static')->dispatch([]));
$this->assertEquals('meow', $router->resolve('GET', '/scoped/meow')->dispatch([]));
$this->assertEquals('meow', $scoped->resolve('POST', '/meow')->dispatch([]));
}
}