Compare commits

..

18 Commits

Author SHA1 Message Date
kj
ce03bdb27d fix(model): Fix postgresql search. 2025-10-27 15:16:54 -03:00
kj
b24598b118 fix(sqlite): Correct base table for RIGHT JOIN conversion 2025-10-27 15:15:45 -03:00
kj
5019b89cc0 docs(readme): Update PostgreSQL compatibility status 2025-10-27 10:27:18 -03:00
kj
d030e8d30e refactor(model)!: Allow groupBy to accept multiple args instead an array 2025-10-27 10:26:12 -03:00
kj
b2672ad92f fix(model): Remove ON clause from cross join 2025-10-27 10:24:25 -03:00
kj
4c57770b43 refactor(model): Remove backticks from column names 2025-10-27 10:23:55 -03:00
kj
8e84450f95 fix(query): Adjust LIMIT OFFSET clause syntax 2025-10-27 10:23:31 -03:00
kj
ece32b9bbe docs(readme): Update supported where clauses 2025-10-27 06:50:13 -03:00
kj
a5cf9d239d refactor(model): Fix typo on WHERE EXISTS/NOT EXISTS methods 2025-10-27 06:46:17 -03:00
kj
b9509c49ed refactor(model): Add string type hint to select columns 2025-10-21 16:26:39 -03:00
kj
6e433b4d06 feat(query-builder): Implement advanced WHERE and new JOIN clauses 2025-10-17 13:13:01 -03:00
kj
892b3614ec refactor(db): Detect DB type using PDO driver name 2025-10-17 13:12:02 -03:00
kj
a0b544eae5 refactor(Model): Rename bindValue to bind and make public 2025-10-17 11:55:37 -03:00
kj
20fd78ab53 docs(Request): Remove param comment 2025-10-16 20:51:52 -03:00
kj
f5411daaa5 Translate comments to English 2025-10-16 20:02:54 -03:00
kj
7e7ec68fd7 refactor(router): Use Neuron object for route parameters 2025-10-16 12:04:05 -03:00
kj
ac9a661bc0 fix(htaccess): Block direct access to vendor and src folders 2025-10-11 12:15:15 -03:00
kj
c8d7b69367 feat(bootstrap): Introduce Libs\Loader for directory loading 2025-10-11 10:44:20 -03:00
8 changed files with 296 additions and 134 deletions

View File

@@ -1,6 +1,9 @@
<IfModule mod_rewrite.c> <IfModule mod_rewrite.c>
RewriteEngine On RewriteEngine On
# Bloquear acceso a las carpetas vendor/ y src/
RewriteRule ^(vendor|src)/.*$ - [F,L]
# Handle Front Controller... # Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-f

View File

@@ -1,15 +1,15 @@
<?php <?php
// Configuración de la base de datos // Database configuration
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', '');
// Configuración del sitio // Site configuration
define('SITE_URL', ''); define('SITE_URL', '');
// Configuración avanzada // Advanced configuration
define('ROOT_DIR', __DIR__); define('ROOT_DIR', __DIR__);
define('ROOT_CORE', ROOT_DIR . '/src'); define('ROOT_CORE', ROOT_DIR . '/src');

View File

@@ -1,8 +1,11 @@
<?php <?php
use Libs\Loader;
use Libs\Router;
require_once('config.php'); require_once('config.php');
// Incluir clases // Class autoloader
spl_autoload_register(function ($className) { spl_autoload_register(function ($className) {
$fp = str_replace('\\', '/', $className); $fp = str_replace('\\', '/', $className);
$name = basename($fp); $name = basename($fp);
@@ -13,11 +16,11 @@ spl_autoload_register(function ($className) {
} }
}); });
// Incluir routers // Router autoloader
$routers = glob(ROOT_CORE . '/Routers/*.php'); Loader::load(ROOT_CORE . '/Routers/');
foreach ($routers as $file) { // Other autoloaders
require_once($file); Loader::load(ROOT_CORE . '/Autoload/');
}
\Libs\Router::apply(); // Run the routers
Router::apply();

View File

