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>
RewriteEngine On
# Bloquear acceso a las carpetas vendor/ y src/
RewriteRule ^(vendor|src)/.*$ - [F,L]
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f

View File

@@ -1,15 +1,15 @@
<?php
// Configuración de la base de datos
// Database configuration
define('DB_TYPE', 'mysql');
define('DB_HOST', 'localhost');
define('DB_NAME', '');
define('DB_USER', '');
define('DB_PASS', '');
// Configuración del sitio
// Site configuration
define('SITE_URL', '');
// Configuración avanzada
// Advanced configuration
define('ROOT_DIR', __DIR__);
define('ROOT_CORE', ROOT_DIR . '/src');

View File

@@ -1,8 +1,11 @@
<?php
use Libs\Loader;
use Libs\Router;
require_once('config.php');
// Incluir clases
// Class autoloader
spl_autoload_register(function ($className) {
$fp = str_replace('\\', '/', $className);
$name = basename($fp);
@@ -13,11 +16,11 @@ spl_autoload_register(function ($className) {
}
});
// Incluir routers
$routers = glob(ROOT_CORE . '/Routers/*.php');
// Router autoloader
Loader::load(ROOT_CORE . '/Routers/');
foreach ($routers as $file) {
require_once($file);
}
// Other autoloaders
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.
+ *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.
+ *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 |
|------------------+---------------+---------------+------------|
| 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 | |
|------------------+---------------+---------------+------------|
|------------------+---------------+---------+------------|
| method | MySQL/MariaDB | sqlite3 | postgreSQL |
|------------------+---------------+---------+------------|
| db | ok | ok | ok |
|------------------+---------------+---------+------------|
| query | ok | ok | ok |
|------------------+---------------+---------+------------|
| resetQuery | ok | ok | ok |
|------------------+---------------+---------+------------|
| buildQuery | ok | ok | ok |
|------------------+---------------+---------+------------|
| getInstance | ok | ok | ok |
|------------------+---------------+---------+------------|
| getVars | ok | ok | ok |
|------------------+---------------+---------+------------|
| className | ok | ok | pk |
|------------------+---------------+---------+------------|
| table | ok | ok | ok |
|------------------+---------------+---------+------------|
| update | ok | ok | ok |
|------------------+---------------+---------+------------|
| beginTransaction | ok | ok | ok |
|------------------+---------------+---------+------------|
| rollBack | ok | ok | ok |
|------------------+---------------+---------+------------|
| commit | ok | ok | ok |
|------------------+---------------+---------+------------|
| add | ok | ok | ok |
|------------------+---------------+---------+------------|
| save | ok | ok | ok |
|------------------+---------------+---------+------------|
| delete | ok | ok | ok |
|------------------+---------------+---------+------------|
| select | ok | ok | ok |
|------------------+---------------+---------+------------|
| from | ok | ok | ok |
|------------------+---------------+---------+------------|
| where | ok | ok | ok |
|------------------+---------------+---------+------------|
| whereIn | ok | ok | ok |
|------------------+---------------+---------+------------|
| whereNotIn | ok | ok | ok |
|------------------+---------------+---------+------------|
| whereNull | ok | ok | ok |
|------------------+---------------+---------+------------|
| whereNotNull | ok | ok | ok |
|------------------+---------------+---------+------------|
| whereExists | ok | ok | ok |
|------------------+---------------+---------+------------|
| whereNotExists | ok | ok | ok |
|------------------+---------------+---------+------------|
| leftJoin | ok | ok | ok |
|------------------+---------------+---------+------------|
| rightJoin | ok | fixed | ok |
|------------------+---------------+---------+------------|
| innerJoin | ok | ok | ok |
|------------------+---------------+---------+------------|
| crossJoin | ok | ok | ok |
|------------------+---------------+---------+------------|
| and | ok | ok | ok |
|------------------+---------------+---------+------------|
| or | ok | ok | ok |
|------------------+---------------+---------+------------|
| groupBy | ok | ok | ok |
|------------------+---------------+---------+------------|
| limit | ok | ok | ok |
|------------------+---------------+---------+------------|
| orderBy | ok | 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

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' => '',
'rightJoin' => '',
'innerJoin' => '',
'crossJoin' => '',
'orderBy' => '',
'groupBy' => '',
'limit' => '',
@@ -165,9 +166,9 @@ class Model
throw new Exception(
"\nError at query to database.\n" .
"Query: $query\n" .
"Vars: $vars\n" .
"Error:\n" . $e->getMessage()
"Query: $query\n" .
"Vars: $vars\n" .
"Error:\n" . $e->getMessage()
);
}
@@ -193,6 +194,7 @@ class Model
'leftJoin' => '',
'rightJoin' => '',
'innerJoin' => '',
'crossJoin' => '',
'orderBy' => '',
'groupBy' => '',
'limit' => '',
@@ -217,6 +219,10 @@ class Model
$sql .= ' FROM ' . static::table();
}
if (static::$querySelect['crossJoin'] != '') {
$sql .= static::$querySelect['crossJoin'];
}
if (static::$querySelect['innerJoin'] != '') {
$sql .= static::$querySelect['innerJoin'];
}
@@ -259,7 +265,7 @@ class Model
* @return string
* Substitution parameter.
*/
private static function bindValue(string $value): string
public static function bind(string $value): string
{
$index = ':v_' . count(static::$queryVars);
static::$queryVars[$index] = $value;
@@ -447,7 +453,7 @@ class Model
foreach ($atts as $key => $value) {
if (isset($value)) {
$into[] = "`$key`";
$into[] = "$key";
$values[] = ":$key";
static::$queryVars[":$key"] = $value;
}
@@ -493,12 +499,12 @@ class Model
/**
* Defines SELECT in the SQL statement.
*
* @param array $columns
* Columns to be selected in the SQL query.
* @param array<string> $columns
* Columns to be selected in the SQL query.
*
* @return static
*/
public static function select(...$columns): static
public static function select(string ...$columns): static
{
static::$querySelect['select'] = $columns;
@@ -582,7 +588,7 @@ class Model
}
if (!$no_filter) {
$value = static::bindValue($value);
$value = static::bind($value);
}
if (static::$querySelect['where'] == '') {
@@ -624,7 +630,7 @@ class Model
}
if (!$no_filter) {
$value = static::bindValue($value);
$value = static::bind($value);
}
if (static::$querySelect['where'] == '') {
@@ -645,36 +651,113 @@ class Model
* @param array $arr
* Array with all values to compare against the column.
*
* @param bool $in
* Defines whether to check positively (IN) or negatively (NOT IN).
*
* @return static
*/
public static function whereIn(
string $column,
array $arr,
bool $in = true
): static {
$arrIn = [];
foreach ($arr as $value) {
$arrIn[] = static::bindValue($value);
}
if ($in) {
$where_in = "$column IN (" . join(', ', $arrIn) . ")";
} else {
$where_in = "$column NOT IN (" . join(', ', $arrIn) . ")";
$arrIn[] = static::bind($value);
}
$where = "$column IN (" . join(', ', $arrIn) . ")";
if (static::$querySelect['where'] == '') {
static::$querySelect['where'] = $where_in;
static::$querySelect['where'] = $where;
} else {
static::$querySelect['where'] .= " AND $where_in";
static::$querySelect['where'] .= " AND $where";
}
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.
*
@@ -738,6 +821,13 @@ class Model
$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";
return new static();
@@ -777,28 +867,53 @@ class Model
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.
*
* @param array $arr
* @param array $columns
* Columns to group by.
*
* @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();
}
/**
* Defines LIMIT in the SQL statement.
*
* @param int $offsetOrQuantity
* Defines the rows to skip or the quantity to take
* if $quantity is not defined.
* @param int $offsetOrQuantity
* Defines the rows to skip or the quantity to take
* if $quantity is not defined.
* @param int|null $quantity
* (Optional) Defines the maximum number of rows to take.
* (Optional) Defines the maximum number of rows to take.
*
* @return static
*/
@@ -807,7 +922,7 @@ class Model
if (is_null($quantity)) {
static::$querySelect['limit'] = $offsetOrQuantity;
} else {
static::$querySelect['limit'] = $offsetOrQuantity . ', ' . $quantity;
static::$querySelect['limit'] = $quantity . ' OFFSET ' . $offsetOrQuantity;
}
return new static();
@@ -922,20 +1037,19 @@ class Model
$in = array_keys((new $className())->getVars());
}
$search = static::bindValue($search);
$search = static::bind($search);
$where = [];
if (DB_TYPE == 'sqlite') {
foreach ($in as $row) {
$where[] = "$row LIKE '%' || $search || '%'";
}
} else {
if (static::db()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql') {
foreach ($in as $row) {
$where[] = "$row LIKE CONCAT('%', $search, '%')";
}
} else {
foreach ($in as $row) {
$where[] = "CAST($row AS TEXT) LIKE '%' || $search || '%'";
}
}
if (static::$querySelect['where'] == '') {
static::$querySelect['where'] = join(' OR ', $where);
} else {

View File

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

View File

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