diff --git a/composer.json b/composer.json index b82fdd6..216888e 100644 --- a/composer.json +++ b/composer.json @@ -33,14 +33,17 @@ }, "autoload-dev": { "classmap": [ - "tests/classes/User.php", - "tests/classes/Hello.php", - "tests/classes/Factory.php", - "tests/classes/TesterClass.php" - ] + "tests/classes/" + ], + "psr-4": { + "Tests\\PHP8\\": [ + "tests/named-arguments" + ] + } }, "require-dev": { "ext-pdo_sqlite": "*", + "flightphp/container": "^1.0", "flightphp/runway": "^0.2.3 || ^1.0", "league/container": "^4.2", "level-2/dice": "^4.0", diff --git a/flight/Engine.php b/flight/Engine.php index f459716..7f6ae0f 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -16,6 +16,7 @@ use flight\net\Router; use flight\template\View; use Throwable; use flight\net\Route; +use Psr\Container\ContainerInterface; /** * The Engine class contains the core functionality of the framework. @@ -265,9 +266,9 @@ class Engine /** * Registers the container handler * - * @param callable|object $containerHandler Callback function or PSR-11 Container object that sets the container and how it will inject classes + * @template T of object * - * @return void + * @param ContainerInterface|callable(class-string $id, array $params): ?T $containerHandler Callback function or PSR-11 Container object that sets the container and how it will inject classes */ public function registerContainerHandler($containerHandler): void { diff --git a/flight/Flight.php b/flight/Flight.php index 064bd16..1c64826 100644 --- a/flight/Flight.php +++ b/flight/Flight.php @@ -9,6 +9,7 @@ use flight\net\Router; use flight\template\View; use flight\net\Route; use flight\core\EventDispatcher; +use Psr\Container\ContainerInterface; require_once __DIR__ . '/autoload.php'; @@ -18,9 +19,11 @@ require_once __DIR__ . '/autoload.php'; * @license MIT, http://flightphp.com/license * @copyright Copyright (c) 2011, Mike Cao * + * @template T of object + * * # Core methods * @method static void start() Starts the framework. - * @method static void path(string $path) Adds a path for autoloading classes. + * @method static void path(string $dir) Adds a path for autoloading classes. * @method static void stop(?int $code = null) Stops the framework and sends a response. * @method static void halt(int $code = 200, string $message = '', bool $actuallyExit = true) * Stop the framework with an optional status code and message. @@ -28,7 +31,10 @@ require_once __DIR__ . '/autoload.php'; * Registers a class to a framework method. * @method static void unregister(string $methodName) * Unregisters a class to a framework method. - * @method static void registerContainerHandler(callable|object $containerHandler) Registers a container handler. + * @method static void registerContainerHandler(ContainerInterface|callable(class-string $id, array $params): ?T $containerHandler) Registers a container handler. + * + * # Class registration + * @method EventDispatcher eventDispatcher() Gets event dispatcher * * # Class registration * @method EventDispatcher eventDispatcher() Gets event dispatcher @@ -104,6 +110,7 @@ class Flight */ private function __construct() { + // } /** @@ -114,6 +121,7 @@ class Flight */ private function __clone() { + // } /** diff --git a/flight/commands/RouteCommand.php b/flight/commands/RouteCommand.php index a34b821..8b5408e 100644 --- a/flight/commands/RouteCommand.php +++ b/flight/commands/RouteCommand.php @@ -106,7 +106,7 @@ class RouteCommand extends AbstractBaseCommand if ($showAll === true) { $boolval = true; } else { - $methods = [ 'GET', 'POST', 'PUT', 'DELETE', 'PATCH' ]; + $methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']; foreach ($methods as $method) { $lowercaseMethod = strtolower($method); if ( diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index 20e27b8..12cf8ac 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -52,7 +52,9 @@ class Dispatcher /** * Sets the dependency injection container handler. * - * @param ContainerInterface|(callable(string $classString, array $params): (null|object)) $containerHandler + * @template T of object + * + * @param ContainerInterface|(callable(class-string $classString, array $params): ?T) $containerHandler * Dependency injection container. * * @throws InvalidArgumentException If $containerHandler is not a `callable` or instance of `Psr\Container\ContainerInterface`. @@ -231,7 +233,7 @@ class Dispatcher if ($parametersNumber === 1) { /** @disregard &$params in after filters are deprecated. */ - $callback = fn (array &$params, &$output) => $callback($output); + $callback = fn(array &$params, &$output) => $callback($output); } } @@ -437,11 +439,12 @@ class Dispatcher public function resolveContainerClass(string $class, array &$params) { // PSR-11 - if ( - is_a($this->containerHandler, '\Psr\Container\ContainerInterface') - && $this->containerHandler->has($class) - ) { - return $this->containerHandler->get($class); + if (is_a($this->containerHandler, '\Psr\Container\ContainerInterface')) { + try { + return $this->containerHandler->get($class); + } catch (Throwable $exception) { + return null; + } } // Just a callable where you configure the behavior (Dice, PHP-DI, etc.) diff --git a/flight/net/Request.php b/flight/net/Request.php index 5164b11..be6f3f9 100644 --- a/flight/net/Request.php +++ b/flight/net/Request.php @@ -211,8 +211,8 @@ class Request $this->data->setData($data); } } - // Check PUT, PATCH, DELETE for application/x-www-form-urlencoded data - } elseif (in_array($this->method, [ 'PUT', 'DELETE', 'PATCH' ], true) === true) { + // Check PUT, PATCH, DELETE for application/x-www-form-urlencoded data + } elseif (in_array($this->method, ['PUT', 'DELETE', 'PATCH'], true) === true) { $body = $this->getBody(); if ($body !== '') { $data = []; diff --git a/flight/util/ReturnTypeWillChange.php b/flight/util/ReturnTypeWillChange.php index 1eba39e..4e27e1c 100644 --- a/flight/util/ReturnTypeWillChange.php +++ b/flight/util/ReturnTypeWillChange.php @@ -5,4 +5,5 @@ declare(strict_types=1); // This file is only here so that the PHP8 attribute for doesn't throw an error in files class ReturnTypeWillChange { + // } diff --git a/phpstan.neon b/phpstan.neon index 9be4ca5..f2de10d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,6 +7,7 @@ parameters: excludePaths: - vendor - flight/util/ReturnTypeWillChange.php + - tests/named-arguments paths: - flight - index.php diff --git a/phpunit.xml b/phpunit.xml index cb460e5..b890bb7 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -22,6 +22,7 @@ tests/ + tests/named-arguments/ diff --git a/tests/EngineTest.php b/tests/EngineTest.php index 737f38d..edf81f5 100644 --- a/tests/EngineTest.php +++ b/tests/EngineTest.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace tests; -use ErrorException; use Exception; use flight\database\PdoWrapper; use flight\Engine; diff --git a/tests/FlightTest.php b/tests/FlightTest.php index 7eb3927..990bf93 100644 --- a/tests/FlightTest.php +++ b/tests/FlightTest.php @@ -389,7 +389,7 @@ class FlightTest extends TestCase html; // if windows replace \n with \r\n - $html = str_replace("\n", PHP_EOL, $html); + $html = str_replace(["\n", "\r\n"], PHP_EOL, $html); $this->expectOutputString($html); diff --git a/tests/ViewTest.php b/tests/ViewTest.php index b8611c5..cf300c9 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -233,8 +233,8 @@ class ViewTest extends TestCase html; - $html1 = str_replace("\n", PHP_EOL, $html1); - $html2 = str_replace("\n", PHP_EOL, $html2); + $html1 = str_replace(["\n", "\r\n"], PHP_EOL, $html1); + $html2 = str_replace(["\n", "\r\n"], PHP_EOL, $html2); return [ [ diff --git a/tests/commands/RouteCommandTest.php b/tests/commands/RouteCommandTest.php index 8a843ea..e973891 100644 --- a/tests/commands/RouteCommandTest.php +++ b/tests/commands/RouteCommandTest.php @@ -105,15 +105,22 @@ PHP; $app->handle(['runway', 'routes']); $this->assertStringContainsString('Routes', file_get_contents(static::$ou)); - $this->assertStringContainsString('+---------+-----------+-------+----------+----------------+ -| Pattern | Methods | Alias | Streamed | Middleware | -+---------+-----------+-------+----------+----------------+ -| / | GET, HEAD | | No | - | -| /post | POST | | No | Closure | -| /delete | DELETE | | No | - | -| /put | PUT | | No | - | -| /patch | PATCH | | No | Bad Middleware | -+---------+-----------+-------+----------+----------------+', $this->removeColors(file_get_contents(static::$ou))); + $expected = <<<'output' + +---------+-----------+-------+----------+----------------+ + | Pattern | Methods | Alias | Streamed | Middleware | + +---------+-----------+-------+----------+----------------+ + | / | GET, HEAD | | No | - | + | /post | POST | | No | Closure | + | /delete | DELETE | | No | - | + | /put | PUT | | No | - | + | /patch | PATCH | | No | Bad Middleware | + +---------+-----------+-------+----------+----------------+ + output; + + $this->assertStringContainsString( + $expected, + $this->removeColors(file_get_contents(static::$ou)) + ); } public function testGetPostRoute() diff --git a/tests/named-arguments/ExampleClass.php b/tests/named-arguments/ExampleClass.php new file mode 100644 index 0000000..d91dd64 --- /dev/null +++ b/tests/named-arguments/ExampleClass.php @@ -0,0 +1,8 @@ +engine = new Engine; + Flight::init(); + Flight::setEngine($this->engine); + } + + ////////////////// + // CORE METHODS // + ////////////////// + public function test_path(): void + { + Flight::path(dir: __DIR__); + + $exampleObject = new ExampleClass(); + self::assertInstanceOf(ExampleClass::class, $exampleObject); + } + + public function test_stop_with_code(): void + { + Flight::stop(code: 500); + + self::expectOutputString(''); + self::assertSame(500, Flight::response()->status()); + } + + public function test_halt(): void + { + Flight::halt(actuallyExit: false, code: 500, message: 'Test'); + + self::expectOutputString('Test'); + self::assertSame(500, Flight::response()->status()); + } + + public function test_register(): void + { + Flight::register( + class: stdClass::class, + name: 'customClass', + callback: static function (stdClass $object): void { + $object->property = 'value'; + }, + params: [] + ); + + $object = Flight::customClass(); + + self::assertInstanceOf(stdClass::class, $object); + self::assertObjectHasProperty('property', $object); + self::assertSame('value', $object->property); + + Flight::unregister(methodName: 'customClass'); + } + + public function test_register_container(): void + { + $dateTime = new DateTimeImmutable(); + + $controller = new class($dateTime) { + public function __construct(private DateTimeImmutable $dateTime) + { + // + } + + public function test(): void + { + echo $this->dateTime->format('Y-m-d'); + } + }; + + Flight::registerContainerHandler( + containerHandler: new Container() + ); + + Flight::request()->url = '/test'; + + Flight::route( + pass_route: true, + alias: 'testRoute', + callback: [$controller::class, 'test'], + pattern: '/test' + ); + + self::expectOutputString($dateTime->format('Y-m-d')); + + Flight::start(); + } + + ///////////////////// + // ROUTING METHODS // + ///////////////////// + public function test_static_route(): void + { + Flight::request()->url = '/test'; + + $route = Flight::route( + pass_route: true, + alias: 'testRoute', + callback: function () { + echo 'test'; + }, + pattern: '/test' + ); + + self::assertInstanceOf(Route::class, $route); + self::expectOutputString('test'); + Flight::start(); + } +}