@@ -28,79 +28,91 @@ En la siguiente tabla se encuentra la lista de estados de los gestores de bases
+ *En blanco* como que no ha sido probado aún. + *En blanco* como que no ha sido probado aún.
+ *error* como que fue probado, no funciona y no ha sido aún arreglado. + *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. + *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. + *fixed* para aquello que no existe en el gestor de BD, pero la librería lo traduce a un equivalente.
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| method | MySQL/MariaDB | sqlite3 | postgreSQL | | method | MySQL/MariaDB | sqlite3 | postgreSQL |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| db | ok | ok | | | db | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| query | ok | ok | | | query | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| resetQuery | ok | ok | | | resetQuery | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| buildQuery | ok | ok | | | buildQuery | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| getInstance | ok | ok | | | getInstance | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| getVars | ok | ok | | | getVars | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| className | ok | ok | | | className | ok | ok | pk |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| table | ok | ok | | | table | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| update | ok | ok | | | update | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| beginTransaction | ok | ok | | | beginTransaction | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| rollBack | ok | ok | | | rollBack | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| commit | ok | ok | | | commit | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| add | ok | ok | | | add | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| save | ok | ok | | | save | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| delete | ok | ok | | | delete | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| select | ok | ok | | | select | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| from | ok | ok | | | from | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| where | ok | ok | | | where | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| where_in | ok | ok | | | whereIn | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| leftJoin | ok | ok | | | whereNotIn | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| rightJoin | ok | not supported | | | whereNull | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| innerJoin | ok | ok | | | whereNotNull | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| and | ok | ok | | | whereExists | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| or | ok | ok | | | whereNotExists | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| groupBy | ok | ok | | | leftJoin | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| limit | ok | ok | | | rightJoin | ok | fixed | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| orderBy | ok | ok | | | innerJoin | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| count | ok | ok | | | crossJoin | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| getById | ok | ok | | | and | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| search | ok | ok | | | or | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| get | ok | ok | | | groupBy | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| getFirst | ok | ok | | | limit | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| all | ok | ok | | | orderBy | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| setNull | ok | ok | | | count | ok | ok | ok |
|------------------+---------------+---------------+------------| |------------------+---------------+---------+------------|
| getById | ok | ok | ok |
|------------------+---------------+---------+------------|
| search | ok | ok | ok |
|------------------+---------------+---------+------------|
| get | ok | ok | ok |
|------------------+---------------+---------+------------|
| getFirst | ok | ok | ok |
|------------------+---------------+---------+------------|
| all | ok | ok | ok |
|------------------+---------------+---------+------------|
| setNull | ok | ok | ok |
|------------------+---------------+---------+------------|
* Contacto * Contacto

31
src/Libs/Loader.php Normal file
View File

@@ -0,0 +1,31 @@
<?php
namespace Libs;
/**
* Loader - DuckBrain
*
* Simple library to bulk load multiple php files inside a folder.
*
* @author KJ
* @website https://kj2.me
* @license MIT
*/
class Loader
{
/**
* Loads all PHP files from a specified directory.
* If the directory does not exist or is not a directory, no files will be loaded.
*
* @param string $directoryPath The path to the directory containing the PHP files to load.
* @return void
*/
public static function load(string $directoryPath): void
{
if (is_dir($directoryPath)) {
foreach (glob(rtrim($directoryPath, '/') . '/*.php') as $file) {
require_once($file);
}
}
}
}

View File

