From 1b6c5b942bc28975ff64de008b32852d4b929c98 Mon Sep 17 00:00:00 2001 From: Austin Collier Date: Wed, 20 Mar 2024 22:51:55 -0600 Subject: [PATCH] refactored some errors. UI Tests and cleanup. --- flight/Flight.php | 9 ++-- flight/core/Dispatcher.php | 77 +++++++++++++++++++----------- tests/DispatcherTest.php | 6 +-- tests/EngineTest.php | 1 - tests/MapTest.php | 1 - tests/classes/Container.php | 2 +- tests/classes/ContainerDefault.php | 5 +- tests/server/LayoutMiddleware.php | 2 + tests/server/index.php | 26 ++++++++++ 9 files changed, 89 insertions(+), 40 deletions(-) diff --git a/flight/Flight.php b/flight/Flight.php index 9a3042c..0a42489 100644 --- a/flight/Flight.php +++ b/flight/Flight.php @@ -19,11 +19,12 @@ require_once __DIR__ . '/autoload.php'; * @copyright Copyright (c) 2011, Mike Cao * * # Core methods - * @method static void start() Starts the framework. - * @method static void path(string $path) 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) + * @method static void start() Starts the framework. + * @method static void path(string $path) 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. + * @method static void registerContainerHandler(callable|object $containerHandler) Registers a container handler. * * # Routing * @method static Route route(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '') diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index 672cb82..2841dd7 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -26,8 +26,8 @@ class Dispatcher public const FILTER_AFTER = 'after'; private const FILTER_TYPES = [self::FILTER_BEFORE, self::FILTER_AFTER]; - /** @var mixed $container_exception Exception message if thrown by setting the container as a callable method */ - protected $container_exception = null; + /** @var mixed $containerException Exception message if thrown by setting the container as a callable method */ + protected $containerException = null; /** @var ?Engine $engine Engine instance */ protected ?Engine $engine = null; @@ -256,8 +256,6 @@ class Dispatcher $callback = $this->parseStringClassAndMethod($callback); } - $this->handleInvalidCallbackType($callback); - return $this->invokeCallable($callback, $params); } @@ -325,10 +323,12 @@ class Dispatcher { // If this is a directly callable function, call it if (is_array($func) === false) { + $this->verifyValidFunction($func); return call_user_func_array($func, $params); } [$class, $method] = $func; + $resolvedClass = null; // Only execute the container handler if it's not a Flight class if ( @@ -343,28 +343,12 @@ class Dispatcher ) { $containerHandler = $this->containerHandler; $resolvedClass = $this->resolveContainerClass($containerHandler, $class, $params); - if($resolvedClass !== null) { + if ($resolvedClass !== null) { $class = $resolvedClass; } } - // 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) { - $this->fixOutputBuffering(); - throw 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 (isset($resolvedClass) === false && $this->container_exception !== null) { - $this->fixOutputBuffering(); - throw $this->container_exception; - } - - // Class is there, but no method - if (is_object($class) === true && method_exists($class, $method) === false) { - $this->fixOutputBuffering(); - throw new Exception("Class found, but method '".get_class($class)."::$method' not found."); - } + $this->verifyValidClassCallable($class, $method, $resolvedClass); // Class is a string, and method exists, create the object by hand and inject only the Engine if (is_string($class) === true) { @@ -382,7 +366,7 @@ class Dispatcher * * @throws InvalidArgumentException If `$callback` is an invalid type */ - protected function handleInvalidCallbackType($callback): void + protected function verifyValidFunction($callback): void { $isInvalidFunctionName = ( is_string($callback) @@ -394,6 +378,41 @@ class Dispatcher } } + + /** + * Verifies if the provided class and method are valid callable. + * + * @param string|object $class The class name. + * @param string $method The method name. + * @param object|null $resolvedClass The resolved class. + * + * @throws Exception If the class or method is not found. + * + * @return void + */ + protected function verifyValidClassCallable($class, $method, $resolvedClass): void + { + $final_exception = null; + + // Final check to make sure it's actually a class and a method, or throw an error + if (is_object($class) === false && class_exists($class) === false) { + $final_exception = new Exception("Class '$class' not found. Is it being correctly autoloaded with Flight::path()?"); + + // If this tried to resolve a class in a container and failed somehow, throw the exception + } elseif (isset($resolvedClass) === false && $this->containerException !== null) { + $final_exception = $this->containerException; + + // Class is there, but no method + } elseif (is_object($class) === true && method_exists($class, $method) === false) { + $final_exception = new Exception("Class found, but method '" . get_class($class) . "::$method' not found."); + } + + if ($final_exception !== null) { + $this->fixOutputBuffering(); + throw $final_exception; + } + } + /** * Resolves the container class. * @@ -417,7 +436,6 @@ class Dispatcher // Just a callable where you configure the behavior (Dice, PHP-DI, etc.) } elseif (is_callable($container_handler) === true) { - // This is to catch all the error that could be thrown by whatever container you are using try { $class_object = call_user_func($container_handler, $class, $params); @@ -425,11 +443,12 @@ class Dispatcher // could not resolve a class for some reason $class_object = null; - // If the container throws an exception, we need to catch it - // and store it somewhere. If we just let it throw itself, it - // doesn't properly close the output buffers and can cause other + // If the container throws an exception, we need to catch it + // and store it somewhere. If we just let it throw itself, it + // doesn't properly close the output buffers and can cause other // issues. - $this->container_exception = $e; + // This is thrown in the verifyValidClassCallable method + $this->containerException = $e; } } @@ -444,7 +463,7 @@ class Dispatcher protected function fixOutputBuffering(): void { // Cause PHPUnit has 1 level of output buffering by default - if(ob_get_level() > (getenv('PHPUNIT_TEST') ? 1 : 0)) { + if (ob_get_level() > (getenv('PHPUNIT_TEST') ? 1 : 0)) { ob_end_clean(); } } diff --git a/tests/DispatcherTest.php b/tests/DispatcherTest.php index a75f18f..f2f3107 100644 --- a/tests/DispatcherTest.php +++ b/tests/DispatcherTest.php @@ -277,21 +277,21 @@ class DispatcherTest extends TestCase public function testExecuteStringClassDefaultContainer(): void { - $this->dispatcher->setEngine(new Engine); + $this->dispatcher->setEngine(new Engine()); $result = $this->dispatcher->execute(ContainerDefault::class . '->testTheContainer'); $this->assertSame('You got it boss!', $result); } public function testExecuteStringClassDefaultContainerDoubleColon(): void { - $this->dispatcher->setEngine(new Engine); + $this->dispatcher->setEngine(new Engine()); $result = $this->dispatcher->execute(ContainerDefault::class . '::testTheContainer'); $this->assertSame('You got it boss!', $result); } public function testExecuteStringClassDefaultContainerArraySyntax(): void { - $this->dispatcher->setEngine(new Engine); + $this->dispatcher->setEngine(new Engine()); $result = $this->dispatcher->execute([ ContainerDefault::class, 'testTheContainer' ]); $this->assertSame('You got it boss!', $result); } diff --git a/tests/EngineTest.php b/tests/EngineTest.php index 64c8911..b79e344 100644 --- a/tests/EngineTest.php +++ b/tests/EngineTest.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace tests; use Exception; -use flight\core\Dispatcher; use flight\database\PdoWrapper; use flight\Engine; use flight\net\Request; diff --git a/tests/MapTest.php b/tests/MapTest.php index bf276fb..445e8eb 100644 --- a/tests/MapTest.php +++ b/tests/MapTest.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace tests; use Exception; -use flight\core\Dispatcher; use flight\Engine; use tests\classes\Hello; use PHPUnit\Framework\TestCase; diff --git a/tests/classes/Container.php b/tests/classes/Container.php index 7e67518..5b9d216 100644 --- a/tests/classes/Container.php +++ b/tests/classes/Container.php @@ -26,7 +26,7 @@ class Container public function testThePdoWrapper() { - $value = (int) $this->pdoWrapper->fetchField('SELECT 5'); + $value = intval($this->pdoWrapper->fetchField('SELECT 5')); echo 'Yay! I injected a PdoWrapper, and it returned the number ' . $value . ' from the database!'; } } diff --git a/tests/classes/ContainerDefault.php b/tests/classes/ContainerDefault.php index 28abf4a..183d287 100644 --- a/tests/classes/ContainerDefault.php +++ b/tests/classes/ContainerDefault.php @@ -8,7 +8,6 @@ use flight\Engine; class ContainerDefault { - protected Engine $app; public function __construct(Engine $engine) @@ -22,4 +21,8 @@ class ContainerDefault return $this->app->get('test_me_out'); } + public function testUi() + { + echo 'Route text: The container successfully injected a value into the engine! Engine class: ' . get_class($this->app) . ' test_me_out Value: ' . $this->app->get('test_me_out') . ''; + } } diff --git a/tests/server/LayoutMiddleware.php b/tests/server/LayoutMiddleware.php index b2e59bb..500cbd7 100644 --- a/tests/server/LayoutMiddleware.php +++ b/tests/server/LayoutMiddleware.php @@ -81,6 +81,8 @@ class LayoutMiddleware
  • Slash in Param
  • UTF8 URL
  • UTF8 URL w/ Param
  • +
  • Dice Container
  • +
  • No Container Registered
  • HTML; echo '
    '; diff --git a/tests/server/index.php b/tests/server/index.php index f00144f..8417759 100644 --- a/tests/server/index.php +++ b/tests/server/index.php @@ -2,6 +2,10 @@ declare(strict_types=1); +use flight\database\PdoWrapper; +use tests\classes\Container; +use tests\classes\ContainerDefault; + /* * This is the test file where we can open up a quick test server and make * sure that the UI is really working the way we would expect it to. @@ -139,6 +143,9 @@ Flight::group('', function () { Flight::route('/redirect/@id', function ($id) { echo 'Route text: This route status is that it ' . ($id === 'before/after' ? 'succeeded' : 'failed') . ' URL Param: ' . $id . ''; }); + + Flight::route('/no-container', ContainerDefault::class . '->testUi'); + Flight::route('/dice', Container::class . '->testThePdoWrapper'); }, [ new LayoutMiddleware() ]); // Test 9: JSON output (should not output any other html) @@ -167,4 +174,23 @@ Flight::map('notFound', function () { echo "Go back"; }); +Flight::map('start', function () { + + if (Flight::request()->url === '/dice') { + $dice = new \Dice\Dice(); + $dice = $dice->addRules([ + PdoWrapper::class => [ + 'shared' => true, + 'constructParams' => [ 'sqlite::memory:' ] + ] + ]); + Flight::registerContainerHandler(function ($class, $params) use ($dice) { + return $dice->create($class, $params); + }); + } + + // Default start behavior now + Flight::_start(); +}); + Flight::start();