refactored some errors. UI Tests and cleanup.

pull/559/head
Austin Collier 10 months ago
parent dbf05ebf40
commit 1b6c5b942b

@ -19,11 +19,12 @@ require_once __DIR__ . '/autoload.php';
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com> * @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* *
* # Core methods * # Core methods
* @method static void start() Starts the framework. * @method static void start() Starts the framework.
* @method static void path(string $path) Adds a path for autoloading classes. * @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 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 halt(int $code = 200, string $message = '', bool $actuallyExit = true)
* Stop the framework with an optional status code and message. * Stop the framework with an optional status code and message.
* @method static void registerContainerHandler(callable|object $containerHandler) Registers a container handler.
* *
* # Routing * # Routing
* @method static Route route(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '') * @method static Route route(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')

@ -26,8 +26,8 @@ class Dispatcher
public const FILTER_AFTER = 'after'; public const FILTER_AFTER = 'after';
private const FILTER_TYPES = [self::FILTER_BEFORE, self::FILTER_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 */ /** @var mixed $containerException Exception message if thrown by setting the container as a callable method */
protected $container_exception = null; protected $containerException = null;
/** @var ?Engine $engine Engine instance */ /** @var ?Engine $engine Engine instance */
protected ?Engine $engine = null; protected ?Engine $engine = null;
@ -256,8 +256,6 @@ class Dispatcher
$callback = $this->parseStringClassAndMethod($callback); $callback = $this->parseStringClassAndMethod($callback);
} }
$this->handleInvalidCallbackType($callback);
return $this->invokeCallable($callback, $params); return $this->invokeCallable($callback, $params);
} }
@ -325,10 +323,12 @@ class Dispatcher
{ {
// If this is a directly callable function, call it // If this is a directly callable function, call it
if (is_array($func) === false) { if (is_array($func) === false) {
$this->verifyValidFunction($func);
return call_user_func_array($func, $params); return call_user_func_array($func, $params);
} }
[$class, $method] = $func; [$class, $method] = $func;
$resolvedClass = null;
// Only execute the container handler if it's not a Flight class // Only execute the container handler if it's not a Flight class
if ( if (
@ -343,28 +343,12 @@ class Dispatcher
) { ) {
$containerHandler = $this->containerHandler; $containerHandler = $this->containerHandler;
$resolvedClass = $this->resolveContainerClass($containerHandler, $class, $params); $resolvedClass = $this->resolveContainerClass($containerHandler, $class, $params);
if($resolvedClass !== null) { if ($resolvedClass !== null) {
$class = $resolvedClass; $class = $resolvedClass;
} }
} }
// Final check to make sure it's actually a class and a method, or throw an error $this->verifyValidClassCallable($class, $method, $resolvedClass);
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.");
}
// Class is a string, and method exists, create the object by hand and inject only the Engine // Class is a string, and method exists, create the object by hand and inject only the Engine
if (is_string($class) === true) { if (is_string($class) === true) {
@ -382,7 +366,7 @@ class Dispatcher
* *
* @throws InvalidArgumentException If `$callback` is an invalid type * @throws InvalidArgumentException If `$callback` is an invalid type
*/ */
protected function handleInvalidCallbackType($callback): void protected function verifyValidFunction($callback): void
{ {
$isInvalidFunctionName = ( $isInvalidFunctionName = (
is_string($callback) 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. * Resolves the container class.
* *
@ -417,7 +436,6 @@ class Dispatcher
// Just a callable where you configure the behavior (Dice, PHP-DI, etc.) // Just a callable where you configure the behavior (Dice, PHP-DI, etc.)
} elseif (is_callable($container_handler) === true) { } elseif (is_callable($container_handler) === true) {
// This is to catch all the error that could be thrown by whatever container you are using // This is to catch all the error that could be thrown by whatever container you are using
try { try {
$class_object = call_user_func($container_handler, $class, $params); $class_object = call_user_func($container_handler, $class, $params);
@ -425,11 +443,12 @@ class Dispatcher
// could not resolve a class for some reason // could not resolve a class for some reason
$class_object = null; $class_object = null;
// If the container throws an exception, we need to catch it // If the container throws an exception, we need to catch it
// and store it somewhere. If we just let it throw itself, it // and store it somewhere. If we just let it throw itself, it
// doesn't properly close the output buffers and can cause other // doesn't properly close the output buffers and can cause other
// issues. // 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 protected function fixOutputBuffering(): void
{ {
// Cause PHPUnit has 1 level of output buffering by default // 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(); ob_end_clean();
} }
} }

@ -277,21 +277,21 @@ class DispatcherTest extends TestCase
public function testExecuteStringClassDefaultContainer(): void public function testExecuteStringClassDefaultContainer(): void
{ {
$this->dispatcher->setEngine(new Engine); $this->dispatcher->setEngine(new Engine());
$result = $this->dispatcher->execute(ContainerDefault::class . '->testTheContainer'); $result = $this->dispatcher->execute(ContainerDefault::class . '->testTheContainer');
$this->assertSame('You got it boss!', $result); $this->assertSame('You got it boss!', $result);
} }
public function testExecuteStringClassDefaultContainerDoubleColon(): void public function testExecuteStringClassDefaultContainerDoubleColon(): void
{ {
$this->dispatcher->setEngine(new Engine); $this->dispatcher->setEngine(new Engine());
$result = $this->dispatcher->execute(ContainerDefault::class . '::testTheContainer'); $result = $this->dispatcher->execute(ContainerDefault::class . '::testTheContainer');
$this->assertSame('You got it boss!', $result); $this->assertSame('You got it boss!', $result);
} }
public function testExecuteStringClassDefaultContainerArraySyntax(): void public function testExecuteStringClassDefaultContainerArraySyntax(): void
{ {
$this->dispatcher->setEngine(new Engine); $this->dispatcher->setEngine(new Engine());
$result = $this->dispatcher->execute([ ContainerDefault::class, 'testTheContainer' ]); $result = $this->dispatcher->execute([ ContainerDefault::class, 'testTheContainer' ]);
$this->assertSame('You got it boss!', $result); $this->assertSame('You got it boss!', $result);
} }

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace tests; namespace tests;
use Exception; use Exception;
use flight\core\Dispatcher;
use flight\database\PdoWrapper; use flight\database\PdoWrapper;
use flight\Engine; use flight\Engine;
use flight\net\Request; use flight\net\Request;

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace tests; namespace tests;
use Exception; use Exception;
use flight\core\Dispatcher;
use flight\Engine; use flight\Engine;
use tests\classes\Hello; use tests\classes\Hello;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;

@ -26,7 +26,7 @@ class Container
public function testThePdoWrapper() 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!'; echo 'Yay! I injected a PdoWrapper, and it returned the number ' . $value . ' from the database!';
} }
} }

@ -8,7 +8,6 @@ use flight\Engine;
class ContainerDefault class ContainerDefault
{ {
protected Engine $app; protected Engine $app;
public function __construct(Engine $engine) public function __construct(Engine $engine)
@ -22,4 +21,8 @@ class ContainerDefault
return $this->app->get('test_me_out'); return $this->app->get('test_me_out');
} }
public function testUi()
{
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>';
}
} }

@ -81,6 +81,8 @@ class LayoutMiddleware
<li><a href="/redirect/before%2Fafter">Slash in Param</a></li> <li><a href="/redirect/before%2Fafter">Slash in Param</a></li>
<li><a href="/わたしはひとです">UTF8 URL</a></li> <li><a href="/わたしはひとです">UTF8 URL</a></li>
<li><a href="/わたしはひとです/ええ">UTF8 URL w/ Param</a></li> <li><a href="/わたしはひとです/ええ">UTF8 URL w/ Param</a></li>
<li><a href="/dice">Dice Container</a></li>
<li><a href="/no-container">No Container Registered</a></li>
</ul> </ul>
HTML; HTML;
echo '<div id="container">'; echo '<div id="container">';

@ -2,6 +2,10 @@
declare(strict_types=1); 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 * 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. * 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) { 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::route('/no-container', ContainerDefault::class . '->testUi');
Flight::route('/dice', Container::class . '->testThePdoWrapper');
}, [ new LayoutMiddleware() ]); }, [ new LayoutMiddleware() ]);
// Test 9: JSON output (should not output any other html) // Test 9: JSON output (should not output any other html)
@ -167,4 +174,23 @@ Flight::map('notFound', function () {
echo "<a href='/'>Go back</a>"; echo "<a href='/'>Go back</a>";
}); });
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(); Flight::start();

Loading…
Cancel
Save