@@ -63,6 +63,7 @@ class Model
'leftJoin' => '', 'leftJoin' => '',
'rightJoin' => '', 'rightJoin' => '',
'innerJoin' => '', 'innerJoin' => '',
'crossJoin' => '',
'orderBy' => '', 'orderBy' => '',
'groupBy' => '', 'groupBy' => '',
'limit' => '', 'limit' => '',
@@ -193,6 +194,7 @@ class Model
'leftJoin' => '', 'leftJoin' => '',
'rightJoin' => '', 'rightJoin' => '',
'innerJoin' => '', 'innerJoin' => '',
'crossJoin' => '',
'orderBy' => '', 'orderBy' => '',
'groupBy' => '', 'groupBy' => '',
'limit' => '', 'limit' => '',
@@ -217,6 +219,10 @@ class Model
$sql .= ' FROM ' . static::table(); $sql .= ' FROM ' . static::table();
} }
if (static::$querySelect['crossJoin'] != '') {
$sql .= static::$querySelect['crossJoin'];
}
if (static::$querySelect['innerJoin'] != '') { if (static::$querySelect['innerJoin'] != '') {
$sql .= static::$querySelect['innerJoin']; $sql .= static::$querySelect['innerJoin'];
} }
@@ -259,7 +265,7 @@ class Model
* @return string * @return string
* Substitution parameter. * Substitution parameter.
*/ */
private static function bindValue(string $value): string public static function bind(string $value): string
{ {
$index = ':v_' . count(static::$queryVars); $index = ':v_' . count(static::$queryVars);
static::$queryVars[$index] = $value; static::$queryVars[$index] = $value;
@@ -447,7 +453,7 @@ class Model
foreach ($atts as $key => $value) { foreach ($atts as $key => $value) {
if (isset($value)) { if (isset($value)) {
$into[] = "`$key`"; $into[] = "$key";
$values[] = ":$key"; $values[] = ":$key";
static::$queryVars[":$key"] = $value; static::$queryVars[":$key"] = $value;
} }
@@ -493,12 +499,12 @@ class Model
/** /**
* Defines SELECT in the SQL statement. * Defines SELECT in the SQL statement.
* *
* @param array $columns * @param array<string> $columns
* Columns to be selected in the SQL query. * Columns to be selected in the SQL query.
* *
* @return static * @return static
*/ */
public static function select(...$columns): static public static function select(string ...$columns): static
{ {
static::$querySelect['select'] = $columns; static::$querySelect['select'] = $columns;
@@ -582,7 +588,7 @@ class Model
} }
if (!$no_filter) { if (!$no_filter) {
$value = static::bindValue($value); $value = static::bind($value);
} }
if (static::$querySelect['where'] == '') { if (static::$querySelect['where'] == '') {
@@ -624,7 +630,7 @@ class Model
} }
if (!$no_filter) { if (!$no_filter) {
$value = static::bindValue($value); $value = static::bind($value);
} }
if (static::$querySelect['where'] == '') { if (static::$querySelect['where'] == '') {
@@ -645,36 +651,113 @@ class Model
* @param array $arr * @param array $arr
* Array with all values to compare against the column. * Array with all values to compare against the column.
* *
* @param bool $in
* Defines whether to check positively (IN) or negatively (NOT IN).
*
* @return static * @return static
*/ */
public static function whereIn( public static function whereIn(
string $column, string $column,
array $arr, array $arr,
bool $in = true
): static { ): static {
$arrIn = []; $arrIn = [];
foreach ($arr as $value) { foreach ($arr as $value) {
$arrIn[] = static::bindValue($value); $arrIn[] = static::bind($value);
}
if ($in) {
$where_in = "$column IN (" . join(', ', $arrIn) . ")";
} else {
$where_in = "$column NOT IN (" . join(', ', $arrIn) . ")";
} }
$where = "$column IN (" . join(', ', $arrIn) . ")";
if (static::$querySelect['where'] == '') { if (static::$querySelect['where'] == '') {
static::$querySelect['where'] = $where_in; static::$querySelect['where'] = $where;
} else { } else {
static::$querySelect['where'] .= " AND $where_in"; static::$querySelect['where'] .= " AND $where";
} }
return new static(); return new static();
} }
/**
* Defines WHERE using NOT IN in the SQL statement.
*
* @param string $column
* The column to compare.
*
* @param array $arr
* Array with all values to compare against the column.
*
* @return static
*/
public static function whereNotIn(
string $column,
array $arr,
): static {
$arrIn = [];
foreach ($arr as $value) {
$arrIn[] = static::bind($value);
}
$where = "$column NOT IN (" . join(', ', $arrIn) . ")";
if (static::$querySelect['where'] == '') {
static::$querySelect['where'] = $where;
} else {
static::$querySelect['where'] .= " AND $where";
}
return new static();
}
/**
* Defines WHERE using IS NULL in the SQL statement.
*
* @param string $column
* The column to compare.
*
* @return static
*/
public static function whereNull(string $column): static
{
static::where($column, 'IS', 'NULL', true);
return new static();
}
/**
* Defines WHERE using IS NOT NULL in the SQL statement.
*
* @param string $column
* The column to compare.
*
* @return static
*/
public static function whereNotNull(string $column): static
{
static::where($column, 'IS NOT', 'NULL', true);
return new static();
}
/**
* Defines WHERE using EXISTS in the SQL statement.
*
* @param string $query
* SQL query.
*
* @return static
*/
public static function whereExists(string $query): static
{
static::where('', 'EXISTS', "($query)", true);
return new static();
}
/**
* Defines WHERE using NOT EXISTS in the SQL statement.
*
* @param string $query
* SQL query.
*
* @return static
*/
public static function whereNotExists(string $query): static
{
static::where('', 'NOT EXISTS', "($query)", true);
return new static();
}
/** /**
* Defines LEFT JOIN in the SQL statement. * Defines LEFT JOIN in the SQL statement.
* *
@@ -738,6 +821,13 @@ class Model
$operatorOrColumnB = '='; $operatorOrColumnB = '=';
} }
if (static::db()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'sqlite') {
$currentTable = empty(static::$querySelect['from']) ?
static::table() : static::$querySelect['from'];
static::$querySelect['from'] = $table;
return static::leftJoin($currentTable, $columnB, $operatorOrColumnB, $columnA);
}
static::$querySelect['rightJoin'] .= ' RIGHT JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB"; static::$querySelect['rightJoin'] .= ' RIGHT JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB";
return new static(); return new static();
@@ -777,17 +867,42 @@ class Model
return new static(); return new static();
} }
/**
* Defines CROSS JOIN in the SQL statement.
*
* @param string $table
* Table to join with the current object's table.
*
* @param string $columnA
* Column to compare for the join.
*
* @param string $operatorOrColumnB
* Operator or column to compare as equal for the join
* if $columnB is not defined.
*
* @param string|null $columnB
* (Optional) Column to compare for the join.
*
* @return static
*/
public static function crossJoin(
string $table,
): static {
static::$querySelect['crossJoin'] .= ' CROSS JOIN ' . $table;
return new static();
}
/** /**
* Defines GROUP BY in the SQL statement. * Defines GROUP BY in the SQL statement.
* *
* @param array $arr * @param array $columns
* Columns to group by. * Columns to group by.
* *
* @return static * @return static
*/ */
public static function groupBy(array $arr): static public static function groupBy(string ...$columns): static
{ {
static::$querySelect['groupBy'] = join(', ', $arr); static::$querySelect['groupBy'] = join(', ', $columns);
return new static(); return new static();
} }
@@ -807,7 +922,7 @@ class Model
if (is_null($quantity)) { if (is_null($quantity)) {
static::$querySelect['limit'] = $offsetOrQuantity; static::$querySelect['limit'] = $offsetOrQuantity;
} else { } else {
static::$querySelect['limit'] = $offsetOrQuantity . ', ' . $quantity; static::$querySelect['limit'] = $quantity . ' OFFSET ' . $offsetOrQuantity;
} }
return new static(); return new static();
@@ -922,19 +1037,18 @@ class Model
$in = array_keys((new $className())->getVars()); $in = array_keys((new $className())->getVars());
} }
$search = static::bindValue($search); $search = static::bind($search);
$where = []; $where = [];
if (DB_TYPE == 'sqlite') { if (static::db()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') {
foreach ($in as $row) {
$where[] = "$row LIKE '%' || $search || '%'";
}
} else {
foreach ($in as $row) { foreach ($in as $row) {
$where[] = "$row LIKE CONCAT('%', $search, '%')"; $where[] = "$row LIKE CONCAT('%', $search, '%')";
} }
} else {
foreach ($in as $row) {
$where[] = "CAST($row AS TEXT) LIKE '%' || $search || '%'";
}
} }
if (static::$querySelect['where'] == '') { if (static::$querySelect['where'] == '') {
static::$querySelect['where'] = join(' OR ', $where); static::$querySelect['where'] = join(' OR ', $where);

View File

@@ -27,8 +27,6 @@ class Request extends Neuron
/** /**
* __construct * __construct
*
* @param string $path Current path, taking the DuckBrain installation as root.
*/ */
public function __construct() public function __construct()
{ {
@@ -38,7 +36,7 @@ class Request extends Neuron
$this->put = new Neuron(); $this->put = new Neuron();
$this->patch = new Neuron(); $this->patch = new Neuron();
$this->delete = new Neuron(); $this->delete = new Neuron();
$this->params = new Neuron(Router::$currentParams); $this->params = Router::$params ?? new Neuron();
$this->body = file_get_contents("php://input"); $this->body = file_get_contents("php://input");
$contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : ''; $contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : '';

View File

@@ -15,28 +15,28 @@ namespace Libs;
*/ */
class Router class Router
{ {
private static $get = []; private static array $get = [];
private static $post = []; private static array $post = [];
private static $put = []; private static array $put = [];
private static $patch = []; private static array $patch = [];
private static $delete = []; private static array $delete = [];
/** /**
* Stores the method and index of the last configured route, e.g., ['get', 0]. * Stores the method and index of the last configured route, e.g., ['get', 0].
* Used for chaining methods like middleware() or reconfigure(). * Used for chaining methods like middleware() or reconfigure().
* *
* @var array|null * @var array $last
*/ */
private static $last; private static array $last;
/** /**
* Stores the parameters extracted from the current matching route. * Stores the parameters extracted from the current matching route.
* *
* @var array * @var Neuron $params
*/ */
public static $currentParams = []; public static Neuron $params;
/** /**
* The callback function to be executed when no route matches. * The callback function to be executed when no route matches.
* *
* @var callable|string * @var callable $notFoundCallback
*/ */
public static $notFoundCallback = 'Libs\Router::defaultNotFound'; public static $notFoundCallback = 'Libs\Router::defaultNotFound';
@@ -345,9 +345,10 @@ class Router
// Checking and storing the variable parameters of the route // Checking and storing the variable parameters of the route
if (isset($matches[1])) { if (isset($matches[1])) {
static::$params = new Neuron();
foreach ($matches as $index => $match) { foreach ($matches as $index => $match) {
$paramName = $router['paramNames'][$index - 1]; $paramName = $router['paramNames'][$index - 1];
static::$currentParams[$paramName] = urldecode($match[0]); static::$params->{$paramName} = urldecode($match[0]);
} }
} }