From a1a15f492ccd759d3ae7b0f3ceda956c0c5b7a8b Mon Sep 17 00:00:00 2001 From: kj Date: Fri, 10 Oct 2025 17:44:49 -0300 Subject: [PATCH] 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. --- src/Libs/Middleware.php | 28 -------- src/Libs/Request.php | 19 ++--- src/Libs/Router.php | 52 +++----------- src/Libs/Synapsis.php | 154 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 166 insertions(+), 87 deletions(-) delete mode 100644 src/Libs/Middleware.php create mode 100644 src/Libs/Synapsis.php diff --git a/src/Libs/Middleware.php b/src/Libs/Middleware.php deleted file mode 100644 index c74cd59..0000000 --- a/src/Libs/Middleware.php +++ /dev/null @@ -1,28 +0,0 @@ -next); - return call_user_func_array($next, [$req]); - } -} diff --git a/src/Libs/Request.php b/src/Libs/Request.php index d220a98..aea343f 100644 --- a/src/Libs/Request.php +++ b/src/Libs/Request.php @@ -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; } /** diff --git a/src/Libs/Router.php b/src/Libs/Router.php index 5367372..4b6aa2d 100644 --- a/src/Libs/Router.php +++ b/src/Libs/Router.php @@ -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); } } diff --git a/src/Libs/Synapsis.php b/src/Libs/Synapsis.php new file mode 100644 index 0000000..8ffcf21 --- /dev/null +++ b/src/Libs/Synapsis.php @@ -0,0 +1,154 @@ + $bindings + */ + private static array $bindings = []; + /** + * @var array $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 $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 $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; + } +}