diff --git a/flight/Engine.php b/flight/Engine.php index b22456e..7f45daa 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -220,13 +220,13 @@ class Engine /** * Registers the container handler * - * @param callable $callback Callback function that sets the container and how it will inject classes + * @param callable|object $containerHandler Callback function or PSR-11 Container object that sets the container and how it will inject classes * * @return void */ - public function registerContainerHandler($callback): void + public function registerContainerHandler($containerHandler): void { - $this->dispatcher->setContainerHandler($callback); + $this->dispatcher->setContainerHandler($containerHandler); } /** diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index 41d9d39..00a521b 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -25,6 +25,9 @@ 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 */ + public static $container_exception = null; + /** @var array Mapped events. */ protected array $events = []; @@ -40,18 +43,18 @@ class Dispatcher * * @var callable|object|null */ - protected $container_handler = null; + protected $containerHandler = null; /** * Sets the dependency injection container handler. * - * @param callable|object $container_handler Dependency injection container + * @param callable|object $containerHandler Dependency injection container * * @return void */ - public function setContainerHandler($container_handler): void + public function setContainerHandler($containerHandler): void { - $this->container_handler = $container_handler; + $this->containerHandler = $containerHandler; } /** @@ -246,11 +249,7 @@ class Dispatcher $this->handleInvalidCallbackType($callback); - if (is_array($callback) === true) { - return $this->invokeMethod($callback, $params); - } - - return $this->callFunction($callback, $params); + return $this->invokeCallable($callback, $params); } /** @@ -311,6 +310,7 @@ class Dispatcher * @return mixed Function results * @throws TypeError For nonexistent class name. * @throws InvalidArgumentException If the constructor requires parameters + * @version 3.7.0 */ public function invokeCallable($func, array &$params = []) { @@ -321,9 +321,9 @@ class Dispatcher [$class, $method] = $func; - // Only execute this if it's not a Flight class + // Only execute the container handler if it's not a Flight class if ( - $this->container_handler !== null && + $this->containerHandler !== null && ( ( is_object($class) === true && @@ -332,13 +332,15 @@ class Dispatcher is_string($class) === true ) ) { - $container_handler = $this->container_handler; - $resolved_class = $this->resolveContainerClass($container_handler, $class, $params); - if($resolved_class !== null) { - $class = $resolved_class; + $containerHandler = $this->containerHandler; + $resolvedClass = $this->resolveContainerClass($containerHandler, $class, $params); + if($resolvedClass !== null) { + $class = $resolvedClass; } } + // If there's no container handler set, you can use [ 'className', 'methodName' ] + // to call a method dynamically. Nothing is injected into the class though. if (is_string($class) && class_exists($class)) { $constructor = (new ReflectionClass($class))->getConstructor(); $constructorParamsNumber = 0; @@ -362,17 +364,20 @@ class Dispatcher // Final check to make sure it's actually a class and a method, or throw an error if (is_object($class) === false) { - if(ob_get_level() > 1) { + // Cause PHPUnit has 1 level of output buffering by default + if(ob_get_level() > (getenv('PHPUNIT_TEST') ? 1 : 0)) { ob_end_clean(); } throw new Exception("Class '$class' not found. Is it being correctly autoloaded with Flight::path()?"); } + // Class is there, but no method if (method_exists($class, $method) === false) { - if(ob_get_level() > 1) { + // Cause PHPUnit has 1 level of output buffering by default + if(ob_get_level() > (getenv('PHPUNIT_TEST') ? 1 : 0)) { ob_end_clean(); } - throw new Exception("Method '".get_class($class)."::$method' not found."); + throw new Exception("Class found, but method '".get_class($class)."::$method' not found."); } return call_user_func_array([ $class, $method ], $params); @@ -428,6 +433,12 @@ class Dispatcher } catch (Exception $e) { // 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 + // issues. + self::$container_exception = $e; } } diff --git a/tests/DispatcherTest.php b/tests/DispatcherTest.php index 7cf2c24..9e3289e 100644 --- a/tests/DispatcherTest.php +++ b/tests/DispatcherTest.php @@ -192,7 +192,7 @@ class DispatcherTest extends TestCase { set_error_handler(function (int $errno, string $errstr): void { $this->assertSame(E_USER_NOTICE, $errno); - $this->assertSame("Event 'myMethod' has been overriden!", $errstr); + $this->assertSame("Event 'myMethod' has been overridden!", $errstr); }); $this->dispatcher->set('myMethod', function (): string { @@ -200,10 +200,10 @@ class DispatcherTest extends TestCase }); $this->dispatcher->set('myMethod', function (): string { - return 'Overriden'; + return 'Overridden'; }); - $this->assertSame('Overriden', $this->dispatcher->run('myMethod')); + $this->assertSame('Overridden', $this->dispatcher->run('myMethod')); restore_error_handler(); } @@ -219,7 +219,7 @@ class DispatcherTest extends TestCase return 'Original'; }) ->hook('myMethod', 'invalid', function (array &$params, &$output): void { - $output = 'Overriden'; + $output = 'Overridden'; }); $this->assertSame('Original', $this->dispatcher->run('myMethod')); diff --git a/tests/EngineTest.php b/tests/EngineTest.php index dc5947b..8ce6dfc 100644 --- a/tests/EngineTest.php +++ b/tests/EngineTest.php @@ -6,6 +6,7 @@ namespace tests; use Exception; use Flight; +use flight\core\Dispatcher; use flight\Engine; use flight\net\Request; use flight\net\Response; @@ -706,6 +707,8 @@ class EngineTest extends TestCase $this->expectExceptionMessage("Class 'BadClass' not found. Is it being correctly autoloaded with Flight::path()?"); $engine->start(); + + $this->assertEquals('Class BadClass not found', Dispatcher::$container_exception->getMessage()); } public function testContainerDiceBadMethod() { @@ -719,9 +722,11 @@ class EngineTest extends TestCase $engine->request()->url = '/container'; $this->expectException(Exception::class); - $this->expectExceptionMessage("Method 'tests\classes\Container::badMethod' not found."); + $this->expectExceptionMessage("Class found, but method 'tests\classes\Container::badMethod' not found."); $engine->start(); + + $this->assertNull(Dispatcher::$container_exception); } public function testContainerPsr11() { @@ -765,7 +770,7 @@ class EngineTest extends TestCase $engine->request()->url = '/container'; $this->expectException(Exception::class); - $this->expectExceptionMessage("Method 'tests\classes\Container::badMethod' not found."); + $this->expectExceptionMessage("Class found, but method 'tests\classes\Container::badMethod' not found."); $engine->start(); }