Compare commits
51 Commits
d48f24ed98
...
master
Author | SHA1 | Date | |
---|---|---|---|
e9126e7cde | |||
7169d2cae3 | |||
66b2bc0d91 | |||
c8ab2aa2cc | |||
1e302a9ea7 | |||
d0d0d4dc76 | |||
595e9c1316 | |||
45abea5301 | |||
d441f001ec | |||
19da122e05 | |||
1a0164c8ed | |||
ad9f8ec67d | |||
31c5c63952 | |||
6aef212350 | |||
c600688725 | |||
3e27b1b7af | |||
73b7b8f72a | |||
7baad428ec | |||
3d2a607768 | |||
df424ffab5 | |||
daf7250882 | |||
05cd83fd10 | |||
6b470a181d | |||
7beb161d2b | |||
701caae7eb | |||
100bdfe006 | |||
f1b79fdbc0 | |||
406f9a10a1 | |||
cc3cb6be41 | |||
59fff2a586 | |||
cd1685d2e7 | |||
b85fb7e034 | |||
a10308a8f6 | |||
9a1e5a2379 | |||
fa60ec5bb4 | |||
b294b1d627 | |||
27b5ed6015 | |||
f9b2c678f7 | |||
e294eaa7af | |||
186d640c8f | |||
c65fcd27c8 | |||
f5f803dde2 | |||
6a1085b224 | |||
64bcfa0b00 | |||
cd01ab9e72 | |||
e2094ccb4a | |||
341d7837a1 | |||
030e1079dc | |||
af673a68b8 | |||
eff0b86762 | |||
39a1f9d85a |
@ -1,11 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
// Configuración de la base de datos
|
||||||
define('DB_TYPE', 'mysql');
|
define('DB_TYPE', 'mysql');
|
||||||
define('DB_HOST', 'localhost');
|
define('DB_HOST', 'localhost');
|
||||||
define('DB_NAME', '');
|
define('DB_NAME', '');
|
||||||
define('DB_USER', '');
|
define('DB_USER', '');
|
||||||
define('DB_PASS', '');
|
define('DB_PASS', '');
|
||||||
|
|
||||||
//define('SITE_URL', '');
|
// Configuración del sitio
|
||||||
|
define('SITE_URL', '');
|
||||||
|
|
||||||
|
// Configuración avanzada
|
||||||
define('ROOT_DIR', __DIR__);
|
define('ROOT_DIR', __DIR__);
|
||||||
?>
|
define('ROOT_CORE', ROOT_DIR.'/src');
|
||||||
|
12
index.php
12
index.php
@ -6,19 +6,15 @@ spl_autoload_register(function ($className) {
|
|||||||
$fp = str_replace('\\','/',$className);
|
$fp = str_replace('\\','/',$className);
|
||||||
$name = basename($fp);
|
$name = basename($fp);
|
||||||
$dir = dirname($fp);
|
$dir = dirname($fp);
|
||||||
$file = ROOT_DIR.'/src/'.$dir.'/'.$name.'.php';
|
$file = ROOT_CORE.'/'.$dir.'/'.$name.'.php';
|
||||||
if (file_exists($file)) {
|
if (file_exists($file))
|
||||||
require_once $file;
|
require_once $file;
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Incluir routers
|
// Incluir routers
|
||||||
$routers = glob(ROOT_DIR.'/src/Routers/*.php');
|
$routers = glob(ROOT_CORE.'/Routers/*.php');
|
||||||
|
|
||||||
foreach($routers as $file){
|
foreach($routers as $file)
|
||||||
require_once($file);
|
require_once($file);
|
||||||
}
|
|
||||||
|
|
||||||
\Libs\Router::apply();
|
\Libs\Router::apply();
|
||||||
?>
|
|
||||||
|
106
readme.org
106
readme.org
@ -1,20 +1,106 @@
|
|||||||
* DuckBrain - PHP Microframework
|
#+TITLE: Duckbrain
|
||||||
|
#+AUTHOR: KJ
|
||||||
|
#+OPTIONS: toc:nil
|
||||||
|
|
||||||
Este microframework PHP tiene el objetivo de presentar un framework sencillo y potente que sea válido especialmente para proyectos pequeños o simples, sin limitar demasiado, ni depender de cosas que agranden innecesariamente proyectos.
|
Este conjunto de librerías (microframework, si así lo prefieres) tiene el objetivo de presentar una herramienta de trabajo sencilla y potente que sea válida especialmente para proyectos pequeños o simples, sin limitar, ni depender de cosas que agranden innecesariamente dichos proyectos.
|
||||||
|
|
||||||
Esto no es un intento de mi parte de reinventar la rueda, de hecho los primeros commits los hice con la intención de que solo fuera una prueba de concepto, pero me gustó como quedó y decidí darle continuidad hasta hacerlo usable para mi mismo.
|
Tuve la idea de hacer esta herramienta luego de ver como proyectos relativamente pequeños eran hechos con frameworks demasiado grandes para la envergadura de dichos proyectos. No es bueno matar moscas a cañonazos.
|
||||||
|
|
||||||
Tuve la idea de hacer este conjunto de librerías/microframework luego de ver como proyectos relativamente pequeños eran hechos con frameworks demasiado pesados para lo que eran.
|
Del mismo modo revisé algunos microframeworks y si bien me parecen una buena opción, aún me quedaba la espinita de que seguían siendo muy complejos para que un programador novato pudiera leer su código, entenderlo y modificarlo, así que intenté hacer algo más simple
|
||||||
|
|
||||||
También revisé otros microframeworks y si bien me parecen una buena opción para hacer los trabajos, aún me quedaba la espinita de que seguían siendo muy grandes para que un programador novato pudiera leerlo, entenderlo y modificarlo, así que intenté hacer algo más simple aún y de ahí salió este proyecto.
|
Lo que busco es mantener un código sencillo, lo suficiente como para que cualquier novato que sepa POO y PHP pueda leer su código rápidamente, entenderlo y modificarlo a gusto. Por este motivo también he decidido desligarlo en lo posible de composer o cualquier cosa similar, ya que no pocos programadores en etapa de aprendizaje al encontrarse con frameworks más complicados o con herramientas como composer, terminan trabajando con cosas que no comprenden ni pueden arreglar por si mismos en caso de fallo, llevándolos a la dependencia total de dichas herramientas.
|
||||||
|
|
||||||
Lo ideal sería mantener el código sencillo, lo suficiente como para que cualquier novato que sepa POO y PHP pueda leerlo rápido, entenderlo y modificarlo a gusto. Por este motivo también he decidido desligarlo en lo posible de composer o cualquier cosa similar, ya que no pocos programadores en etapa de aprendizaje al encontrarse con frameworks más complicados o con herramientas como composer, terminan trabajando con cosas que no comprenden ni pueden arreglar por si mismos en caso de fallo, ya que son completamente dependientes de dichas herramientas.
|
|
||||||
|
|
||||||
El código no es perfecto, pero lo iré perfeccionando, ya que es algo que estoy usando actualmente para mis trabajos, de modo que puedo ir viendo que cosas se pueden ir mejorando.
|
|
||||||
|
|
||||||
* Uso / Documentación
|
* Uso / Documentación
|
||||||
|
|
||||||
Queda pendiente, si quieres usarlo ya mismo, puedes leer los comentarios que he colocado en el código o usar [[https://www.phpdoc.org/][phpdoc]] para generarla a partir de los bloques de comentarios.
|
Actualmente existe un manual de inicio que puedes leerlo desde [[https://tools.kj2.me/duckbrain/starting-manual.html][aquí]] o desde [[https://git.kj2.me/kj/duckbrain-docs/src/branch/master/starting-manual.org][aquí]].
|
||||||
|
|
||||||
|
Si quieres leer el manual de inicio en su formato original (necesitas un editor/lector que soporte org-mode) puedes descargarla desde [[https://tools.kj2.me/duckbrain/starting-manual.org][aquí]].
|
||||||
|
|
||||||
|
Para el resto de la documentación, el código usa [[https://www.phpdoc.org/][phpdoc]], por lo que puedes tener toda la documentación API desde tu IDE o Editor de texto preferido siempre que soporte dicha característica. O si lo prefieres, puedes generarla en unbonito htm local usando la herramienta [[https://docs.phpdoc.org/3.0/packages/phpDocumentor.html][phpDocumentor]].
|
||||||
|
|
||||||
|
* Soporte de gestores de bases de datos
|
||||||
|
|
||||||
|
En la siguiente tabla se encuentra la lista de estados de los gestores de bases de datos que he probado o que planeo probar para asegurarme de que sean realmente compatibles.
|
||||||
|
|
||||||
|
*Entiéndase*:
|
||||||
|
|
||||||
|
+ *ok* como que ha sido probado y funciona.
|
||||||
|
+ *En blanco* como que no ha sido probado aún.
|
||||||
|
+ *error* como que fue probado, no funciona y no ha sido aún arreglado.
|
||||||
|
+ *not supported* como no soportado por el gestor de bases de datos.
|
||||||
|
+ *fixed* para aquello que no existe en el gestor de DB, pero la librería lo traduce a un equivalente.
|
||||||
|
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| method | MySQL/MariaDB | sqlite3 | postgreSQL |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| db | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| query | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| resetQuery | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| buildQuery | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| getInstance | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| getVars | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| className | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| table | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| update | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| beginTransaction | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| rollBack | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| commit | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| add | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| save | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| delete | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| select | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| from | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| where | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| where_in | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| leftJoin | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| rightJoin | ok | not supported | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| innerJoin | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| and | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| or | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| groupBy | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| limit | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| orderBy | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| count | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| getById | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| search | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| get | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| getFirst | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| all | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
| setNull | ok | ok | |
|
||||||
|
|------------------+---------------+---------------+------------|
|
||||||
|
|
||||||
* Contacto
|
* Contacto
|
||||||
|
|
||||||
|
95
rework.org
95
rework.org
@ -1,95 +0,0 @@
|
|||||||
* Sobre la actualización
|
|
||||||
Previamente, el sistema usaba [[https://www.php.net/mysqli][MySQLi]] para el Modelo/ORM, como actualización, se ha cambiado para funcionar con [[https://www.php.net/pdo][PDO]].
|
|
||||||
|
|
||||||
* Notas de migración
|
|
||||||
Estos son detalles importantes a tomar en cuenta a la hora de migrar del anterior ModelMySQL (MySQLi) al actual Model (PDO).
|
|
||||||
|
|
||||||
+ El modelo base ha cambiado de nombre de =ModelMySQL= a =Model=.
|
|
||||||
+ El método =query= ahora devuelve un =array= en lugar de un =mysqli_result=.
|
|
||||||
+ Se han depurado los métodos =sql_calc_found_rows= y =found_rows=.
|
|
||||||
+ Se ha cambiado =dbname=, =dbuser= y =dbpass= a =DB_NAME=, =DB_USER= y =DB_PASS=, respectivamente.
|
|
||||||
+ Se ha añadido la necesitad de la constante =DB_TYPE= para indicar el driver PDO a usar (ej. mysql, sqlite).
|
|
||||||
|
|
||||||
* Pruebas
|
|
||||||
|
|
||||||
En la siguiente tabla se encuentra la lista de estados de los SGBD que he probado (MySQL/MariaDB) o que planeo probar (sqlite3, postgreSQL) para asegurarme de que sean realmente compatibles.
|
|
||||||
|
|
||||||
*Entiéndase*:
|
|
||||||
|
|
||||||
+ *ok* como que ha sido probado y funciona.
|
|
||||||
+ En blanco como que no ha sido probado aún.
|
|
||||||
+ *error* como que fue probado, no funciona y no ha sido aún arreglado.
|
|
||||||
+ *not supported* como no soportado por el SGBD.
|
|
||||||
+ *fixed* para aquello que no existe, pero la librería lo traduce a un equivalente.
|
|
||||||
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| method | MySQL/MariaDB | sqlite3 | postgreSQL |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| db | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| query | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| resetQuery | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| buildQuery | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| getInstance | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| getVars | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| className | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| table | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| update | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| beginTransaction | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| rollBack | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| commit | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| add | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| save | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| delete | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| select | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| from | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| where | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| where_in | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| leftJoin | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| rightJoin | ok | not supported | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| innerJoin | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| and | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| or | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| groupBy | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| limit | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| orderBy | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| count | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| getById | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| search | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| get | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| getFirst | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| all | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
||||||
| setNull | ok | ok | |
|
|
||||||
|------------------+---------------+---------------+------------|
|
|
@ -3,11 +3,6 @@
|
|||||||
* Database - DuckBrain
|
* Database - DuckBrain
|
||||||
*
|
*
|
||||||
* Clase diseñada para crear y devolver una única instancia PDO (database).
|
* Clase diseñada para crear y devolver una única instancia PDO (database).
|
||||||
* Hace uso de las siguientes constantes:
|
|
||||||
* DB_TYPE, DB_NAME, DB_HOST, DB_USER, DB_PASS
|
|
||||||
*
|
|
||||||
* Si DB_TYPE es sqlite, usará DB_NAME como el nombre del archivo sqlite.
|
|
||||||
* Además DB_USER y DB_PASS, no será necesariop que estén definidos.
|
|
||||||
*
|
*
|
||||||
* @author KJ
|
* @author KJ
|
||||||
* @website https://kj2.me
|
* @website https://kj2.me
|
||||||
@ -21,7 +16,7 @@ use PDOException;
|
|||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
class Database extends PDO {
|
class Database extends PDO {
|
||||||
static private ?PDO $db = null;
|
static private array $databases = [];
|
||||||
|
|
||||||
private function __construct() {}
|
private function __construct() {}
|
||||||
|
|
||||||
@ -30,29 +25,34 @@ class Database extends PDO {
|
|||||||
*
|
*
|
||||||
* @return PDO
|
* @return PDO
|
||||||
*/
|
*/
|
||||||
static public function getInstance() : PDO {
|
static public function getInstance(
|
||||||
if (is_null(self::$db)) {
|
string $type = 'mysql',
|
||||||
|
string $host = 'localhost',
|
||||||
|
string $name = '',
|
||||||
|
string $user = '',
|
||||||
|
string $pass = '',
|
||||||
|
): PDO
|
||||||
|
{
|
||||||
|
$key = $type.'/'.$host.'/'.$name.'/'.$user;
|
||||||
|
if (empty(static::$databases[$key])) {
|
||||||
|
|
||||||
if (DB_TYPE == 'sqlite') {
|
if ($type == 'sqlite') {
|
||||||
$dsn = DB_TYPE .':'. DB_NAME;
|
$dsn = $type .':'. $name;
|
||||||
!defined('DB_USER') && define('DB_USER', '');
|
|
||||||
!defined('DB_PASS') && define('DB_PASS', '');
|
|
||||||
} else
|
} else
|
||||||
$dsn = DB_TYPE.':dbname='.DB_NAME.';host='.DB_HOST;
|
$dsn = $type.':dbname='.$name.';host='.$host;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
self::$db = new PDO($dsn, DB_USER, DB_PASS);
|
static::$databases[$key] = new PDO($dsn, $user, $pass);
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
echo "<pre>";
|
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
'Error at connect to database: ' . $e->getMessage()
|
'Error at connect to database: ' . $e->getMessage()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self::$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
static::$databases[$key]->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
self::$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
static::$databases[$key]->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
||||||
}
|
}
|
||||||
return self::$db;
|
return static::$databases[$key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
@ -20,7 +20,8 @@ class Middleware {
|
|||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public static function next(Neuron $req): mixed {
|
public static function next(Neuron $req): mixed
|
||||||
|
{
|
||||||
$next = array_pop($req->next);
|
$next = array_pop($req->next);
|
||||||
return call_user_func_array($next, [$req]);
|
return call_user_func_array($next, [$req]);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
* Model - DuckBrain
|
* Model - DuckBrain
|
||||||
*
|
*
|
||||||
* Modelo ORM para objetos que hagan uso de una base de datos.
|
* Modelo ORM para objetos que hagan uso de una base de datos.
|
||||||
* Depende de Libs\Database.
|
* Depende de Libs\Database y hace uso de las constantes
|
||||||
|
* DB_TYPE, DB_HOST, DB_NAME, DB_USER y DB_PASS.
|
||||||
*
|
*
|
||||||
* @author KJ
|
* @author KJ
|
||||||
* @website https://kj2.me
|
* @website https://kj2.me
|
||||||
@ -30,7 +31,6 @@ class Model {
|
|||||||
static protected array $forceSave = [];
|
static protected array $forceSave = [];
|
||||||
static protected string $table;
|
static protected string $table;
|
||||||
static protected string $tableSufix = 's';
|
static protected string $tableSufix = 's';
|
||||||
static protected ?PDO $db = null;
|
|
||||||
static protected array $queryVars = [];
|
static protected array $queryVars = [];
|
||||||
static protected array $querySelect = [
|
static protected array $querySelect = [
|
||||||
'select' => ['*'],
|
'select' => ['*'],
|
||||||
@ -49,11 +49,21 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return PDO
|
* @return PDO
|
||||||
*/
|
*/
|
||||||
protected static function db() : PDO {
|
protected static function db(): PDO
|
||||||
if (is_null(static::$db))
|
{
|
||||||
static::$db = Database::getInstance();
|
if (DB_TYPE == 'sqlite')
|
||||||
|
return Database::getInstance(
|
||||||
return static::$db;
|
type: DB_TYPE,
|
||||||
|
name: DB_NAME
|
||||||
|
);
|
||||||
|
else
|
||||||
|
return Database::getInstance(
|
||||||
|
DB_TYPE,
|
||||||
|
DB_HOST,
|
||||||
|
DB_NAME,
|
||||||
|
DB_USER,
|
||||||
|
DB_PASS
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,7 +72,8 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function beginTransaction() : bool {
|
public function beginTransaction(): bool
|
||||||
|
{
|
||||||
return static::db()->beginTransaction();
|
return static::db()->beginTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,8 +83,12 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function rollBack() : bool {
|
public function rollBack(): bool
|
||||||
return static::db()->rollBack();
|
{
|
||||||
|
if ( static::db()->inTransaction())
|
||||||
|
return static::db()->rollBack();
|
||||||
|
else
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,8 +97,12 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function commit() : bool {
|
public function commit(): bool
|
||||||
return static::db()->commit();
|
{
|
||||||
|
if (static::db()->inTransaction())
|
||||||
|
return static::db()->commit();
|
||||||
|
else
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,7 +122,8 @@ class Model {
|
|||||||
* @return array
|
* @return array
|
||||||
* Contiene el resultado de la llamada SQL .
|
* Contiene el resultado de la llamada SQL .
|
||||||
*/
|
*/
|
||||||
protected static function query(string $query, bool $resetQuery = true) : array {
|
protected static function query(string $query, bool $resetQuery = true): array
|
||||||
|
{
|
||||||
$db = static::db();
|
$db = static::db();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -115,7 +135,6 @@ class Model {
|
|||||||
|
|
||||||
$vars = json_encode(static::$queryVars);
|
$vars = json_encode(static::$queryVars);
|
||||||
|
|
||||||
echo "<pre>";
|
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
"\nError at query to database.\n" .
|
"\nError at query to database.\n" .
|
||||||
"Query: $query\n" .
|
"Query: $query\n" .
|
||||||
@ -136,7 +155,8 @@ class Model {
|
|||||||
* Reinicia la configuración de la sentencia SQL.
|
* Reinicia la configuración de la sentencia SQL.
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected static function resetQuery(): void {
|
protected static function resetQuery(): void
|
||||||
|
{
|
||||||
static::$querySelect = [
|
static::$querySelect = [
|
||||||
'select' => ['*'],
|
'select' => ['*'],
|
||||||
'where' => '',
|
'where' => '',
|
||||||
@ -158,7 +178,8 @@ class Model {
|
|||||||
* @return string
|
* @return string
|
||||||
* Contiene la sentencia SQL.
|
* Contiene la sentencia SQL.
|
||||||
*/
|
*/
|
||||||
protected static function buildQuery() : string {
|
protected static function buildQuery(): string
|
||||||
|
{
|
||||||
$sql = 'SELECT '.join(', ', static::$querySelect['select']);
|
$sql = 'SELECT '.join(', ', static::$querySelect['select']);
|
||||||
|
|
||||||
if (static::$querySelect['from'] != '')
|
if (static::$querySelect['from'] != '')
|
||||||
@ -201,7 +222,8 @@ class Model {
|
|||||||
* @return string
|
* @return string
|
||||||
* Parámetro de sustitución.
|
* Parámetro de sustitución.
|
||||||
*/
|
*/
|
||||||
private static function bindValue(string $value) : string{
|
private static function bindValue(string $value): string
|
||||||
|
{
|
||||||
$index = ':v_'.count(static::$queryVars);
|
$index = ':v_'.count(static::$queryVars);
|
||||||
static::$queryVars[$index] = $value;
|
static::$queryVars[$index] = $value;
|
||||||
return $index;
|
return $index;
|
||||||
@ -217,12 +239,20 @@ class Model {
|
|||||||
* @return static
|
* @return static
|
||||||
* Retorna un objeto de la clase actual.
|
* Retorna un objeto de la clase actual.
|
||||||
*/
|
*/
|
||||||
protected static function getInstance(array $elem = []) : static {
|
protected static function getInstance(array $elem = []): static
|
||||||
$class = get_called_class();
|
{
|
||||||
$instance = new $class;
|
$class = get_called_class();
|
||||||
|
$instance = new $class;
|
||||||
|
$reflection = new ReflectionClass($instance);
|
||||||
|
$properties = $reflection->getProperties();
|
||||||
|
$propertyNames = array_column($properties, 'name');
|
||||||
|
|
||||||
foreach ($elem as $key => $value) {
|
foreach ($elem as $key => $value) {
|
||||||
$instance->$key = $value;
|
$index = array_search($key, $propertyNames);
|
||||||
|
if (is_numeric($index) && enum_exists($properties[$index]->getType()->getName()))
|
||||||
|
$instance->$key = $properties[$index]->getType()->getName()::tryfrom($value);
|
||||||
|
else
|
||||||
|
$instance->$key = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $instance;
|
return $instance;
|
||||||
@ -237,7 +267,8 @@ class Model {
|
|||||||
* @return array
|
* @return array
|
||||||
* Contiene los atributos indexados del objeto actual.
|
* Contiene los atributos indexados del objeto actual.
|
||||||
*/
|
*/
|
||||||
protected function getVars() : array {
|
protected function getVars(): array
|
||||||
|
{
|
||||||
$reflection = new ReflectionClass($this);
|
$reflection = new ReflectionClass($this);
|
||||||
$properties = $reflection->getProperties(ReflectionProperty::IS_PUBLIC);
|
$properties = $reflection->getProperties(ReflectionProperty::IS_PUBLIC);
|
||||||
$result = [];
|
$result = [];
|
||||||
@ -253,10 +284,14 @@ class Model {
|
|||||||
$result[$value] = isset($this->$value)
|
$result[$value] = isset($this->$value)
|
||||||
? $this->$value: null;
|
? $this->$value: null;
|
||||||
|
|
||||||
foreach ($result as $i => $property)
|
foreach ($result as $i => $property) {
|
||||||
if (gettype($property) == 'boolean')
|
if (gettype($property) == 'boolean')
|
||||||
$result[$i] = $property ? '1' : '0';
|
$result[$i] = $property ? '1' : '0';
|
||||||
|
|
||||||
|
if ($property instanceof \UnitEnum)
|
||||||
|
$result[$i] = $property->value;
|
||||||
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,8 +301,11 @@ class Model {
|
|||||||
* @return string
|
* @return string
|
||||||
* Devuelve el nombre de la clase actual.
|
* Devuelve el nombre de la clase actual.
|
||||||
*/
|
*/
|
||||||
public static function className() : string {
|
public static function className(): string
|
||||||
return strtolower(substr(strrchr(get_called_class(), '\\'), 1));
|
{
|
||||||
|
return substr(
|
||||||
|
strrchr(get_called_class(), '\\'), 1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -277,17 +315,25 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected static function table() : string {
|
protected static function table(): string
|
||||||
|
{
|
||||||
if (isset(static::$table))
|
if (isset(static::$table))
|
||||||
return static::$table;
|
return static::$table;
|
||||||
return static::className().static::$tableSufix;
|
|
||||||
|
return strtolower(
|
||||||
|
preg_replace(
|
||||||
|
'/(?<!^)[A-Z]/', '_$0',
|
||||||
|
static::className()
|
||||||
|
)
|
||||||
|
).static::$tableSufix;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Actualiza los valores en la BD con los valores del objeto actual.
|
* Actualiza los valores en la BD con los valores del objeto actual.
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected function update(): void {
|
protected function update(): void
|
||||||
|
{
|
||||||
$atts = $this->getVars();
|
$atts = $this->getVars();
|
||||||
|
|
||||||
foreach ($atts as $key => $value) {
|
foreach ($atts as $key => $value) {
|
||||||
@ -316,7 +362,8 @@ class Model {
|
|||||||
* objeto actual.
|
* objeto actual.
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected function add(): void {
|
protected function add(): void
|
||||||
|
{
|
||||||
$db = static::db();
|
$db = static::db();
|
||||||
$atts = $this->getVars();
|
$atts = $this->getVars();
|
||||||
|
|
||||||
@ -341,7 +388,8 @@ class Model {
|
|||||||
* llama a update para actualizar o add para insertar una nueva fila.
|
* llama a update para actualizar o add para insertar una nueva fila.
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function save(): void {
|
public function save(): void
|
||||||
|
{
|
||||||
$pk = static::$primaryKey;
|
$pk = static::$primaryKey;
|
||||||
if (isset($this->$pk))
|
if (isset($this->$pk))
|
||||||
$this->update();
|
$this->update();
|
||||||
@ -370,7 +418,8 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function select(array $columns) : static {
|
public static function select(array $columns): static
|
||||||
|
{
|
||||||
static::$querySelect['select'] = $columns;
|
static::$querySelect['select'] = $columns;
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
@ -384,7 +433,8 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function from(array $tables) : static {
|
public static function from(array $tables): static
|
||||||
|
{
|
||||||
static::$querySelect['from'] = join(', ', $tables);
|
static::$querySelect['from'] = join(', ', $tables);
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
@ -399,7 +449,7 @@ class Model {
|
|||||||
* @param string $operatorOrValue
|
* @param string $operatorOrValue
|
||||||
* El operador o el valor a comparar como igual en caso de que $value no se defina.
|
* El operador o el valor a comparar como igual en caso de que $value no se defina.
|
||||||
*
|
*
|
||||||
* @param string $value
|
* @param string|null $value
|
||||||
* (opcional) El valor a comparar en la columna.
|
* (opcional) El valor a comparar en la columna.
|
||||||
*
|
*
|
||||||
* @param bool $no_filter
|
* @param bool $no_filter
|
||||||
@ -408,8 +458,19 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function where(string $column, string $operatorOrValue, string $value=null, bool $no_filter = false) : static {
|
public static function where(
|
||||||
return static::and($column, $operatorOrValue, $value, $no_filter);
|
string $column,
|
||||||
|
string $operatorOrValue,
|
||||||
|
?string $value = null,
|
||||||
|
bool $no_filter = false
|
||||||
|
): static
|
||||||
|
{
|
||||||
|
return static::and(
|
||||||
|
$column,
|
||||||
|
$operatorOrValue,
|
||||||
|
$value,
|
||||||
|
$no_filter
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -421,7 +482,7 @@ class Model {
|
|||||||
* @param string $operatorOrValue
|
* @param string $operatorOrValue
|
||||||
* El operador o el valor a comparar como igual en caso de que $value no se defina.
|
* El operador o el valor a comparar como igual en caso de que $value no se defina.
|
||||||
*
|
*
|
||||||
* @param string $value
|
* @param string|null $value
|
||||||
* (opcional) El valor el valor a comparar en la columna.
|
* (opcional) El valor el valor a comparar en la columna.
|
||||||
*
|
*
|
||||||
* @param bool $no_filter
|
* @param bool $no_filter
|
||||||
@ -430,7 +491,13 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function and(string $column, string $operatorOrValue, string $value=null, bool $no_filter = false) : static {
|
public static function and(
|
||||||
|
string $column,
|
||||||
|
string $operatorOrValue,
|
||||||
|
?string $value = null,
|
||||||
|
bool $no_filter = false
|
||||||
|
): static
|
||||||
|
{
|
||||||
if (is_null($value)) {
|
if (is_null($value)) {
|
||||||
$value = $operatorOrValue;
|
$value = $operatorOrValue;
|
||||||
$operatorOrValue = '=';
|
$operatorOrValue = '=';
|
||||||
@ -456,7 +523,7 @@ class Model {
|
|||||||
* @param string $operatorOrValue
|
* @param string $operatorOrValue
|
||||||
* El operador o el valor a comparar como igual en caso de que $value no se defina.
|
* El operador o el valor a comparar como igual en caso de que $value no se defina.
|
||||||
*
|
*
|
||||||
* @param string $value
|
* @param string|null $value
|
||||||
* (opcional) El valor el valor a comparar en la columna.
|
* (opcional) El valor el valor a comparar en la columna.
|
||||||
*
|
*
|
||||||
* @param bool $no_filter
|
* @param bool $no_filter
|
||||||
@ -465,7 +532,13 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function or(string $column, string $operatorOrValue, string $value=null, bool $no_filter = false) : static {
|
public static function or(
|
||||||
|
string $column,
|
||||||
|
string $operatorOrValue,
|
||||||
|
?string $value = null,
|
||||||
|
bool $no_filter = false
|
||||||
|
): static
|
||||||
|
{
|
||||||
if (is_null($value)) {
|
if (is_null($value)) {
|
||||||
$value = $operatorOrValue;
|
$value = $operatorOrValue;
|
||||||
$operatorOrValue = '=';
|
$operatorOrValue = '=';
|
||||||
@ -496,16 +569,26 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function where_in(string $column, array $arr, bool $in = true) : static {
|
public static function where_in(
|
||||||
|
string $column,
|
||||||
|
array $arr,
|
||||||
|
bool $in = true
|
||||||
|
): static
|
||||||
|
{
|
||||||
$arrIn = [];
|
$arrIn = [];
|
||||||
foreach($arr as $value) {
|
foreach($arr as $value) {
|
||||||
$arrIn[] = static::bindValue($value);
|
$arrIn[] = static::bindValue($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($in)
|
if ($in)
|
||||||
static::$querySelect['where'] = "$column IN (".join(', ', $arrIn).")";
|
$where_in = "$column IN (".join(', ', $arrIn).")";
|
||||||
else
|
else
|
||||||
static::$querySelect['where'] = "$column NOT IN (".join(', ', $arrIn).")";
|
$where_in = "$column NOT IN (".join(', ', $arrIn).")";
|
||||||
|
|
||||||
|
if (static::$querySelect['where'] == '')
|
||||||
|
static::$querySelect['where'] = $where_in;
|
||||||
|
else
|
||||||
|
static::$querySelect['where'] .= " AND $where_in";
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
}
|
}
|
||||||
@ -522,12 +605,18 @@ class Model {
|
|||||||
* @param string $operatorOrColumnB
|
* @param string $operatorOrColumnB
|
||||||
* Operador o columna a comparar como igual para hacer el join en caso de que $columnB no se defina.
|
* Operador o columna a comparar como igual para hacer el join en caso de que $columnB no se defina.
|
||||||
*
|
*
|
||||||
* @param string $columnB
|
* @param string|null $columnB
|
||||||
* (opcional) Columna a comparar para hacer el join.
|
* (opcional) Columna a comparar para hacer el join.
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function leftJoin(string $table, string $columnA, string $operatorOrColumnB, string $columnB = null) : static {
|
public static function leftJoin(
|
||||||
|
string $table,
|
||||||
|
string $columnA,
|
||||||
|
string $operatorOrColumnB,
|
||||||
|
?string $columnB = null
|
||||||
|
): static
|
||||||
|
{
|
||||||
if (is_null($columnB)) {
|
if (is_null($columnB)) {
|
||||||
$columnB = $operatorOrColumnB;
|
$columnB = $operatorOrColumnB;
|
||||||
$operatorOrColumnB = '=';
|
$operatorOrColumnB = '=';
|
||||||
@ -550,12 +639,18 @@ class Model {
|
|||||||
* @param string $operatorOrColumnB
|
* @param string $operatorOrColumnB
|
||||||
* Operador o columna a comparar como igual para hacer el join en caso de que $columnB no se defina.
|
* Operador o columna a comparar como igual para hacer el join en caso de que $columnB no se defina.
|
||||||
*
|
*
|
||||||
* @param string $columnB
|
* @param string|null $columnB
|
||||||
* (opcional) Columna a comparar para hacer el join.
|
* (opcional) Columna a comparar para hacer el join.
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function rightJoin(string $table, string $columnA, string $operatorOrColumnB, string $columnB = null) : static {
|
public static function rightJoin(
|
||||||
|
string $table,
|
||||||
|
string $columnA,
|
||||||
|
string $operatorOrColumnB,
|
||||||
|
?string $columnB = null
|
||||||
|
): static
|
||||||
|
{
|
||||||
if (is_null($columnB)) {
|
if (is_null($columnB)) {
|
||||||
$columnB = $operatorOrColumnB;
|
$columnB = $operatorOrColumnB;
|
||||||
$operatorOrColumnB = '=';
|
$operatorOrColumnB = '=';
|
||||||
@ -578,12 +673,18 @@ class Model {
|
|||||||
* @param string $operatorOrColumnB
|
* @param string $operatorOrColumnB
|
||||||
* Operador o columna a comparar como igual para hacer el join en caso de que $columnB no se defina.
|
* Operador o columna a comparar como igual para hacer el join en caso de que $columnB no se defina.
|
||||||
*
|
*
|
||||||
* @param string $columnB
|
* @param string|null $columnB
|
||||||
* (opcional) Columna a comparar para hacer el join.
|
* (opcional) Columna a comparar para hacer el join.
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function innerJoin(string $table, string $columnA, string $operatorOrColumnB, string $columnB = null) : static {
|
public static function innerJoin(
|
||||||
|
string $table,
|
||||||
|
string $columnA,
|
||||||
|
string $operatorOrColumnB,
|
||||||
|
?string $columnB = null
|
||||||
|
): static
|
||||||
|
{
|
||||||
if (is_null($columnB)) {
|
if (is_null($columnB)) {
|
||||||
$columnB = $operatorOrColumnB;
|
$columnB = $operatorOrColumnB;
|
||||||
$operatorOrColumnB = '=';
|
$operatorOrColumnB = '=';
|
||||||
@ -602,7 +703,8 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function groupBy(array $arr) : static {
|
public static function groupBy(array $arr): static
|
||||||
|
{
|
||||||
static::$querySelect['groupBy'] = join(', ', $arr);
|
static::$querySelect['groupBy'] = join(', ', $arr);
|
||||||
return new static();
|
return new static();
|
||||||
}
|
}
|
||||||
@ -618,7 +720,8 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function limit(int $offsetOrQuantity, ?int $quantity = null) : static {
|
public static function limit(int $offsetOrQuantity, ?int $quantity = null): static
|
||||||
|
{
|
||||||
if (is_null($quantity))
|
if (is_null($quantity))
|
||||||
static::$querySelect['limit'] = $offsetOrQuantity;
|
static::$querySelect['limit'] = $offsetOrQuantity;
|
||||||
else
|
else
|
||||||
@ -639,7 +742,8 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function orderBy(string $value, string $order = 'ASC') : static {
|
public static function orderBy(string $value, string $order = 'ASC'): static
|
||||||
|
{
|
||||||
if ($value == "RAND") {
|
if ($value == "RAND") {
|
||||||
static::$querySelect['orderBy'] = 'RAND()';
|
static::$querySelect['orderBy'] = 'RAND()';
|
||||||
return new static();
|
return new static();
|
||||||
@ -665,7 +769,8 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public static function count(bool $resetQuery = true, bool $useLimit = false) : int {
|
public static function count(bool $resetQuery = true, bool $useLimit = false): int
|
||||||
|
{
|
||||||
if (!$resetQuery)
|
if (!$resetQuery)
|
||||||
$backup = [
|
$backup = [
|
||||||
'select' => static::$querySelect['select'],
|
'select' => static::$querySelect['select'],
|
||||||
@ -707,7 +812,8 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return static|null
|
* @return static|null
|
||||||
*/
|
*/
|
||||||
public static function getById(mixed $id): ?static {
|
public static function getById(mixed $id): ?static
|
||||||
|
{
|
||||||
return static::where(static::$primaryKey, $id)->getFirst();
|
return static::where(static::$primaryKey, $id)->getFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -717,19 +823,18 @@ class Model {
|
|||||||
* @param string $search
|
* @param string $search
|
||||||
* Contenido a buscar.
|
* Contenido a buscar.
|
||||||
*
|
*
|
||||||
* @param array $in
|
* @param array|null $in
|
||||||
* (opcional) Columnas en las que se va a buscar (null para buscar en todas).
|
* (opcional) Columnas en las que se va a buscar (null para buscar en todas).
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function search(string $search, array $in = null) : static {
|
public static function search(string $search, ?array $in = null): static
|
||||||
|
{
|
||||||
if ($in == null) {
|
if ($in == null) {
|
||||||
$className = get_called_class();
|
$className = get_called_class();
|
||||||
$in = array_keys((new $className())->getVars());
|
$in = array_keys((new $className())->getVars());
|
||||||
}
|
}
|
||||||
|
|
||||||
$db = static::db();
|
|
||||||
|
|
||||||
$search = static::bindValue($search);
|
$search = static::bindValue($search);
|
||||||
$where = [];
|
$where = [];
|
||||||
|
|
||||||
@ -755,10 +860,11 @@ class Model {
|
|||||||
* @param bool $resetQuery
|
* @param bool $resetQuery
|
||||||
* (opcional) Indica si el query debe reiniciarse o no (por defecto es true).
|
* (opcional) Indica si el query debe reiniciarse o no (por defecto es true).
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array<static>
|
||||||
* Contiene un arreglo de instancias de la clase actual.
|
* Arreglo con instancias del la clase actual resultantes del query.
|
||||||
*/
|
*/
|
||||||
public static function get(bool $resetQuery = true) : array { // Devuelve array vacío si no encuentra nada.
|
public static function get(bool $resetQuery = true): array
|
||||||
|
{
|
||||||
$sql = static::buildQuery();
|
$sql = static::buildQuery();
|
||||||
$result = static::query($sql, $resetQuery);
|
$result = static::query($sql, $resetQuery);
|
||||||
|
|
||||||
@ -778,9 +884,10 @@ class Model {
|
|||||||
* (opcional) Indica si el query debe reiniciarse o no (por defecto es true).
|
* (opcional) Indica si el query debe reiniciarse o no (por defecto es true).
|
||||||
*
|
*
|
||||||
* @return static|null
|
* @return static|null
|
||||||
* Puede retornar un objeto static o null.
|
* Puede retornar una instancia de la clase actual o null.
|
||||||
*/
|
*/
|
||||||
public static function getFirst(bool $resetQuery = true): ?static { // Devuelve null si no encuentra nada.
|
public static function getFirst(bool $resetQuery = true): ?static
|
||||||
|
{
|
||||||
static::limit(1);
|
static::limit(1);
|
||||||
$instances = static::get($resetQuery);
|
$instances = static::get($resetQuery);
|
||||||
return empty($instances) ? null : $instances[0];
|
return empty($instances) ? null : $instances[0];
|
||||||
@ -789,10 +896,11 @@ class Model {
|
|||||||
/**
|
/**
|
||||||
* Obtener todos los elementos del la tabla de la instancia actual.
|
* Obtener todos los elementos del la tabla de la instancia actual.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array<static>
|
||||||
* Contiene un arreglo de instancias de la clase actual.
|
* Contiene un arreglo de instancias de la clase actual.
|
||||||
*/
|
*/
|
||||||
public static function all() : array {
|
public static function all(): array
|
||||||
|
{
|
||||||
$sql = 'SELECT * FROM '.static::table();
|
$sql = 'SELECT * FROM '.static::table();
|
||||||
$result = static::query($sql);
|
$result = static::query($sql);
|
||||||
|
|
||||||
@ -813,7 +921,8 @@ class Model {
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function setNull(string|array $atts): void {
|
public function setNull(string|array $atts): void
|
||||||
|
{
|
||||||
if (is_array($atts)) {
|
if (is_array($atts)) {
|
||||||
foreach ($atts as $att)
|
foreach ($atts as $att)
|
||||||
if (!in_array($att, $this->toNull))
|
if (!in_array($att, $this->toNull))
|
||||||
|
@ -25,9 +25,16 @@ class Neuron {
|
|||||||
/**
|
/**
|
||||||
* __construct
|
* __construct
|
||||||
*
|
*
|
||||||
* @param object|array $data
|
* @param array $data
|
||||||
*/
|
*/
|
||||||
public function __construct(array|object $data = []) {
|
public function __construct(...$data)
|
||||||
|
{
|
||||||
|
if (count($data) === 1 &&
|
||||||
|
isset($data[0]) &&
|
||||||
|
(is_array($data[0]) ||
|
||||||
|
is_object($data[0])))
|
||||||
|
$data = $data[0];
|
||||||
|
|
||||||
foreach($data as $key => $value)
|
foreach($data as $key => $value)
|
||||||
$this->{$key} = $value;
|
$this->{$key} = $value;
|
||||||
}
|
}
|
||||||
@ -36,11 +43,11 @@ class Neuron {
|
|||||||
* __get
|
* __get
|
||||||
*
|
*
|
||||||
* @param string $index
|
* @param string $index
|
||||||
* @return mixed
|
* @return null
|
||||||
*/
|
*/
|
||||||
public function __get(string $index) {
|
public function __get(string $index): null
|
||||||
return (isset($this->{$index}) &&
|
{
|
||||||
$this->{$index} != '') ? $this->{$index} : null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
149
src/Libs/Request.php
Normal file
149
src/Libs/Request.php
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Request - DuckBrain
|
||||||
|
*
|
||||||
|
* Libería complementaria de la libería Router.
|
||||||
|
* Contiene el cuerpo básico de la petición http (POST, GET, JSON, etc).
|
||||||
|
*
|
||||||
|
* @author KJ
|
||||||
|
* @website https://kj2.me
|
||||||
|
* @licence MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Libs;
|
||||||
|
|
||||||
|
class Request extends Neuron {
|
||||||
|
public Neuron $get;
|
||||||
|
public Neuron $post;
|
||||||
|
public Neuron $put;
|
||||||
|
public Neuron $patch;
|
||||||
|
public Neuron $delete;
|
||||||
|
public Neuron $json;
|
||||||
|
public Neuron $params;
|
||||||
|
public string $path;
|
||||||
|
public string $error;
|
||||||
|
public string $body;
|
||||||
|
public array $next;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __construct
|
||||||
|
*
|
||||||
|
* @param string $path Ruta actual tomando como raíz la instalación de DuckBrain.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->path = Router::currentPath();
|
||||||
|
$this->get = new Neuron($_GET);
|
||||||
|
$this->post = new Neuron($_POST);
|
||||||
|
$this->put = new Neuron();
|
||||||
|
$this->patch = new Neuron();
|
||||||
|
$this->delete = new Neuron();
|
||||||
|
$this->body = file_get_contents("php://input");
|
||||||
|
|
||||||
|
$contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : '';
|
||||||
|
if ($contentType === "application/json")
|
||||||
|
$this->json = new Neuron(
|
||||||
|
(object) json_decode(trim($this->body), false)
|
||||||
|
);
|
||||||
|
else {
|
||||||
|
$this->json = new Neuron();
|
||||||
|
if (in_array($_SERVER['REQUEST_METHOD'], ['PUT', 'PATCH', 'DELETE']) &&
|
||||||
|
preg_match('/^[^;?\/:@&=+$,]{1,255}[=]/', $this->body, $matches)) { // Con la expresión regular verificamos que sea un http query string válido y evitamos errores de memoria en caso de que el body tenga algo más grande que eso.
|
||||||
|
parse_str($this->body, $input_vars);
|
||||||
|
$this->{strtolower($_SERVER['REQUEST_METHOD'])} = new Neuron($input_vars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$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);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inicia la validación que se haya configurado.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validate(): bool
|
||||||
|
{
|
||||||
|
$actual = match($_SERVER['REQUEST_METHOD']) {
|
||||||
|
'POST', 'PUT', 'PATCH', 'DELETE' => $this->{strtolower($_SERVER['REQUEST_METHOD'])},
|
||||||
|
default => $this->get
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Validator::validateList(static::paramRules(), $this->params) &&
|
||||||
|
Validator::validateList(static::getRules(), $this->get ) &&
|
||||||
|
Validator::validateList(static::rules(), $actual))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (isset(static::messages()[Validator::$lastFailed]))
|
||||||
|
$error = static::messages()[Validator::$lastFailed];
|
||||||
|
else {
|
||||||
|
|
||||||
|
$error = 'Error: validation failed of '.preg_replace('/\./', ' as ', Validator::$lastFailed, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static::onInvalid($error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reglas para el método actual.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules(): array {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reglas para los parámetros por URL.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function paramRules(): array {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reglas para los parámetros GET.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getRules(): array {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mensajes de error en caso de fallar una validación.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function messages(): array {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Función a ejecutar cuando se ha detectado un valor no válido.
|
||||||
|
*
|
||||||
|
* @param string $error
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function onInvalid(string $error): void
|
||||||
|
{
|
||||||
|
http_response_code(422);
|
||||||
|
print($error);
|
||||||
|
}
|
||||||
|
}
|
@ -14,9 +14,10 @@
|
|||||||
namespace Libs;
|
namespace Libs;
|
||||||
|
|
||||||
class Router {
|
class Router {
|
||||||
private static $get = [];
|
private static $get = [];
|
||||||
private static $post = [];
|
private static $post = [];
|
||||||
private static $put = [];
|
private static $put = [];
|
||||||
|
private static $patch = [];
|
||||||
private static $delete = [];
|
private static $delete = [];
|
||||||
private static $last;
|
private static $last;
|
||||||
public static $notFoundCallback = 'Libs\Router::defaultNotFound';
|
public static $notFoundCallback = 'Libs\Router::defaultNotFound';
|
||||||
@ -27,7 +28,8 @@ class Router {
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function defaultNotFound (): void {
|
public static function defaultNotFound (): void
|
||||||
|
{
|
||||||
header("HTTP/1.0 404 Not Found");
|
header("HTTP/1.0 404 Not Found");
|
||||||
echo '<h2 style="text-align: center;margin: 25px 0px;">Error 404 - Página no encontrada</h2>';
|
echo '<h2 style="text-align: center;margin: 25px 0px;">Error 404 - Página no encontrada</h2>';
|
||||||
}
|
}
|
||||||
@ -43,7 +45,7 @@ class Router {
|
|||||||
* @param string $path
|
* @param string $path
|
||||||
* Ruta con pseudovariables.
|
* Ruta con pseudovariables.
|
||||||
*
|
*
|
||||||
* @param mixed $callback
|
* @param callable $callback
|
||||||
* Callback que será llamado cuando la ruta configurada en $path coincida.
|
* Callback que será llamado cuando la ruta configurada en $path coincida.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
@ -51,7 +53,8 @@ class Router {
|
|||||||
* path - Contiene la ruta con las pseudovariables reeplazadas por expresiones regulares.
|
* path - Contiene la ruta con las pseudovariables reeplazadas por expresiones regulares.
|
||||||
* callback - Contiene el callback en formato Namespace\Clase::Método.
|
* callback - Contiene el callback en formato Namespace\Clase::Método.
|
||||||
*/
|
*/
|
||||||
private static function parse(string $path, $callback): array {
|
private static function parse(string $path, callable $callback): array
|
||||||
|
{
|
||||||
preg_match_all('/{(\w+)}/s', $path, $matches, PREG_PATTERN_ORDER);
|
preg_match_all('/{(\w+)}/s', $path, $matches, PREG_PATTERN_ORDER);
|
||||||
$paramNames = $matches[1];
|
$paramNames = $matches[1];
|
||||||
|
|
||||||
@ -61,10 +64,6 @@ class Router {
|
|||||||
['([^\/]+)'],
|
['([^\/]+)'],
|
||||||
$path);
|
$path);
|
||||||
|
|
||||||
if (!is_callable($callback)) {
|
|
||||||
$callback = 'Controllers\\'.$callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'path' => $path,
|
'path' => $path,
|
||||||
'callback' => [$callback],
|
'callback' => [$callback],
|
||||||
@ -81,9 +80,10 @@ class Router {
|
|||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function basePath(): string {
|
public static function basePath(): string
|
||||||
if (defined('SITE_URL'))
|
{
|
||||||
return parse_url(SITE_URL, PHP_URL_PATH);
|
if (defined('SITE_URL') && !empty(SITE_URL))
|
||||||
|
return rtrim(parse_url(SITE_URL, PHP_URL_PATH), '/').'/';
|
||||||
return str_replace($_SERVER['DOCUMENT_ROOT'], '/', ROOT_DIR);
|
return str_replace($_SERVER['DOCUMENT_ROOT'], '/', ROOT_DIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,32 +98,30 @@ class Router {
|
|||||||
* redirigidos a "https://ejemplo.com/duckbrain/docs".
|
* redirigidos a "https://ejemplo.com/duckbrain/docs".
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function redirect(string $path): void {
|
public static function redirect(string $path): void
|
||||||
header('Location: '.static::basePath().substr($path,1));
|
{
|
||||||
|
header('Location: '.static::basePath().ltrim($path, '/'));
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Añade un middleware a la última ruta usada.
|
* Añade un middleware a la última ruta usada.
|
||||||
* Solo se puede usar un middleware a la vez.
|
* Solo se puede usar un middleware a la vez.
|
||||||
*
|
*
|
||||||
* @param mixed $callback
|
* @param callable $callback
|
||||||
* @param int $prioriry
|
* @param int $prioriry
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
* Devuelve un enlace estático.
|
* Devuelve la instancia actual.
|
||||||
*/
|
*/
|
||||||
|
public static function middleware(callable $callback, ?int $priority = null): static
|
||||||
public static function middleware($callback, int $priority = null): static {
|
{
|
||||||
if (!isset(static::$last))
|
if (!isset(static::$last))
|
||||||
return new static();
|
return new static();
|
||||||
|
|
||||||
$method = static::$last[0];
|
$method = static::$last[0];
|
||||||
$index = static::$last[1];
|
$index = static::$last[1];
|
||||||
|
|
||||||
if (!is_callable($callback)) {
|
|
||||||
$callback = 'Middlewares\\'.$callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($priority) && $priority <= 0)
|
if (isset($priority) && $priority <= 0)
|
||||||
$priority = 1;
|
$priority = 1;
|
||||||
|
|
||||||
@ -141,33 +139,62 @@ class Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Neuron
|
* Reconfigura el callback final de la última ruta.
|
||||||
* Devuelve un objeto que contiene los atributos:
|
|
||||||
* post - Donde se encuentran los valores enviados por $_POST.
|
|
||||||
* get - Donde se encuentran los valores enviados por $_GET.
|
|
||||||
* json - Donde se encuentran los valores JSON enviados en el body.
|
|
||||||
*
|
*
|
||||||
|
* @param callable $callback
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
*/
|
*/
|
||||||
private static function getReq(): Neuron {
|
public static function reconfigure(callable $callback): static
|
||||||
$req = new Neuron();
|
{
|
||||||
$req->get = new Neuron($_GET);
|
if (empty(static::$last))
|
||||||
$req->post = new Neuron($_POST);
|
return new static();
|
||||||
$req->json = new Neuron(static::get_json());
|
|
||||||
$req->params = new Neuron();
|
$method = static::$last[0];
|
||||||
$req->path = static::currentPath();
|
$index = static::$last[1];
|
||||||
return $req;
|
|
||||||
|
static::$$method[$index]['callback'][0] = $callback;
|
||||||
|
|
||||||
|
return new static();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return object
|
* Configura calquier método para todas las rutas.
|
||||||
* Devuelve un objeto con los datos recibidos en JSON.
|
*
|
||||||
|
* En caso de no recibir un callback, busca la ruta actual
|
||||||
|
* solo configura la ruta como la última configurada
|
||||||
|
* siempre y cuando la misma haya sido configurada previamente.
|
||||||
|
*
|
||||||
|
* @param string $method
|
||||||
|
* Método http.
|
||||||
|
* @param string $path
|
||||||
|
* Ruta con pseudovariables.
|
||||||
|
* @param callable|null $callback
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Devuelve la instancia actual.
|
||||||
*/
|
*/
|
||||||
private static function get_json(): object {
|
public static function configure(string $method, string $path, ?callable $callback = null): static
|
||||||
$contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : '';
|
{
|
||||||
if ($contentType === "application/json") {
|
if (is_null($callback)) {
|
||||||
return (object) json_decode(trim(file_get_contents("php://input")));
|
$path = preg_quote($path, '/');
|
||||||
|
$path = preg_replace(
|
||||||
|
['/\\\{\w+\\\}/s'],
|
||||||
|
['([^\/]+)'],
|
||||||
|
$path);
|
||||||
|
|
||||||
|
foreach(static::$$method as $index => $router)
|
||||||
|
if ($router['path'] == $path) {
|
||||||
|
static::$last = [$method, $index];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new static();
|
||||||
}
|
}
|
||||||
return (object) '';
|
|
||||||
|
static::$$method[] = static::parse($path, $callback);
|
||||||
|
static::$last = [$method, count(static::$$method)-1];
|
||||||
|
return new static();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -175,17 +202,15 @@ class Router {
|
|||||||
*
|
*
|
||||||
* @param string $path
|
* @param string $path
|
||||||
* Ruta con pseudovariables.
|
* Ruta con pseudovariables.
|
||||||
*
|
* @param callable|null $callback
|
||||||
* @param mixed $callback
|
|
||||||
* Callback que será llamado cuando la ruta configurada en $path coincida.
|
* Callback que será llamado cuando la ruta configurada en $path coincida.
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
* Devuelve un enlace estático.
|
* Devuelve la instancia actual.
|
||||||
*/
|
*/
|
||||||
public static function get(string $path, $callback): static {
|
public static function get(string $path, ?callable $callback = null): static
|
||||||
static::$get[] = static::parse($path, $callback);
|
{
|
||||||
static::$last = ['get', count(static::$get)-1];
|
return static::configure('get', $path, $callback);
|
||||||
return new static();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,17 +218,15 @@ class Router {
|
|||||||
*
|
*
|
||||||
* @param string $path
|
* @param string $path
|
||||||
* Ruta con pseudovariables.
|
* Ruta con pseudovariables.
|
||||||
*
|
* @param callable|null $callback
|
||||||
* @param mixed $callback
|
|
||||||
* Callback que será llamado cuando la ruta configurada en $path coincida.
|
* Callback que será llamado cuando la ruta configurada en $path coincida.
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
* Devuelve un enlace estático.
|
* Devuelve la instancia actual.
|
||||||
*/
|
*/
|
||||||
public static function post(string $path, $callback): static {
|
public static function post(string $path, ?callable $callback = null): static
|
||||||
static::$post[] = static::parse($path, $callback);
|
{
|
||||||
static::$last = ['post', count(static::$post)-1];
|
return static::configure('post', $path, $callback);
|
||||||
return new static();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -211,18 +234,32 @@ class Router {
|
|||||||
*
|
*
|
||||||
* @param string $path
|
* @param string $path
|
||||||
* Ruta con pseudovariables.
|
* Ruta con pseudovariables.
|
||||||
*
|
* @param callable|null $callback
|
||||||
* @param mixed $callback
|
|
||||||
* Callback que será llamado cuando la ruta configurada en $path coincida.
|
* Callback que será llamado cuando la ruta configurada en $path coincida.
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
* Devuelve un enlace estático
|
* Devuelve la instancia actual
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public static function put(string $path, $callback): static {
|
public static function put(string $path, ?callable $callback = null): static
|
||||||
static::$put[] = static::parse($path, $callback);
|
{
|
||||||
static::$last = ['put', count(static::$put)-1];
|
return static::configure('put', $path, $callback);
|
||||||
return new static();
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define los routers para el método PATCH.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* Ruta con pseudovariables.
|
||||||
|
* @param callable|null $callback
|
||||||
|
* Callback que será llamado cuando la ruta configurada en $path coincida.
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
* Devuelve la instancia actual
|
||||||
|
*/
|
||||||
|
public static function patch(string $path, ?callable $callback = null): static
|
||||||
|
{
|
||||||
|
return static::configure('patch', $path, $callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -230,86 +267,98 @@ class Router {
|
|||||||
*
|
*
|
||||||
* @param string $path
|
* @param string $path
|
||||||
* Ruta con pseudovariables
|
* Ruta con pseudovariables
|
||||||
*
|
* @param callable|null $callback
|
||||||
* @param callable $callback
|
|
||||||
* Callback que será llamado cuando la ruta configurada en $path coincida.
|
* Callback que será llamado cuando la ruta configurada en $path coincida.
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
* Devuelve un enlace estático
|
* Devuelve la instancia actual
|
||||||
*/
|
*/
|
||||||
public static function delete(string $path, $callback): static {
|
public static function delete(string $path, ?callable $callback = null): static
|
||||||
static::$delete[] = static::parse($path, $callback);
|
{
|
||||||
static::$last = ['delete', count(static::$delete)-1];
|
return static::configure('delete', $path, $callback);
|
||||||
return new static();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Devuelve la ruta actual.
|
* Devuelve la ruta actual tomando como raíz la ruta de instalación de DuckBrain.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function currentPath() : string {
|
public static function currentPath() : string
|
||||||
|
{
|
||||||
return preg_replace('/'.preg_quote(static::basePath(), '/').'/',
|
return preg_replace('/'.preg_quote(static::basePath(), '/').'/',
|
||||||
'/', strtok($_SERVER['REQUEST_URI'], '?'), 1);
|
'/', strtok($_SERVER['REQUEST_URI'], '?'), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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|null $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
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function apply(): void {
|
public static function apply(?string $path = null): void
|
||||||
$path = static::currentPath();
|
{
|
||||||
$routers = [];
|
$path = $path ?? static::currentPath();
|
||||||
switch ($_SERVER['REQUEST_METHOD']){ // Según el método selecciona un arreglo de routers configurados
|
$routers = match($_SERVER['REQUEST_METHOD']) { // Según el método selecciona un arreglo de routers
|
||||||
case 'POST':
|
'POST' => static::$post,
|
||||||
$routers = static::$post;
|
'PUT' => static::$put,
|
||||||
break;
|
'PATCH' => static::$patch,
|
||||||
case 'PUT':
|
'DELETE' => static::$delete,
|
||||||
$routers = static::$put;
|
default => static::$get
|
||||||
break;
|
};
|
||||||
case 'DELETE':
|
|
||||||
$routers = static::$delete;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$routers = static::$get;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$req = static::getReq();
|
|
||||||
|
|
||||||
foreach ($routers as $router) { // revisa todos los routers para ver si coinciden con la ruta actual
|
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)) {
|
if (preg_match_all('/^'.$router['path'].'\/?$/si',$path, $matches, PREG_PATTERN_ORDER)) {
|
||||||
unset($matches[0]);
|
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])) {
|
if (isset($matches[1])) {
|
||||||
foreach ($matches as $index => $match) {
|
foreach ($matches as $index => $match) {
|
||||||
$paramName = $router['paramNames'][$index-1];
|
$paramName = $router['paramNames'][$index-1];
|
||||||
$req->params->$paramName = urldecode($match[0]);
|
$request->params->$paramName = urldecode($match[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Llamar al último callback configurado
|
// Llama a la validación y luego procesa la cola de callbacks
|
||||||
$next = array_pop($router['callback']);
|
$request->next = $router['callback'];
|
||||||
$req->next = $router['callback'];
|
$data = $request->handle();
|
||||||
$data = call_user_func_array($next, [$req]);
|
|
||||||
|
|
||||||
|
// Por defecto imprime como JSON si se retorna algo
|
||||||
if (isset($data)) {
|
if (isset($data)) {
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
print(json_encode($data));
|
print(json_encode($data));
|
||||||
@ -320,7 +369,6 @@ class Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Si no hay router que coincida llamamos a $notFoundCallBack
|
// Si no hay router que coincida llamamos a $notFoundCallBack
|
||||||
call_user_func_array(static::$notFoundCallback, [$req]);
|
call_user_func_array(static::$notFoundCallback, [new Request]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
|
||||||
|
201
src/Libs/Validator.php
Normal file
201
src/Libs/Validator.php
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Validator - DuckBrain
|
||||||
|
*
|
||||||
|
* Libería complementaria de la libería Request.
|
||||||
|
* Sirve para simplpificar la verificación de valores.
|
||||||
|
*
|
||||||
|
* Tiene la posibilida de verificar tanto reglas individuales como en lote.
|
||||||
|
*
|
||||||
|
* |----------+--------------------------------------------------------|
|
||||||
|
* | Regla | Descripción |
|
||||||
|
* |----------+--------------------------------------------------------|
|
||||||
|
* | not | Niega la siguiente regla. Ej: not:float |
|
||||||
|
* | exists | Es requerido; debe estar definido y puede estar vacío |
|
||||||
|
* | required | Es requerido; debe estar definido y no vacío |
|
||||||
|
* | number | Es numérico |
|
||||||
|
* | int | Es entero |
|
||||||
|
* | float | Es un float |
|
||||||
|
* | bool | Es booleano |
|
||||||
|
* | email | Es un correo |
|
||||||
|
* | enum | Esta en un lista ve valores. Ej: enum:admin,user,guest |
|
||||||
|
* | url | Es una url válida |
|
||||||
|
* |----------+--------------------------------------------------------|
|
||||||
|
*
|
||||||
|
* Las listas de reglas están separadas por |, Ej: required|email
|
||||||
|
*
|
||||||
|
* @author KJ
|
||||||
|
* @website https://kj2.me
|
||||||
|
* @licence MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comprueba que que esté definido/exista.
|
||||||
|
*
|
||||||
|
* @param mixed $subject
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function exists(mixed $subject): bool
|
||||||
|
{
|
||||||
|
return isset($subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comprueba que que esté definido y no esté vacío.
|
||||||
|
*
|
||||||
|
* @param mixed $subject
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function required(mixed $subject): bool
|
||||||
|
{
|
||||||
|
return isset($subject) && !empty($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
|
||||||
|
{
|
||||||
|
return 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);
|
||||||
|
}
|
||||||
|
}
|
@ -14,52 +14,121 @@ namespace Libs;
|
|||||||
class View extends Neuron {
|
class View extends Neuron {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Función que "renderiza" las vistas
|
* Incluye el archivo.
|
||||||
*
|
*
|
||||||
* @param string $viewName
|
* @param string $viewName Ruta relativa y el nommbre sin extensión del archivo.
|
||||||
* Ruta relativa y el nommbre sin extensión del archivo ubicado en src/Views
|
* @param string|null $viewPath (opcional) Ruta donde se encuentra la vista.
|
||||||
*
|
* @param string $extension (opcional) Extensión del archivo.
|
||||||
* @param array $params
|
|
||||||
* (opcional) Arreglo que podrá ser usado en la vista mediante $view ($param['index'] se usaría así: $view->index)
|
|
||||||
*
|
|
||||||
* @param string $viewPath
|
|
||||||
* (opcional) Ruta donde se encuentra la vista. En caso de que la vista no se encuentre en esa ruta, se usará la ruta por defecto "src/Views/".
|
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function render(string $viewName, array $params = [], string $viewPath = null): void {
|
protected function include(
|
||||||
|
string $viewName,
|
||||||
|
?string $viewPath = null,
|
||||||
|
string $extension = 'php'
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
$view = $this;
|
||||||
|
|
||||||
|
if (isset($viewPath) &&
|
||||||
|
file_exists("$viewPath$viewName.$extension")) {
|
||||||
|
include("$viewPath$viewName.$extension");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
include(ROOT_CORE."/Views/$viewName.$extension");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Función que "renderiza" las vistas
|
||||||
|
*
|
||||||
|
* @param string $viewName Ruta relativa y el nommbre sin extensión del archivo.
|
||||||
|
* @param array|Neuron $params (opcional) Arreglo que podrá ser usado en la vista mediante $view ($param['index'] se usaría así: $view->index)
|
||||||
|
* @param string|null $viewPath (opcional) Ruta donde se encuentra la vista. En caso de que la vista no se encuentre en esa ruta, se usará la ruta por defecto "src/Views/".
|
||||||
|
* @param string $extension (opcional) Extensión del archivo.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function render(
|
||||||
|
string $viewName,
|
||||||
|
array|Neuron $params = [],
|
||||||
|
?string $viewPath = null,
|
||||||
|
string $extension = 'php'
|
||||||
|
): void
|
||||||
|
{
|
||||||
$instance = new View($params);
|
$instance = new View($params);
|
||||||
$instance->html($viewName, $viewPath);
|
$instance->html($viewName, $viewPath, $extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renderiza las vistas HTML
|
* Renderiza las vistas HTML
|
||||||
*
|
*
|
||||||
* @param string $viewName
|
* @param string $viewName Ruta relativa y el nommbre sin extensión del archivo ubicado en src/Views
|
||||||
* Ruta relativa y el nommbre sin extensión del archivo ubicado en src/Views
|
* @param string|null $viewPath (opcional) Ruta donde se encuentra la vista. En caso de que la vista no se encuentre en esa ruta, se usará la ruta por defecto "src/Views/".
|
||||||
|
* @param string $extension (opcional) Extensión del archivo.
|
||||||
*
|
*
|
||||||
* @param string $viewPath
|
|
||||||
* (opcional) Ruta donde se encuentra la vista. En caso de que la vista no se encuentre en esa ruta, se usará la ruta por defecto "src/Views/".
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function html(string $viewName, string $viewPath = null): void {
|
public function html(
|
||||||
$view = $this;
|
string $viewName,
|
||||||
|
?string $viewPath = null,
|
||||||
|
string $extension = 'php'
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
$this->include(
|
||||||
|
$viewName,
|
||||||
|
$viewPath,
|
||||||
|
$extension
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($viewPath) && file_exists($viewPath.$viewName.'.php')) {
|
/**
|
||||||
include($viewPath.$viewName.'.php');
|
* Renderiza código CSS.
|
||||||
return;
|
*
|
||||||
}
|
* @param string $viewName Ruta relativa y el nommbre sin extensión del archivo ubicado en src/Views
|
||||||
|
* @param string|null $viewPath (opcional) Ruta donde se encuentra la vista. En caso de que la vista no se encuentre en esa ruta, se usará la ruta por defecto "src/Views/".
|
||||||
|
* @param string $extension (opcional) Extensión del archivo.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function css(
|
||||||
|
string $viewName,
|
||||||
|
?string $viewPath = null,
|
||||||
|
string $extension = 'css'
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
header("Content-type: text/css");
|
||||||
|
$this->include($viewName, $viewPath, $extension);
|
||||||
|
}
|
||||||
|
|
||||||
include(ROOT_DIR.'/src/Views/'.$viewName.'.php');
|
/**
|
||||||
|
* Renderiza código Javascript.
|
||||||
|
*
|
||||||
|
* @param string $viewName Ruta relativa y el nommbre sin extensión del archivo ubicado en src/Views
|
||||||
|
* @param string|null $viewPath (opcional) Ruta donde se encuentra la vista. En caso de que la vista no se encuentre en esa ruta, se usará la ruta por defecto "src/Views/".
|
||||||
|
* @param string $extension (opcional) Extensión del archivo.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function js(
|
||||||
|
string $viewName,
|
||||||
|
?string $viewPath = null,
|
||||||
|
string $extension = 'js'
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
header("Content-type: application/javascript");
|
||||||
|
$this->include($viewName, $viewPath, $extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imprime los datos en Json.
|
* Imprime los datos en Json.
|
||||||
*
|
*
|
||||||
* @param object|array $data
|
* @param object|array $data Objeto o array que se imprimirá a JSON.
|
||||||
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function json(object|array $data): void {
|
public function json(object|array $data): void
|
||||||
|
{
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
print(json_encode($data));
|
print(json_encode($data));
|
||||||
}
|
}
|
||||||
@ -67,12 +136,29 @@ class View extends Neuron {
|
|||||||
/**
|
/**
|
||||||
* Imprime los datos en texto plano
|
* Imprime los datos en texto plano
|
||||||
*
|
*
|
||||||
* @param string $txt
|
* @param string $txt Contenido de texto.
|
||||||
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function text(string $txt): void {
|
public function text(string $txt): void
|
||||||
|
{
|
||||||
header('Content-Type: text/plain; charset=utf-8');
|
header('Content-Type: text/plain; charset=utf-8');
|
||||||
print($txt);
|
print($txt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intenta devolver la url absoluta a partir de una ruta relativa.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function route(string $path = '/'): string
|
||||||
|
{
|
||||||
|
if (defined('SITE_URL') && !empty(SITE_URL))
|
||||||
|
return rtrim(SITE_URL, '/').'/'.ltrim($path, '/');
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
Reference in New Issue
Block a user