refactor!: add Synapsis for dependency resolution and injection
- Remove Middleware class and custom callback handling logic. - Implement Synapsis as a dependency injection container for automatic resolution. - Refactor Router to use Synapsis for process route callbacks and not found handler. - Update Request to remove middleware-specific properties and use Router::$currentParams for path parameters.
This commit is contained in:
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Libs;
|
||||
|
||||
/**
|
||||
* Middleware - DuckBrain
|
||||
*
|
||||
* Librería base para middlewares.
|
||||
*
|
||||
* @author KJ
|
||||
* @website https://kj2.me
|
||||
* @licence MIT
|
||||
*/
|
||||
class Middleware
|
||||
{
|
||||
/**
|
||||
* Llama al siguiente callback.
|
||||
*
|
||||
* @param Neuron $req
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function next(Neuron $req): mixed
|
||||
{
|
||||
$next = array_pop($req->next);
|
||||
return call_user_func_array($next, [$req]);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ class Request extends Neuron
|
||||
public string $path;
|
||||
public string $error;
|
||||
public string $body;
|
||||
public array $next;
|
||||
|
||||
/**
|
||||
* __construct
|
||||
@@ -39,6 +38,7 @@ class Request extends Neuron
|
||||
$this->put = new Neuron();
|
||||
$this->patch = new Neuron();
|
||||
$this->delete = new Neuron();
|
||||
$this->params = new Neuron(Router::$currentParams);
|
||||
$this->body = file_get_contents("php://input");
|
||||
|
||||
$contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : '';
|
||||
@@ -60,21 +60,10 @@ class Request extends Neuron
|
||||
}
|
||||
}
|
||||
|
||||
$this->params = new Neuron();
|
||||
}
|
||||
|
||||
/**
|
||||
* Corre las validaciones e intenta continuar con la pila de callbacks.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(): mixed
|
||||
{
|
||||
if ($this->validate()) {
|
||||
return Middleware::next($this);
|
||||
// Corremos las validaciones configuradas
|
||||
if (!$this->validate()) {
|
||||
exit();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,7 @@ class Router
|
||||
private static $patch = [];
|
||||
private static $delete = [];
|
||||
private static $last;
|
||||
public static $currentParams = [];
|
||||
public static $notFoundCallback = 'Libs\Router::defaultNotFound';
|
||||
|
||||
/**
|
||||
@@ -325,55 +326,18 @@ class Router
|
||||
if (preg_match_all('/^' . $router['path'] . '\/?$/si', $path, $matches, PREG_PATTERN_ORDER)) {
|
||||
unset($matches[0]);
|
||||
|
||||
// Objtener un reflection del callback
|
||||
$lastCallback = $router['callback'][0];
|
||||
if ($lastCallback instanceof \Closure) { // si es función anónima
|
||||
$reflectionCallback = new \ReflectionFunction($lastCallback);
|
||||
} else {
|
||||
if (is_string($lastCallback)) {
|
||||
$lastCallback = preg_split('/::/', $lastCallback);
|
||||
}
|
||||
|
||||
// Revisamos su es un método o solo una función
|
||||
if (count($lastCallback) == 2) {
|
||||
$reflectionCallback = new \ReflectionMethod($lastCallback[0], $lastCallback[1]);
|
||||
} else {
|
||||
$reflectionCallback = new \ReflectionFunction($lastCallback[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Obtener los parámetros
|
||||
$arguments = $reflectionCallback->getParameters();
|
||||
if (isset($arguments[0])) {
|
||||
// Obtenemos la clase del primer parámetro
|
||||
$argumentClass = strval($arguments[0]->getType());
|
||||
|
||||
// Verificamos si la clase está o no tipada
|
||||
if (empty($argumentClass)) {
|
||||
$request = new Request();
|
||||
} else {
|
||||
$request = new $argumentClass();
|
||||
|
||||
// Verificamos que sea instancia de Request (requerimiento)
|
||||
if (!($request instanceof Request)) {
|
||||
throw new \Exception('Bad argument type on router callback.');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$request = new Request();
|
||||
}
|
||||
|
||||
// Comprobando y guardando los parámetros variables de la ruta
|
||||
if (isset($matches[1])) {
|
||||
foreach ($matches as $index => $match) {
|
||||
$paramName = $router['paramNames'][$index - 1];
|
||||
$request->params->$paramName = urldecode($match[0]);
|
||||
$paramName = $router['paramNames'][$index - 1];
|
||||
static::$currentParams[$paramName] = urldecode($match[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Llama a la validación y luego procesa la cola de callbacks
|
||||
$request->next = $router['callback'];
|
||||
$data = $request->handle();
|
||||
// Procesa la cola de callbacks
|
||||
foreach (array_reverse($router['callback']) as $callback) {
|
||||
$data = Synapsis::resolve($callback);
|
||||
}
|
||||
|
||||
// Por defecto imprime como JSON si se retorna algo
|
||||
if (isset($data)) {
|
||||
@@ -386,6 +350,6 @@ class Router
|
||||
}
|
||||
|
||||
// Si no hay router que coincida llamamos a $notFoundCallBack
|
||||
call_user_func_array(static::$notFoundCallback, [new Request()]);
|
||||
Synapsis::resolve(static::$notFoundCallback);
|
||||
}
|
||||
}
|
||||
|
||||
154
src/Libs/Synapsis.php
Normal file
154
src/Libs/Synapsis.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Libs;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
use ReflectionMethod;
|
||||
use ReflectionParameter;
|
||||
|
||||
/**
|
||||
* Synapsis - DuckBrain
|
||||
*
|
||||
* Clase encargada de la resolución e inyección de dependencias
|
||||
*
|
||||
* @author KJ
|
||||
* @website https://kj2.me
|
||||
* @licence MIT
|
||||
*/
|
||||
class Synapsis
|
||||
{
|
||||
/**
|
||||
* @var array<class-string, class-string> $bindings
|
||||
*/
|
||||
private static array $bindings = [];
|
||||
/**
|
||||
* @var array<class-string, mixed> $instances
|
||||
*/
|
||||
private static array $instances = [];
|
||||
|
||||
/**
|
||||
* Asocia una interface a una clase para instanciarla en su lugar.
|
||||
*
|
||||
* @param class-string $interfaceName
|
||||
* @param class-string $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bind(string $interfaceName, string $className): void
|
||||
{
|
||||
if (interface_exists($interfaceName) && class_exists($className)) {
|
||||
static::$bindings[$interfaceName] = $className;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception('Error at binding non existant Interface or ClassName.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asocia en lote múltiples interfaces con múltiples clases.
|
||||
*
|
||||
* @param array<class-string, class-string> $bindings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bulkBind(array $bindings): void
|
||||
{
|
||||
foreach ($bindings as $interfaceName => $className) {
|
||||
static::bind($interfaceName, $className);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resuelve e inyecta las dependencias de un callable y devuelve su resultado.
|
||||
*
|
||||
* @param callable $action
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function resolve(callable $action): mixed
|
||||
{
|
||||
if ($action instanceof Closure) { // si es función anónima
|
||||
$reflectionCallback = new ReflectionFunction($action);
|
||||
} else { // Areglo o string con el método
|
||||
if (is_string($action)) {
|
||||
$action = preg_split('/::/', $action);
|
||||
}
|
||||
|
||||
// Revisamos su es un método o solo una función
|
||||
if (count($action) == 2) {
|
||||
$reflectionCallback = new ReflectionMethod($action[0], $action[1]);
|
||||
} else {
|
||||
$reflectionCallback = new ReflectionFunction($action[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Obtenemos los parámetros
|
||||
return call_user_func_array(
|
||||
$action,
|
||||
static::resolveParameterValues($reflectionCallback->getParameters())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resuelve e inyecta las dependencias de una clase y devuelve su resultado.
|
||||
*
|
||||
* @param class-string $className
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function &resolveInstance(string $className): mixed
|
||||
{
|
||||
if (interface_exists($className)) {
|
||||
if (isset(static::$bindings[$className])) {
|
||||
$className = static::$bindings[$className];
|
||||
} else {
|
||||
throw new Exception("Missing binding for interface: {$className}");
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists($className)) {
|
||||
throw new Exception("Cannot resolve: {$className}");
|
||||
}
|
||||
|
||||
if (!isset(static::$instances[$className])) {
|
||||
// Si la instancia no ha sido resuelta antes, la devolvemos
|
||||
$reflectionClass = new ReflectionClass($className);
|
||||
$constructor = $reflectionClass->getConstructor();
|
||||
static::$instances[$className] = new $className(
|
||||
...static::resolveParameterValues($constructor->getParameters())
|
||||
);
|
||||
}
|
||||
|
||||
return static::$instances[$className];
|
||||
}
|
||||
|
||||
/**
|
||||
* resolveParameterValues
|
||||
*
|
||||
* @param array<ReflectionParameter> $parameters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function resolveParameterValues(array $parameters): array
|
||||
{
|
||||
$values = [];
|
||||
foreach ($parameters as $parameter) {
|
||||
if ($parameter->isOptional()) { // Siempre usamos el valor por defecto primero
|
||||
$values[] = $parameter->getDefaultValue();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Intentamos darle un valor equivalente a nulo si es una clase primitiva pare evitar
|
||||
// errores innecesarios, lo
|
||||
if ($parameter->getType()->isBuiltin()) {
|
||||
throw new Exception('Primitive parameters expect at least a default value.');
|
||||
}
|
||||
|
||||
$values[] = static::resolveInstance($parameter->getType()->getName());
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user