From f752073f7d1fa6a5b80b474899e840bc6a9ff037 Mon Sep 17 00:00:00 2001 From: Austin Collier Date: Tue, 19 Mar 2024 23:52:39 -0600 Subject: [PATCH] more testing and catching things. --- flight/core/Dispatcher.php | 57 ++++++++++++++++-------------------- tests/DispatcherTest.php | 23 ++++++--------- tests/EngineTest.php | 58 +++++++++++++++++++++++++++++++++++-- tests/MapTest.php | 2 ++ tests/classes/Container.php | 11 ++++++- 5 files changed, 101 insertions(+), 50 deletions(-) diff --git a/flight/core/Dispatcher.php b/flight/core/Dispatcher.php index 00a521b..e36644c 100644 --- a/flight/core/Dispatcher.php +++ b/flight/core/Dispatcher.php @@ -339,44 +339,24 @@ class Dispatcher } } - // 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; - - if ($constructor !== null) { - $constructorParamsNumber = count($constructor->getParameters()); - } - - if ($constructorParamsNumber > 0) { - $exceptionMessage = "Method '$class::$method' cannot be called statically. "; - $exceptionMessage .= sprintf( - "$class::__construct require $constructorParamsNumber parameter%s", - $constructorParamsNumber > 1 ? 's' : '' - ); - - throw new InvalidArgumentException($exceptionMessage, E_ERROR); - } - - $class = new $class(); - } - // Final check to make sure it's actually a class and a method, or throw an error - if (is_object($class) === false) { - // Cause PHPUnit has 1 level of output buffering by default - if(ob_get_level() > (getenv('PHPUNIT_TEST') ? 1 : 0)) { - ob_end_clean(); - } + 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 && self::$container_exception !== null) { + $this->fixOutputBuffering(); + throw self::$container_exception; + } + + // If we made it this far, we can forget the container exception and move on... + self::$container_exception = null; + // Class is there, but no method if (method_exists($class, $method) === false) { - // Cause PHPUnit has 1 level of output buffering by default - if(ob_get_level() > (getenv('PHPUNIT_TEST') ? 1 : 0)) { - ob_end_clean(); - } + $this->fixOutputBuffering(); throw new Exception("Class found, but method '".get_class($class)."::$method' not found."); } @@ -445,6 +425,19 @@ class Dispatcher return $class_object; } + /** + * Because this could throw an exception in the middle of an output buffer, + * + * @return void + */ + protected function fixOutputBuffering(): void + { + // Cause PHPUnit has 1 level of output buffering by default + if(ob_get_level() > (getenv('PHPUNIT_TEST') ? 1 : 0)) { + ob_end_clean(); + } + } + /** * Resets the object to the initial state. * diff --git a/tests/DispatcherTest.php b/tests/DispatcherTest.php index 9e3289e..5fb902c 100644 --- a/tests/DispatcherTest.php +++ b/tests/DispatcherTest.php @@ -21,6 +21,7 @@ class DispatcherTest extends TestCase protected function setUp(): void { $this->dispatcher = new Dispatcher(); + Dispatcher::$container_exception = null; } public function testClosureMapping(): void @@ -137,20 +138,6 @@ class DispatcherTest extends TestCase $this->dispatcher->execute('nonexistentGlobalFunction'); } - public function testInvalidCallbackBecauseConstructorParameters(): void - { - $class = TesterClass::class; - $method = 'instanceMethod'; - $exceptionMessage = "Method '$class::$method' cannot be called statically. "; - $exceptionMessage .= "$class::__construct require 6 parameters"; - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage($exceptionMessage); - - static $params = []; - $this->dispatcher->invokeMethod([$class, $method], $params); - } - // It will be useful for executing instance Controller methods statically public function testCanExecuteAnNonStaticMethodStatically(): void { @@ -252,4 +239,12 @@ class DispatcherTest extends TestCase $this->assertSame('helloparam1param2param3param4param5param6', $result); } + + public function testInvokeMethod(): void + { + $class = new TesterClass('param1', 'param2', 'param3', 'param4', 'param5', 'param6'); + $result = $this->dispatcher->invokeMethod([ $class, 'instanceMethod' ]); + + $this->assertSame('param1', $class->param2); + } } diff --git a/tests/EngineTest.php b/tests/EngineTest.php index 8ce6dfc..ba8739b 100644 --- a/tests/EngineTest.php +++ b/tests/EngineTest.php @@ -5,12 +5,13 @@ declare(strict_types=1); namespace tests; use Exception; -use Flight; use flight\core\Dispatcher; +use flight\database\PdoWrapper; use flight\Engine; use flight\net\Request; use flight\net\Response; use flight\util\Collection; +use PDOException; use PHPUnit\Framework\TestCase; use tests\classes\Container; @@ -20,6 +21,7 @@ class EngineTest extends TestCase public function setUp(): void { $_SERVER = []; + Dispatcher::$container_exception = null; } public function tearDown(): void @@ -682,6 +684,12 @@ class EngineTest extends TestCase public function testContainerDice() { $engine = new Engine(); $dice = new \Dice\Dice(); + $dice = $dice->addRules([ + PdoWrapper::class => [ + 'shared' => true, + 'constructParams' => [ 'sqlite::memory:' ] + ] + ]); $engine->registerContainerHandler(function ($class, $params) use ($dice) { return $dice->create($class, $params); }); @@ -693,6 +701,42 @@ class EngineTest extends TestCase $this->expectOutputString('yay! I injected a collection, and it has 1 items'); } + public function testContainerDicePdoWrapperTest() { + $engine = new Engine(); + $dice = new \Dice\Dice(); + $dice = $dice->addRules([ + PdoWrapper::class => [ + 'shared' => true, + 'constructParams' => [ 'sqlite::memory:' ] + ] + ]); + $engine->registerContainerHandler(function ($class, $params) use ($dice) { + return $dice->create($class, $params); + }); + + $engine->route('/container', Container::class.'->testThePdoWrapper'); + $engine->request()->url = '/container'; + $engine->start(); + + $this->expectOutputString('Yay! I injected a PdoWrapper, and it returned the number 5 from the database!'); + } + + public function testContainerDicePdoWrapperTestBadParams() { + $engine = new Engine(); + $dice = new \Dice\Dice(); + $engine->registerContainerHandler(function ($class, $params) use ($dice) { + return $dice->create($class, $params); + }); + + $engine->route('/container', Container::class.'->testThePdoWrapper'); + $engine->request()->url = '/container'; + + $this->expectException(PDOException::class); + $this->expectExceptionMessage("invalid data source name"); + + $engine->start(); + } + public function testContainerDiceBadClass() { $engine = new Engine(); $dice = new \Dice\Dice(); @@ -714,6 +758,12 @@ class EngineTest extends TestCase public function testContainerDiceBadMethod() { $engine = new Engine(); $dice = new \Dice\Dice(); + $dice = $dice->addRules([ + PdoWrapper::class => [ + 'shared' => true, + 'constructParams' => [ 'sqlite::memory:' ] + ] + ]); $engine->registerContainerHandler(function ($class, $params) use ($dice) { return $dice->create($class, $params); }); @@ -732,8 +782,9 @@ class EngineTest extends TestCase public function testContainerPsr11() { $engine = new Engine(); $container = new \League\Container\Container(); - $container->add(Container::class)->addArgument(Collection::class); + $container->add(Container::class)->addArgument(Collection::class)->addArgument(PdoWrapper::class); $container->add(Collection::class); + $container->add(PdoWrapper::class)->addArgument('sqlite::memory:'); $engine->registerContainerHandler($container); $engine->route('/container', Container::class.'->testTheContainer'); @@ -762,8 +813,9 @@ class EngineTest extends TestCase public function testContainerPsr11MethodNotFound() { $engine = new Engine(); $container = new \League\Container\Container(); - $container->add(Container::class)->addArgument(Collection::class); + $container->add(Container::class)->addArgument(Collection::class)->addArgument(PdoWrapper::class); $container->add(Collection::class); + $container->add(PdoWrapper::class)->addArgument('sqlite::memory:'); $engine->registerContainerHandler($container); $engine->route('/container', Container::class.'->badMethod'); diff --git a/tests/MapTest.php b/tests/MapTest.php index 445e8eb..79c39dc 100644 --- a/tests/MapTest.php +++ b/tests/MapTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace tests; use Exception; +use flight\core\Dispatcher; use flight\Engine; use tests\classes\Hello; use PHPUnit\Framework\TestCase; @@ -15,6 +16,7 @@ class MapTest extends TestCase protected function setUp(): void { + Dispatcher::$container_exception = null; $this->app = new Engine(); } diff --git a/tests/classes/Container.php b/tests/classes/Container.php index ffb6fe1..7e67518 100644 --- a/tests/classes/Container.php +++ b/tests/classes/Container.php @@ -4,15 +4,18 @@ declare(strict_types=1); namespace tests\classes; +use flight\database\PdoWrapper; use flight\util\Collection; class Container { protected Collection $collection; + protected PdoWrapper $pdoWrapper; - public function __construct(Collection $collection) + public function __construct(Collection $collection, PdoWrapper $pdoWrapper) { $this->collection = $collection; + $this->pdoWrapper = $pdoWrapper; } public function testTheContainer() @@ -20,4 +23,10 @@ class Container $this->collection->whatever = 'yay!'; echo 'yay! I injected a collection, and it has ' . $this->collection->count() . ' items'; } + + public function testThePdoWrapper() + { + $value = (int) $this->pdoWrapper->fetchField('SELECT 5'); + echo 'Yay! I injected a PdoWrapper, and it returned the number ' . $value . ' from the database!'; + } }