Added processors.
This commit is contained in:
parent
69e22beb08
commit
4f092a3259
25 changed files with 471 additions and 66 deletions
VERSION
src/Http/Routing
Filters
HandlerAttribute.phpHandlerContext.phpProcessors
After.phpBefore.phpPostprocessor.phpPreprocessor.phpProcessAttribute.phpProcessorAttribute.phpProcessorInfo.phpProcessorTarget.php
Router.phpRoutes
UriMatchers
tests
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.2503.72025
|
||||
0.2503.72337
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
// Created: 2024-03-28
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing;
|
||||
namespace Index\Http\Routing\Filters;
|
||||
|
||||
use Attribute;
|
||||
use Closure;
|
||||
use Index\Http\Routing\HandlerAttribute;
|
||||
|
||||
/**
|
||||
* Provides an attribute for marking methods in a class as a filter.
|
|
@ -3,9 +3,10 @@
|
|||
// Created: 2025-03-07
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing;
|
||||
namespace Index\Http\Routing\Filters;
|
||||
|
||||
use Closure;
|
||||
use Index\Http\Routing\UriMatchers\{PatternPathUriMatcher,PrefixPathUriMatcher,UriMatcher};
|
||||
|
||||
/**
|
||||
* Information of a filter.
|
|
@ -3,7 +3,7 @@
|
|||
// Created: 2024-03-28
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing;
|
||||
namespace Index\Http\Routing\Filters;
|
||||
|
||||
use Attribute;
|
||||
use Closure;
|
|
@ -3,7 +3,7 @@
|
|||
// Created: 2024-03-28
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing;
|
||||
namespace Index\Http\Routing\Filters;
|
||||
|
||||
use Attribute;
|
||||
use Closure;
|
|
@ -7,6 +7,7 @@ namespace Index\Http\Routing;
|
|||
|
||||
use ReflectionAttribute;
|
||||
use ReflectionObject;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Provides base for attributes that mark methods in a class as handlers.
|
||||
|
@ -19,20 +20,24 @@ abstract class HandlerAttribute {
|
|||
* @param RouteHandler $handler Handler instance.
|
||||
*/
|
||||
public static function register(Router $router, RouteHandler $handler): void {
|
||||
$objectInfo = new ReflectionObject($handler);
|
||||
$methodInfos = $objectInfo->getMethods();
|
||||
$object = new ReflectionObject($handler);
|
||||
$methods = $object->getMethods();
|
||||
|
||||
foreach($methodInfos as $methodInfo) {
|
||||
$attrInfos = $methodInfo->getAttributes(self::class, ReflectionAttribute::IS_INSTANCEOF);
|
||||
foreach($methods as $method) {
|
||||
$attrs = $method->getAttributes(self::class, ReflectionAttribute::IS_INSTANCEOF);
|
||||
|
||||
foreach($attrInfos as $attrInfo) {
|
||||
$handlerInfo = $attrInfo->newInstance();
|
||||
$closure = $methodInfo->getClosure($methodInfo->isStatic() ? null : $handler);
|
||||
foreach($attrs as $attr) {
|
||||
$info = $attr->newInstance();
|
||||
$closure = $method->getClosure($method->isStatic() ? null : $handler);
|
||||
|
||||
if($handlerInfo instanceof RouteAttribute)
|
||||
$router->route($handlerInfo->createInstance($closure));
|
||||
elseif($handlerInfo instanceof FilterAttribute)
|
||||
$router->filter($handlerInfo->createInstance($closure));
|
||||
if($info instanceof Routes\RouteAttribute)
|
||||
$router->route($info->createInstance($closure));
|
||||
elseif($info instanceof Filters\FilterAttribute)
|
||||
$router->filter($info->createInstance($closure));
|
||||
elseif($info instanceof Processors\ProcessorAttribute)
|
||||
$router->processor($info->createInstance($closure));
|
||||
else
|
||||
throw new RuntimeException('unsupported HandlerAttribute encountered');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,11 @@ class HandlerContext {
|
|||
*/
|
||||
public private(set) bool $stopped = false;
|
||||
|
||||
/**
|
||||
* Value returned by the route handler.
|
||||
*/
|
||||
public mixed $result = null;
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request Request that is being handled.
|
||||
*/
|
||||
|
|
14
src/Http/Routing/Processors/After.php
Normal file
14
src/Http/Routing/Processors/After.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
// After.php
|
||||
// Created: 2025-03-07
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing\Processors;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* Requires a preprocessor on a route handler.
|
||||
*/
|
||||
#[Attribute(ProcessAttribute::FLAGS)]
|
||||
class After extends ProcessAttribute {}
|
14
src/Http/Routing/Processors/Before.php
Normal file
14
src/Http/Routing/Processors/Before.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
// Before.php
|
||||
// Created: 2025-03-07
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing\Processors;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* Requires a postprocessor on a route handler.
|
||||
*/
|
||||
#[Attribute(ProcessAttribute::FLAGS)]
|
||||
class Before extends ProcessAttribute {}
|
26
src/Http/Routing/Processors/Postprocessor.php
Normal file
26
src/Http/Routing/Processors/Postprocessor.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
// Postprocessor.php
|
||||
// Created: 2025-03-07
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing\Processors;
|
||||
|
||||
use Attribute;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Marks a method as a post-processor.
|
||||
*/
|
||||
#[Attribute(ProcessorAttribute::FLAGS)]
|
||||
class Postprocessor extends ProcessorAttribute {
|
||||
/**
|
||||
* @param string $name Name of the post-processor.
|
||||
*/
|
||||
public function __construct(
|
||||
private string $name,
|
||||
) {}
|
||||
|
||||
public function createInstance(Closure $handler): ProcessorInfo {
|
||||
return ProcessorInfo::post($this->name, $handler);
|
||||
}
|
||||
}
|
26
src/Http/Routing/Processors/Preprocessor.php
Normal file
26
src/Http/Routing/Processors/Preprocessor.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
// Preprocessor.php
|
||||
// Created: 2025-03-07
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing\Processors;
|
||||
|
||||
use Attribute;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Marks a method as a pre-processor.
|
||||
*/
|
||||
#[Attribute(ProcessorAttribute::FLAGS)]
|
||||
class Preprocessor extends ProcessorAttribute {
|
||||
/**
|
||||
* @param string $name Name of the pre-processor.
|
||||
*/
|
||||
public function __construct(
|
||||
private string $name,
|
||||
) {}
|
||||
|
||||
public function createInstance(Closure $handler): ProcessorInfo {
|
||||
return ProcessorInfo::pre($this->name, $handler);
|
||||
}
|
||||
}
|
77
src/Http/Routing/Processors/ProcessAttribute.php
Normal file
77
src/Http/Routing/Processors/ProcessAttribute.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
// ProcessAttribute.php
|
||||
// Created: 2025-03-07
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing\Processors;
|
||||
|
||||
use Attribute;
|
||||
use Closure;
|
||||
use ReflectionAttribute;
|
||||
use ReflectionFunction;
|
||||
use ReflectionFunctionAbstract;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Provides an attribute for requiring processors on route handler.
|
||||
*/
|
||||
abstract class ProcessAttribute {
|
||||
/**
|
||||
* Flags to set for the Attribute attribute.
|
||||
*/
|
||||
public const int FLAGS = Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE;
|
||||
|
||||
/**
|
||||
* Arguments to pass to the handler.
|
||||
*
|
||||
* @var array<string|int, mixed>
|
||||
*/
|
||||
public private(set) array $args;
|
||||
|
||||
/**
|
||||
* @param string $name Name of the processor to require.
|
||||
* @param mixed ...$args Arguments to pass to the handler.
|
||||
*/
|
||||
public function __construct(
|
||||
public private(set) string $name,
|
||||
mixed ...$args
|
||||
) {
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads attributes from methods in a RouteHandler instance and registers them to a given Router instance.
|
||||
*
|
||||
* @param ReflectionFunctionAbstract|Closure $function Router instance.
|
||||
* @return object{after: array<string, array<string|int, mixed>>, before: array<string, array<string|int, mixed>>}
|
||||
*/
|
||||
public static function read(ReflectionFunctionAbstract|Closure $function): object {
|
||||
if($function instanceof Closure)
|
||||
$function = new ReflectionFunction($function);
|
||||
|
||||
// i HATE how this is considered more acceptable than a stdClass instance
|
||||
$process = (object)[
|
||||
'after' => [],
|
||||
'before' => [],
|
||||
];
|
||||
|
||||
$attrs = $function->getAttributes(self::class, ReflectionAttribute::IS_INSTANCEOF);
|
||||
foreach($attrs as $attr) {
|
||||
$handler = $attr->newInstance();
|
||||
if($handler instanceof After) {
|
||||
if(array_key_exists($handler->name, $process->after))
|
||||
throw new RuntimeException(sprintf('postprocessor "%s" already required', $handler->name));
|
||||
|
||||
$process->after[$handler->name] = $handler->args;
|
||||
} elseif($handler instanceof Before) {
|
||||
if(array_key_exists($handler->name, $process->before))
|
||||
throw new RuntimeException(sprintf('postprocessor "%s" already required', $handler->name));
|
||||
|
||||
$process->before[$handler->name] = $handler->args;
|
||||
} else
|
||||
throw new RuntimeException('unsupported ProcessAttribute encountered');
|
||||
}
|
||||
|
||||
return $process;
|
||||
}
|
||||
}
|
28
src/Http/Routing/Processors/ProcessorAttribute.php
Normal file
28
src/Http/Routing/Processors/ProcessorAttribute.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
// ProcessorAttribute.php
|
||||
// Created: 2025-03-07
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing\Processors;
|
||||
|
||||
use Attribute;
|
||||
use Closure;
|
||||
use Index\Http\Routing\HandlerAttribute;
|
||||
|
||||
/**
|
||||
* Provides an attribute for marking methods in a class as a processor.
|
||||
*/
|
||||
abstract class ProcessorAttribute extends HandlerAttribute {
|
||||
/**
|
||||
* Flags to set for the Attribute attribute.
|
||||
*/
|
||||
public const int FLAGS = Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD;
|
||||
|
||||
/**
|
||||
* Creates a ProcessorInfo instance.
|
||||
*
|
||||
* @param Closure $handler Handler method.
|
||||
* @return ProcessorInfo
|
||||
*/
|
||||
abstract public function createInstance(Closure $handler): ProcessorInfo;
|
||||
}
|
46
src/Http/Routing/Processors/ProcessorInfo.php
Normal file
46
src/Http/Routing/Processors/ProcessorInfo.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
// ProcessorInfo.php
|
||||
// Created: 2025-03-07
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing\Processors;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Information of a processor.
|
||||
*/
|
||||
class ProcessorInfo {
|
||||
/**
|
||||
* @param string $name Name of the processor.
|
||||
* @param ProcessorTarget $target Kind of processor.
|
||||
* @param Closure $handler Handler for this filter.
|
||||
*/
|
||||
public function __construct(
|
||||
public private(set) string $name,
|
||||
public private(set) ProcessorTarget $target,
|
||||
public private(set) Closure $handler,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a pre-processor ProcessorInfo instance.
|
||||
*
|
||||
* @param string $name Name of the processor.
|
||||
* @param Closure $handler Handler for this processor.
|
||||
* @return ProcessorInfo
|
||||
*/
|
||||
public static function pre(string $name, Closure $handler): ProcessorInfo {
|
||||
return new ProcessorInfo($name, ProcessorTarget::Pre, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a post-processor ProcessorInfo instance.
|
||||
*
|
||||
* @param string $name Name of the processor.
|
||||
* @param Closure $handler Handler for this processor.
|
||||
* @return ProcessorInfo
|
||||
*/
|
||||
public static function post(string $name, Closure $handler): ProcessorInfo {
|
||||
return new ProcessorInfo($name, ProcessorTarget::Post, $handler);
|
||||
}
|
||||
}
|
21
src/Http/Routing/Processors/ProcessorTarget.php
Normal file
21
src/Http/Routing/Processors/ProcessorTarget.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
// ProcessorTarget.php
|
||||
// Created: 2025-03-07
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing\Processors;
|
||||
|
||||
/**
|
||||
* Indicates the kind of processor.
|
||||
*/
|
||||
enum ProcessorTarget {
|
||||
/**
|
||||
* Processor is a preprocessor.
|
||||
*/
|
||||
case Pre;
|
||||
|
||||
/**
|
||||
* Processor is a postprocessor.
|
||||
*/
|
||||
case Post;
|
||||
}
|
|
@ -5,12 +5,16 @@
|
|||
|
||||
namespace Index\Http\Routing;
|
||||
|
||||
use Stringable;
|
||||
use InvalidArgumentException;
|
||||
use JsonSerializable;
|
||||
use RuntimeException;
|
||||
use Stringable;
|
||||
use Index\Bencode\{Bencode,BencodeSerializable};
|
||||
use Index\Http\{HttpRequest,HttpStream};
|
||||
use Index\Http\Routing\ErrorHandling\{ErrorHandler,HtmlErrorHandler,PlainErrorHandler};
|
||||
use Index\Http\Routing\Filters\FilterInfo;
|
||||
use Index\Http\Routing\Processors\{ProcessorInfo,ProcessorTarget};
|
||||
use Index\Http\Routing\Routes\RouteInfo;
|
||||
use Psr\Http\Message\{ResponseInterface,ServerRequestInterface};
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
|
@ -34,39 +38,19 @@ class Router implements RequestHandlerInterface {
|
|||
/** @var RouteInfo[] */
|
||||
private array $routes = [];
|
||||
|
||||
private string $charSetValue;
|
||||
/** @var array<string, ProcessorInfo> */
|
||||
private array $preprocs = [];
|
||||
|
||||
/** @var array<string, ProcessorInfo> */
|
||||
private array $postprocs = [];
|
||||
|
||||
/**
|
||||
* @param string $charSet Default character set to specify when none is present.
|
||||
* @param ErrorHandler|'html'|'plain' $errorHandler Error handling to use for error responses with an empty body. 'html' for the default HTML implementation, 'plain' for the plaintext implementation.
|
||||
*/
|
||||
public function __construct(
|
||||
string $charSet = '',
|
||||
ErrorHandler|string $errorHandler = 'html'
|
||||
) {
|
||||
$this->charSetValue = $charSet;
|
||||
public function __construct(ErrorHandler|string $errorHandler = 'html') {
|
||||
$this->setErrorHandler($errorHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the normalised name of the preferred character set.
|
||||
*
|
||||
* @return string Normalised character set name.
|
||||
*/
|
||||
public string $charSet {
|
||||
get {
|
||||
if($this->charSetValue === '') {
|
||||
$charSet = mb_preferred_mime_name(mb_internal_encoding());
|
||||
if($charSet === false)
|
||||
$charSet = 'UTF-8';
|
||||
|
||||
return strtolower($charSet);
|
||||
}
|
||||
|
||||
return $this->charSetValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error handler instance.
|
||||
*/
|
||||
|
@ -120,6 +104,54 @@ class Router implements RequestHandlerInterface {
|
|||
$this->routes[] = $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a processor.
|
||||
*
|
||||
* @param ProcessorInfo $processor ProcessorInfo instance to register.
|
||||
* @throws InvalidArgumentException if the processor target is not supported.
|
||||
* @throws RuntimeException if the processor has already been registered.
|
||||
*/
|
||||
public function processor(ProcessorInfo $processor): void {
|
||||
if($processor->target === ProcessorTarget::Pre)
|
||||
$procs = &$this->preprocs;
|
||||
elseif($processor->target === ProcessorTarget::Post)
|
||||
$procs = &$this->postprocs;
|
||||
else
|
||||
throw new InvalidArgumentException('$processor target is not supported');
|
||||
|
||||
if(array_key_exists($processor->name, $procs))
|
||||
throw new RuntimeException('$processor has already been registered');
|
||||
|
||||
$procs[$processor->name] = $processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a pre-processor.
|
||||
*
|
||||
* @param ProcessorInfo $processor ProcessorInfo instance to register.
|
||||
* @throws InvalidArgumentException if the processor target is not a preprocessor.
|
||||
* @throws RuntimeException if the processor has already been registered.
|
||||
*/
|
||||
public function preprocessor(ProcessorInfo $processor): void {
|
||||
if($processor->target !== ProcessorTarget::Pre)
|
||||
throw new InvalidArgumentException('$processor is not a preprocessor');
|
||||
|
||||
$this->processor($processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a post-processor.
|
||||
*
|
||||
* @param ProcessorInfo $processor ProcessorInfo instance to register.
|
||||
* @throws InvalidArgumentException if the processor target is not a postprocessor.
|
||||
*/
|
||||
public function postprocessor(ProcessorInfo $processor): void {
|
||||
if($processor->target !== ProcessorTarget::Post)
|
||||
throw new InvalidArgumentException('$processor is not a postprocessor');
|
||||
|
||||
$this->processor($processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a route handler class instance.
|
||||
*
|
||||
|
@ -176,11 +208,11 @@ class Router implements RequestHandlerInterface {
|
|||
|
||||
if(!$context->response->hasContentType()) {
|
||||
if(mb_stripos($result, '<!doctype html') === 0)
|
||||
$context->response->setTypeHtml($this->charSet);
|
||||
$context->response->setTypeHtml();
|
||||
elseif(mb_stripos($result, '<?xml') === 0)
|
||||
$context->response->setTypeXml($this->charSet);
|
||||
$context->response->setTypeXml();
|
||||
else
|
||||
$context->response->setTypePlain($this->charSet);
|
||||
$context->response->setTypePlain();
|
||||
}
|
||||
|
||||
$context->response->body = HttpStream::createStream($result);
|
||||
|
@ -215,7 +247,7 @@ class Router implements RequestHandlerInterface {
|
|||
foreach($this->routes as $routeInfo) {
|
||||
$match = $routeInfo->matcher->match($context->request->uri);
|
||||
if($match !== false)
|
||||
$methods[$routeInfo->method] = [
|
||||
$methods[$routeInfo->method] = (object)[
|
||||
'info' => $routeInfo,
|
||||
'matches' => is_array($match) ? $match : null,
|
||||
];
|
||||
|
@ -256,12 +288,32 @@ class Router implements RequestHandlerInterface {
|
|||
}
|
||||
|
||||
$route = $methods[$context->request->method];
|
||||
$result = $context->call($route['info']->handler, $route['matches']);
|
||||
$route->info->readProcessors();
|
||||
|
||||
// wait for the pipeline stuff
|
||||
//if($result !== null || $context->stopped)
|
||||
foreach($route->info->preprocs as $name => $args) {
|
||||
if(!array_key_exists($name, $this->preprocs))
|
||||
throw new RuntimeException(sprintf('preprocessor "%s" was not found', $name));
|
||||
|
||||
$this->defaultResultHandler($context, $result);
|
||||
$result = $context->call($this->preprocs[$name]->handler, $args);
|
||||
if($result !== null || $context->stopped) {
|
||||
$this->defaultResultHandler($context, $result);
|
||||
return $context->response->toResponse();
|
||||
}
|
||||
}
|
||||
|
||||
$context->result = $context->call($route->info->handler, $route->matches);
|
||||
|
||||
if(!$context->stopped)
|
||||
foreach($route->info->postprocs as $name => $args) {
|
||||
if(!array_key_exists($name, $this->postprocs))
|
||||
throw new RuntimeException(sprintf('postprocessor "%s" was not found', $name));
|
||||
|
||||
$result = $context->call($this->postprocs[$name]->handler, $args);
|
||||
if($result !== null || $context->stopped) // @phpstan-ignore booleanOr.rightAlwaysFalse
|
||||
break;
|
||||
}
|
||||
|
||||
$this->defaultResultHandler($context, $context->result);
|
||||
return $context->response->toResponse();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// Created: 2024-03-28
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing;
|
||||
namespace Index\Http\Routing\Routes;
|
||||
|
||||
use Attribute;
|
||||
use Closure;
|
|
@ -3,7 +3,7 @@
|
|||
// Created: 2024-03-28
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing;
|
||||
namespace Index\Http\Routing\Routes;
|
||||
|
||||
use Attribute;
|
||||
use Closure;
|
|
@ -3,10 +3,11 @@
|
|||
// Created: 2024-03-28
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing;
|
||||
namespace Index\Http\Routing\Routes;
|
||||
|
||||
use Attribute;
|
||||
use Closure;
|
||||
use Index\Http\Routing\HandlerAttribute;
|
||||
|
||||
/**
|
||||
* Provides an attribute for marking methods in a class as a route.
|
|
@ -3,10 +3,12 @@
|
|||
// Created: 2025-03-02
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing;
|
||||
namespace Index\Http\Routing\Routes;
|
||||
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
use Index\Http\Routing\Processors\ProcessAttribute;
|
||||
use Index\Http\Routing\UriMatchers\{ExactPathUriMatcher,PatternPathUriMatcher,UriMatcher};
|
||||
|
||||
/**
|
||||
* Information of a route.
|
||||
|
@ -31,6 +33,8 @@ class RouteInfo {
|
|||
*/
|
||||
public private(set) array $postprocs = [];
|
||||
|
||||
private bool $processorsRead = false;
|
||||
|
||||
/**
|
||||
* @param string $method HTTP method this route serves.
|
||||
* @param UriMatcher $matcher URI matcher to use.
|
||||
|
@ -78,6 +82,21 @@ class RouteInfo {
|
|||
$this->postprocs[$name] = $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads processor attributes and merges them with the already registered instances.
|
||||
*/
|
||||
public function readProcessors(): void {
|
||||
if($this->processorsRead)
|
||||
return;
|
||||
$this->processorsRead = true;
|
||||
|
||||
$info = ProcessAttribute::read($this->handler);
|
||||
foreach($info->after as $name => $args)
|
||||
$this->after($name, $args);
|
||||
foreach($info->before as $name => $args)
|
||||
$this->before($name, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates RouteInfo instance using ExactPathUriMatcher.
|
||||
*
|
|
@ -3,7 +3,7 @@
|
|||
// Created: 2025-03-07
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing;
|
||||
namespace Index\Http\Routing\UriMatchers;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
// Created: 2025-03-07
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing;
|
||||
namespace Index\Http\Routing\UriMatchers;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
// Created: 2025-03-07
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing;
|
||||
namespace Index\Http\Routing\UriMatchers;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
// Created: 2025-03-07
|
||||
// Updated: 2025-03-07
|
||||
|
||||
namespace Index\Http\Routing;
|
||||
namespace Index\Http\Routing\UriMatchers;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
|
@ -7,13 +7,11 @@ declare(strict_types=1);
|
|||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use Index\Http\{HttpHeaders,HttpResponseBuilder,HttpRequest,HttpUri,NullStream};
|
||||
use Index\Http\Routing\{
|
||||
ExactRoute,
|
||||
FilterInfo,
|
||||
PatternRoute,PrefixFilter,
|
||||
RouteInfo,RouteHandler,RouteHandlerCommon,Router
|
||||
};
|
||||
use Index\Http\{HttpHeaders,HttpResponseBuilder,HttpRequest,HttpStream,HttpUri,NullStream};
|
||||
use Index\Http\Routing\{HandlerContext,RouteHandler,RouteHandlerCommon,Router};
|
||||
use Index\Http\Routing\Filters\{FilterInfo,PrefixFilter};
|
||||
use Index\Http\Routing\Processors\{After,Before,Postprocessor,Preprocessor,ProcessorInfo};
|
||||
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute,RouteInfo};
|
||||
|
||||
/**
|
||||
* This test isn't super representative of the current functionality
|
||||
|
@ -158,4 +156,75 @@ final class RouterTest extends TestCase {
|
|||
$this->assertEquals('No Content', $response->getReasonPhrase());
|
||||
$this->assertEquals('GET, HEAD, OPTIONS, PATCH, POST', $response->getHeaderLine('Allow'));
|
||||
}
|
||||
|
||||
public function testProcessors(): void {
|
||||
$router = new Router;
|
||||
|
||||
$router->register(new class implements RouteHandler {
|
||||
use RouteHandlerCommon;
|
||||
|
||||
#[Preprocessor('base64-decode-body')]
|
||||
public function decodePre(HandlerContext $context, HttpRequest $request): void {
|
||||
$context->addArgument(base64_decode((string)$request->getBody()));
|
||||
}
|
||||
|
||||
#[Postprocessor('crash')]
|
||||
public function crashPost(): void {
|
||||
throw new RuntimeException('this shouldnt run!');
|
||||
}
|
||||
|
||||
#[Postprocessor('json-encode')]
|
||||
public function encodePost(HandlerContext $context, int $flags): void {
|
||||
$context->halt();
|
||||
$context->response->setTypeJson();
|
||||
$context->response->body = HttpStream::createStream(
|
||||
json_encode($context->result, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR | $flags)
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array{data: string} */
|
||||
#[ExactRoute('POST', '/test')]
|
||||
#[Before('base64-decode-body')]
|
||||
#[After('json-encode', flags: JSON_PRETTY_PRINT)]
|
||||
#[After('crash')]
|
||||
public function route(string $decoded): array {
|
||||
return ['data' => $decoded];
|
||||
}
|
||||
});
|
||||
|
||||
$router->preprocessor(ProcessorInfo::pre('intercept', function(HttpResponseBuilder $response) {
|
||||
$response->statusCode = 403;
|
||||
return 'it can work like a filter too';
|
||||
}));
|
||||
|
||||
$router->route(RouteInfo::exact(
|
||||
'PUT', '/alternate',
|
||||
#[Before('base64-decode-body')]
|
||||
#[After('json-encode', flags: JSON_PRETTY_PRINT)]
|
||||
function(HttpRequest $request, string $decoded): array {
|
||||
return ['path' => $request->uri->path, 'data' => $decoded];
|
||||
}
|
||||
));
|
||||
|
||||
$router->route(RouteInfo::exact(
|
||||
'GET', '/filtered',
|
||||
#[Before('intercept')]
|
||||
#[Before('base64-decode-body')]
|
||||
function(): string {
|
||||
return 'should not get here';
|
||||
}
|
||||
));
|
||||
|
||||
$response = $router->handle(new HttpRequest('1.1', [], HttpStream::createStream(base64_encode('mewow')), [], 'POST', HttpUri::createUri('/test'), [], []));
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals("{\n \"data\": \"mewow\"\n}", (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(new HttpRequest('1.1', [], HttpStream::createStream(base64_encode('soap')), [], 'PUT', HttpUri::createUri('/alternate'), [], []));
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals("{\n \"path\": \"/alternate\",\n \"data\": \"soap\"\n}", (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(new HttpRequest('1.1', [], NullStream::instance(), [], 'GET', HttpUri::createUri('/filtered'), [], []));
|
||||
$this->assertEquals(403, $response->getStatusCode());
|
||||
$this->assertEquals('it can work like a filter too', (string)$response->getBody());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue