Merge pull request #685 from flightphp/fix-phpcs-errors

Fix phpcs errors
master
n0nag0n 9 hours ago committed by GitHub
commit 3941d44c70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -38,12 +38,15 @@
"psr-4": { "psr-4": {
"Tests\\PHP8\\": [ "Tests\\PHP8\\": [
"tests/named-arguments" "tests/named-arguments"
] ],
"Tests\\Server\\": "tests/server",
"Tests\\ServerV2\\": "tests/server-v2",
"tests\\groupcompactsyntax\\": "tests/groupcompactsyntax"
} }
}, },
"require-dev": { "require-dev": {
"ext-pdo_sqlite": "*", "ext-pdo_sqlite": "*",
"flightphp/container": "^1.0", "flightphp/container": "^1.3",
"flightphp/runway": "^1.2", "flightphp/runway": "^1.2",
"league/container": "^4.2", "league/container": "^4.2",
"level-2/dice": "^4.0", "level-2/dice": "^4.0",
@ -51,7 +54,7 @@
"phpstan/phpstan": "^2.1", "phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^9.6", "phpunit/phpunit": "^9.6",
"rregeer/phpunit-coverage-check": "^0.3.1", "rregeer/phpunit-coverage-check": "^0.3.1",
"squizlabs/php_codesniffer": "^3.11" "squizlabs/php_codesniffer": "^4.0"
}, },
"config": { "config": {
"allow-plugins": { "allow-plugins": {
@ -84,7 +87,7 @@
], ],
"lint": "phpstan --no-progress --memory-limit=256M", "lint": "phpstan --no-progress --memory-limit=256M",
"beautify": "phpcbf --standard=phpcs.xml", "beautify": "phpcbf --standard=phpcs.xml",
"phpcs": "phpcs --standard=phpcs.xml -n", "phpcs": "phpcs",
"post-install-cmd": [ "post-install-cmd": [
"php -r \"if (!file_exists('phpcs.xml')) copy('phpcs.xml.dist', 'phpcs.xml');\"", "php -r \"if (!file_exists('phpcs.xml')) copy('phpcs.xml.dist', 'phpcs.xml');\"",
"php -r \"if (!file_exists('phpstan.neon')) copy('phpstan.dist.neon', 'phpstan.neon');\"", "php -r \"if (!file_exists('phpstan.neon')) copy('phpstan.dist.neon', 'phpstan.neon');\"",

@ -267,7 +267,8 @@ class Engine
/** /**
* Registers the container handler * Registers the container handler
* *
* @param ContainerInterface|callable(class-string<T> $id, array<int|string, mixed> $params): ?T $containerHandler Callback function or PSR-11 Container object that sets the container and how it will inject classes * @param ContainerInterface|callable(class-string<T> $id, array<int|string, mixed> $params): ?T $containerHandler
* Callback function or PSR-11 Container object that sets the container and how it will inject classes
* *
* @template T of object * @template T of object
*/ */
@ -488,7 +489,14 @@ class Engine
// Which loosely translates to $class->$method($params) // Which loosely translates to $class->$method($params)
$start = microtime(true); $start = microtime(true);
$middlewareResult = $middlewareObject($params); $middlewareResult = $middlewareObject($params);
$this->triggerEvent('flight.middleware.executed', $route, $middleware, $eventName, microtime(true) - $start);
$this->triggerEvent(
'flight.middleware.executed',
$route,
$middleware,
$eventName,
microtime(true) - $start
);
if ($useV3OutputBuffering === true) { if ($useV3OutputBuffering === true) {
$this->response()->write(ob_get_clean()); $this->response()->write(ob_get_clean());
@ -860,7 +868,12 @@ class Engine
public function _methodNotFound(Route $route): void public function _methodNotFound(Route $route): void
{ {
$this->response()->setHeader('Allow', implode(', ', $route->methods)); $this->response()->setHeader('Allow', implode(', ', $route->methods));
$this->halt(405, 'Method Not Allowed. Allowed Methods are: ' . implode(', ', $route->methods), empty(getenv('PHPUNIT_TEST')));
$this->halt(
405,
'Method Not Allowed. Allowed Methods are: ' . implode(', ', $route->methods),
empty(getenv('PHPUNIT_TEST'))
);
} }
/** /**

@ -80,8 +80,10 @@ require_once __DIR__ . '/autoload.php';
* @phpstan-method static void jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0) * @phpstan-method static void jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* @phpstan-method static void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512) * @phpstan-method static void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
* *
* Note: IDEs will use standard @method tags for autocompletion, while PHPStan will use @phpstan-* tags for advanced type checking. * Note: IDEs will use standard @method tags for autocompletion,
* while PHPStan will use @phpstan-* tags for advanced type checking.
*/ */
// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace
class Flight class Flight
{ {
/** /**

@ -20,7 +20,13 @@ class AiGenerateInstructionsCommand extends AbstractBaseCommand
public function __construct(array $config) public function __construct(array $config)
{ {
parent::__construct('ai:generate-instructions', 'Generate project-specific AI coding instructions', $config); parent::__construct('ai:generate-instructions', 'Generate project-specific AI coding instructions', $config);
$this->option('--config-file', 'Path to .runway-config.json file (deprecated, use config.php instead)', null, '');
$this->option(
'--config-file',
'Path to .runway-config.json file (deprecated, use config.php instead)',
null,
''
);
} }
/** /**
@ -40,7 +46,12 @@ class AiGenerateInstructionsCommand extends AbstractBaseCommand
if (empty($this->config['runway'])) { if (empty($this->config['runway'])) {
$configFile = $this->configFile; $configFile = $this->configFile;
$io = $this->app()->io(); $io = $this->app()->io();
$io->warn('The --config-file option is deprecated. Move your config values to the \'runway\' key in the config.php file for configuration.', true);
$io->warn(
'The --config-file option is deprecated. '
. 'Move your config values to the \'runway\' key in the config.php file for configuration.',
true
);
$runwayConfig = json_decode(file_get_contents($configFile), true) ?? []; $runwayConfig = json_decode(file_get_contents($configFile), true) ?? [];
} else { } else {
$runwayConfig = $this->config['runway']; $runwayConfig = $this->config['runway'];
@ -56,12 +67,30 @@ class AiGenerateInstructionsCommand extends AbstractBaseCommand
// Ask questions // Ask questions
$projectDesc = $io->prompt('Please describe what your project is for?'); $projectDesc = $io->prompt('Please describe what your project is for?');
$database = $io->prompt('What database are you planning on using? (e.g. MySQL, SQLite, PostgreSQL, none)', 'none');
$templating = $io->prompt('What HTML templating engine will you plan on using (if any)? (recommend latte)', 'latte'); $database = $io->prompt(
'What database are you planning on using? (e.g. MySQL, SQLite, PostgreSQL, none)',
'none'
);
$templating = $io->prompt(
'What HTML templating engine will you plan on using (if any)? (recommend latte)',
'latte'
);
$security = $io->confirm('Is security an important element of this project?', 'y'); $security = $io->confirm('Is security an important element of this project?', 'y');
$performance = $io->confirm('Is performance and speed an important part of this project?', 'y'); $performance = $io->confirm('Is performance and speed an important part of this project?', 'y');
$composerLibs = $io->prompt('What major composer libraries will you be using if you know them right now?', 'none');
$envSetup = $io->prompt('How will you set up your development environment? (e.g. Docker, Vagrant, PHP dev server, other)', 'Docker'); $composerLibs = $io->prompt(
'What major composer libraries will you be using if you know them right now?',
'none'
);
$envSetup = $io->prompt(
'How will you set up your development environment? (e.g. Docker, Vagrant, PHP dev server, other)',
'Docker'
);
$teamSize = $io->prompt('How many developers will be working on this project?', '1'); $teamSize = $io->prompt('How many developers will be working on this project?', '1');
$api = $io->confirm('Will this project expose an API?', 'n'); $api = $io->confirm('Will this project expose an API?', 'n');
$other = $io->prompt('Any other important requirements or context? (optional)', 'no'); $other = $io->prompt('Any other important requirements or context? (optional)', 'no');
@ -107,7 +136,13 @@ class AiGenerateInstructionsCommand extends AbstractBaseCommand
$data = [ $data = [
'model' => $model, 'model' => $model,
'messages' => [ 'messages' => [
['role' => 'system', 'content' => 'You are a helpful AI coding assistant focused on the Flight Framework for PHP. You are up to date with all your knowledge from https://docs.flightphp.com. As an expert into the programming language PHP, you are top notch at architecting out proper instructions for FlightPHP projects.'], [
'role' => 'system',
'content' => 'You are a helpful AI coding assistant focused on the Flight Framework for PHP. '
. 'You are up to date with all your knowledge from https://docs.flightphp.com. '
. 'As an expert into the programming language PHP, '
. 'you are top notch at architecting out proper instructions for FlightPHP projects.'
],
['role' => 'user', 'content' => $prompt], ['role' => 'user', 'content' => $prompt],
], ],
'temperature' => 0.2, 'temperature' => 0.2,
@ -129,7 +164,11 @@ class AiGenerateInstructionsCommand extends AbstractBaseCommand
} }
// Write to files // Write to files
$io->info('Updating .github/copilot-instructions.md, .cursor/rules/project-overview.mdc, .gemini/GEMINI.md and .windsurfrules...', true); $io->info(
'Updating .github/copilot-instructions.md, .cursor/rules/project-overview.mdc, .gemini/GEMINI.md and .windsurfrules...',
true
);
if (!is_dir($this->projectRoot . '.github')) { if (!is_dir($this->projectRoot . '.github')) {
mkdir($this->projectRoot . '.github', 0755, true); mkdir($this->projectRoot . '.github', 0755, true);
} }

@ -79,7 +79,11 @@ class AiInitCommand extends AbstractBaseCommand
$defaultModel = 'claude-sonnet-4-5'; $defaultModel = 'claude-sonnet-4-5';
break; break;
} }
$model = trim($io->prompt('Enter the model name you want to use (e.g. gpt-5, claude-sonnet-4-5, etc)', $defaultModel));
$model = trim($io->prompt(
'Enter the model name you want to use (e.g. gpt-5, claude-sonnet-4-5, etc)',
$defaultModel
));
$runwayAiConfig = [ $runwayAiConfig = [
'provider' => $api, 'provider' => $api,

@ -31,8 +31,16 @@ class ControllerCommand extends AbstractBaseCommand
$io = $this->app()->io(); $io = $this->app()->io();
if (empty($this->config['runway'])) { if (empty($this->config['runway'])) {
$io->warn('Using a .runway-config.json file is deprecated. Move your config values to app/config/config.php with `php runway config:migrate`.', true); // @codeCoverageIgnore $io->warn(
$runwayConfig = json_decode(file_get_contents($this->projectRoot . '/.runway-config.json'), true); // @codeCoverageIgnore 'Using a .runway-config.json file is deprecated. '
. 'Move your config values to app/config/config.php with `php runway config:migrate`.',
true
); // @codeCoverageIgnore
$runwayConfig = json_decode(
file_get_contents($this->projectRoot . '/.runway-config.json'),
true
); // @codeCoverageIgnore
} else { } else {
$runwayConfig = $this->config['runway']; $runwayConfig = $this->config['runway'];
} }
@ -95,6 +103,9 @@ class ControllerCommand extends AbstractBaseCommand
protected function persistClass(string $controllerName, PhpFile $file, string $appRoot) protected function persistClass(string $controllerName, PhpFile $file, string $appRoot)
{ {
$printer = new \Nette\PhpGenerator\PsrPrinter(); $printer = new \Nette\PhpGenerator\PsrPrinter();
file_put_contents($this->projectRoot . '/' . $appRoot . 'controllers/' . $controllerName . '.php', $printer->printFile($file)); file_put_contents(
$this->projectRoot . '/' . $appRoot . 'controllers/' . $controllerName . '.php',
$printer->printFile($file)
);
} }
} }

@ -118,13 +118,7 @@ class RouteCommand extends AbstractBaseCommand
$methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']; $methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
foreach ($methods as $method) { foreach ($methods as $method) {
$lowercaseMethod = strtolower($method); $lowercaseMethod = strtolower($method);
if ( if ($this->{$lowercaseMethod} === true && ($route->methods[0] === '*' || in_array($method, $route->methods, true) === true)) {
$this->{$lowercaseMethod} === true &&
(
$route->methods[0] === '*' ||
in_array($method, $route->methods, true) === true
)
) {
$boolval = true; $boolval = true;
break; break;
} }

@ -58,16 +58,14 @@ class Dispatcher
* *
* @template T of object * @template T of object
* *
* @throws InvalidArgumentException If $containerHandler is not a `callable` or instance of `Psr\Container\ContainerInterface`. * @throws InvalidArgumentException
* If $containerHandler is not a `callable` or instance of `Psr\Container\ContainerInterface`.
*/ */
public function setContainerHandler($containerHandler): void public function setContainerHandler($containerHandler): void
{ {
$containerInterfaceNS = '\Psr\Container\ContainerInterface'; $containerInterfaceNS = '\Psr\Container\ContainerInterface';
if ( if (is_a($containerHandler, $containerInterfaceNS) || is_callable($containerHandler)) {
is_a($containerHandler, $containerInterfaceNS)
|| is_callable($containerHandler)
) {
$this->containerHandler = $containerHandler; $this->containerHandler = $containerHandler;
return; return;
@ -289,10 +287,7 @@ class Dispatcher
*/ */
public function execute($callback, array &$params = []) public function execute($callback, array &$params = [])
{ {
if ( if (is_string($callback) === true && (strpos($callback, '->') !== false || strpos($callback, '::') !== false)) {
is_string($callback) === true
&& (strpos($callback, '->') !== false || strpos($callback, '::') !== false)
) {
$callback = $this->parseStringClassAndMethod($callback); $callback = $this->parseStringClassAndMethod($callback);
} }
@ -419,7 +414,10 @@ class Dispatcher
// Final check to make sure it's actually a class and a method, or throw an error // 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) { if (is_object($class) === false && class_exists($class) === false) {
$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 // If this tried to resolve a class in a container and failed somehow, throw the exception
} elseif (!$resolvedClass && $this->containerException !== null) { } elseif (!$resolvedClass && $this->containerException !== null) {

@ -439,15 +439,13 @@ class Request
*/ */
public static function getScheme(): string public static function getScheme(): string
{ {
if ( if (strtolower(self::getVar('HTTPS')) === 'on') {
(strtolower(self::getVar('HTTPS')) === 'on') return 'https';
|| } elseif (self::getVar('HTTP_X_FORWARDED_PROTO') === 'https') {
(self::getVar('HTTP_X_FORWARDED_PROTO') === 'https') return 'https';
|| } elseif (self::getVar('HTTP_FRONT_END_HTTPS') === 'on') {
(self::getVar('HTTP_FRONT_END_HTTPS') === 'on') return 'https';
|| } elseif (self::getVar('REQUEST_SCHEME') === 'https') {
(self::getVar('REQUEST_SCHEME') === 'https')
) {
return 'https'; return 'https';
} }
@ -478,7 +476,8 @@ class Request
/** /**
* Retrieves the array of uploaded files. * Retrieves the array of uploaded files.
* *
* @return array<string, UploadedFile|array<int, UploadedFile>> Key is field name; value is either a single UploadedFile or an array of UploadedFile when multiple were uploaded. * @return array<string, UploadedFile|array<int, UploadedFile>>
* Key is field name; value is either a single UploadedFile or an array of UploadedFile when multiple were uploaded.
*/ */
public function getUploadedFiles(): array public function getUploadedFiles(): array
{ {

@ -484,7 +484,8 @@ class Response
* Downloads a file. * Downloads a file.
* *
* @param string $filePath The path to the file to be downloaded. * @param string $filePath The path to the file to be downloaded.
* @param string $fileName The name the downloaded file should have. If not provided or is an empty string, the name of the file on disk will be used. * @param string $fileName The name the downloaded file should have.
* If not provided or is an empty string, the name of the file on disk will be used.
* *
* @throws Exception If the file cannot be found. * @throws Exception If the file cannot be found.
* *

@ -48,8 +48,14 @@ class UploadedFile
* @param int $error The error code associated with the uploaded file. * @param int $error The error code associated with the uploaded file.
* @param bool|null $isPostUploadedFile Indicates if the file was uploaded via POST method. * @param bool|null $isPostUploadedFile Indicates if the file was uploaded via POST method.
*/ */
public function __construct(string $name, string $mimeType, int $size, string $tmpName, int $error, ?bool $isPostUploadedFile = null) public function __construct(
{ string $name,
string $mimeType,
int $size,
string $tmpName,
int $error,
?bool $isPostUploadedFile = null
) {
$this->name = $name; $this->name = $name;
$this->mimeType = $mimeType; $this->mimeType = $mimeType;
$this->size = $size; $this->size = $size;
@ -154,7 +160,7 @@ class UploadedFile
$uploadFunctionToCall = $isUploadedFile === true ? $uploadFunctionToCall = $isUploadedFile === true ?
// Standard POST upload - use move_uploaded_file for security // Standard POST upload - use move_uploaded_file for security
'move_uploaded_file' : 'move_uploaded_file' :
// Handle non-POST uploads (PATCH, PUT, DELETE) or other valid temp files // Handle non-POST uploads (PATCH, PUT, DELETE) or other valid temp files
'rename'; 'rename';
$result = $uploadFunctionToCall($this->tmpName, $targetPath); $result = $uploadFunctionToCall($this->tmpName, $targetPath);

@ -3,6 +3,7 @@
declare(strict_types=1); declare(strict_types=1);
// This file is only here so that the PHP8 attribute for doesn't throw an error in files // This file is only here so that the PHP8 attribute for doesn't throw an error in files
// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace
class ReturnTypeWillChange class ReturnTypeWillChange
{ {
// //

@ -1,54 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ruleset <ruleset
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/squizlabs/php_codesniffer/phpcs.xsd" xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
name="pcsg-generated-ruleset"> <!-- <arg name="report" value="source" /> -->
<description> <!-- <arg name="report" value="summary" /> -->
Created with the PHP Coding Standard Generator. <!-- <arg name="report" value="diff" /> -->
http://edorian.github.io/php-coding-standard-generator/ <arg name="report" value="full" />
</description> <arg name="report-width" value="80" />
<arg name="colors" /> <arg name="colors" />
<arg name="encoding" value="utf-8" />
<arg name="tab-width" value="4" /> <arg name="tab-width" value="4" />
<rule ref="PSR1"> <rule ref="PSR1">
<exclude name="PSR1.Classes.ClassDeclaration.MissingNamespace" />
</rule> </rule>
<rule ref="PSR12" />
<rule ref="Generic"> <rule ref="PSR2">
<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.Commenting.DocComment.ContentAfterOpen" />
<exclude name="Generic.Commenting.DocComment.ContentBeforeClose" />
<exclude name="Generic.Commenting.DocComment.MissingShort" />
<exclude name="Generic.Commenting.DocComment.SpacingBeforeShort" />
<exclude name="Generic.Formatting.NoSpaceAfterCast.SpaceFound" />
<exclude name="Generic.Functions.OpeningFunctionBraceKernighanRitchie.BraceOnNewLine" />
<exclude name="Generic.Formatting.MultipleStatementAlignment.NotSameWarning" />
<exclude name="Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine" />
<exclude name="Generic.PHP.DisallowRequestSuperglobal.Found" />
<exclude name="Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsedHeredocCloser" />
</rule> </rule>
<rule ref="Generic.Files.LineLength">
<properties> <rule ref="PSR12">
<property name="ignoreComments" value="true" /> <exclude name="PSR12.Classes.AnonClassDeclaration.SpaceAfterKeyword"/>
</properties>
</rule> </rule>
<rule ref="Generic.Formatting.SpaceAfterNot">
<properties> <file>index.php</file>
<property name="spacing" value="0" /> <file>flight</file>
</properties> <file>tests</file>
</rule>
<rule ref="Generic.WhiteSpace.ArbitraryParenthesesSpacing">
<properties>
<property name="ignoreNewlines" value="true" />
</properties>
</rule>
<file>flight/</file>
<file>tests/</file>
<exclude-pattern>tests/views/*</exclude-pattern>
</ruleset> </ruleset>

@ -38,7 +38,7 @@ class DispatcherTest extends TestCase
public function testFunctionMapping(): void public function testFunctionMapping(): void
{ {
$this->dispatcher->set('map2', fn (): string => 'hello'); $this->dispatcher->set('map2', fn(): string => 'hello');
$this->assertSame('hello', $this->dispatcher->run('map2')); $this->assertSame('hello', $this->dispatcher->run('map2'));
} }
@ -46,6 +46,7 @@ class DispatcherTest extends TestCase
public function testHasEvent(): void public function testHasEvent(): void
{ {
$this->dispatcher->set('map-event', function (): void { $this->dispatcher->set('map-event', function (): void {
//
}); });
$this->assertTrue($this->dispatcher->has('map-event')); $this->assertTrue($this->dispatcher->has('map-event'));
@ -54,6 +55,7 @@ class DispatcherTest extends TestCase
public function testClearAllRegisteredEvents(): void public function testClearAllRegisteredEvents(): void
{ {
$customFunction = $anotherFunction = function (): void { $customFunction = $anotherFunction = function (): void {
//
}; };
$this->dispatcher $this->dispatcher
@ -72,6 +74,7 @@ class DispatcherTest extends TestCase
public function testClearDeclaredRegisteredEvent(): void public function testClearDeclaredRegisteredEvent(): void
{ {
$customFunction = $anotherFunction = function (): void { $customFunction = $anotherFunction = function (): void {
//
}; };
$this->dispatcher $this->dispatcher
@ -110,7 +113,7 @@ class DispatcherTest extends TestCase
public function testBeforeAndAfter(): void public function testBeforeAndAfter(): void
{ {
$this->dispatcher->set('hello', fn (string $name): string => "Hello, $name!"); $this->dispatcher->set('hello', fn(string $name): string => "Hello, $name!");
$this->dispatcher $this->dispatcher
->hook('hello', Dispatcher::FILTER_BEFORE, function (array &$params): void { ->hook('hello', Dispatcher::FILTER_BEFORE, function (array &$params): void {
@ -129,7 +132,7 @@ class DispatcherTest extends TestCase
public function testBeforeAndAfterWithShortAfterFilterSyntax(): void public function testBeforeAndAfterWithShortAfterFilterSyntax(): void
{ {
$this->dispatcher->set('hello', fn (string $name): string => "Hello, $name!"); $this->dispatcher->set('hello', fn(string $name): string => "Hello, $name!");
$this->dispatcher $this->dispatcher
->hook('hello', Dispatcher::FILTER_BEFORE, function (array &$params): void { ->hook('hello', Dispatcher::FILTER_BEFORE, function (array &$params): void {
@ -149,7 +152,11 @@ class DispatcherTest extends TestCase
public function testInvalidCallback(): void public function testInvalidCallback(): void
{ {
$this->expectException(Exception::class); $this->expectException(Exception::class);
$this->expectExceptionMessage("Class 'NonExistentClass' not found. Is it being correctly autoloaded with Flight::path()?");
$this->expectExceptionMessage(
"Class 'NonExistentClass' not found. "
. "Is it being correctly autoloaded with Flight::path()?"
);
$this->dispatcher->execute(['NonExistentClass', 'nonExistentMethod']); $this->dispatcher->execute(['NonExistentClass', 'nonExistentMethod']);
} }
@ -247,6 +254,7 @@ class DispatcherTest extends TestCase
$invalidCallable = 'invalidGlobalFunction'; $invalidCallable = 'invalidGlobalFunction';
$validCallable = function (): void { $validCallable = function (): void {
//
}; };
$this->dispatcher->filter([$validCallable, $invalidCallable], $params, $output); $this->dispatcher->filter([$validCallable, $invalidCallable], $params, $output);
@ -275,7 +283,12 @@ class DispatcherTest extends TestCase
public function testExecuteStringClassBadConstructParams(): void public function testExecuteStringClassBadConstructParams(): void
{ {
$this->expectException(ArgumentCountError::class); $this->expectException(ArgumentCountError::class);
$this->expectExceptionMessageMatches('#Too few arguments to function tests\\\\classes\\\\TesterClass::__construct\(\), 1 passed .+ and exactly 6 expected#');
$this->expectExceptionMessageMatches(
'#Too few arguments to function tests\\\\classes\\\\TesterClass::__construct\(\), 1 passed'
. ' .+ and exactly 6 expected#'
);
$this->dispatcher->execute(TesterClass::class . '->instanceMethod'); $this->dispatcher->execute(TesterClass::class . '->instanceMethod');
} }
@ -327,8 +340,12 @@ class DispatcherTest extends TestCase
public function testExecuteStringClassDefaultContainerButForgotInjectingEngine(): void public function testExecuteStringClassDefaultContainerButForgotInjectingEngine(): void
{ {
$this->expectException(TypeError::class); $this->expectException(TypeError::class);
$this->expectExceptionMessageMatches('#tests\\\\classes\\\\ContainerDefault::__construct\(\).+flight\\\\Engine, null given#');
$result = $this->dispatcher->execute([ContainerDefault::class, 'testTheContainer']); $this->expectExceptionMessageMatches(
'#tests\\\\classes\\\\ContainerDefault::__construct\(\).+flight\\\\Engine, null given#'
);
$this->dispatcher->execute([ContainerDefault::class, 'testTheContainer']);
} }
public function testContainerDicePdoWrapperTestBadParams(): void public function testContainerDicePdoWrapperTestBadParams(): void
@ -341,6 +358,6 @@ class DispatcherTest extends TestCase
$this->expectException(Exception::class); $this->expectException(Exception::class);
$this->expectExceptionMessage('This is an exception in the constructor'); $this->expectExceptionMessage('This is an exception in the constructor');
$this->dispatcher->invokeCallable([ ClassWithExceptionInConstruct::class, '__construct' ]); $this->dispatcher->invokeCallable([ClassWithExceptionInConstruct::class, '__construct']);
} }
} }

@ -40,16 +40,16 @@ class EngineTest extends TestCase
}; };
$this->assertTrue($engine->getInitializedVar()); $this->assertTrue($engine->getInitializedVar());
// we need to setup a dummy route // we need to setup a dummy route
$engine->route('/someRoute', function () { }); $engine->route('/someRoute', function () {});
$engine->request()->url = '/someRoute'; $engine->request()->url = '/someRoute';
$engine->start(); $engine->start();
$this->assertFalse($engine->router()->caseSensitive); $this->assertFalse($engine->router()->caseSensitive);
$this->assertTrue($engine->response()->content_length); $this->assertTrue($engine->response()->content_length);
} }
public function testInitBeforeStartV2OutputBuffering(): void public function testInitBeforeStartV2OutputBuffering(): void
{ {
$engine = new class extends Engine { $engine = new class extends Engine {
public function getInitializedVar(): bool public function getInitializedVar(): bool
@ -57,12 +57,12 @@ class EngineTest extends TestCase
return $this->initialized; return $this->initialized;
} }
}; };
$engine->set('flight.v2.output_buffering', true); $engine->set('flight.v2.output_buffering', true);
$this->assertTrue($engine->getInitializedVar()); $this->assertTrue($engine->getInitializedVar());
$engine->start(); $engine->start();
// This is a necessary evil because of how the v2 output buffer works. // This is a necessary evil because of how the v2 output buffer works.
ob_end_clean(); ob_end_clean();
$this->assertFalse($engine->router()->caseSensitive); $this->assertFalse($engine->router()->caseSensitive);
$this->assertTrue($engine->response()->content_length); $this->assertTrue($engine->response()->content_length);
@ -96,8 +96,7 @@ class EngineTest extends TestCase
$engine = new Engine(); $engine = new Engine();
$this->expectException(Exception::class); $this->expectException(Exception::class);
$this->expectExceptionMessage('Cannot override an existing framework method.'); $this->expectExceptionMessage('Cannot override an existing framework method.');
$engine->map('_start', function () { $engine->map('_start', function () {});
});
} }
public function testRegisterExistingMethod(): void public function testRegisterExistingMethod(): void
@ -111,7 +110,7 @@ class EngineTest extends TestCase
public function testSetArrayOfValues(): void public function testSetArrayOfValues(): void
{ {
$engine = new Engine(); $engine = new Engine();
$engine->set([ 'key1' => 'value1', 'key2' => 'value2']); $engine->set(['key1' => 'value1', 'key2' => 'value2']);
$this->assertEquals('value1', $engine->get('key1')); $this->assertEquals('value1', $engine->get('key1'));
$this->assertEquals('value2', $engine->get('key2')); $this->assertEquals('value2', $engine->get('key2'));
} }
@ -153,8 +152,8 @@ class EngineTest extends TestCase
$this->expectOutputString('<h1>404 Not Found</h1><h3>The page you have requested could not be found.</h3>'); $this->expectOutputString('<h1>404 Not Found</h1><h3>The page you have requested could not be found.</h3>');
$engine->start(); $engine->start();
} }
public function testStartWithRouteButReturnedValueThrows404V2OutputBuffering(): void public function testStartWithRouteButReturnedValueThrows404V2OutputBuffering(): void
{ {
$_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['REQUEST_URI'] = '/someRoute'; $_SERVER['REQUEST_URI'] = '/someRoute';
@ -165,7 +164,7 @@ class EngineTest extends TestCase
return $this->initialized; return $this->initialized;
} }
}; };
$engine->set('flight.v2.output_buffering', true); $engine->set('flight.v2.output_buffering', true);
$engine->route('/someRoute', function () { $engine->route('/someRoute', function () {
echo 'i ran'; echo 'i ran';
return true; return true;
@ -185,18 +184,18 @@ class EngineTest extends TestCase
return $this->initialized; return $this->initialized;
} }
}; };
// First route that returns true (should continue routing) // First route that returns true (should continue routing)
$engine->route('/someRoute', function () { $engine->route('/someRoute', function () {
echo 'first route ran, '; echo 'first route ran, ';
return true; return true;
}, true); }, true);
// Second route that should be found and executed // Second route that should be found and executed
$engine->route('/someRoute', function () { $engine->route('/someRoute', function () {
echo 'second route executed!'; echo 'second route executed!';
}, true); }, true);
$this->expectOutputString('first route ran, second route executed!'); $this->expectOutputString('first route ran, second route executed!');
$engine->start(); $engine->start();
} }
@ -211,13 +210,13 @@ class EngineTest extends TestCase
{ {
return $this->initialized; return $this->initialized;
} }
public function getLoader() public function getLoader()
{ {
return $this->loader; return $this->loader;
} }
}; };
// Mock response to prevent actual headers // Mock response to prevent actual headers
$engine->getLoader()->register('response', function () { $engine->getLoader()->register('response', function () {
return new class extends Response { return new class extends Response {
@ -227,23 +226,23 @@ class EngineTest extends TestCase
} }
}; };
}); });
// First route that returns true and matches POST // First route that returns true and matches POST
$engine->route('POST /someRoute', function () { $engine->route('POST /someRoute', function () {
echo 'first POST route ran, '; echo 'first POST route ran, ';
return true; return true;
}, true); }, true);
// Second route that matches URL but wrong method (GET) - should be captured for 405 // Second route that matches URL but wrong method (GET) - should be captured for 405
$engine->route('GET /someRoute', function () { $engine->route('GET /someRoute', function () {
echo 'should not execute'; echo 'should not execute';
}, true); }, true);
// Third route that matches POST and should execute // Third route that matches POST and should execute
$engine->route('POST /someRoute', function () { $engine->route('POST /someRoute', function () {
echo 'second POST route executed!'; echo 'second POST route executed!';
}, true); }, true);
$this->expectOutputString('first POST route ran, second POST route executed!'); $this->expectOutputString('first POST route ran, second POST route executed!');
$engine->start(); $engine->start();
} }
@ -259,46 +258,46 @@ class EngineTest extends TestCase
return $this->initialized; return $this->initialized;
} }
}; };
// Route that returns true (continues iteration) // Route that returns true (continues iteration)
$engine->route('/someRoute', function () { $engine->route('/someRoute', function () {
echo 'first route ran, '; echo 'first route ran, ';
return true; return true;
}, true); }, true);
// Route with different URL that won't match // Route with different URL that won't match
$engine->route('/differentRoute', function () { $engine->route('/differentRoute', function () {
echo 'should not execute'; echo 'should not execute';
}, true); }, true);
// No more matching routes - should reach end of iterator and return 404 // No more matching routes - should reach end of iterator and return 404
$this->expectOutputString('<h1>404 Not Found</h1><h3>The page you have requested could not be found.</h3>'); $this->expectOutputString('<h1>404 Not Found</h1><h3>The page you have requested could not be found.</h3>');
$engine->start(); $engine->start();
} }
public function testDoubleStart(): void public function testDoubleStart(): void
{ {
$engine = new Engine(); $engine = new Engine();
$engine->route('/someRoute', function () { $engine->route('/someRoute', function () {
echo 'i ran'; echo 'i ran';
}, true); }, true);
$engine->request()->url = '/someRoute'; $engine->request()->url = '/someRoute';
$engine->start(); $engine->start();
$request = $engine->request(); $request = $engine->request();
$response = $engine->response(); $response = $engine->response();
// This is pretending like this is embodied in a platform like swoole where // This is pretending like this is embodied in a platform like swoole where
// another request comes in while still holding all the same state. // another request comes in while still holding all the same state.
$_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['REQUEST_URI'] = '/someRoute'; $_SERVER['REQUEST_URI'] = '/someRoute';
$engine->start(); $engine->start();
$this->assertFalse($request === $engine->request()); $this->assertFalse($request === $engine->request());
$this->assertFalse($response === $engine->response()); $this->assertFalse($response === $engine->response());
$this->expectOutputString('i rani ran'); $this->expectOutputString('i rani ran');
} }
public function testStopWithCode(): void public function testStopWithCode(): void
{ {
@ -323,7 +322,7 @@ class EngineTest extends TestCase
$this->assertEquals(500, $engine->response()->status()); $this->assertEquals(500, $engine->response()->status());
} }
public function testStopWithCodeV2OutputBuffering(): void public function testStopWithCodeV2OutputBuffering(): void
{ {
$engine = new class extends Engine { $engine = new class extends Engine {
public function getLoader() public function getLoader()
@ -340,13 +339,13 @@ class EngineTest extends TestCase
} }
}; };
}); });
$engine->set('flight.v2.output_buffering', true); $engine->set('flight.v2.output_buffering', true);
$engine->route('/testRoute', function () use ($engine) { $engine->route('/testRoute', function () use ($engine) {
echo 'I am a teapot'; echo 'I am a teapot';
$engine->stop(500); $engine->stop(500);
}); });
$engine->request()->url = '/testRoute'; $engine->request()->url = '/testRoute';
$engine->start(); $engine->start();
$this->expectOutputString('I am a teapot'); $this->expectOutputString('I am a teapot');
$this->assertEquals(500, $engine->response()->status()); $this->assertEquals(500, $engine->response()->status());
} }
@ -409,7 +408,7 @@ class EngineTest extends TestCase
$this->expectOutputString(''); $this->expectOutputString('');
} }
public function testOptionsRoute(): void public function testOptionsRoute(): void
{ {
$engine = new Engine(); $engine = new Engine();
$engine->route('GET /someRoute', function () { $engine->route('GET /someRoute', function () {
@ -421,7 +420,7 @@ class EngineTest extends TestCase
// No body should be sent // No body should be sent
$this->expectOutputString(''); $this->expectOutputString('');
$this->assertEquals('GET, HEAD, OPTIONS', $engine->response()->headers()['Allow']); $this->assertEquals('GET, HEAD, OPTIONS', $engine->response()->headers()['Allow']);
} }
public function testHalt(): void public function testHalt(): void
@ -498,46 +497,46 @@ class EngineTest extends TestCase
$engine->json(['key1' => 'value1', 'key2' => 'value2']); $engine->json(['key1' => 'value1', 'key2' => 'value2']);
$this->assertEquals('application/json', $engine->response()->headers()['Content-Type']); $this->assertEquals('application/json', $engine->response()->headers()['Content-Type']);
$this->assertEquals(200, $engine->response()->status()); $this->assertEquals(200, $engine->response()->status());
$this->assertEquals('{"key1":"value1","key2":"value2"}', $engine->response()->getBody()); $this->assertEquals('{"key1":"value1","key2":"value2"}', $engine->response()->getBody());
} }
public function testJsonWithDuplicateDefaultFlags() public function testJsonWithDuplicateDefaultFlags()
{
$engine = new Engine();
$flags = JSON_HEX_TAG | JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
// utf8 emoji
$engine->json(['key1' => 'value1', 'key2' => 'value2', 'utf8_emoji' => '😀'], 201, true, '', $flags);
$this->assertEquals('application/json', $engine->response()->headers()['Content-Type']);
$this->assertEquals(201, $engine->response()->status());
$this->assertEquals('{"key1":"value1","key2":"value2","utf8_emoji":"😀"}', $engine->response()->getBody());
}
public function testJsonThrowOnErrorByDefault(): void
{
$engine = new Engine();
$this->expectException(Exception::class);
$this->expectExceptionMessage('Malformed UTF-8 characters, possibly incorrectly encoded');
$engine->json(['key1' => 'value1', 'key2' => 'value2', 'utf8_emoji' => "\xB1\x31"]);
}
public function testJsonV2OutputBuffering(): void
{ {
$engine = new Engine(); $engine = new Engine();
$engine->response()->v2_output_buffering = true; $flags = JSON_HEX_TAG | JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
// utf8 emoji
$engine->json(['key1' => 'value1', 'key2' => 'value2', 'utf8_emoji' => '😀'], 201, true, '', $flags);
$this->assertEquals('application/json', $engine->response()->headers()['Content-Type']);
$this->assertEquals(201, $engine->response()->status());
$this->assertEquals('{"key1":"value1","key2":"value2","utf8_emoji":"😀"}', $engine->response()->getBody());
}
public function testJsonThrowOnErrorByDefault(): void
{
$engine = new Engine();
$this->expectException(Exception::class);
$this->expectExceptionMessage('Malformed UTF-8 characters, possibly incorrectly encoded');
$engine->json(['key1' => 'value1', 'key2' => 'value2', 'utf8_emoji' => "\xB1\x31"]);
}
public function testJsonV2OutputBuffering(): void
{
$engine = new Engine();
$engine->response()->v2_output_buffering = true;
$engine->json(['key1' => 'value1', 'key2' => 'value2']); $engine->json(['key1' => 'value1', 'key2' => 'value2']);
$this->expectOutputString('{"key1":"value1","key2":"value2"}'); $this->expectOutputString('{"key1":"value1","key2":"value2"}');
$this->assertEquals('application/json', $engine->response()->headers()['Content-Type']); $this->assertEquals('application/json', $engine->response()->headers()['Content-Type']);
$this->assertEquals(200, $engine->response()->status()); $this->assertEquals(200, $engine->response()->status());
} }
public function testJsonHalt(): void public function testJsonHalt(): void
{ {
$engine = new Engine(); $engine = new Engine();
$this->expectOutputString('{"key1":"value1","key2":"value2"}'); $this->expectOutputString('{"key1":"value1","key2":"value2"}');
$engine->jsonHalt(['key1' => 'value1', 'key2' => 'value2']); $engine->jsonHalt(['key1' => 'value1', 'key2' => 'value2']);
$this->assertEquals('application/json', $engine->response()->headers()['Content-Type']); $this->assertEquals('application/json', $engine->response()->headers()['Content-Type']);
$this->assertEquals(200, $engine->response()->status()); $this->assertEquals(200, $engine->response()->status());
$this->assertEquals('{"key1":"value1","key2":"value2"}', $engine->response()->getBody()); $this->assertEquals('{"key1":"value1","key2":"value2"}', $engine->response()->getBody());
} }
public function testJsonP(): void public function testJsonP(): void
@ -547,13 +546,13 @@ class EngineTest extends TestCase
$engine->jsonp(['key1' => 'value1', 'key2' => 'value2']); $engine->jsonp(['key1' => 'value1', 'key2' => 'value2']);
$this->assertEquals('application/javascript; charset=utf-8', $engine->response()->headers()['Content-Type']); $this->assertEquals('application/javascript; charset=utf-8', $engine->response()->headers()['Content-Type']);
$this->assertEquals(200, $engine->response()->status()); $this->assertEquals(200, $engine->response()->status());
$this->assertEquals('whatever({"key1":"value1","key2":"value2"});', $engine->response()->getBody()); $this->assertEquals('whatever({"key1":"value1","key2":"value2"});', $engine->response()->getBody());
} }
public function testJsonPV2OutputBuffering(): void public function testJsonPV2OutputBuffering(): void
{ {
$engine = new Engine(); $engine = new Engine();
$engine->response()->v2_output_buffering = true; $engine->response()->v2_output_buffering = true;
$engine->request()->query['jsonp'] = 'whatever'; $engine->request()->query['jsonp'] = 'whatever';
$engine->jsonp(['key1' => 'value1', 'key2' => 'value2']); $engine->jsonp(['key1' => 'value1', 'key2' => 'value2']);
$this->expectOutputString('whatever({"key1":"value1","key2":"value2"});'); $this->expectOutputString('whatever({"key1":"value1","key2":"value2"});');
@ -570,10 +569,10 @@ class EngineTest extends TestCase
$this->assertEquals(200, $engine->response()->status()); $this->assertEquals(200, $engine->response()->status());
} }
public function testJsonpBadParamV2OutputBuffering(): void public function testJsonpBadParamV2OutputBuffering(): void
{ {
$engine = new Engine(); $engine = new Engine();
$engine->response()->v2_output_buffering = true; $engine->response()->v2_output_buffering = true;
$engine->jsonp(['key1' => 'value1', 'key2' => 'value2']); $engine->jsonp(['key1' => 'value1', 'key2' => 'value2']);
$this->expectOutputString('({"key1":"value1","key2":"value2"});'); $this->expectOutputString('({"key1":"value1","key2":"value2"});');
$this->assertEquals('application/javascript; charset=utf-8', $engine->response()->headers()['Content-Type']); $this->assertEquals('application/javascript; charset=utf-8', $engine->response()->headers()['Content-Type']);
@ -608,7 +607,7 @@ class EngineTest extends TestCase
$engine = new Engine; $engine = new Engine;
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = 'Fri, 13 Feb 2009 23:31:30 GMT'; $_SERVER['HTTP_IF_MODIFIED_SINCE'] = 'Fri, 13 Feb 2009 23:31:30 GMT';
$engine->lastModified(1234567890); $engine->lastModified(1234567890);
$this->assertTrue(empty($engine->response()->headers()['Last-Modified'])); $this->assertTrue(empty($engine->response()->headers()['Last-Modified']));
$this->assertEquals(304, $engine->response()->status()); $this->assertEquals(304, $engine->response()->status());
} }
@ -618,7 +617,7 @@ class EngineTest extends TestCase
$engine->route('/path1/@param:[0-9]{3}', function () { $engine->route('/path1/@param:[0-9]{3}', function () {
echo 'I win'; echo 'I win';
}, false, 'path1'); }, false, 'path1');
$url = $engine->getUrl('path1', [ 'param' => 123 ]); $url = $engine->getUrl('path1', ['param' => 123]);
$this->assertEquals('/path1/123', $url); $this->assertEquals('/path1/123', $url);
} }
@ -628,7 +627,7 @@ class EngineTest extends TestCase
$engine->route('/item/@item_param:[a-z0-9]{16}/by-status/@token:[a-z0-9]{16}', function () { $engine->route('/item/@item_param:[a-z0-9]{16}/by-status/@token:[a-z0-9]{16}', function () {
echo 'I win'; echo 'I win';
}, false, 'path_item_1'); }, false, 'path_item_1');
$url = $engine->getUrl('path_item_1', [ 'item_param' => 1234567890123456, 'token' => 6543210987654321 ]); $url = $engine->getUrl('path_item_1', ['item_param' => 1234567890123456, 'token' => 6543210987654321]);
$this->assertEquals('/item/1234567890123456/by-status/6543210987654321', $url); $this->assertEquals('/item/1234567890123456/by-status/6543210987654321', $url);
} }
@ -666,8 +665,7 @@ class EngineTest extends TestCase
public function testMiddlewareCallableFunctionReturnFalse(): void public function testMiddlewareCallableFunctionReturnFalse(): void
{ {
$engine = new class extends Engine { $engine = new class extends Engine {};
};
$engine->route('/path1/@id', function ($id) { $engine->route('/path1/@id', function ($id) {
echo 'OK' . $id; echo 'OK' . $id;
}) })
@ -742,7 +740,7 @@ class EngineTest extends TestCase
$this->expectOutputString('OK123after123'); $this->expectOutputString('OK123after123');
} }
public function testMiddlewareClassStringNoContainer(): void public function testMiddlewareClassStringNoContainer(): void
{ {
$middleware = new class { $middleware = new class {
public function after($params) public function after($params)
@ -761,10 +759,10 @@ class EngineTest extends TestCase
$this->expectOutputString('OK123after123'); $this->expectOutputString('OK123after123');
} }
public function testMiddlewareClassStringWithContainer(): void public function testMiddlewareClassStringWithContainer(): void
{ {
$engine = new Engine(); $engine = new Engine();
$dice = new \Dice\Dice(); $dice = new \Dice\Dice();
$dice = $dice->addRule('*', [ $dice = $dice->addRule('*', [
'substitutions' => [ 'substitutions' => [
@ -774,7 +772,7 @@ class EngineTest extends TestCase
$engine->registerContainerHandler(function ($class, $params) use ($dice) { $engine->registerContainerHandler(function ($class, $params) use ($dice) {
return $dice->create($class, $params); return $dice->create($class, $params);
}); });
$engine->route('/path1/@id', function ($id) { $engine->route('/path1/@id', function ($id) {
echo 'OK' . $id; echo 'OK' . $id;
@ -794,8 +792,7 @@ class EngineTest extends TestCase
return false; return false;
} }
}; };
$engine = new class extends Engine { $engine = new class extends Engine {};
};
$engine->route('/path1/@id', function ($id) { $engine->route('/path1/@id', function ($id) {
echo 'OK' . $id; echo 'OK' . $id;
@ -850,7 +847,7 @@ class EngineTest extends TestCase
$engine = new Engine(); $engine = new Engine();
$engine->route('/path1/@id/subpath1/@another_id', function () { $engine->route('/path1/@id/subpath1/@another_id', function () {
echo 'OK'; echo 'OK';
})->addMiddleware([ $middleware, $middleware2 ]); })->addMiddleware([$middleware, $middleware2]);
$engine->request()->url = '/path1/123/subpath1/456'; $engine->request()->url = '/path1/123/subpath1/456';
$engine->start(); $engine->start();
@ -887,14 +884,15 @@ class EngineTest extends TestCase
$router->map('/@cool_id', function () { $router->map('/@cool_id', function () {
echo 'OK'; echo 'OK';
}); });
}, [ $middleware, $middleware2 ]); }, [$middleware, $middleware2]);
$engine->request()->url = '/path1/123/subpath1/456'; $engine->request()->url = '/path1/123/subpath1/456';
$engine->start(); $engine->start();
$this->expectOutputString('before456before123OKafter123456after123'); $this->expectOutputString('before456before123OKafter123456after123');
} }
public function testContainerBadClass() { public function testContainerBadClass()
{
$engine = new Engine(); $engine = new Engine();
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
@ -902,47 +900,50 @@ class EngineTest extends TestCase
$engine->registerContainerHandler('BadClass'); $engine->registerContainerHandler('BadClass');
} }
public function testContainerDice() { public function testContainerDice()
{
$engine = new Engine(); $engine = new Engine();
$dice = new \Dice\Dice(); $dice = new \Dice\Dice();
$dice = $dice->addRules([ $dice = $dice->addRules([
PdoWrapper::class => [ PdoWrapper::class => [
'shared' => true, 'shared' => true,
'constructParams' => [ 'sqlite::memory:' ] 'constructParams' => ['sqlite::memory:']
] ]
]); ]);
$engine->registerContainerHandler(function ($class, $params) use ($dice) { $engine->registerContainerHandler(function ($class, $params) use ($dice) {
return $dice->create($class, $params); return $dice->create($class, $params);
}); });
$engine->route('/container', Container::class.'->testTheContainer'); $engine->route('/container', Container::class . '->testTheContainer');
$engine->request()->url = '/container'; $engine->request()->url = '/container';
$engine->start(); $engine->start();
$this->expectOutputString('yay! I injected a collection, and it has 1 items'); $this->expectOutputString('yay! I injected a collection, and it has 1 items');
} }
public function testContainerDicePdoWrapperTest() { public function testContainerDicePdoWrapperTest()
{
$engine = new Engine(); $engine = new Engine();
$dice = new \Dice\Dice(); $dice = new \Dice\Dice();
$dice = $dice->addRules([ $dice = $dice->addRules([
PdoWrapper::class => [ PdoWrapper::class => [
'shared' => true, 'shared' => true,
'constructParams' => [ 'sqlite::memory:' ] 'constructParams' => ['sqlite::memory:']
] ]
]); ]);
$engine->registerContainerHandler(function ($class, $params) use ($dice) { $engine->registerContainerHandler(function ($class, $params) use ($dice) {
return $dice->create($class, $params); return $dice->create($class, $params);
}); });
$engine->route('/container', Container::class.'->testThePdoWrapper'); $engine->route('/container', Container::class . '->testThePdoWrapper');
$engine->request()->url = '/container'; $engine->request()->url = '/container';
$engine->start(); $engine->start();
$this->expectOutputString('Yay! I injected a PdoWrapper, and it returned the number 5 from the database!'); $this->expectOutputString('Yay! I injected a PdoWrapper, and it returned the number 5 from the database!');
} }
public function testContainerDiceFlightEngine() { public function testContainerDiceFlightEngine()
{
$engine = new Engine(); $engine = new Engine();
$engine->set('test_me_out', 'You got it boss!'); $engine->set('test_me_out', 'You got it boss!');
$dice = new \Dice\Dice(); $dice = new \Dice\Dice();
@ -954,44 +955,46 @@ class EngineTest extends TestCase
$engine->registerContainerHandler(function ($class, $params) use ($dice) { $engine->registerContainerHandler(function ($class, $params) use ($dice) {
return $dice->create($class, $params); return $dice->create($class, $params);
}); });
$engine->route('/container', ContainerDefault::class.'->echoTheContainer'); $engine->route('/container', ContainerDefault::class . '->echoTheContainer');
$engine->request()->url = '/container'; $engine->request()->url = '/container';
$engine->start(); $engine->start();
$this->expectOutputString('You got it boss!'); $this->expectOutputString('You got it boss!');
} }
public function testContainerDiceBadClass() { public function testContainerDiceBadClass()
{
$engine = new Engine(); $engine = new Engine();
$dice = new \Dice\Dice(); $dice = new \Dice\Dice();
$engine->registerContainerHandler(function ($class, $params) use ($dice) { $engine->registerContainerHandler(function ($class, $params) use ($dice) {
return $dice->create($class, $params); return $dice->create($class, $params);
}); });
$engine->route('/container', 'BadClass->testTheContainer'); $engine->route('/container', 'BadClass->testTheContainer');
$engine->request()->url = '/container'; $engine->request()->url = '/container';
$this->expectException(Exception::class); $this->expectException(Exception::class);
$this->expectExceptionMessage("Class 'BadClass' not found. Is it being correctly autoloaded with Flight::path()?"); $this->expectExceptionMessage("Class 'BadClass' not found. Is it being correctly autoloaded with Flight::path()?");
$engine->start(); $engine->start();
} }
public function testContainerDiceBadMethod() { public function testContainerDiceBadMethod()
{
$engine = new Engine(); $engine = new Engine();
$dice = new \Dice\Dice(); $dice = new \Dice\Dice();
$dice = $dice->addRules([ $dice = $dice->addRules([
PdoWrapper::class => [ PdoWrapper::class => [
'shared' => true, 'shared' => true,
'constructParams' => [ 'sqlite::memory:' ] 'constructParams' => ['sqlite::memory:']
] ]
]); ]);
$engine->registerContainerHandler(function ($class, $params) use ($dice) { $engine->registerContainerHandler(function ($class, $params) use ($dice) {
return $dice->create($class, $params); return $dice->create($class, $params);
}); });
$engine->route('/container', Container::class.'->badMethod'); $engine->route('/container', Container::class . '->badMethod');
$engine->request()->url = '/container'; $engine->request()->url = '/container';
$this->expectException(Exception::class); $this->expectException(Exception::class);
@ -1000,46 +1003,49 @@ class EngineTest extends TestCase
$engine->start(); $engine->start();
} }
public function testContainerPsr11(): void { public function testContainerPsr11(): void
{
$engine = new Engine(); $engine = new Engine();
$container = new \League\Container\Container(); $container = new \League\Container\Container();
$container->add(Container::class)->addArgument(Collection::class)->addArgument(PdoWrapper::class); $container->add(Container::class)->addArgument(Collection::class)->addArgument(PdoWrapper::class);
$container->add(Collection::class); $container->add(Collection::class);
$container->add(PdoWrapper::class)->addArgument('sqlite::memory:'); $container->add(PdoWrapper::class)->addArgument('sqlite::memory:');
$engine->registerContainerHandler($container); $engine->registerContainerHandler($container);
$engine->route('/container', Container::class.'->testTheContainer'); $engine->route('/container', Container::class . '->testTheContainer');
$engine->request()->url = '/container'; $engine->request()->url = '/container';
$engine->start(); $engine->start();
$this->expectOutputString('yay! I injected a collection, and it has 1 items'); $this->expectOutputString('yay! I injected a collection, and it has 1 items');
} }
public function testContainerPsr11ClassNotFound() { public function testContainerPsr11ClassNotFound()
{
$engine = new Engine(); $engine = new Engine();
$container = new \League\Container\Container(); $container = new \League\Container\Container();
$container->add(Container::class)->addArgument(Collection::class); $container->add(Container::class)->addArgument(Collection::class);
$container->add(Collection::class); $container->add(Collection::class);
$engine->registerContainerHandler($container); $engine->registerContainerHandler($container);
$engine->route('/container', 'BadClass->testTheContainer'); $engine->route('/container', 'BadClass->testTheContainer');
$engine->request()->url = '/container'; $engine->request()->url = '/container';
$this->expectException(Exception::class); $this->expectException(Exception::class);
$this->expectExceptionMessage("Class 'BadClass' not found. Is it being correctly autoloaded with Flight::path()?"); $this->expectExceptionMessage("Class 'BadClass' not found. Is it being correctly autoloaded with Flight::path()?");
$engine->start(); $engine->start();
} }
public function testContainerPsr11MethodNotFound(): void { public function testContainerPsr11MethodNotFound(): void
{
$engine = new Engine(); $engine = new Engine();
$container = new \League\Container\Container(); $container = new \League\Container\Container();
$container->add(Container::class)->addArgument(Collection::class)->addArgument(PdoWrapper::class); $container->add(Container::class)->addArgument(Collection::class)->addArgument(PdoWrapper::class);
$container->add(Collection::class); $container->add(Collection::class);
$container->add(PdoWrapper::class)->addArgument('sqlite::memory:'); $container->add(PdoWrapper::class)->addArgument('sqlite::memory:');
$engine->registerContainerHandler($container); $engine->registerContainerHandler($container);
$engine->route('/container', Container::class.'->badMethod'); $engine->route('/container', Container::class . '->badMethod');
$engine->request()->url = '/container'; $engine->request()->url = '/container';
$this->expectException(Exception::class); $this->expectException(Exception::class);
@ -1048,7 +1054,8 @@ class EngineTest extends TestCase
$engine->start(); $engine->start();
} }
public function testRouteFoundButBadMethod(): void { public function testRouteFoundButBadMethod(): void
{
$engine = new class extends Engine { $engine = new class extends Engine {
public function getLoader() public function getLoader()
{ {
@ -1068,30 +1075,30 @@ class EngineTest extends TestCase
}; };
}); });
$engine->route('POST /path1/@id', function ($id) { $engine->route('POST /path1/@id', function ($id) {
echo 'OK' . $id; echo 'OK' . $id;
}); });
$engine->route('GET /path2/@id', function ($id) { $engine->route('GET /path2/@id', function ($id) {
echo 'OK' . $id; echo 'OK' . $id;
}); });
$engine->route('PATCH /path3/@id', function ($id) { $engine->route('PATCH /path3/@id', function ($id) {
echo 'OK' . $id; echo 'OK' . $id;
}); });
$engine->request()->url = '/path1/123'; $engine->request()->url = '/path1/123';
$engine->request()->method = 'GET'; $engine->request()->method = 'GET';
$engine->start(); $engine->start();
$this->expectOutputString('Method Not Allowed. Allowed Methods are: POST, OPTIONS'); $this->expectOutputString('Method Not Allowed. Allowed Methods are: POST, OPTIONS');
$this->assertEquals(405, $engine->response()->status()); $this->assertEquals(405, $engine->response()->status());
$this->assertEquals('Method Not Allowed. Allowed Methods are: POST, OPTIONS', $engine->response()->getBody()); $this->assertEquals('Method Not Allowed. Allowed Methods are: POST, OPTIONS', $engine->response()->getBody());
$this->assertEquals('POST, OPTIONS', $engine->response()->headers()['Allow']); $this->assertEquals('POST, OPTIONS', $engine->response()->headers()['Allow']);
} }
public function testDownload(): void public function testDownload(): void
{ {
$engine = new class extends Engine { $engine = new class extends Engine {
public function getLoader() public function getLoader()
@ -1102,26 +1109,26 @@ class EngineTest extends TestCase
// doing this so we can overwrite some parts of the response // doing this so we can overwrite some parts of the response
$engine->getLoader()->register('response', function () { $engine->getLoader()->register('response', function () {
return new class extends Response { return new class extends Response {
public $headersSent = []; public $headersSent = [];
public function setRealHeader( public function setRealHeader(
string $header_string, string $header_string,
bool $replace = true, bool $replace = true,
int $response_code = 0 int $response_code = 0
): self { ): self {
$this->headersSent[] = $header_string; $this->headersSent[] = $header_string;
return $this; return $this;
} }
}; };
}); });
$tmpfile = tmpfile(); $tmpfile = tmpfile();
fwrite($tmpfile, 'I am a teapot'); fwrite($tmpfile, 'I am a teapot');
$streamPath = stream_get_meta_data($tmpfile)['uri']; $streamPath = stream_get_meta_data($tmpfile)['uri'];
$this->expectOutputString('I am a teapot'); $this->expectOutputString('I am a teapot');
$engine->download($streamPath); $engine->download($streamPath);
$this->assertContains('Content-Disposition: attachment; filename="'.basename($streamPath).'"', $engine->response()->headersSent); $this->assertContains('Content-Disposition: attachment; filename="' . basename($streamPath) . '"', $engine->response()->headersSent);
} }
public function testDownloadWithDefaultFileName(): void public function testDownloadWithDefaultFileName(): void
{ {
$engine = new class extends Engine { $engine = new class extends Engine {
public function getLoader() public function getLoader()
@ -1132,30 +1139,30 @@ class EngineTest extends TestCase
// doing this so we can overwrite some parts of the response // doing this so we can overwrite some parts of the response
$engine->getLoader()->register('response', function () { $engine->getLoader()->register('response', function () {
return new class extends Response { return new class extends Response {
public $headersSent = []; public $headersSent = [];
public function setRealHeader( public function setRealHeader(
string $header_string, string $header_string,
bool $replace = true, bool $replace = true,
int $response_code = 0 int $response_code = 0
): self { ): self {
$this->headersSent[] = $header_string; $this->headersSent[] = $header_string;
return $this; return $this;
} }
}; };
}); });
$tmpfile = tmpfile(); $tmpfile = tmpfile();
fwrite($tmpfile, 'I am a teapot'); fwrite($tmpfile, 'I am a teapot');
$streamPath = stream_get_meta_data($tmpfile)['uri']; $streamPath = stream_get_meta_data($tmpfile)['uri'];
$this->expectOutputString('I am a teapot'); $this->expectOutputString('I am a teapot');
$engine->download($streamPath, 'something.txt'); $engine->download($streamPath, 'something.txt');
$this->assertContains('Content-Disposition: attachment; filename="something.txt"', $engine->response()->headersSent); $this->assertContains('Content-Disposition: attachment; filename="something.txt"', $engine->response()->headersSent);
} }
public function testDownloadBadPath() { public function testDownloadBadPath()
$engine = new Engine(); {
$this->expectException(Exception::class); $engine = new Engine();
$this->expectExceptionMessage("/path/to/nowhere cannot be found."); $this->expectException(Exception::class);
$engine->download('/path/to/nowhere'); $this->expectExceptionMessage("/path/to/nowhere cannot be found.");
} $engine->download('/path/to/nowhere');
}
} }

@ -123,6 +123,7 @@ class EventSystemTest extends TestCase
$called = true; $called = true;
}); });
Flight::onEvent('test.event', function () { Flight::onEvent('test.event', function () {
//
}); });
$this->assertTrue($called, 'Overridden onEvent method should be called.'); $this->assertTrue($called, 'Overridden onEvent method should be called.');
} }
@ -231,12 +232,19 @@ class EventSystemTest extends TestCase
*/ */
public function testHasListeners(): void public function testHasListeners(): void
{ {
$this->assertFalse(Flight::eventDispatcher()->hasListeners('test.event'), 'Event should not have listeners before registration'); $this->assertFalse(
Flight::eventDispatcher()->hasListeners('test.event'),
'Event should not have listeners before registration'
);
Flight::onEvent('test.event', function () { Flight::onEvent('test.event', function () {
//
}); });
$this->assertTrue(Flight::eventDispatcher()->hasListeners('test.event'), 'Event should have listeners after registration'); $this->assertTrue(
Flight::eventDispatcher()->hasListeners('test.event'),
'Event should have listeners after registration'
);
} }
/** /**
@ -245,11 +253,16 @@ class EventSystemTest extends TestCase
public function testGetListeners(): void public function testGetListeners(): void
{ {
$callback1 = function () { $callback1 = function () {
//
}; };
$callback2 = function () { $callback2 = function () {
//
}; };
$this->assertEmpty(Flight::eventDispatcher()->getListeners('test.event'), 'Event should have no listeners before registration'); $this->assertEmpty(
Flight::eventDispatcher()->getListeners('test.event'),
'Event should have no listeners before registration'
);
Flight::onEvent('test.event', $callback1); Flight::onEvent('test.event', $callback1);
Flight::onEvent('test.event', $callback2); Flight::onEvent('test.event', $callback2);
@ -275,11 +288,16 @@ class EventSystemTest extends TestCase
*/ */
public function testGetAllRegisteredEvents(): void public function testGetAllRegisteredEvents(): void
{ {
$this->assertEmpty(Flight::eventDispatcher()->getAllRegisteredEvents(), 'No events should be registered initially'); $this->assertEmpty(
Flight::eventDispatcher()->getAllRegisteredEvents(),
'No events should be registered initially'
);
Flight::onEvent('test.event1', function () { Flight::onEvent('test.event1', function () {
//
}); });
Flight::onEvent('test.event2', function () { Flight::onEvent('test.event2', function () {
//
}); });
$events = Flight::eventDispatcher()->getAllRegisteredEvents(); $events = Flight::eventDispatcher()->getAllRegisteredEvents();
@ -303,7 +321,11 @@ class EventSystemTest extends TestCase
Flight::onEvent('test.event', $callback1); Flight::onEvent('test.event', $callback1);
Flight::onEvent('test.event', $callback2); Flight::onEvent('test.event', $callback2);
$this->assertCount(2, Flight::eventDispatcher()->getListeners('test.event'), 'Event should have two listeners initially'); $this->assertCount(
2,
Flight::eventDispatcher()->getListeners('test.event'),
'Event should have two listeners initially'
);
Flight::eventDispatcher()->removeListener('test.event', $callback1); Flight::eventDispatcher()->removeListener('test.event', $callback1);
@ -318,19 +340,36 @@ class EventSystemTest extends TestCase
public function testRemoveAllListeners(): void public function testRemoveAllListeners(): void
{ {
Flight::onEvent('test.event', function () { Flight::onEvent('test.event', function () {
//
}); });
Flight::onEvent('test.event', function () { Flight::onEvent('test.event', function () {
//
}); });
Flight::onEvent('another.event', function () { Flight::onEvent('another.event', function () {
//
}); });
$this->assertTrue(Flight::eventDispatcher()->hasListeners('test.event'), 'Event should have listeners before removal'); $this->assertTrue(
$this->assertTrue(Flight::eventDispatcher()->hasListeners('another.event'), 'Another event should have listeners'); Flight::eventDispatcher()->hasListeners('test.event'),
'Event should have listeners before removal'
);
$this->assertTrue(
Flight::eventDispatcher()->hasListeners('another.event'),
'Another event should have listeners'
);
Flight::eventDispatcher()->removeAllListeners('test.event'); Flight::eventDispatcher()->removeAllListeners('test.event');
$this->assertFalse(Flight::eventDispatcher()->hasListeners('test.event'), 'Event should have no listeners after removal'); $this->assertFalse(
$this->assertTrue(Flight::eventDispatcher()->hasListeners('another.event'), 'Another event should still have listeners'); Flight::eventDispatcher()->hasListeners('test.event'),
'Event should have no listeners after removal'
);
$this->assertTrue(
Flight::eventDispatcher()->hasListeners('another.event'),
'Another event should still have listeners'
);
} }
/** /**
@ -340,9 +379,13 @@ class EventSystemTest extends TestCase
{ {
// Should not throw any errors // Should not throw any errors
Flight::eventDispatcher()->removeListener('nonexistent.event', function () { Flight::eventDispatcher()->removeListener('nonexistent.event', function () {
//
}); });
Flight::eventDispatcher()->removeAllListeners('nonexistent.event'); Flight::eventDispatcher()->removeAllListeners('nonexistent.event');
$this->assertTrue(true, 'Removing listeners for nonexistent events should not throw errors'); $this->assertTrue(
true,
'Removing listeners for nonexistent events should not throw errors'
);
} }
} }

@ -121,7 +121,7 @@ class LoaderTest extends TestCase
{ {
$this->loader->register('g', User::class); $this->loader->register('g', User::class);
$current_class = $this->loader->get('g'); $current_class = $this->loader->get('g');
$this->assertEquals([ User::class, [], null ], $current_class); $this->assertEquals([User::class, [], null], $current_class);
$this->loader->unregister('g'); $this->loader->unregister('g');
$unregistered_class_result = $this->loader->get('g'); $unregistered_class_result = $this->loader->get('g');
$this->assertNull($unregistered_class_result); $this->assertNull($unregistered_class_result);
@ -129,7 +129,7 @@ class LoaderTest extends TestCase
public function testNewInstance6Params(): void public function testNewInstance6Params(): void
{ {
$TesterClass = $this->loader->newInstance(TesterClass::class, ['Bob','Fred', 'Joe', 'Jane', 'Sally', 'Suzie']); $TesterClass = $this->loader->newInstance(TesterClass::class, ['Bob', 'Fred', 'Joe', 'Jane', 'Sally', 'Suzie']);
$this->assertEquals('Bob', $TesterClass->param1); $this->assertEquals('Bob', $TesterClass->param1);
$this->assertEquals('Fred', $TesterClass->param2); $this->assertEquals('Fred', $TesterClass->param2);
$this->assertEquals('Joe', $TesterClass->param3); $this->assertEquals('Joe', $TesterClass->param3);

@ -99,7 +99,7 @@ class PdoWrapperTest extends TestCase
public function testFetchAllWithNamedParams(): void public function testFetchAllWithNamedParams(): void
{ {
$rows = $this->pdo_wrapper->fetchAll('SELECT * FROM test WHERE name = :name', [ 'name' => 'two']); $rows = $this->pdo_wrapper->fetchAll('SELECT * FROM test WHERE name = :name', ['name' => 'two']);
$this->assertCount(1, $rows); $this->assertCount(1, $rows);
$this->assertEquals(2, $rows[0]['id']); $this->assertEquals(2, $rows[0]['id']);
$this->assertEquals('two', $rows[0]['name']); $this->assertEquals('two', $rows[0]['name']);
@ -107,19 +107,19 @@ class PdoWrapperTest extends TestCase
public function testFetchAllWithInInt(): void public function testFetchAllWithInInt(): void
{ {
$rows = $this->pdo_wrapper->fetchAll('SELECT id FROM test WHERE id IN(? )', [ [1,2 ]]); $rows = $this->pdo_wrapper->fetchAll('SELECT id FROM test WHERE id IN(? )', [[1, 2]]);
$this->assertEquals(2, count($rows)); $this->assertEquals(2, count($rows));
} }
public function testFetchAllWithInString(): void public function testFetchAllWithInString(): void
{ {
$rows = $this->pdo_wrapper->fetchAll('SELECT id FROM test WHERE name IN(?)', [ ['one','two' ]]); $rows = $this->pdo_wrapper->fetchAll('SELECT id FROM test WHERE name IN(?)', [['one', 'two']]);
$this->assertEquals(2, count($rows)); $this->assertEquals(2, count($rows));
} }
public function testFetchAllWithInStringCommas(): void public function testFetchAllWithInStringCommas(): void
{ {
$rows = $this->pdo_wrapper->fetchAll('SELECT id FROM test WHERE id > ? AND name IN( ?) ', [ 0, 'one,two' ]); $rows = $this->pdo_wrapper->fetchAll('SELECT id FROM test WHERE id > ? AND name IN( ?) ', [0, 'one,two']);
$this->assertEquals(2, count($rows)); $this->assertEquals(2, count($rows));
} }
@ -178,7 +178,15 @@ class PdoWrapperTest extends TestCase
$queriesData = null; $queriesData = null;
$dispatcher = EventDispatcher::getInstance(); $dispatcher = EventDispatcher::getInstance();
$dispatcher->on('flight.db.queries', function ($conn, $queries) use (&$eventTriggered, &$connectionData, &$queriesData) {
$dispatcher->on('flight.db.queries', function (
$conn,
$queries
) use (
&$eventTriggered,
&$connectionData,
&$queriesData
) {
$eventTriggered = true; $eventTriggered = true;
$connectionData = $conn; $connectionData = $conn;
$queriesData = $queries; $queriesData = $queries;

@ -31,6 +31,6 @@ class RenderTest extends TestCase
$this->app->render('hello', ['name' => 'Bob'], 'content'); $this->app->render('hello', ['name' => 'Bob'], 'content');
$this->app->render('layouts/layout'); $this->app->render('layouts/layout');
$this->expectOutputString('<html>Hello, Bob!</html>'); $this->expectOutputString("<body>Hello, Bob!</body>\n");
} }
} }

@ -266,13 +266,13 @@ class RequestBodyParserTest extends TestCase
// Use PHP CLI with -d to set upload_max_filesize (ini_set can't change this setting in many SAPIs) // Use PHP CLI with -d to set upload_max_filesize (ini_set can't change this setting in many SAPIs)
$cases = [ $cases = [
// No unit yields default branch which returns 0 in current implementation // No unit yields default branch which returns 0 in current implementation
['1' , 0], // no unit and number too small ['1', 0], // no unit and number too small
['1K' , 1024], ['1K', 1024],
['2M' , 2 * 1024 * 1024], ['2M', 2 * 1024 * 1024],
['1G' , 1024 * 1024 * 1024], ['1G', 1024 * 1024 * 1024],
['1T' , 1024 * 1024 * 1024 * 1024], ['1T', 1024 * 1024 * 1024 * 1024],
['1Z' , 0 ], // Unknown unit and number too small ['1Z', 0], // Unknown unit and number too small
[ '1024', 1024 ] ['1024', 1024]
]; ];
foreach ($cases as [$iniVal, $expected]) { foreach ($cases as [$iniVal, $expected]) {
@ -321,7 +321,9 @@ class RequestBodyParserTest extends TestCase
$parts[] = "Content-Type: text/plain\r\n\r\nignoredvalue"; $parts[] = "Content-Type: text/plain\r\n\r\nignoredvalue";
// C: header too long (>16384) => skipped // C: header too long (>16384) => skipped
$longHeader = 'Content-Disposition: form-data; name="toolong"; filename="toolong.txt"; ' . str_repeat('x', 16500); $longHeader = 'Content-Disposition: form-data; name="toolong"; filename="toolong.txt"; '
. str_repeat('x', 16500);
$parts[] = $longHeader . "\r\n\r\nlongvalue"; $parts[] = $longHeader . "\r\n\r\nlongvalue";
// D: header line without colon gets skipped but rest processed; becomes non-file field // D: header line without colon gets skipped but rest processed; becomes non-file field
@ -334,14 +336,24 @@ class RequestBodyParserTest extends TestCase
$parts[] = "Content-Disposition: form-data; name=\"\"; filename=\"empty.txt\"\r\n\r\nemptyNameValue"; $parts[] = "Content-Disposition: form-data; name=\"\"; filename=\"empty.txt\"\r\n\r\nemptyNameValue";
// G: invalid filename triggers sanitized fallback // G: invalid filename triggers sanitized fallback
$parts[] = "Content-Disposition: form-data; name=\"filebad\"; filename=\"a*b?.txt\"\r\nContent-Type: text/plain\r\n\r\nFILEBAD"; $parts[] = "Content-Disposition: form-data; "
. "name=\"filebad\"; "
. "filename=\"a*b?.txt\"\r\nContent-Type: text/plain\r\n\r\nFILEBAD";
// H1 & H2: two files same key for aggregation logic (arrays) // H1 & H2: two files same key for aggregation logic (arrays)
$parts[] = "Content-Disposition: form-data; name=\"filemulti\"; filename=\"one.txt\"\r\nContent-Type: text/plain\r\n\r\nONE"; $parts[] = "Content-Disposition: form-data; "
$parts[] = "Content-Disposition: form-data; name=\"filemulti\"; filename=\"two.txt\"\r\nContent-Type: text/plain\r\n\r\nTWO"; . "name=\"filemulti\"; "
. "filename=\"one.txt\"\r\nContent-Type: text/plain\r\n\r\nONE";
$parts[] = "Content-Disposition: form-data; "
. "name=\"filemulti\"; "
. "filename=\"two.txt\"\r\nContent-Type: text/plain\r\n\r\nTWO";
// I: file exceeding total bytes triggers UPLOAD_ERR_INI_SIZE // I: file exceeding total bytes triggers UPLOAD_ERR_INI_SIZE
$parts[] = "Content-Disposition: form-data; name=\"filebig\"; filename=\"big.txt\"\r\nContent-Type: text/plain\r\n\r\n" . str_repeat('A', 10); $parts[] = "Content-Disposition: form-data; "
. "name=\"filebig\"; "
. "filename=\"big.txt\"\r\nContent-Type: text/plain\r\n\r\n"
. str_repeat('A', 10);
// Build full body // Build full body
$body = ''; $body = '';
@ -394,11 +406,31 @@ class RequestBodyParserTest extends TestCase
public function testMultipartEmptyArrayNameStripped(): void public function testMultipartEmptyArrayNameStripped(): void
{ {
// Covers line where keyName becomes empty after removing [] (name="[]") and header param extraction (preg_match_all) // Covers line where keyName becomes empty after removing [] (name="[]")
// and header param extraction (preg_match_all)
$boundary = 'BOUNDARYEMPTY'; $boundary = 'BOUNDARYEMPTY';
$validFilePart = "Content-Disposition: form-data; name=\"fileok\"; filename=\"ok.txt\"\r\nContent-Type: text/plain\r\n\r\nOK";
$emptyNameFilePart = "Content-Disposition: form-data; name=\"[]\"; filename=\"empty.txt\"\r\nContent-Type: text/plain\r\n\r\nSHOULD_SKIP"; $validFilePart = "Content-Disposition: form-data; "
$body = '--' . $boundary . "\r\n" . $validFilePart . "\r\n" . '--' . $boundary . "\r\n" . $emptyNameFilePart . "\r\n" . '--' . $boundary . "--\r\n"; . "name=\"fileok\"; "
. "filename=\"ok.txt\"\r\nContent-Type: text/plain\r\n\r\nOK";
$emptyNameFilePart = "Content-Disposition: form-data; "
. "name=\"[]\"; "
. "filename=\"empty.txt\"\r\nContent-Type: text/plain\r\n\r\nSHOULD_SKIP";
$body = '--'
. $boundary
. "\r\n"
. $validFilePart
. "\r\n"
. '--'
. $boundary
. "\r\n"
. $emptyNameFilePart
. "\r\n"
. '--'
. $boundary
. "--\r\n";
$tmp = tmpfile(); $tmp = tmpfile();
$path = stream_get_meta_data($tmp)['uri']; $path = stream_get_meta_data($tmp)['uri'];

@ -197,7 +197,7 @@ class RequestTest extends TestCase
'stream_path' => $stream_path, 'stream_path' => $stream_path,
'method' => 'POST' 'method' => 'POST'
]); ]);
$this->assertEquals([ 'foo' => 'bar' ], $request->data->getData()); $this->assertEquals(['foo' => 'bar'], $request->data->getData());
$this->assertEquals('{"foo":"bar"}', $request->getBody()); $this->assertEquals('{"foo":"bar"}', $request->getBody());
} }
@ -430,7 +430,12 @@ class RequestTest extends TestCase
// Find best match first // Find best match first
$_SERVER['HTTP_ACCEPT'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'; $_SERVER['HTTP_ACCEPT'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
$request = new Request(); $request = new Request();
$this->assertEquals('application/xml', $request->negotiateContentType(['application/xml', 'application/json', 'text/html']));
$this->assertEquals('application/xml', $request->negotiateContentType([
'application/xml',
'application/json',
'text/html'
]));
// Find the first match // Find the first match
$_SERVER['HTTP_ACCEPT'] = 'application/json,text/html'; $_SERVER['HTTP_ACCEPT'] = 'application/json,text/html';

@ -412,6 +412,7 @@ class RouterTest extends TestCase
public function testRouteBeingReturned(): void public function testRouteBeingReturned(): void
{ {
$route = $this->router->map('/hi', function () { $route = $this->router->map('/hi', function () {
//
}); });
$route_in_router = $this->router->getRoutes()[0]; $route_in_router = $this->router->getRoutes()[0];
$this->assertSame($route, $route_in_router); $this->assertSame($route, $route_in_router);
@ -420,6 +421,7 @@ class RouterTest extends TestCase
public function testRouteSetAlias(): void public function testRouteSetAlias(): void
{ {
$route = $this->router->map('/hi', function () { $route = $this->router->map('/hi', function () {
//
}); });
$route->setAlias('hello'); $route->setAlias('hello');
$this->assertEquals('hello', $route->alias); $this->assertEquals('hello', $route->alias);
@ -773,7 +775,7 @@ class RouterTest extends TestCase
public function testStripMultipleSlashesFromUrlAndStillMatch(): void public function testStripMultipleSlashesFromUrlAndStillMatch(): void
{ {
$this->router->get('/', [ $this, 'ok' ]); $this->router->get('/', [$this, 'ok']);
$this->request->url = '///'; $this->request->url = '///';
$this->request->method = 'GET'; $this->request->method = 'GET';
$this->check('OK'); $this->check('OK');

@ -88,7 +88,13 @@ class SimplePdoTest extends TestCase
public function testRunQueryWithoutParamsWithMaxQueryMetrics(): void public function testRunQueryWithoutParamsWithMaxQueryMetrics(): void
{ {
$db = new class ('sqlite::memory:', null, null, null, ['maxQueryMetrics' => 2, 'trackApmQueries' => true]) extends SimplePdo { $db = new class(
'sqlite::memory:',
null,
null,
null,
['maxQueryMetrics' => 2, 'trackApmQueries' => true]
) extends SimplePdo {
public function getQueryMetrics(): array public function getQueryMetrics(): array
{ {
return $this->queryMetrics; return $this->queryMetrics;

@ -41,14 +41,17 @@ class UploadedFileTest extends TestCase
public function getFileErrorMessageTests(): array public function getFileErrorMessageTests(): array
{ {
return [ return [
[ UPLOAD_ERR_INI_SIZE, 'The uploaded file exceeds the upload_max_filesize directive in php.ini.', ], [UPLOAD_ERR_INI_SIZE, 'The uploaded file exceeds the upload_max_filesize directive in php.ini.',],
[ UPLOAD_ERR_FORM_SIZE, 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.', ], [
[ UPLOAD_ERR_PARTIAL, 'The uploaded file was only partially uploaded.', ], UPLOAD_ERR_FORM_SIZE,
[ UPLOAD_ERR_NO_FILE, 'No file was uploaded.', ], 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
[ UPLOAD_ERR_NO_TMP_DIR, 'Missing a temporary folder.', ], ],
[ UPLOAD_ERR_CANT_WRITE, 'Failed to write file to disk.', ], [UPLOAD_ERR_PARTIAL, 'The uploaded file was only partially uploaded.',],
[ UPLOAD_ERR_EXTENSION, 'A PHP extension stopped the file upload.', ], [UPLOAD_ERR_NO_FILE, 'No file was uploaded.',],
[ -1, 'An unknown error occurred. Error code: -1' ] [UPLOAD_ERR_NO_TMP_DIR, 'Missing a temporary folder.',],
[UPLOAD_ERR_CANT_WRITE, 'Failed to write file to disk.',],
[UPLOAD_ERR_EXTENSION, 'A PHP extension stopped the file upload.',],
[-1, 'An unknown error occurred. Error code: -1']
]; ];
} }

@ -104,7 +104,7 @@ class ViewTest extends TestCase
$this->view->render('world'); $this->view->render('world');
$this->expectOutputString('Hello world, Bob!'); $this->expectOutputString("Hello world, Bob!\n");
} }
public function testGetTemplateAbsolutePath(): void public function testGetTemplateAbsolutePath(): void

@ -33,6 +33,11 @@ class ContainerDefault
public function testUi(): void public function testUi(): void
{ {
echo '<span id="infotext">Route text:</span> The container successfully injected a value into the engine! Engine class: <b>' . get_class($this->app) . '</b> test_me_out Value: <b>' . $this->app->get('test_me_out') . '</b>'; echo '<span id="infotext">Route text:</span> '
. 'The container successfully injected a value into the engine! Engine class: <b>'
. get_class($this->app)
. '</b> test_me_out Value: <b>'
. $this->app->get('test_me_out')
. '</b>';
} }
} }

@ -9,6 +9,7 @@ class Factory
// Cannot be instantiated // Cannot be instantiated
private function __construct() private function __construct()
{ {
//
} }
public static function create(): self public static function create(): self

@ -21,7 +21,13 @@ class AiGenerateInstructionsCommandTest extends TestCase
self::$ou = __DIR__ . DIRECTORY_SEPARATOR . 'output.test' . uniqid('', true) . '.txt'; self::$ou = __DIR__ . DIRECTORY_SEPARATOR . 'output.test' . uniqid('', true) . '.txt';
file_put_contents(self::$in, ''); file_put_contents(self::$in, '');
file_put_contents(self::$ou, ''); file_put_contents(self::$ou, '');
$this->baseDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'flightphp-test-basedir-' . uniqid('', true) . DIRECTORY_SEPARATOR;
$this->baseDir = sys_get_temp_dir()
. DIRECTORY_SEPARATOR
. 'flightphp-test-basedir-'
. uniqid('', true)
. DIRECTORY_SEPARATOR;
if (!is_dir($this->baseDir)) { if (!is_dir($this->baseDir)) {
mkdir($this->baseDir, 0777, true); mkdir($this->baseDir, 0777, true);
} }

@ -95,7 +95,10 @@ PHP;
$app->add(new RouteCommand(['runway' => ['something' => 'else']])); $app->add(new RouteCommand(['runway' => ['something' => 'else']]));
@$app->handle(['runway', 'routes']); @$app->handle(['runway', 'routes']);
$this->assertStringContainsString('index_root not set in app/config/config.php', file_get_contents(static::$ou)); $this->assertStringContainsString(
'index_root not set in app/config/config.php',
file_get_contents(static::$ou)
);
} }
public function testGetRoutes(): void public function testGetRoutes(): void

@ -2,14 +2,14 @@
declare(strict_types=1); declare(strict_types=1);
namespace tests\groupcompactsyntax;
use Flight;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use tests\groupcompactsyntax\PostsController; use tests\groupcompactsyntax\PostsController;
use tests\groupcompactsyntax\TodosController; use tests\groupcompactsyntax\TodosController;
use tests\groupcompactsyntax\UsersController; use tests\groupcompactsyntax\UsersController;
require_once __DIR__ . '/UsersController.php';
require_once __DIR__ . '/PostsController.php';
final class FlightRouteCompactSyntaxTest extends TestCase final class FlightRouteCompactSyntaxTest extends TestCase
{ {
public function setUp(): void public function setUp(): void
@ -38,7 +38,7 @@ final class FlightRouteCompactSyntaxTest extends TestCase
public function testOptionsOnly(): void public function testOptionsOnly(): void
{ {
Flight::resource('/users', UsersController::class, [ Flight::resource('/users', UsersController::class, [
'only' => [ 'index', 'destroy' ] 'only' => ['index', 'destroy']
]); ]);
$routes = Flight::router()->getRoutes(); $routes = Flight::router()->getRoutes();
@ -100,7 +100,7 @@ final class FlightRouteCompactSyntaxTest extends TestCase
public function testOptionsExcept(): void public function testOptionsExcept(): void
{ {
Flight::resource('/todos', TodosController::class, [ Flight::resource('/todos', TodosController::class, [
'except' => [ 'create', 'store', 'update', 'destroy', 'edit' ] 'except' => ['create', 'store', 'update', 'destroy', 'edit']
]); ]);
$routes = Flight::router()->getRoutes(); $routes = Flight::router()->getRoutes();
@ -119,7 +119,7 @@ final class FlightRouteCompactSyntaxTest extends TestCase
public function testOptionsMiddlewareAndAliasBase(): void public function testOptionsMiddlewareAndAliasBase(): void
{ {
Flight::resource('/todos', TodosController::class, [ Flight::resource('/todos', TodosController::class, [
'middleware' => [ 'auth' ], 'middleware' => ['auth'],
'alias_base' => 'nothanks' 'alias_base' => 'nothanks'
]); ]);

@ -8,29 +8,36 @@ final class PostsController
{ {
public function index(): void public function index(): void
{ {
//
} }
public function show(string $id): void public function show(string $id): void
{ {
//
} }
public function create(): void public function create(): void
{ {
//
} }
public function store(): void public function store(): void
{ {
//
} }
public function edit(string $id): void public function edit(string $id): void
{ {
//
} }
public function update(string $id): void public function update(string $id): void
{ {
//
} }
public function destroy(string $id): void public function destroy(string $id): void
{ {
//
} }
} }

@ -8,9 +8,11 @@ final class TodosController
{ {
public function index(): void public function index(): void
{ {
//
} }
public function show(string $id): void public function show(string $id): void
{ {
//
} }
} }

@ -2,6 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace
class ExampleClass class ExampleClass
{ {
// //

@ -75,7 +75,7 @@ final class FlightTest extends TestCase
{ {
$dateTime = new DateTimeImmutable(); $dateTime = new DateTimeImmutable();
$controller = new class ($dateTime) { $controller = new class($dateTime) {
public function __construct(private DateTimeImmutable $dateTime) public function __construct(private DateTimeImmutable $dateTime)
{ {
// //

@ -69,4 +69,4 @@ echo "----------------------------------------"
echo "Getting post-test memory reading..." echo "Getting post-test memory reading..."
final_memory_response=$(curl -s "${URL}?memory=1") final_memory_response=$(curl -s "${URL}?memory=1")
final_memory=$(echo "$final_memory_response" | grep "Memory:" | awk '{print $2}') final_memory=$(echo "$final_memory_response" | grep "Memory:" | awk '{print $2}')
echo "Final memory usage: ${final_memory} KB" echo "Final memory usage: ${final_memory} KB"

@ -23,4 +23,4 @@ for ((i = 0; i < count; i++)); do
echo " ${php_versions[$i]} vendor/bin/phpcs --standard=phpcs.xml -n" echo " ${php_versions[$i]} vendor/bin/phpcs --standard=phpcs.xml -n"
${php_versions[$i]} vendor/bin/phpcs --standard=phpcs.xml -n ${php_versions[$i]} vendor/bin/phpcs --standard=phpcs.xml -n
fi fi
done done

@ -9,60 +9,82 @@ declare(strict_types=1);
* @author Kristaps Muižnieks https://github.com/krmu * @author Kristaps Muižnieks https://github.com/krmu
*/ */
require file_exists(__DIR__ . '/../../vendor/autoload.php') ? __DIR__ . '/../../vendor/autoload.php' : __DIR__ . '/../../flight/autoload.php'; namespace Tests\ServerV2 {
class AuthCheck
Flight::set('flight.content_length', false); {
Flight::set('flight.views.path', './'); public function before(): void
Flight::set('flight.views.extension', '.phtml'); {
// This enables the old functionality of Flight output buffering if (!isset($_COOKIE['user'])) {
Flight::set('flight.v2.output_buffering', true); echo '<span id="infotext">Middleware text:</span> You are not authorized to access this route!';
}
// Test 1: Root route }
Flight::route('/', function () {
echo '<span id="infotext">Route text:</span> Root route works!';
if (Flight::request()->query->redirected) {
echo '<br>Redirected from /redirect route successfully!';
} }
}); }
Flight::route('/querytestpath', function () {
echo '<span id="infotext">Route text:</span> This ir query route<br>'; namespace {
echo "I got such query parameters:<pre>";
print_r(Flight::request()->query); use Tests\ServerV2\AuthCheck;
echo "</pre>";
}, false, "querytestpath"); require_once __DIR__ . '/../phpunit_autoload.php';
// Test 2: Simple route Flight::set('flight.content_length', false);
Flight::route('/test', function () { Flight::set('flight.views.path', './');
echo '<span id="infotext">Route text:</span> Test route works!'; Flight::set('flight.views.extension', '.phtml');
}); // This enables the old functionality of Flight output buffering
Flight::set('flight.v2.output_buffering', true);
// Test 3: Route with parameter
Flight::route('/user/@name', function ($name) { // Test 1: Root route
echo "<span id='infotext'>Route text:</span> Hello, $name!"; Flight::route('/', function () {
}); echo '<span id="infotext">Route text:</span> Root route works!';
Flight::route('POST /postpage', function () {
echo '<span id="infotext">Route text:</span> THIS IS POST METHOD PAGE'; if (Flight::request()->query['redirected']) {
}, false, "postpage"); echo '<br>Redirected from /redirect route successfully!';
}
// Test 4: Grouped routes });
Flight::group('/group', function () {
Flight::route('/querytestpath', function () {
echo '<span id="infotext">Route text:</span> This ir query route<br>';
echo 'I got such query parameters:<pre>';
print_r(Flight::request()->query);
echo '</pre>';
}, false, 'querytestpath');
// Test 2: Simple route
Flight::route('/test', function () { Flight::route('/test', function () {
echo '<span id="infotext">Route text:</span> Group test route works!'; echo '<span id="infotext">Route text:</span> Test route works!';
}); });
// Test 3: Route with parameter
Flight::route('/user/@name', function ($name) { Flight::route('/user/@name', function ($name) {
echo "<span id='infotext'>Route text:</span> There is variable called name and it is $name"; echo "<span id='infotext'>Route text:</span> Hello, $name!";
}); });
Flight::group('/group1', function () {
Flight::group('/group2', function () { Flight::route('POST /postpage', function () {
Flight::group('/group3', function () { echo '<span id="infotext">Route text:</span> THIS IS POST METHOD PAGE';
Flight::group('/group4', function () { }, false, 'postpage');
Flight::group('/group5', function () {
Flight::group('/group6', function () { // Test 4: Grouped routes
Flight::group('/group7', function () { Flight::group('/group', function () {
Flight::group('/group8', function () { Flight::route('/test', function () {
Flight::route('/final_group', function () { echo '<span id="infotext">Route text:</span> Group test route works!';
echo 'Mega Group test route works!'; });
}, false, "final_group");
Flight::route('/user/@name', function ($name) {
echo "<span id='infotext'>Route text:</span> There is variable called name and it is $name";
});
Flight::group('/group1', function () {
Flight::group('/group2', function () {
Flight::group('/group3', function () {
Flight::group('/group4', function () {
Flight::group('/group5', function () {
Flight::group('/group6', function () {
Flight::group('/group7', function () {
Flight::group('/group8', function () {
Flight::route('/final_group', function () {
echo 'Mega Group test route works!';
}, false, 'final_group');
});
}); });
}); });
}); });
@ -71,152 +93,151 @@ Flight::group('/group', function () {
}); });
}); });
}); });
});
// Test 5: Route alias
// Test 5: Route alias Flight::route('/alias', function () {
Flight::route('/alias', function () { echo '<span id="infotext">Route text:</span> Alias route works!';
echo '<span id="infotext">Route text:</span> Alias route works!'; }, false, 'aliasroute');
}, false, 'aliasroute');
class AuthCheck $middle = new AuthCheck();
{
public function before(): void // Test 6: Route with middleware
{ Flight::route('/protected', function () {
if (!isset($_COOKIE['user'])) { echo '<span id="infotext">Route text:</span> Protected route works!';
echo '<span id="infotext">Middleware text:</span> You are not authorized to access this route!'; })->addMiddleware([$middle]);
// Test 7: Route with template
Flight::route('/template/@name', function ($name) {
Flight::render('template.phtml', ['name' => $name]);
});
// Test 8: Throw an error
Flight::route('/error', function () {
trigger_error('This is a successful error');
});
// Test 9: JSON output (should not output any other html)
Flight::route('/json', function () {
echo "\n\n\n\n\n";
Flight::json(['message' => 'JSON renders successfully!']);
echo "\n\n\n\n\n";
});
// Test 13: JSONP output (should not output any other html)
Flight::route('/jsonp', function () {
echo "\n\n\n\n\n";
Flight::jsonp(['message' => 'JSONP renders successfully!'], 'jsonp');
echo "\n\n\n\n\n";
});
Flight::route('/json-halt', function () {
Flight::jsonHalt(['message' => 'JSON rendered and halted successfully with no other body content!']);
});
// Test 10: Halt
Flight::route('/halt', function () {
Flight::halt(400, 'Halt worked successfully');
});
// Test 11: Redirect
Flight::route('/redirect', function () {
Flight::redirect('/?redirected=1');
});
Flight::set('flight.views.path', './');
Flight::map('error', function (Throwable $error) {
echo "<h1> An error occurred, mapped error method worked, error below </h1>";
echo '<pre style="border: 2px solid red; padding: 21px; background: lightgray; font-weight: bold;">';
echo str_replace(getenv('PWD'), "***CLASSIFIED*****", $error->getTraceAsString());
echo "</pre>";
echo "<a href='/'>Go back</a>";
});
Flight::map('notFound', function () {
echo '<span id="infotext">Route text:</span> The requested URL was not found';
echo "<a href='/'>Go back</a>";
});
echo '
<style>
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
} }
}
}
$middle = new AuthCheck();
// Test 6: Route with middleware
Flight::route('/protected', function () {
echo '<span id="infotext">Route text:</span> Protected route works!';
})->addMiddleware([$middle]);
// Test 7: Route with template
Flight::route('/template/@name', function ($name) {
Flight::render('template.phtml', ['name' => $name]);
});
// Test 8: Throw an error
Flight::route('/error', function () {
trigger_error('This is a successful error');
});
// Test 9: JSON output (should not output any other html)
Flight::route('/json', function () {
echo "\n\n\n\n\n";
Flight::json(['message' => 'JSON renders successfully!']);
echo "\n\n\n\n\n";
});
// Test 13: JSONP output (should not output any other html)
Flight::route('/jsonp', function () {
echo "\n\n\n\n\n";
Flight::jsonp(['message' => 'JSONP renders successfully!'], 'jsonp');
echo "\n\n\n\n\n";
});
Flight::route('/json-halt', function () {
Flight::jsonHalt(['message' => 'JSON rendered and halted successfully with no other body content!']);
});
// Test 10: Halt
Flight::route('/halt', function () {
Flight::halt(400, 'Halt worked successfully');
});
// Test 11: Redirect
Flight::route('/redirect', function () {
Flight::redirect('/?redirected=1');
});
Flight::set('flight.views.path', './');
Flight::map('error', function (Throwable $error) {
echo "<h1> An error occurred, mapped error method worked, error below </h1>";
echo '<pre style="border: 2px solid red; padding: 21px; background: lightgray; font-weight: bold;">';
echo str_replace(getenv('PWD'), "***CLASSIFIED*****", $error->getTraceAsString());
echo "</pre>";
echo "<a href='/'>Go back</a>";
});
Flight::map('notFound', function () {
echo '<span id="infotext">Route text:</span> The requested URL was not found';
echo "<a href='/'>Go back</a>";
});
echo '
<style>
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
}
li { li {
float: left; float: left;
} }
#infotext { #infotext {
font-weight: bold; font-weight: bold;
color: blueviolet; color: blueviolet;
}
li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
} }
li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
li a:hover { li a:hover {
background-color: #111; background-color: #111;
} }
#container { #container {
color: #333; color: #333;
font-size: 16px; font-size: 16px;
line-height: 1.5; line-height: 1.5;
margin: 20px 0; margin: 20px 0;
padding: 10px; padding: 10px;
border: 1px solid #ddd; border: 1px solid #ddd;
background-color: #f9f9f9; background-color: #f9f9f9;
} }
#debugrequest { #debugrequest {
color: #333; color: #333;
font-size: 16px; font-size: 16px;
line-height: 1.5; line-height: 1.5;
margin: 20px 0; margin: 20px 0;
padding: 10px; padding: 10px;
border: 1px solid #ddd; border: 1px solid #ddd;
background-color: #f9f9f9; background-color: #f9f9f9;
} }
</style> </style>
<ul> <ul>
<li><a href="/">Root Route</a></li> <li><a href="/">Root Route</a></li>
<li><a href="/test">Test Route</a></li> <li><a href="/test">Test Route</a></li>
<li><a href="/user/John">User Route with Parameter (John)</a></li> <li><a href="/user/John">User Route with Parameter (John)</a></li>
<li><a href="/group/test">Grouped Test Route</a></li> <li><a href="/group/test">Grouped Test Route</a></li>
<li><a href="/group/user/Jane">Grouped User Route with Parameter (Jane)</a></li> <li><a href="/group/user/Jane">Grouped User Route with Parameter (Jane)</a></li>
<li><a href="/alias">Alias Route</a></li> <li><a href="/alias">Alias Route</a></li>
<li><a href="/protected">Protected path</a></li> <li><a href="/protected">Protected path</a></li>
<li><a href="/template/templatevariable">Template path</a></li> <li><a href="/template/templatevariable">Template path</a></li>
<li><a href="/querytestpath?test=1&variable2=uuid&variable3=tester">Query path</a></li> <li><a href="/querytestpath?test=1&variable2=uuid&variable3=tester">Query path</a></li>
<li><a href="/postpage">Post method test page - should be 404</a></li> <li><a href="/postpage">Post method test page - should be 404</a></li>
<li><a href="' . Flight::getUrl('final_group') . '">Mega group</a></li> <li><a href="' . Flight::getUrl('final_group') . '">Mega group</a></li>
<li><a href="/error">Error</a></li> <li><a href="/error">Error</a></li>
<li><a href="/json">JSON</a></li> <li><a href="/json">JSON</a></li>
<li><a href="/jsonp?jsonp=myjson">JSONP</a></li> <li><a href="/jsonp?jsonp=myjson">JSONP</a></li>
<li><a href="/json-halt">JSON Halt</a></li> <li><a href="/json-halt">JSON Halt</a></li>
<li><a href="/halt">Halt</a></li> <li><a href="/halt">Halt</a></li>
<li><a href="/redirect">Redirect</a></li> <li><a href="/redirect">Redirect</a></li>
</ul>'; </ul>';
Flight::before('start', function ($params) {
echo '<div id="container">'; Flight::before('start', function () {
}); echo '<div id="container">';
Flight::after('start', function ($params) { });
echo '</div>';
echo '<div id="debugrequest">'; Flight::after('start', function () {
echo "Request information<pre>"; echo '</div>';
print_r(Flight::request()); echo '<div id="debugrequest">';
echo "</pre>"; echo "Request information<pre>";
echo "</div>"; print_r(Flight::request());
}); echo "</pre>";
Flight::start(); echo "</div>";
});
Flight::start();
}

@ -1 +1 @@
<span id="infotext">Route text:</span> Template <?=$name?> works! <span id="infotext">Route text:</span> Template <?= $name ?> works!

@ -2,6 +2,8 @@
declare(strict_types=1); declare(strict_types=1);
namespace Tests\Server;
class AuthCheck class AuthCheck
{ {
public function before(): void public function before(): void

@ -2,6 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace Tests\Server;
use Flight;
class LayoutMiddleware class LayoutMiddleware
{ {
public function before(): void public function before(): void
@ -9,50 +13,50 @@ class LayoutMiddleware
$final_route = Flight::getUrl('final_group'); $final_route = Flight::getUrl('final_group');
echo <<<HTML echo <<<HTML
<style> <style>
ul { ul {
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
background-color: #333; background-color: #333;
} }
li { li {
float: left; float: left;
} }
#infotext { #infotext {
font-weight: bold; font-weight: bold;
color: blueviolet; color: blueviolet;
} }
li a { li a {
display: block; display: block;
color: white; color: white;
text-align: center; text-align: center;
padding: 14px 16px; padding: 14px 16px;
text-decoration: none; text-decoration: none;
} }
li a:hover { li a:hover {
background-color: #111; background-color: #111;
} }
#container { #container {
color: #333; color: #333;
font-size: 16px; font-size: 16px;
line-height: 1.5; line-height: 1.5;
margin: 20px 0; margin: 20px 0;
padding: 10px; padding: 10px;
border: 1px solid #ddd; border: 1px solid #ddd;
background-color: #f9f9f9; background-color: #f9f9f9;
} }
#debugrequest { #debugrequest {
color: #333; color: #333;
font-size: 16px; font-size: 16px;
line-height: 1.5; line-height: 1.5;
margin: 20px 0; margin: 20px 0;
padding: 10px; padding: 10px;
border: 1px solid #ddd; border: 1px solid #ddd;
background-color: #f9f9f9; background-color: #f9f9f9;
} }
</style> </style>
<ul> <ul>
<li><a href="/">Root Route</a></li> <li><a href="/">Root Route</a></li>

@ -2,11 +2,20 @@
declare(strict_types=1); declare(strict_types=1);
namespace Tests\Server;
use Flight;
class OverwriteBodyMiddleware class OverwriteBodyMiddleware
{ {
public function after(): void public function after(): void
{ {
$response = Flight::response(); $response = Flight::response();
$response->write(str_replace('<span style="color:red; font-weight: bold;">failed</span>', '<span style="color:green; font-weight: bold;">successfully works!</span>', $response->getBody()), true);
$response->write(str_replace(
'<span style="color:red; font-weight: bold;">failed</span>',
'<span style="color:green; font-weight: bold;">successfully works!</span>',
$response->getBody()
), true);
} }
} }

@ -2,6 +2,8 @@
declare(strict_types=1); declare(strict_types=1);
namespace Tests\Server;
class Pascal_Snake_Case // phpcs:ignore class Pascal_Snake_Case // phpcs:ignore
{ {
public function doILoad() // phpcs:ignore public function doILoad() // phpcs:ignore

@ -2,10 +2,15 @@
declare(strict_types=1); declare(strict_types=1);
use Dice\Dice;
use flight\core\Loader; use flight\core\Loader;
use flight\database\PdoWrapper; use flight\database\PdoWrapper;
use tests\classes\Container; use tests\classes\Container;
use tests\classes\ContainerDefault; use tests\classes\ContainerDefault;
use Tests\Server\AuthCheck;
use Tests\Server\LayoutMiddleware;
use Tests\Server\OverwriteBodyMiddleware;
use Tests\Server\Pascal_Snake_Case;
/* /*
* This is the test file where we can open up a quick test server and make * This is the test file where we can open up a quick test server and make
@ -14,7 +19,7 @@ use tests\classes\ContainerDefault;
* @author Kristaps Muižnieks https://github.com/krmu * @author Kristaps Muižnieks https://github.com/krmu
*/ */
require file_exists(__DIR__ . '/../../vendor/autoload.php') ? __DIR__ . '/../../vendor/autoload.php' : __DIR__ . '/../../flight/autoload.php'; require_once __DIR__ . '/../phpunit_autoload.php';
Flight::set('flight.content_length', false); Flight::set('flight.content_length', false);
Flight::set('flight.views.path', './'); Flight::set('flight.views.path', './');
@ -23,20 +28,20 @@ Loader::setV2ClassLoading(false);
Flight::path(__DIR__); Flight::path(__DIR__);
Flight::group('', function () { Flight::group('', function () {
// Test 1: Root route // Test 1: Root route
Flight::route('/', function () { Flight::route('/', function () {
echo '<span id="infotext">Route text:</span> Root route works!'; echo '<span id="infotext">Route text:</span> Root route works!';
if (Flight::request()->query->redirected) { if (Flight::request()->query['redirected']) {
echo '<br>Redirected from /redirect route successfully!'; echo '<br>Redirected from /redirect route successfully!';
} }
}); });
Flight::route('/querytestpath', function () { Flight::route('/querytestpath', function () {
echo '<span id="infotext">Route text:</span> This is query route<br>'; echo '<span id="infotext">Route text:</span> This is query route<br>';
echo "Query parameters:<pre>"; echo 'Query parameters:<pre>';
print_r(Flight::request()->query); print_r(Flight::request()->query);
echo "</pre>"; echo '</pre>';
}, false, "querytestpath"); }, false, 'querytestpath');
// Test 2: Simple route // Test 2: Simple route
Flight::route('/test', function () { Flight::route('/test', function () {
@ -47,18 +52,21 @@ Flight::group('', function () {
Flight::route('/user/@name', function ($name) { Flight::route('/user/@name', function ($name) {
echo "<span id='infotext'>Route text:</span> Hello, $name!"; echo "<span id='infotext'>Route text:</span> Hello, $name!";
}); });
Flight::route('POST /postpage', function () { Flight::route('POST /postpage', function () {
echo '<span id="infotext">Route text:</span> THIS IS POST METHOD PAGE'; echo '<span id="infotext">Route text:</span> THIS IS POST METHOD PAGE';
}, false, "postpage"); }, false, 'postpage');
// Test 4: Grouped routes // Test 4: Grouped routes
Flight::group('/group', function () { Flight::group('/group', function () {
Flight::route('/test', function () { Flight::route('/test', function () {
echo '<span id="infotext">Route text:</span> Group test route works!'; echo '<span id="infotext">Route text:</span> Group test route works!';
}); });
Flight::route('/user/@name', function ($name) { Flight::route('/user/@name', function ($name) {
echo "<span id='infotext'>Route text:</span> There is variable called name and it is $name"; echo "<span id='infotext'>Route text:</span> There is variable called name and it is $name";
}); });
Flight::group('/group1', function () { Flight::group('/group1', function () {
Flight::group('/group2', function () { Flight::group('/group2', function () {
Flight::group('/group3', function () { Flight::group('/group3', function () {
@ -69,7 +77,7 @@ Flight::group('', function () {
Flight::group('/group8', function () { Flight::group('/group8', function () {
Flight::route('/final_group', function () { Flight::route('/final_group', function () {
echo 'Mega Group test route works!'; echo 'Mega Group test route works!';
}, false, "final_group"); }, false, 'final_group');
}); });
}); });
}); });
@ -86,8 +94,8 @@ Flight::group('', function () {
}, false, 'aliasroute'); }, false, 'aliasroute');
/** Middleware test */ /** Middleware test */
include_once 'AuthCheck.php';
$middle = new AuthCheck(); $middle = new AuthCheck();
// Test 6: Route with middleware // Test 6: Route with middleware
Flight::route('/protected', function () { Flight::route('/protected', function () {
echo '<span id="infotext">Route text:</span> Protected route works!'; echo '<span id="infotext">Route text:</span> Protected route works!';
@ -119,51 +127,75 @@ Flight::group('', function () {
// Test 12: Redirect with status code // Test 12: Redirect with status code
Flight::route('/streamResponse', function () { Flight::route('/streamResponse', function () {
echo "Streaming a response"; echo 'Streaming a response';
for ($i = 1; $i <= 50; $i++) { for ($i = 1; $i <= 50; $i++) {
echo "."; echo ".";
usleep(50000); usleep(50000);
ob_flush(); ob_flush();
} }
echo "is successful!!";
echo 'is successful!!';
})->stream(); })->stream();
// Test 12: Redirect with status code // Test 12: Redirect with status code
Flight::route('/streamWithHeaders', function () { Flight::route('/streamWithHeaders', function () {
echo "Streaming a response"; echo 'Streaming a response';
for ($i = 1; $i <= 50; $i++) { for ($i = 1; $i <= 50; $i++) {
echo "."; echo ".";
usleep(50000); usleep(50000);
ob_flush(); ob_flush();
} }
echo "is successful!!";
})->streamWithHeaders(['Content-Type' => 'text/html', 'status' => 200 ]); echo 'is successful!!';
})->streamWithHeaders(['Content-Type' => 'text/html', 'status' => 200]);
// Test 14: Overwrite the body with a middleware // Test 14: Overwrite the body with a middleware
Flight::route('/overwrite', function () { Flight::route('/overwrite', function () {
echo '<span id="infotext">Route text:</span> This route status is that it <span style="color:red; font-weight: bold;">failed</span>'; echo <<<'html'
<span id="infotext">Route text:</span>
This route status is that it
<span style="color:red; font-weight: bold;">failed</span>
html;
})->addMiddleware([new OverwriteBodyMiddleware()]); })->addMiddleware([new OverwriteBodyMiddleware()]);
// Test 15: UTF8 Chars in url // Test 15: UTF8 Chars in url
Flight::route('/わたしはひとです', function () { Flight::route('/わたしはひとです', function () {
echo '<span id="infotext">Route text:</span> This route status is that it <span style="color:green; font-weight: bold;">succeeded はい!!!</span>'; echo <<<'html'
<span id="infotext">Route text:</span>
This route status is that it
<span style="color:green; font-weight: bold;">succeeded はい!!!</span>
html;
}); });
// Test 16: UTF8 Chars in url with utf8 params // Test 16: UTF8 Chars in url with utf8 params
Flight::route('/わたしはひとです/@name', function ($name) { Flight::route('/わたしはひとです/@name', function ($name) {
echo '<span id="infotext">Route text:</span> This route status is that it <span style="color:' . ($name === 'ええ' ? 'green' : 'red') . '; font-weight: bold;">' . ($name === 'ええ' ? 'succeeded' : 'failed') . ' URL Param: ' . $name . '</span>'; echo '<span id="infotext">Route text:</span> This route status is that it <span style="color:'
. ($name === 'ええ' ? 'green' : 'red')
. '; font-weight: bold;">'
. ($name === 'ええ' ? 'succeeded' : 'failed')
. ' URL Param: '
. $name
. '</span>';
}); });
// Test 17: Slash in param // Test 17: Slash in param
Flight::route('/redirect/@id', function ($id) { Flight::route('/redirect/@id', function ($id) {
echo '<span id="infotext">Route text:</span> This route status is that it <span style="color:' . ($id === 'before/after' ? 'green' : 'red') . '; font-weight: bold;">' . ($id === 'before/after' ? 'succeeded' : 'failed') . ' URL Param: ' . $id . '</span>'; echo '<span id="infotext">Route text:</span> This route status is that it <span style="color:'
. ($id === 'before/after' ? 'green' : 'red')
. '; font-weight: bold;">'
. ($id === 'before/after' ? 'succeeded' : 'failed')
. ' URL Param: '
. $id
. '</span>';
}); });
Flight::set('test_me_out', 'You got it boss!'); // used in /no-container route Flight::set('test_me_out', 'You got it boss!'); // used in /no-container route
Flight::route('/no-container', ContainerDefault::class . '->testUi'); Flight::route('/no-container', ContainerDefault::class . '->testUi');
Flight::route('/dice', Container::class . '->testThePdoWrapper'); Flight::route('/dice', Container::class . '->testThePdoWrapper');
Flight::route('/Pascal_Snake_Case', Pascal_Snake_Case::class . '->doILoad'); Flight::route('/Pascal_Snake_Case', Pascal_Snake_Case::class . '->doILoad');
}, [ new LayoutMiddleware() ]); }, [new LayoutMiddleware()]);
// Test 9: JSON output (should not output any other html) // Test 9: JSON output (should not output any other html)
Flight::route('/json', function () { Flight::route('/json', function () {
@ -195,23 +227,25 @@ Flight::map('error', function (Throwable $e) {
$e->getCode(), $e->getCode(),
str_replace(getenv('PWD'), '***CONFIDENTIAL***', $e->getTraceAsString()) str_replace(getenv('PWD'), '***CONFIDENTIAL***', $e->getTraceAsString())
); );
echo "<br><a href='/'>Go back</a>"; echo "<br><a href='/'>Go back</a>";
}); });
Flight::map('notFound', function () { Flight::map('notFound', function () {
echo '<span id="infotext">Route text:</span> The requested URL was not found<br>'; echo '<span id="infotext">Route text:</span> The requested URL was not found<br>';
echo "<a href='/'>Go back</a>"; echo "<a href='/'>Go back</a>";
}); });
Flight::map('start', function () { Flight::map('start', function () {
if (Flight::request()->url === '/dice') { if (Flight::request()->url === '/dice') {
$dice = new \Dice\Dice(); $dice = new Dice();
$dice = $dice->addRules([ $dice = $dice->addRules([
PdoWrapper::class => [ PdoWrapper::class => [
'shared' => true, 'shared' => true,
'constructParams' => [ 'sqlite::memory:' ] 'constructParams' => ['sqlite::memory:']
] ]
]); ]);
Flight::registerContainerHandler(function ($class, $params) use ($dice) { Flight::registerContainerHandler(function ($class, $params) use ($dice) {
return $dice->create($class, $params); return $dice->create($class, $params);
}); });

@ -1,5 +1,5 @@
<?php if(isset($name)): ?> <?php if (isset($name)): ?>
<span id="infotext">Route text:</span> Template <?=$name?> works! <span id="infotext">Route text:</span> Template <?= $name ?> works!
<?php elseif(isset($data)): ?> <?php elseif (isset($data)): ?>
<span id="infotext">Route text:</span> Template with variable name "data" works! See: <?=$data?> <span id="infotext">Route text:</span> Template with variable name "data" works! See: <?= $data ?>
<?php endif; ?> <?php endif; ?>

@ -1 +1 @@
This file downloaded successfully! This file downloaded successfully!

@ -1 +1 @@
<html><?php echo $content; ?></html> <body><?= $content ?></body>

@ -1 +1 @@
Hello world, <?php echo $name; ?>! Hello world, <?php echo $name; ?>!

Loading…
Cancel
Save