Merge pull request #567 from flightphp/dispatcher-refactor

Improvements to Dispatcher class
pull/571/head
n0nag0n 10 months ago committed by GitHub
commit d1584a8aeb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

1
.gitignore vendored

@ -6,3 +6,4 @@ composer.lock
coverage/
*.sublime*
clover.xml
phpcs.xml

@ -64,7 +64,10 @@
"test-coverage:win": "del clover.xml && phpunit --coverage-html=coverage --coverage-clover=clover.xml && coverage-check clover.xml 100",
"lint": "phpstan --no-progress -cphpstan.neon",
"beautify": "phpcbf --standard=phpcs.xml",
"phpcs": "phpcs --standard=phpcs.xml -n"
"phpcs": "phpcs --standard=phpcs.xml -n",
"post-install-cmd": [
"php -r \"if (!file_exists('phpcs.xml')) copy('phpcs.xml.dist', 'phpcs.xml');\""
]
},
"suggest": {
"latte/latte": "Latte template engine",

@ -4,10 +4,12 @@ declare(strict_types=1);
namespace flight\core;
use Closure;
use Exception;
use flight\Engine;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;
use ReflectionFunction;
use Throwable;
use TypeError;
/**
@ -23,41 +25,54 @@ class Dispatcher
{
public const FILTER_BEFORE = 'before';
public const FILTER_AFTER = 'after';
private const FILTER_TYPES = [self::FILTER_BEFORE, self::FILTER_AFTER];
/** @var mixed $containerException Exception message if thrown by setting the container as a callable method */
protected $containerException = null;
/** Exception message if thrown by setting the container as a callable method. */
protected ?Throwable $containerException = null;
/** @var ?Engine $engine Engine instance */
/** @var ?Engine $engine Engine instance. */
protected ?Engine $engine = null;
/** @var array<string, Closure(): (void|mixed)> Mapped events. */
/** @var array<string, callable(): (void|mixed)> Mapped events. */
protected array $events = [];
/**
* Method filters.
*
* @var array<string, array<'before'|'after', array<int, Closure(array<int, mixed> &$params, mixed &$output): (void|false)>>>
* @var array<string, array<'before'|'after', array<int, callable(array<int, mixed> &$params, mixed &$output): (void|false)>>>
*/
protected array $filters = [];
/**
* This is a container for the dependency injection.
*
* @var callable|object|null
* @var null|ContainerInterface|(callable(string $classString, array<int, mixed> $params): (null|object))
*/
protected $containerHandler = null;
/**
* Sets the dependency injection container handler.
*
* @param callable|object $containerHandler Dependency injection container
* @param ContainerInterface|(callable(string $classString, array<int, mixed> $params): (null|object)) $containerHandler
* Dependency injection container.
*
* @return void
* @throws InvalidArgumentException If $containerHandler is not a `callable` or instance of `Psr\Container\ContainerInterface`.
*/
public function setContainerHandler($containerHandler): void
{
$this->containerHandler = $containerHandler;
$containerInterfaceNS = '\Psr\Container\ContainerInterface';
if (
is_a($containerHandler, $containerInterfaceNS)
|| is_callable($containerHandler)
) {
$this->containerHandler = $containerHandler;
return;
}
throw new InvalidArgumentException(
"\$containerHandler must be of type callable or instance $containerInterfaceNS"
);
}
public function setEngine(Engine $engine): void
@ -68,11 +83,11 @@ class Dispatcher
/**
* Dispatches an event.
*
* @param string $name Event name
* @param string $name Event name.
* @param array<int, mixed> $params Callback parameters.
*
* @return mixed Output of callback
* @throws Exception If event name isn't found or if event throws an `Exception`
* @throws Exception If event name isn't found or if event throws an `Exception`.
*/
public function run(string $name, array $params = [])
{
@ -110,7 +125,7 @@ class Dispatcher
$requestedMethod = $this->get($eventName);
if ($requestedMethod === null) {
throw new Exception("Event '{$eventName}' isn't found.");
throw new Exception("Event '$eventName' isn't found.");
}
return $this->execute($requestedMethod, $params);
@ -138,8 +153,8 @@ class Dispatcher
/**
* Assigns a callback to an event.
*
* @param string $name Event name
* @param Closure(): (void|mixed) $callback Callback function
* @param string $name Event name.
* @param callable(): (void|mixed) $callback Callback function.
*
* @return $this
*/
@ -153,9 +168,9 @@ class Dispatcher
/**
* Gets an assigned callback.
*
* @param string $name Event name
* @param string $name Event name.
*
* @return null|(Closure(): (void|mixed)) $callback Callback function
* @return null|(callable(): (void|mixed)) $callback Callback function.
*/
public function get(string $name): ?callable
{
@ -165,9 +180,9 @@ class Dispatcher
/**
* Checks if an event has been set.
*
* @param string $name Event name
* @param string $name Event name.
*
* @return bool Event status
* @return bool If event exists or doesn't exists.
*/
public function has(string $name): bool
{
@ -177,7 +192,7 @@ class Dispatcher
/**
* Clears an event. If no name is given, all events will be removed.
*
* @param ?string $name Event name
* @param ?string $name Event name.
*/
public function clear(?string $name = null): void
{
@ -188,27 +203,38 @@ class Dispatcher
return;
}
$this->events = [];
$this->filters = [];
$this->reset();
}
/**
* Hooks a callback to an event.
*
* @param string $name Event name
* @param 'before'|'after' $type Filter type
* @param Closure(array<int, mixed> &$params, string &$output): (void|false) $callback
* @param 'before'|'after' $type Filter type.
* @param callable(array<int, mixed> &$params, mixed &$output): (void|false)|callable(mixed &$output): (void|false) $callback
*
* @return $this
*/
public function hook(string $name, string $type, callable $callback): self
{
if (!in_array($type, self::FILTER_TYPES, true)) {
$noticeMessage = "Invalid filter type '$type', use " . join('|', self::FILTER_TYPES);
static $filterTypes = [self::FILTER_BEFORE, self::FILTER_AFTER];
if (!in_array($type, $filterTypes, true)) {
$noticeMessage = "Invalid filter type '$type', use " . join('|', $filterTypes);
trigger_error($noticeMessage, E_USER_NOTICE);
}
if ($type === self::FILTER_AFTER) {
$callbackInfo = new ReflectionFunction($callback);
$parametersNumber = $callbackInfo->getNumberOfParameters();
if ($parametersNumber === 1) {
/** @disregard &$params in after filters are deprecated. */
$callback = fn (array &$params, &$output) => $callback($output);
}
}
$this->filters[$name][$type][] = $callback;
return $this;
@ -217,10 +243,10 @@ class Dispatcher
/**
* Executes a chain of method filters.
*
* @param array<int, Closure(array<int, mixed> &$params, mixed &$output): (void|false)> $filters
* Chain of filters-
* @param array<int, mixed> $params Method parameters
* @param mixed $output Method output
* @param array<int, callable(array<int, mixed> &$params, mixed &$output): (void|false)> $filters
* Chain of filters.
* @param array<int, mixed> $params Method parameters.
* @param mixed $output Method output.
*
* @throws Exception If an event throws an `Exception` or if `$filters` contains an invalid filter.
*/
@ -242,16 +268,19 @@ class Dispatcher
/**
* Executes a callback function.
*
* @param callable-string|(Closure(): mixed)|array{class-string|object, string} $callback
* Callback function
* @param array<int, mixed> $params Function parameters
* @param callable-string|(callable(): mixed)|array{class-string|object, string} $callback
* Callback function.
* @param array<int, mixed> $params Function parameters.
*
* @return mixed Function results
* @return mixed Function results.
* @throws Exception If `$callback` also throws an `Exception`.
*/
public function execute($callback, array &$params = [])
{
if (is_string($callback) === true && (strpos($callback, '->') !== false || strpos($callback, '::') !== false)) {
if (
is_string($callback) === true
&& (strpos($callback, '->') !== false || strpos($callback, '::') !== false)
) {
$callback = $this->parseStringClassAndMethod($callback);
}
@ -263,28 +292,26 @@ class Dispatcher
*
* @param string $classAndMethod Class and method
*
* @return array{class-string|object, string} Class and method
* @return array{0: class-string|object, 1: string} Class and method
*/
public function parseStringClassAndMethod(string $classAndMethod): array
{
$class_parts = explode('->', $classAndMethod);
if (count($class_parts) === 1) {
$class_parts = explode('::', $class_parts[0]);
}
$classParts = explode('->', $classAndMethod);
$class = $class_parts[0];
$method = $class_parts[1];
if (count($classParts) === 1) {
$classParts = explode('::', $classParts[0]);
}
return [ $class, $method ];
return $classParts;
}
/**
* Calls a function.
*
* @param callable $func Name of function to call
* @param array<int, mixed> &$params Function parameters
* @param callable $func Name of function to call.
* @param array<int, mixed> &$params Function parameters.
*
* @return mixed Function results
* @return mixed Function results.
* @deprecated 3.7.0 Use invokeCallable instead
*/
public function callFunction(callable $func, array &$params = [])
@ -295,12 +322,12 @@ class Dispatcher
/**
* Invokes a method.
*
* @param array{class-string|object, string} $func Class method
* @param array<int, mixed> &$params Class method parameters
* @param array{0: class-string|object, 1: string} $func Class method.
* @param array<int, mixed> &$params Class method parameters.
*
* @return mixed Function results
* @return mixed Function results.
* @throws TypeError For nonexistent class name.
* @deprecated 3.7.0 Use invokeCallable instead
* @deprecated 3.7.0 Use invokeCallable instead.
*/
public function invokeMethod(array $func, array &$params = [])
{
@ -310,12 +337,12 @@ class Dispatcher
/**
* Invokes a callable (anonymous function or Class->method).
*
* @param array{class-string|object, string}|Callable $func Class method
* @param array<int, mixed> &$params Class method parameters
* @param array{0: class-string|object, 1: string}|callable $func Class method.
* @param array<int, mixed> &$params Class method parameters.
*
* @return mixed Function results
* @return mixed Function results.
* @throws TypeError For nonexistent class name.
* @throws InvalidArgumentException If the constructor requires parameters
* @throws InvalidArgumentException If the constructor requires parameters.
* @version 3.7.0
*/
public function invokeCallable($func, array &$params = [])
@ -323,56 +350,46 @@ class Dispatcher
// If this is a directly callable function, call it
if (is_array($func) === false) {
$this->verifyValidFunction($func);
return call_user_func_array($func, $params);
}
[$class, $method] = $func;
$resolvedClass = null;
// Only execute the container handler if it's not a Flight class
if (
$this->containerHandler !== null &&
(
(
is_object($class) === true &&
strpos(get_class($class), 'flight\\') === false
) ||
is_string($class) === true
)
) {
$containerHandler = $this->containerHandler;
$resolvedClass = $this->resolveContainerClass($containerHandler, $class, $params);
if ($resolvedClass !== null) {
$mustUseTheContainer = $this->containerHandler !== null && (
(is_object($class) === true && strpos(get_class($class), 'flight\\') === false)
|| is_string($class)
);
if ($mustUseTheContainer === true) {
$resolvedClass = $this->resolveContainerClass($class, $params);
if ($resolvedClass) {
$class = $resolvedClass;
}
}
$this->verifyValidClassCallable($class, $method, $resolvedClass);
$this->verifyValidClassCallable($class, $method, $resolvedClass ?? null);
// Class is a string, and method exists, create the object by hand and inject only the Engine
if (is_string($class) === true) {
if (is_string($class)) {
$class = new $class($this->engine);
}
return call_user_func_array([ $class, $method ], $params);
return call_user_func_array([$class, $method], $params);
}
/**
* Handles invalid callback types.
*
* @param callable-string|(Closure(): mixed)|array{class-string|object, string} $callback
* Callback function
* @param callable-string|(callable(): mixed)|array{0: class-string|object, 1: string} $callback
* Callback function.
*
* @throws InvalidArgumentException If `$callback` is an invalid type
* @throws InvalidArgumentException If `$callback` is an invalid type.
*/
protected function verifyValidFunction($callback): void
{
$isInvalidFunctionName = (
is_string($callback)
&& !function_exists($callback)
);
if ($isInvalidFunctionName) {
if (is_string($callback) && !function_exists($callback)) {
throw new InvalidArgumentException('Invalid callback specified.');
}
}
@ -381,84 +398,77 @@ class Dispatcher
/**
* Verifies if the provided class and method are valid callable.
*
* @param string|object $class The class name.
* @param class-string|object $class The class name.
* @param string $method The method name.
* @param object|null $resolvedClass The resolved class.
*
* @throws Exception If the class or method is not found.
*
* @return void
*/
protected function verifyValidClassCallable($class, $method, $resolvedClass): void
{
$final_exception = null;
$exception = null;
// Final check to make sure it's actually a class and a method, or throw an error
if (is_object($class) === false && class_exists($class) === false) {
$final_exception = new Exception("Class '$class' not found. Is it being correctly autoloaded with Flight::path()?");
$exception = new Exception("Class '$class' not found. Is it being correctly autoloaded with Flight::path()?");
// If this tried to resolve a class in a container and failed somehow, throw the exception
} elseif (isset($resolvedClass) === false && $this->containerException !== null) {
$final_exception = $this->containerException;
// If this tried to resolve a class in a container and failed somehow, throw the exception
} elseif (!$resolvedClass && $this->containerException !== null) {
$exception = $this->containerException;
// Class is there, but no method
// Class is there, but no method
} elseif (is_object($class) === true && method_exists($class, $method) === false) {
$final_exception = new Exception("Class found, but method '" . get_class($class) . "::$method' not found.");
$classNamespace = get_class($class);
$exception = new Exception("Class found, but method '$classNamespace::$method' not found.");
}
if ($final_exception !== null) {
if ($exception !== null) {
$this->fixOutputBuffering();
throw $final_exception;
throw $exception;
}
}
/**
* Resolves the container class.
*
* @param callable|object $container_handler Dependency injection container
* @param class-string $class Class name
* @param array<int, mixed> &$params Class constructor parameters
* @param class-string $class Class name.
* @param array<int, mixed> &$params Class constructor parameters.
*
* @return object Class object
* @return ?object Class object.
*/
protected function resolveContainerClass($container_handler, $class, array &$params)
protected function resolveContainerClass(string $class, array &$params)
{
$class_object = null;
// PSR-11
if (
is_object($container_handler) === true &&
method_exists($container_handler, 'has') === true &&
$container_handler->has($class)
is_a($this->containerHandler, '\Psr\Container\ContainerInterface')
&& $this->containerHandler->has($class)
) {
$class_object = call_user_func([$container_handler, 'get'], $class);
return $this->containerHandler->get($class);
}
// Just a callable where you configure the behavior (Dice, PHP-DI, etc.)
} elseif (is_callable($container_handler) === true) {
// This is to catch all the error that could be thrown by whatever container you are using
if (is_callable($this->containerHandler)) {
/* This is to catch all the error that could be thrown by whatever
container you are using */
try {
$class_object = call_user_func($container_handler, $class, $params);
} catch (Exception $e) {
// could not resolve a class for some reason
$class_object = null;
return ($this->containerHandler)($class, $params);
// could not resolve a class for some reason
} catch (Exception $exception) {
// If the container throws an exception, we need to catch it
// and store it somewhere. If we just let it throw itself, it
// doesn't properly close the output buffers and can cause other
// issues.
// This is thrown in the verifyValidClassCallable method
$this->containerException = $e;
// This is thrown in the verifyValidClassCallable method.
$this->containerException = $exception;
}
}
return $class_object;
return null;
}
/**
* Because this could throw an exception in the middle of an output buffer,
*
* @return void
*/
/** Because this could throw an exception in the middle of an output buffer, */
protected function fixOutputBuffering(): void
{
// Cause PHPUnit has 1 level of output buffering by default

@ -139,7 +139,7 @@ class Response
*/
public function status(?int $code = null)
{
if (null === $code) {
if ($code === null) {
return $this->status;
}
@ -279,19 +279,22 @@ class Response
*/
public function cache($expires): self
{
if (false === $expires) {
if ($expires === false) {
$this->headers['Expires'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
$this->headers['Cache-Control'] = [
'no-store, no-cache, must-revalidate',
'post-check=0, pre-check=0',
'max-age=0',
];
$this->headers['Pragma'] = 'no-cache';
} else {
$expires = \is_int($expires) ? $expires : strtotime($expires);
$this->headers['Expires'] = gmdate('D, d M Y H:i:s', $expires) . ' GMT';
$this->headers['Cache-Control'] = 'max-age=' . ($expires - time());
if (isset($this->headers['Pragma']) && 'no-cache' == $this->headers['Pragma']) {
if (isset($this->headers['Pragma']) && $this->headers['Pragma'] === 'no-cache') {
unset($this->headers['Pragma']);
}
}
@ -307,7 +310,7 @@ class Response
public function sendHeaders(): self
{
// Send status code header
if (false !== strpos(\PHP_SAPI, 'cgi')) {
if (strpos(\PHP_SAPI, 'cgi') !== false) {
// @codeCoverageIgnoreStart
$this->setRealHeader(
sprintf(

@ -105,7 +105,7 @@ class Route
public function matchUrl(string $url, bool $case_sensitive = false): bool
{
// Wildcard or exact match
if ('*' === $this->pattern || $this->pattern === $url) {
if ($this->pattern === '*' || $this->pattern === $url) {
return true;
}
@ -120,8 +120,9 @@ class Route
for ($i = 0; $i < $len; $i++) {
if ($url[$i] === '/') {
$n++;
++$n;
}
if ($n === $count) {
break;
}
@ -153,24 +154,20 @@ class Route
$regex
);
if ('/' === $last_char) { // Fix trailing slash
$regex .= '?';
} else { // Allow trailing slash
$regex .= '/?';
}
$regex .= $last_char === '/' ? '?' : '/?';
// Attempt to match route and named parameters
if (preg_match('#^' . $regex . '(?:\?[\s\S]*)?$#' . (($case_sensitive) ? '' : 'i'), $url, $matches)) {
foreach ($ids as $k => $v) {
$this->params[$k] = (\array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null;
}
$this->regex = $regex;
if (!preg_match('#^' . $regex . '(?:\?[\s\S]*)?$#' . (($case_sensitive) ? '' : 'i'), $url, $matches)) {
return false;
}
return true;
foreach (array_keys($ids) as $k) {
$this->params[$k] = (\array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null;
}
return false;
$this->regex = $regex;
return true;
}
/**

@ -88,7 +88,7 @@ class View
*/
public function clear(?string $key = null): self
{
if (null === $key) {
if ($key === null) {
$this->vars = [];
} else {
unset($this->vars[$key]);
@ -169,7 +169,7 @@ class View
$is_windows = \strtoupper(\substr(PHP_OS, 0, 3)) === 'WIN';
if (('/' == \substr($file, 0, 1)) || ($is_windows === true && ':' == \substr($file, 1, 1))) {
if ((\substr($file, 0, 1) === '/') || ($is_windows && \substr($file, 1, 1) === ':')) {
return $file;
}

@ -95,7 +95,7 @@ class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value): void
{
if (null === $offset) {
if ($offset === null) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
@ -166,9 +166,7 @@ class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable
*/
public function valid(): bool
{
$key = key($this->data);
return null !== $key;
return key($this->data) !== null;
}
/**

@ -0,0 +1,8 @@
<?php
declare(strict_types=1);
// This file is only here so that the PHP8 attribute for doesn't throw an error in files
class ReturnTypeWillChange
{
}

@ -1,7 +1,9 @@
<?php
// require 'flight/Flight.php';
require 'flight/autoload.php';
declare(strict_types=1);
require 'flight/Flight.php';
// require 'flight/autoload.php';
Flight::route('/', function () {
echo 'hello world!';

@ -9,35 +9,42 @@
</description>
<arg name="colors" />
<arg name="tab-width" value="4" />
<rule ref="PSR12" />
<rule ref="PSR1">
<exclude name="PSR1.Classes.ClassDeclaration.MissingNamespace" />
</rule>
<rule ref="PSR12" />
<rule ref="Generic">
<exclude name="Generic.Files.LineEndings.InvalidEOLChar" />
<exclude name="Generic.PHP.ClosingPHPTag.NotFound" />
<exclude name="Generic.PHP.UpperCaseConstant.Found" />
<exclude name="Generic.Arrays.DisallowShortArraySyntax.Found" />
<exclude name="Generic.Files.EndFileNoNewline.Found" />
<exclude name="Generic.Files.LowercasedFilename.NotFound" />
<exclude name="Generic.Commenting.DocComment.TagValueIndent" />
<exclude name="Generic.Classes.OpeningBraceSameLine.BraceOnNewLine" />
<exclude name="Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed" />
<exclude name="Generic.Files.LowercasedFilename.NotFound" />
<exclude name="Generic.Commenting.DocComment.ContentAfterOpen" />
<exclude name="Generic.Commenting.DocComment.ContentBeforeClose" />
<exclude name="Generic.Commenting.DocComment.MissingShort" />
<exclude name="Generic.Commenting.DocComment.SpacingBeforeShort" />
<exclude name="Generic.Functions.OpeningFunctionBraceKernighanRitchie.BraceOnNewLine" />
<exclude name="Generic.Formatting.MultipleStatementAlignment.NotSameWarning" />
<exclude name="Generic.Formatting.SpaceAfterNot.Incorrect" />
<exclude name="Generic.Commenting.DocComment.SpacingBeforeShort" />
<exclude name="Generic.Commenting.DocComment.ContentAfterOpen" />
<exclude name="Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine" />
<exclude name="Generic.PHP.DisallowRequestSuperglobal.Found" />
<exclude name="Generic.Commenting.DocComment.ContentBeforeClose" />
<exclude name="Generic.ControlStructures.DisallowYodaConditions.Found" />
<exclude name="Generic.Strings.UnnecessaryStringConcat.Found" />
<exclude name="Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition" />
<exclude name="Generic.Commenting.DocComment.MissingShort" />
<exclude name="Generic.Commenting.DocComment.SpacingBeforeTags" />
<exclude name="Generic.WhiteSpace.ArbitraryParenthesesSpacing.SpaceAfterOpen" />
<exclude name="Generic.WhiteSpace.ArbitraryParenthesesSpacing.SpaceBeforeClose" />
</rule>
<rule ref="Generic.Files.LineLength">
<properties>
<property name="ignoreComments" value="true" />
</properties>
</rule>
<rule ref="Generic.Formatting.SpaceAfterNot">
<properties>
<property name="spacing" value="0" />
</properties>
</rule>
<rule ref="Generic.WhiteSpace.ArbitraryParenthesesSpacing">
<properties>
<property name="ignoreNewlines" value="true" />
</properties>
</rule>
<file>flight/</file>
<file>tests/</file>

@ -1,6 +0,0 @@
parameters:
ignoreErrors:
-
message: "#^Parameter \\#2 \\$callback of method flight\\\\core\\\\Dispatcher\\:\\:set\\(\\) expects Closure\\(\\)\\: mixed, array\\{\\$this\\(flight\\\\Engine\\), literal\\-string&non\\-falsy\\-string\\} given\\.$#"
count: 1
path: flight/Engine.php

@ -37,9 +37,7 @@ class DispatcherTest extends TestCase
public function testFunctionMapping(): void
{
$this->dispatcher->set('map2', function (): string {
return 'hello';
});
$this->dispatcher->set('map2', fn (): string => 'hello');
$this->assertSame('hello', $this->dispatcher->run('map2'));
}
@ -61,6 +59,9 @@ class DispatcherTest extends TestCase
->set('map-event', $customFunction)
->set('map-event-2', $anotherFunction);
$this->assertTrue($this->dispatcher->has('map-event'));
$this->assertTrue($this->dispatcher->has('map-event-2'));
$this->dispatcher->clear();
$this->assertFalse($this->dispatcher->has('map-event'));
@ -76,6 +77,9 @@ class DispatcherTest extends TestCase
->set('map-event', $customFunction)
->set('map-event-2', $anotherFunction);
$this->assertTrue($this->dispatcher->has('map-event'));
$this->assertTrue($this->dispatcher->has('map-event-2'));
$this->dispatcher->clear('map-event');
$this->assertFalse($this->dispatcher->has('map-event'));
@ -105,9 +109,7 @@ class DispatcherTest extends TestCase
public function testBeforeAndAfter(): void
{
$this->dispatcher->set('hello', function (string $name): string {
return "Hello, $name!";
});
$this->dispatcher->set('hello', fn (string $name): string => "Hello, $name!");
$this->dispatcher
->hook('hello', Dispatcher::FILTER_BEFORE, function (array &$params): void {
@ -124,6 +126,25 @@ class DispatcherTest extends TestCase
$this->assertSame('Hello, Fred! Have a nice day!', $result);
}
public function testBeforeAndAfterWithShortAfterFilterSyntax(): void
{
$this->dispatcher->set('hello', fn (string $name): string => "Hello, $name!");
$this->dispatcher
->hook('hello', Dispatcher::FILTER_BEFORE, function (array &$params): void {
// Manipulate the parameter
$params[0] = 'Fred';
})
->hook('hello', Dispatcher::FILTER_AFTER, function (string &$output): void {
// Manipulate the output
$output .= ' Have a nice day!';
});
$result = $this->dispatcher->run('hello', ['Bob']);
$this->assertSame('Hello, Fred! Have a nice day!', $result);
}
public function testInvalidCallback(): void
{
$this->expectException(Exception::class);
@ -245,7 +266,7 @@ class DispatcherTest extends TestCase
public function testInvokeMethod(): void
{
$class = new TesterClass('param1', 'param2', 'param3', 'param4', 'param5', 'param6');
$result = $this->dispatcher->invokeMethod([ $class, 'instanceMethod' ]);
$result = $this->dispatcher->invokeMethod([$class, 'instanceMethod']);
$this->assertSame('param1', $class->param2);
}
@ -271,7 +292,7 @@ class DispatcherTest extends TestCase
public function testExecuteStringClassNoConstructArraySyntax(): void
{
$result = $this->dispatcher->execute([ Hello::class, 'sayHi' ]);
$result = $this->dispatcher->execute([Hello::class, 'sayHi']);
$this->assertSame('hello', $result);
}
@ -298,7 +319,7 @@ class DispatcherTest extends TestCase
$engine = new Engine();
$engine->set('test_me_out', 'You got it boss!');
$this->dispatcher->setEngine($engine);
$result = $this->dispatcher->execute([ ContainerDefault::class, 'testTheContainer' ]);
$result = $this->dispatcher->execute([ContainerDefault::class, 'testTheContainer']);
$this->assertSame('You got it boss!', $result);
}
@ -306,6 +327,6 @@ class DispatcherTest extends TestCase
{
$this->expectException(TypeError::class);
$this->expectExceptionMessageMatches('#tests\\\\classes\\\\ContainerDefault::__construct\(\).+flight\\\\Engine, null given#');
$result = $this->dispatcher->execute([ ContainerDefault::class, 'testTheContainer' ]);
$result = $this->dispatcher->execute([ContainerDefault::class, 'testTheContainer']);
}
}

@ -21,7 +21,7 @@ class RedirectTest extends TestCase
public function getBaseUrl($base, $url)
{
if ('/' !== $base && false === strpos($url, '://')) {
if ($base !== '/' && strpos($url, '://') === false) {
$url = preg_replace('#/+#', '/', $base . '/' . $url);
}
@ -67,11 +67,7 @@ class RedirectTest extends TestCase
public function testBaseOverride()
{
$url = 'login';
if (null !== $this->app->get('flight.base_url')) {
$base = $this->app->get('flight.base_url');
} else {
$base = $this->app->request()->base;
}
$base = $this->app->get('flight.base_url') ?? $this->app->request()->base;
self::assertEquals('/testdir/login', $this->getBaseUrl($base, $url));
}

@ -336,12 +336,14 @@ class RouterTest extends TestCase
{
$this->router->map('GET /api/intune/hey', [$this, 'ok']);
$error_description = 'error_description=AADSTS65004%3a+User+declined+to+consent+to+access+the';
$error_description .= '+app.%0d%0aTrace+ID%3a+747c0cc1-ccbd-4e53-8e2f-48812eb24100%0d%0a';
$error_description .= 'Correlation+ID%3a+362e3cb3-20ef-400b-904e-9983bd989184%0d%0a';
$error_description .= 'Timestamp%3a+2022-09-08+09%3a58%3a12Z';
$query_params = [
'error=access_denied',
'error_description=AADSTS65004%3a+User+declined+to+consent+to+access+the'
. '+app.%0d%0aTrace+ID%3a+747c0cc1-ccbd-4e53-8e2f-48812eb24100%0d%0a'
. 'Correlation+ID%3a+362e3cb3-20ef-400b-904e-9983bd989184%0d%0a'
. 'Timestamp%3a+2022-09-08+09%3a58%3a12Z',
$error_description,
'error_uri=https%3a%2f%2flogin.microsoftonline.com%2ferror%3fcode%3d65004',
'admin_consent=True',
'state=x2EUE0fcSj#'

Loading…
Cancel
Save