From 59fff2a5867e75f9138c123572675f587ef833d7 Mon Sep 17 00:00:00 2001 From: KJ Date: Sat, 25 May 2024 16:59:59 -0400 Subject: [PATCH] Add validation on Request. --- src/Libs/Request.php | 97 ++++++++++++++++++++----- src/Libs/Router.php | 77 ++++++++++++-------- src/Libs/Validator.php | 159 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 287 insertions(+), 46 deletions(-) create mode 100644 src/Libs/Validator.php diff --git a/src/Libs/Request.php b/src/Libs/Request.php index 501690f..98eb0c0 100644 --- a/src/Libs/Request.php +++ b/src/Libs/Request.php @@ -13,35 +13,22 @@ namespace Libs; class Request extends Neuron { - /** - * @var Neuron $get Objeto con todos los valores de $_GET. - */ public Neuron $get; - /** - * @var Neuron $post Objeto con todos los valores de $_POST. - */ public Neuron $post; - /** - * @var Neuron $json Objeto con todos los valores json enviados. - */ public Neuron $json; - /** - * @var mixed $params Objeto con todos los valores pseudovariables de la uri. - */ public Neuron $params; - /** - * @var mixed $path Ruta actual tomando como raíz la instalación de DuckBrain. - */ public string $path; + public string $error; + public array $next; /** * __construct * * @param string $path Ruta actual tomando como raíz la instalación de DuckBrain. */ - public function __construct(string $path = '/') + public function __construct() { - $this->path = $path; + $this->path = Router::currentPath(); $this->get = new Neuron($_GET); $this->post = new Neuron($_POST); @@ -55,4 +42,80 @@ class Request extends Neuron { $this->params = new Neuron(); } + + /** + * Inicia la validación que se haya configurado. + * + * @return mixed + */ + public function validate(): mixed + { + if ($_SERVER['REQUEST_METHOD'] == 'GET') + $actual = $this->get; + else + $actual = $this->post; + + if (Validator::validateList(static::paramRules(), $this->params) && + Validator::validateList(static::getRules(), $this->get ) && + Validator::validateList(static::rules(), $actual)) + return Middleware::next($this); + + if (isset(static::messages()[Validator::$lastFailed])) + $error = static::messages()[Validator::$lastFailed]; + else { + + $error = 'Error: validation failed of '.preg_replace('/\./', ' as ', Validator::$lastFailed, 1); + } + + return static::onInvalid($error); + } + + /** + * Reglas para el método actual. + * + * @return array + */ + public static function rules(): array { + return []; + } + + /** + * Reglas para los parámetros por URL. + * + * @return array + */ + public static function paramRules(): array { + return []; + } + + /** + * Reglas para los parámetros GET. + * + * @return array + */ + public static function getRules(): array { + return []; + } + + /** + * Mensajes de error en caso de fallar una validación. + * + * @return array + */ + public static function messages(): array { + return []; + } + + /** + * Función a ejecutar cuando se ha detectado un valor no válido. + * + * @param string $error + * + * @return mixed + */ + protected function onInvalid(string $error): mixed + { + print($error); + return null; + } } diff --git a/src/Libs/Router.php b/src/Libs/Router.php index 5f23af9..f3bce2c 100644 --- a/src/Libs/Router.php +++ b/src/Libs/Router.php @@ -290,29 +290,16 @@ class Router { } /** - * Aplica los routers. + * Aplica la configuración de rutas. * - * Este método ha de ser llamado luego de que todos los routers hayan sido configurados. + * @param string $path (opcional) Ruta a usar. Si no se define, detecta la ruta actual. * - * En caso que la ruta actual coincida con un router configurado, se comprueba si hay middleware; Si hay - * middleware, se enviará el callback y los datos de la petición como un Neuron. Caso contrario, se enviarán - * los datos directamente al callback. - * - * Con middleware: - * $middleware($callback, $req) - * - * Sin middleware: - * $callback($req) - * - * $req es una instancia de Neuron que tiene los datos de la petición. - * - * Si no la ruta no coincide con ninguna de las rutas configuradas, ejecutará el callback $notFoundCallback * @return void */ - public static function apply(): void + public static function apply(string $path = null): void { - $path = static::currentPath(); - $routers = match($_SERVER['REQUEST_METHOD']) { // Según el método selecciona un arreglo de routers configurados + $path = $path ?? static::currentPath(); + $routers = match($_SERVER['REQUEST_METHOD']) { // Según el método selecciona un arreglo de routers 'POST' => static::$post, 'PUT' => static::$put, 'PATCH' => static::$patch, @@ -320,25 +307,58 @@ class Router { default => static::$get }; - $req = new Request(static::currentPath()); - foreach ($routers as $router) { // revisa todos los routers para ver si coinciden con la ruta actual if (preg_match_all('/^'.$router['path'].'\/?$/si',$path, $matches, PREG_PATTERN_ORDER)) { unset($matches[0]); - // Comprobando pseudo variables en la ruta + // 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]; - $req->params->$paramName = urldecode($match[0]); + $paramName = $router['paramNames'][$index-1]; + $request->params->$paramName = urldecode($match[0]); } } - // Llamar al último callback configurado - $next = array_pop($router['callback']); - $req->next = $router['callback']; - $data = call_user_func_array($next, [$req]); + // Llama a la validación y luego procesa la cola de callbacks + $request->next = $router['callback']; + $data = $request->validate($request); + // Por defecto imprime como JSON si se retorna algo if (isset($data)) { header('Content-Type: application/json'); print(json_encode($data)); @@ -349,7 +369,6 @@ class Router { } // Si no hay router que coincida llamamos a $notFoundCallBack - call_user_func_array(static::$notFoundCallback, [$req]); + call_user_func_array(static::$notFoundCallback, [new Request]); } } -?> diff --git a/src/Libs/Validator.php b/src/Libs/Validator.php new file mode 100644 index 0000000..eee9f4e --- /dev/null +++ b/src/Libs/Validator.php @@ -0,0 +1,159 @@ + $rules) { + $rules = preg_split('/\|/', $rules); + foreach ($rules as $rule) { + if (static::checkRule($haystack->{$target}, $rule)) + continue; + static::$lastFailed = $target.'.'.$rule; + return false; + } + } + + return true; + } + + /** + * Revisa si una regla se cumple. + * + * @param mixed $subject Lo que se va a verfificar. + * @param string $rule La regla a probar. + * + * @return bool + */ + public static function checkRule(mixed $subject, string $rule): bool + { + $arguments = preg_split('/[:,]/', $rule); + $rule = [static::class, $arguments[0]]; + $arguments[0] = $subject; + + if (is_callable($rule)) + return call_user_func_array($rule, $arguments); + + throw new \Exception('Bad rule: "'.preg_split('/::/', $rule)[1].'"' ); + } + + /** + * Verifica la regla de manera negativa. + * + * @param mixed $subject Lo que se va a verfificar. + * @param mixed $rule La regla a probar. + * + * @return bool + */ + public static function not(mixed $subject, ...$rule): bool + { + return !static::checkRule($subject, join(':', $rule)); + } + + /** + * required + * + * @param mixed $subject + * + * @return bool + */ + public static function required(mixed $subject): bool + { + return isset($subject); + } + + /** + * number + * + * @param mixed $subject + * + * @return bool + */ + public static function number(mixed $subject): bool + { + return is_numeric($subject); + } + + /** + * int + * + * @param mixed $subject + * + * @return bool + */ + public static function int(mixed $subject): bool + { + return filter_var($subject, FILTER_VALIDATE_INT); + } + + /** + * float + * + * @param mixed $subject + * + * @return bool + */ + public static function float(mixed $subject): bool + { + return filter_var($subject, FILTER_VALIDATE_FLOAT); + } + + /** + * bool + * + * @param mixed $subject + * + * @return bool + */ + public static function bool(mixed $subject): bool + { + return filter_var($subject, FILTER_VALIDATE_BOOLEAN); + } + + /** + * email + * + * @param mixed $subject + * + * @return bool + */ + public static function email(mixed $subject): bool + { + return filter_var($subject, FILTER_VALIDATE_EMAIL); + } + + /** + * url + * + * @param mixed $subject + * + * @return bool + */ + public static function url(mixed $subject): bool + { + filter_var($subject, FILTER_VALIDATE_URL); + } + + /** + * enum + * + * @param mixed $subject + * @param mixed $values + * + * @return bool + */ + public static function enum(mixed $subject, ...$values): bool + { + return in_array($subject, $values); + } +}