Add validation on Request.

This commit is contained in:
KJ 2024-05-25 16:59:59 -04:00
parent cd1685d2e7
commit 59fff2a586
3 changed files with 287 additions and 46 deletions

View File

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

View File

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

159
src/Libs/Validator.php Normal file
View File

@ -0,0 +1,159 @@
<?php
namespace Libs;
class Validator {
public static string $lastFailed = '';
/**
* Validar lista de reglas sobre las propiedades de un objeto.
*
* @param array $rulesList Lista de reglas.
* @param Neuron $haystack Objeto al que se le verificarán las reglas.
*
* @return bool Retorna true solo si todas las reglas se cumplen y false en cuanto una falle.
*/
public static function validateList(array $rulesList, Neuron $haystack): bool
{
foreach ($rulesList as $target => $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);
}
}