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:
kj
2025-10-10 17:44:49 -03:00
parent b19e7d8789
commit a1a15f492c
4 changed files with 166 additions and 87 deletions

View File

@@ -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]);
}
}

View File

@@ -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;
}
/**

View File

@@ -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
View 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;
}
}