initial commit for containerization

pull/559/head
n0nag0n 10 months ago
parent 412596e863
commit 6d41115e9a

@ -41,9 +41,11 @@
},
"require-dev": {
"ext-pdo_sqlite": "*",
"phpunit/phpunit": "^9.5",
"phpstan/phpstan": "^1.10",
"league/container": "^4.2",
"level-2/dice": "^4.0",
"phpstan/extension-installer": "^1.3",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.5",
"rregeer/phpunit-coverage-check": "^0.3.1",
"squizlabs/php_codesniffer": "^3.8"
},

@ -30,17 +30,17 @@ use flight\net\Route;
* @method void halt(int $code = 200, string $message = '', bool $actuallyExit = true) Stops processing and returns a given response.
*
* # Routing
* @method Route route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* @method Route route(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
* Routes a URL to a callback function with all applicable methods
* @method void group(string $pattern, callable $callback, array<int, callable|object> $group_middlewares = [])
* Groups a set of routes together under a common prefix.
* @method Route post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* @method Route post(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
* Routes a POST URL to a callback function.
* @method Route put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* @method Route put(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
* Routes a PUT URL to a callback function.
* @method Route patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* @method Route patch(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
* Routes a PATCH URL to a callback function.
* @method Route delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* @method Route delete(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
* Routes a DELETE URL to a callback function.
* @method Router router() Gets router
* @method string getUrl(string $alias) Gets a url from an alias
@ -217,6 +217,18 @@ class Engine
$this->error($e);
}
/**
* Registers the container handler
*
* @param callable $callback Callback function that sets the container and how it will inject classes
*
* @return void
*/
public function registerContainerHandler($callback): void
{
$this->dispatcher->setContainerHandler($callback);
}
/**
* Maps a callback to a framework method.
*
@ -605,11 +617,11 @@ class Engine
* Routes a URL to a callback function.
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param callable|string $callback Callback function
* @param bool $pass_route Pass the matching route object to the callback
* @param string $alias The alias for the route
*/
public function _route(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): Route
public function _route(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
{
return $this->router()->map($pattern, $callback, $pass_route, $alias);
}
@ -630,10 +642,10 @@ class Engine
* Routes a URL to a callback function.
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param callable|string $callback Callback function or string class->method
* @param bool $pass_route Pass the matching route object to the callback
*/
public function _post(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void
public function _post(string $pattern, $callback, bool $pass_route = false, string $route_alias = ''): void
{
$this->router()->map('POST ' . $pattern, $callback, $pass_route, $route_alias);
}
@ -642,10 +654,10 @@ class Engine
* Routes a URL to a callback function.
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param callable|string $callback Callback function or string class->method
* @param bool $pass_route Pass the matching route object to the callback
*/
public function _put(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void
public function _put(string $pattern, $callback, bool $pass_route = false, string $route_alias = ''): void
{
$this->router()->map('PUT ' . $pattern, $callback, $pass_route, $route_alias);
}
@ -654,10 +666,10 @@ class Engine
* Routes a URL to a callback function.
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param callable|string $callback Callback function or string class->method
* @param bool $pass_route Pass the matching route object to the callback
*/
public function _patch(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void
public function _patch(string $pattern, $callback, bool $pass_route = false, string $route_alias = ''): void
{
$this->router()->map('PATCH ' . $pattern, $callback, $pass_route, $route_alias);
}
@ -666,10 +678,10 @@ class Engine
* Routes a URL to a callback function.
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param callable|string $callback Callback function or string class->method
* @param bool $pass_route Pass the matching route object to the callback
*/
public function _delete(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void
public function _delete(string $pattern, $callback, bool $pass_route = false, string $route_alias = ''): void
{
$this->router()->map('DELETE ' . $pattern, $callback, $pass_route, $route_alias);
}

@ -26,17 +26,17 @@ require_once __DIR__ . '/autoload.php';
* Stop the framework with an optional status code and message.
*
* # Routing
* @method static Route route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* @method static Route route(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
* Maps a URL pattern to a callback with all applicable methods.
* @method static void group(string $pattern, callable $callback, callable[] $group_middlewares = [])
* Groups a set of routes together under a common prefix.
* @method static Route post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* @method static Route post(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
* Routes a POST URL to a callback function.
* @method static Route put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* @method static Route put(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
* Routes a PUT URL to a callback function.
* @method static Route patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* @method static Route patch(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
* Routes a PATCH URL to a callback function.
* @method static Route delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '')
* @method static Route delete(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
* Routes a DELETE URL to a callback function.
* @method static Router router() Returns Router instance.
* @method static string getUrl(string $alias, array<string, mixed> $params = []) Gets a url from an alias
@ -101,34 +101,6 @@ class Flight
{
}
/**
* Registers a class to a framework method.
*
* # Usage example:
* ```
* Flight::register('user', User::class);
*
* Flight::user(); # <- Return a User instance
* ```
*
* @param string $name Static method name
* @param class-string<T> $class Fully Qualified Class Name
* @param array<int, mixed> $params Class constructor params
* @param ?Closure(T $instance): void $callback Perform actions with the instance
*
* @template T of object
*/
public static function register($name, $class, $params = [], $callback = null): void
{
static::__callStatic('register', [$name, $class, $params, $callback]);
}
/** Unregisters a class. */
public static function unregister(string $methodName): void
{
static::__callStatic('unregister', [$methodName]);
}
/**
* Handles calls to static methods.
*
@ -140,7 +112,7 @@ class Flight
*/
public static function __callStatic(string $name, array $params)
{
return Dispatcher::invokeMethod([self::app(), $name], $params);
return self::app()->{$name}(...$params);
}
/** @return Engine Application instance */

@ -35,6 +35,25 @@ class Dispatcher
*/
protected array $filters = [];
/**
* This is a container for the dependency injection.
*
* @var callable|object|null
*/
protected $container_handler = null;
/**
* Sets the dependency injection container handler.
*
* @param callable|object $container_handler Dependency injection container
*
* @return void
*/
public function setContainerHandler($container_handler): void
{
$this->container_handler = $container_handler;
}
/**
* Dispatches an event.
*
@ -80,10 +99,10 @@ class Dispatcher
$requestedMethod = $this->get($eventName);
if ($requestedMethod === null) {
throw new Exception("Event '$eventName' isn't found.");
throw new Exception("Event '{$eventName}' isn't found.");
}
return $requestedMethod(...$params);
return $this->execute($requestedMethod, $params);
}
/**
@ -194,7 +213,7 @@ class Dispatcher
*
* @throws Exception If an event throws an `Exception` or if `$filters` contains an invalid filter.
*/
public static function filter(array $filters, array &$params, &$output): void
public function filter(array $filters, array &$params, &$output): void
{
foreach ($filters as $key => $callback) {
if (!is_callable($callback)) {
@ -219,22 +238,39 @@ class Dispatcher
* @return mixed Function results
* @throws Exception If `$callback` also throws an `Exception`.
*/
public static function execute($callback, array &$params = [])
public function execute($callback, array &$params = [])
{
$isInvalidFunctionName = (
is_string($callback)
&& !function_exists($callback)
);
if (is_string($callback) === true && (strpos($callback, '->') !== false || strpos($callback, '::') !== false)) {
$callback = $this->parseStringClassAndMethod($callback);
}
if ($isInvalidFunctionName) {
throw new InvalidArgumentException('Invalid callback specified.');
$this->handleInvalidCallbackType($callback);
if (is_array($callback) === true) {
return $this->invokeMethod($callback, $params);
}
if (is_array($callback)) {
return self::invokeMethod($callback, $params);
return $this->callFunction($callback, $params);
}
/**
* Parses a string into a class and method.
*
* @param string $classAndMethod Class and method
*
* @return array{class-string|object, string} Class and method
*/
public function parseStringClassAndMethod(string $classAndMethod): array
{
$class_parts = explode('->', $classAndMethod);
if (count($class_parts) === 1) {
$class_parts = explode('::', $class_parts[0]);
}
return self::callFunction($callback, $params);
$class = $class_parts[0];
$method = $class_parts[1];
return [ $class, $method ];
}
/**
@ -244,10 +280,11 @@ class Dispatcher
* @param array<int, mixed> &$params Function parameters
*
* @return mixed Function results
* @deprecated 3.7.0 Use invokeCallable instead
*/
public static function callFunction(callable $func, array &$params = [])
public function callFunction(callable $func, array &$params = [])
{
return call_user_func_array($func, $params);
return $this->invokeCallable($func, $params);
}
/**
@ -257,12 +294,48 @@ class Dispatcher
* @param array<int, mixed> &$params Class method parameters
*
* @return mixed Function results
* @throws TypeError For unexistent class name.
* @throws TypeError For nonexistent class name.
* @deprecated 3.7.0 Use invokeCallable instead
*/
public function invokeMethod(array $func, array &$params = [])
{
return $this->invokeCallable($func, $params);
}
/**
* Invokes a callable (anonymous function or Class->method).
*
* @param array{class-string|object, string}|Callable $func Class method
* @param array<int, mixed> &$params Class method parameters
*
* @return mixed Function results
* @throws TypeError For nonexistent class name.
* @throws InvalidArgumentException If the constructor requires parameters
*/
public static function invokeMethod(array $func, array &$params = [])
public function invokeCallable($func, array &$params = [])
{
// If this is a directly callable function, call it
if (is_array($func) === false) {
return call_user_func_array($func, $params);
}
[$class, $method] = $func;
// Only execute this if it's not a Flight class
if (
$this->container_handler !== null &&
(
(
is_object($class) === true &&
strpos(get_class($class), 'flight\\') === false
) ||
is_string($class) === true
)
) {
$container_handler = $this->container_handler;
$class = $this->resolveContainerClass($container_handler, $class, $params);
}
if (is_string($class) && class_exists($class)) {
$constructor = (new ReflectionClass($class))->getConstructor();
$constructorParamsNumber = 0;
@ -284,7 +357,56 @@ class Dispatcher
$class = new $class();
}
return call_user_func_array([$class, $method], $params);
return call_user_func_array([ $class, $method ], $params);
}
/**
* Handles invalid callback types.
*
* @param callable-string|(Closure(): mixed)|array{class-string|object, string} $callback
* Callback function
*
* @throws InvalidArgumentException If `$callback` is an invalid type
*/
protected function handleInvalidCallbackType($callback): void
{
$isInvalidFunctionName = (
is_string($callback)
&& !function_exists($callback)
);
if ($isInvalidFunctionName) {
throw new InvalidArgumentException('Invalid callback specified.');
}
}
/**
* Resolves the container class.
*
* @param callable|object $container_handler Dependency injection container
* @param class-string $class Class name
* @param array<int, mixed> &$params Class constructor parameters
*
* @return object Class object
*/
protected function resolveContainerClass($container_handler, $class, array &$params)
{
$class_object = null;
// PSR-11
if (
is_object($container_handler) === true &&
method_exists($container_handler, 'has') === true &&
$container_handler->has($class)
) {
$class_object = call_user_func([$container_handler, 'get'], $class);
// Just a callable where you configure the behavior (Dice, PHP-DI, etc.)
} elseif (is_callable($container_handler) === true) {
$class_object = call_user_func($container_handler, $class, $params);
}
return $class_object;
}
/**

@ -81,11 +81,11 @@ class Route
* Constructor.
*
* @param string $pattern URL pattern
* @param callable $callback Callback function
* @param callable|string $callback Callback function
* @param array<int, string> $methods HTTP methods
* @param bool $pass Pass self in callback parameters
*/
public function __construct(string $pattern, callable $callback, array $methods, bool $pass, string $alias = '')
public function __construct(string $pattern, $callback, array $methods, bool $pass, string $alias = '')
{
$this->pattern = $pattern;
$this->callback = $callback;

@ -80,11 +80,11 @@ class Router
* Maps a URL pattern to a callback function.
*
* @param string $pattern URL pattern to match.
* @param callable $callback Callback function.
* @param callable|string $callback Callback function or string class->method
* @param bool $pass_route Pass the matching route object to the callback.
* @param string $route_alias Alias for the route.
*/
public function map(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): Route
public function map(string $pattern, $callback, bool $pass_route = false, string $route_alias = ''): Route
{
// This means that the route ies defined in a group, but the defined route is the base
@ -133,11 +133,11 @@ class Router
* Creates a GET based route
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param callable|string $callback Callback function or string class->method
* @param bool $pass_route Pass the matching route object to the callback
* @param string $alias Alias for the route
*/
public function get(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): Route
public function get(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
{
return $this->map('GET ' . $pattern, $callback, $pass_route, $alias);
}
@ -146,11 +146,11 @@ class Router
* Creates a POST based route
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param callable|string $callback Callback function or string class->method
* @param bool $pass_route Pass the matching route object to the callback
* @param string $alias Alias for the route
*/
public function post(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): Route
public function post(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
{
return $this->map('POST ' . $pattern, $callback, $pass_route, $alias);
}
@ -159,11 +159,11 @@ class Router
* Creates a PUT based route
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param callable|string $callback Callback function or string class->method
* @param bool $pass_route Pass the matching route object to the callback
* @param string $alias Alias for the route
*/
public function put(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): Route
public function put(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
{
return $this->map('PUT ' . $pattern, $callback, $pass_route, $alias);
}
@ -172,11 +172,11 @@ class Router
* Creates a PATCH based route
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param callable|string $callback Callback function or string class->method
* @param bool $pass_route Pass the matching route object to the callback
* @param string $alias Alias for the route
*/
public function patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): Route
public function patch(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
{
return $this->map('PATCH ' . $pattern, $callback, $pass_route, $alias);
}
@ -185,11 +185,11 @@ class Router
* Creates a DELETE based route
*
* @param string $pattern URL pattern to match
* @param callable $callback Callback function
* @param callable|string $callback Callback function or string class->method
* @param bool $pass_route Pass the matching route object to the callback
* @param string $alias Alias for the route
*/
public function delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): Route
public function delete(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
{
return $this->map('DELETE ' . $pattern, $callback, $pass_route, $alias);
}

@ -107,11 +107,11 @@ class DispatcherTest extends TestCase
});
$this->dispatcher
->hook('hello', $this->dispatcher::FILTER_BEFORE, function (array &$params): void {
->hook('hello', Dispatcher::FILTER_BEFORE, function (array &$params): void {
// Manipulate the parameter
$params[0] = 'Fred';
})
->hook('hello', $this->dispatcher::FILTER_AFTER, function (array &$params, string &$output): void {
->hook('hello', Dispatcher::FILTER_AFTER, function (array &$params, string &$output): void {
// Manipulate the output
$output .= ' Have a nice day!';
});
@ -125,7 +125,7 @@ class DispatcherTest extends TestCase
{
$this->expectException(TypeError::class);
Dispatcher::execute(['NonExistentClass', 'nonExistentMethod']);
$this->dispatcher->execute(['NonExistentClass', 'nonExistentMethod']);
}
public function testInvalidCallableString(): void
@ -133,7 +133,7 @@ class DispatcherTest extends TestCase
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid callback specified.');
Dispatcher::execute('inexistentGlobalFunction');
$this->dispatcher->execute('inexistentGlobalFunction');
}
public function testInvalidCallbackBecauseConstructorParameters(): void
@ -147,13 +147,13 @@ class DispatcherTest extends TestCase
$this->expectExceptionMessage($exceptionMessage);
static $params = [];
Dispatcher::invokeMethod([$class, $method], $params);
$this->dispatcher->invokeMethod([$class, $method], $params);
}
// It will be useful for executing instance Controller methods statically
public function testCanExecuteAnNonStaticMethodStatically(): void
{
$this->assertSame('hello', Dispatcher::execute([Hello::class, 'sayHi']));
$this->assertSame('hello', $this->dispatcher->execute([Hello::class, 'sayHi']));
}
public function testItThrowsAnExceptionWhenRunAnUnregistedEventName(): void
@ -237,7 +237,7 @@ class DispatcherTest extends TestCase
$validCallable = function (): void {
};
Dispatcher::filter([$validCallable, $invalidCallable], $params, $output);
$this->dispatcher->filter([$validCallable, $invalidCallable], $params, $output);
}
public function testCallFunction6Params(): void
@ -247,7 +247,7 @@ class DispatcherTest extends TestCase
};
$params = ['param1', 'param2', 'param3', 'param4', 'param5', 'param6'];
$result = Dispatcher::callFunction($func, $params);
$result = $this->dispatcher->callFunction($func, $params);
$this->assertSame('helloparam1param2param3param4param5param6', $result);
}

@ -11,6 +11,7 @@ use flight\net\Request;
use flight\net\Response;
use flight\util\Collection;
use PHPUnit\Framework\TestCase;
use tests\classes\Container;
// phpcs:ignoreFile PSR2.Methods.MethodDeclaration.Underscore
class EngineTest extends TestCase
@ -676,4 +677,32 @@ class EngineTest extends TestCase
$engine->start();
$this->expectOutputString('before456before123OKafter123456after123');
}
public function testContainerDice() {
$engine = new Engine();
$dice = new \Dice\Dice();
$engine->registerContainerHandler(function ($class, $params) use ($dice) {
return $dice->create($class, $params);
});
$engine->route('/container', Container::class.'->testTheContainer');
$engine->request()->url = '/container';
$engine->start();
$this->expectOutputString('yay! I injected a collection, and it has 1 items');
}
public function testContainerPsr11() {
$engine = new Engine();
$container = new \League\Container\Container();
$container->add(Container::class)->addArgument(Collection::class);
$container->add(Collection::class);
$engine->registerContainerHandler($container);
$engine->route('/container', Container::class.'->testTheContainer');
$engine->request()->url = '/container';
$engine->start();
$this->expectOutputString('yay! I injected a collection, and it has 1 items');
}
}

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace tests\classes;
use flight\util\Collection;
class Container
{
protected Collection $collection;
public function __construct(Collection $collection)
{
$this->collection = $collection;
}
public function testTheContainer()
{
$this->collection->whatever = 'yay!';
echo 'yay! I injected a collection, and it has ' . $this->collection->count() . ' items';
}
}
Loading…
Cancel
Save