Compare commits

95 Commits
Author SHA1 Message Date
kj 3b6da45b01 refactor(validator): rename enum rule and method to in 2026-09-14 16:48:02 -03:00
kj b7260b5199 refactor(Router): use string callable syntax for default callbacks 2026-09-08 12:08:32 -03:00
kj 1f29622914 refactor(router): remove exit statement from redirect method 2026-09-07 20:00:15 -03:00
kj eb882db0c8 sync: 2026-09-07
70b1016 refactor(autoload): remove global exception handler
a534613 refactor(request): propagate validation failures as exceptions
5f3fc16 feat(router): add error boundary with exception callback
9dbfd7d feat: add named parameter injection to resolve()
2026-09-07 18:00:18 -03:00
kj 9ba43e3f25 sync: 2026-09-05
25db49f fix(model): reset query state when database query fails
9cb41d5 fix(model): use RANDOM() for non-MySQL databases in orderBy
2026-09-05 16:49:10 -03:00
kj 54e96ba4bb sync: first sync with the new develop branch
a4611e8 chore(src): rename .keep files to .gitkeep
2026-09-05 14:21:52 -03:00
kj 9027f36748 refactor(model): use reflection for search field discovery 2026-09-05 00:39:16 -03:00
kj 2ae9ef39f3 feat(request): negotiate error response frmat based on Accept header 2026-09-03 18:23:42 -03:00
kj ee0db0307e docs(Validator): Expand documentation with complete rules reference 2026-09-03 18:22:52 -03:00
kj 44cbe6bbdc feat(validator): add file upload validation rules 2026-09-03 18:07:54 -03:00
kj 31728af351 docs: Clarify phpdoc 2026-09-03 16:12:54 -03:00
kj 4845d14ef5 chore(validator): suppress PHPCS rule for non-camel-case method 2026-09-03 15:59:48 -03:00
kj aca4e732ed feat(validation): Add human-readable validation error messages 2026-09-03 15:59:36 -03:00
kj f475c9125f feat(Validator): Add confirmed, required_with, and required_if rules 2026-09-03 15:03:28 -03:00
kj 4fe7314389 feat(Validator): Add regex and date validation methods 2026-09-03 14:42:15 -03:00
kj 1f366830bc feat(validation): add type and size-based validation methods
Add string, array, min, max, between, and size validators to the
Validator class. A private measure helper centralizes size semantics
for numbers (value), strings (length), and arrays (element count).
2026-09-03 14:35:02 -03:00
kj 15df7c8d96 refactor(Validator): improve rule argument parsing 2026-09-03 14:20:02 -03:00
kj b8dd1fd0f6 refactor(model): remove default 'id' from dbIgnoreSave list 2026-06-19 16:08:29 -03:00
kj 83c80bd4ab fix(model): prevent overwriting primary key on save 2026-06-19 15:34:47 -03:00
kj b13c07d3c4 fix(model): Remove the NULL check in the attribute loop 2026-06-19 15:29:51 -03:00
kj b95747bfe0 feat(model): add type casting for DateTime 2026-06-19 15:14:25 -03:00
kj 01c5eceeb4 refactor(model): Use match expression for PDO type detection 2026-06-19 14:52:25 -03:00
kj ee9c109ed9 fix(model): improve PDO parameter binding and update logic 2026-06-19 14:47:01 -03:00
kj 7eed69725d refactor(model): extract type casting logic into helper methods 2026-06-19 14:46:30 -03:00
kj e37035eed8 fix(model): support both backed and unit enums 2026-06-13 16:48:07 -03:00
kj 5312e5060e fix(model): reset dbQueryVariables before update and insert
Clear the static $dbQueryVariables array before performing update
and insert operations to prevent data leakage between queries.
2026-06-13 15:30:45 -03:00
kj 3c9db3ac53 refactor(neuron): remove return type hint from __get method 2026-06-05 13:28:19 -03:00
outcontrol 2b5d19ecd5 fix(synapsis): Skip variadic parameters in argument resolution 2026-04-15 16:35:32 -03:00
outcontrol 53e862d126 feat(exception): Register global exception handler
This handler catches uncaught exceptions and prints their message
and stack trace.
2026-03-16 06:38:26 -03:00
outcontrol b6baeef32f style(Synapsis): Wrap long exception message for readability 2026-03-16 06:25:09 -03:00
outcontrol 159da01d5c fix(synapsis): Include parameter name in untyped parameter error 2026-03-16 06:24:16 -03:00
outcontrol 3770dd6d9b refactor(model): Track model persistence state 2026-03-11 18:48:32 -03:00
outcontrol 76a69e5ef8 refactor(Model): Standarize property names 2026-03-11 18:22:16 -03:00
kj c5a4967ae4 refactor: Extract autoloader to dedicated file 2026-02-10 14:09:13 -03:00
kj c0b9f3851a chore(router): Remove redundant middleware count check 2026-02-02 10:23:48 -03:00
kj 78e199e300 feat(router): Introduce withMiddleware() for scoped middlewares 2026-02-01 08:25:54 -03:00
kj 8cd5e114dd refactor(router): Align type hint for optional parameter in docblock 2026-02-01 07:47:04 -03:00
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
kj 7f62e06ff9 docs: Improve documentation and translate comments to English 2025-10-10 21:18:22 -03:00
kj 674c9d5ff4 fix(validator): Improve filter_var robustness in validation methods 2025-10-10 21:17:41 -03:00
kj 2f50532e13 refactor(Model): Type hint setNull variadic arguments 2025-10-10 21:06:11 -03:00
kj b41514a491 fix(di): Improve parameter resolution and validation 2025-10-10 19:15:07 -03:00
kj a1a15f492c refactor!: add Synapsis for dependency resolution and injection
- Remove Middleware class and custom callback handling logic.
- Implement Synapsis as a dependency injection container for automatic
resolution.
- Refactor Router to use Synapsis for process route callbacks and not found
handler.
- Update Request to remove middleware-specific properties and use
Router::$currentParams for path parameters.
2025-10-10 17:44:49 -03:00
kj b19e7d8789 Remove a forgotten debug line. 2025-09-07 18:52:33 -03:00
kj 4dfdb52519 Remove unnecessary and really never-used tableSufix property. 2025-09-07 15:25:48 -03:00
kj b0891885f9 BREAKING CHANGE: Make select, from, and setNull methods variadic. 2025-09-07 15:20:06 -03:00
kj b282d5479f Convert object/database naming conventions.
Adhere to PSR-12 by converting object properties to "lowerCamelCase" for objects
and "snake_case" for database interactions.

Backward compatibility is maintained: object properties already using
"snake_case" will continue to work without issue.
2025-09-07 15:01:45 -03:00
kj c9f467345b BREAKING CHANGE: Adhere to PSR-12 coding standards.
- Model: where_in method was renamed as whereIn.
2025-09-07 11:07:07 -03:00
kj 0f46848d15 Remove the predefined 'id' property.
This property is not necessary and gives less flexibility.
2025-08-15 14:51:16 -03:00
kj b2cb8d6883 Allow statically call beginTransaction, rollback and commit methods. 2025-07-15 18:12:41 -03:00
kj e9126e7cde Fix: Implicitly marking parameter as nullable is deprecated.
PHP 8.4 deprecation.
2025-06-07 14:27:47 -03:00
kj 7169d2cae3 Fix: render is not using the defined extension. 2025-06-07 14:17:11 -03:00
kj 66b2bc0d91 Remove unnecesary php close. 2025-06-07 14:14:16 -03:00
kj c8ab2aa2cc Remove unnecesary echo. 2025-05-20 12:49:20 -03:00
kj 1e302a9ea7 BREAKING CHANGE: Change unnecesary false return type. 2025-04-19 15:44:16 -03:00
kj d0d0d4dc76 Verify if a valid http query string after run parse_str. 2025-02-20 08:22:47 -03:00
kj 595e9c1316 Save body request as a property. 2025-02-20 08:22:37 -03:00
kj 45abea5301 Add delete request params. 2025-02-20 06:28:03 -03:00
kj d441f001ec Add type of items on array on dockblock for "all" method. 2025-02-03 16:04:39 -03:00
kj 19da122e05 Add type of items on array on dockblock for get method. 2025-02-03 16:02:27 -03:00
kj 1a0164c8ed Change static methods to non-static and made onInvalid public. 2024-10-30 11:53:44 -04:00
kj ad9f8ec67d Remove unnecesary brackets. 2024-10-29 19:12:25 -04:00
kj 31c5c63952 Remove innecesary return. 2024-10-29 19:10:47 -04:00
kj 6aef212350 Fix className not returning the classname in the right format. 2024-10-25 10:40:57 -04:00
kj c600688725 Improve return array dockblocks. 2024-09-23 18:09:38 -04:00
kj 3e27b1b7af Allow null on enum properties. 2024-09-23 15:06:44 -04:00
kj 73b7b8f72a Change required valitator to not allow empty values and add exists.
The exists validator do the same as the old required.
2024-09-18 14:33:33 -04:00
kj 7baad428ec Refactor request library. 2024-09-08 14:43:56 -04:00
kj 3d2a607768 Fix where_in is wiping previous where/and/or.
For now, works as an AND, but maybe later, same as where will exists
new methods: AndIn and OrIN.
2024-08-30 16:26:03 -04:00
kj df424ffab5 Model properties now can be typed as enums.
With this PHP 8.0 support is dropped.
2024-08-27 19:01:02 -04:00
kj daf7250882 Catch and verify put and patch input values. 2024-08-13 10:22:44 -04:00
kj 05cd83fd10 Remove unused variable. 2024-07-31 03:29:49 -04:00
kj 6b470a181d Fix: Remove unnecesary parameter. 2024-07-10 09:06:51 -04:00
kj 7beb161d2b Ensure db is in transaction to commit or rollback. 2024-06-04 07:18:30 -04:00
kj 701caae7eb Change route method to static. 2024-05-29 13:24:20 -04:00
kj 100bdfe006 Change private method to protected instead. 2024-05-28 22:27:20 -04:00
kj f1b79fdbc0 Add http code 422 on verification failed. 2024-05-25 17:41:33 -04:00
kj 406f9a10a1 Add head comment. 2024-05-25 17:19:08 -04:00
20 changed files with 2615 additions and 868 deletions
+3
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
+14
View File
@@ -0,0 +1,14 @@
<?php
require_once('config.php');
// Autoloader for the core classes
spl_autoload_register(function ($className) {
$fp = str_replace('\\', '/', $className);
$name = basename($fp);
$dir = dirname($fp);
$file = ROOT_CORE . '/' . $dir . '/' . $name . '.php';
if (file_exists($file)) {
require_once $file;
}
});
+5 -4
View File
@@ -1,14 +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');
+10 -19
View File
@@ -1,24 +1,15 @@
<?php <?php
require_once('config.php');
// Incluir clases use Libs\Loader;
spl_autoload_register(function ($className) { use Libs\Router;
$fp = str_replace('\\','/',$className);
$name = basename($fp);
$dir = dirname($fp);
$file = ROOT_CORE.'/'.$dir.'/'.$name.'.php';
if (file_exists($file)) {
require_once $file;
return;
}
});
// Incluir routers require_once('autoload.php');
$routers = glob(ROOT_CORE.'/Routers/*.php');
foreach($routers as $file){ // Router autoload
require_once($file); Loader::load(ROOT_CORE . '/Routers/');
}
\Libs\Router::apply(); // Other autoloads
?> Loader::load(ROOT_CORE . '/Autoloads/');
// Run the routers
Router::apply();
+84 -72
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
+29 -24
View File
@@ -1,45 +1,51 @@
<?php <?php
/**
* Database - DuckBrain
*
* Clase diseñada para crear y devolver una única instancia PDO (database).
*
* @author KJ
* @website https://kj2.me
* @licence MIT
*/
namespace Libs; namespace Libs;
use Exception;
use PDO; use PDO;
use PDOException; use PDOException;
use Exception;
class Database extends PDO { /**
static private array $databases = []; * Database - DuckBrain
*
private function __construct() {} * Class designed to create and return a single PDO instance (database).
*
* @author KJ
* @website https://kj2.me
* @license MIT
*/
class Database extends PDO
{
private static array $databases = [];
/** /**
* Devuelve una instancia homogénea (singlenton) de la base de datos (PDO). * Private constructor to prevent direct instantiation.
*/
private function __construct()
{
}
/**
* Returns a homogeneous (singleton) instance of the database (PDO).
* *
* @return PDO * @return PDO
* @throws Exception If there is an error connecting to the database.
*/ */
static public function getInstance( public static function getInstance(
string $type = 'mysql', string $type = 'mysql',
string $host = 'localhost', string $host = 'localhost',
string $name = '', string $name = '',
string $user = '', string $user = '',
string $pass = '', string $pass = '',
): PDO ): PDO {
{ $key = $type . '/' . $host . '/' . $name . '/' . $user;
$key = $type.'/'.$host.'/'.$name.'/'.$user;
if (empty(static::$databases[$key])) { if (empty(static::$databases[$key])) {
if ($type == 'sqlite') { if ($type == 'sqlite') {
$dsn = $type .':'. $name; $dsn = $type . ':' . $name;
} else } else {
$dsn = $type.':dbname='.$name.';host='.$host; $dsn = $type . ':dbname=' . $name . ';host=' . $host;
}
try { try {
static::$databases[$key] = new PDO($dsn, $user, $pass); static::$databases[$key] = new PDO($dsn, $user, $pass);
@@ -55,4 +61,3 @@ class Database extends PDO {
return static::$databases[$key]; return static::$databases[$key];
} }
} }
?>
+31
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);
}
}
}
}
-28
View File
@@ -1,28 +0,0 @@
<?php
/**
* Middleware - DuckBrain
*
* Librería base para middlewares.
*
* @author KJ
* @website https://kj2.me
* @licence MIT
*/
namespace Libs;
class Middleware {
/**
* Llama al siguiente callback.
*
* @param Neuron $req
*
* @return mixed
*/
public static function next(Neuron $req): mixed
{
$next = array_pop($req->next);
return call_user_func_array($next, [$req]);
}
}
+638 -354
View File
File diff suppressed because it is too large Load Diff
+30 -25
View File
@@ -1,54 +1,59 @@
<?php <?php
namespace Libs;
use AllowDynamicProperties;
/** /**
* Neuron - DuckBrain * Neuron - DuckBrain
* *
* Neuron, sirve para crear un objeto que alojará valores, pero * Neuron, serves to create an object that will hold values.
* además tiene la característica especial de que al intentar * In addition, it has the special characteristic that when trying
* acceder a un atributo que no está definido devolerá nulo en * to access an undefined property, it will return null instead
* lugar de generar un error php notice que indica que se está * of generating a notice (PHP notice) for an undefined variable or property.
* intentando acceder a un valor no definido.
* *
* El constructor recibe un objeto o arreglo con los valores que * The constructor accepts an object or an array containing the
* sí estarán definidos. * values that will be defined.
* *
* @author KJ * @author KJ
* @website https://kj2.me * @website https://kj2.me
* @licence MIT * @license MIT
*/ */
namespace Libs;
use AllowDynamicProperties;
#[AllowDynamicProperties] #[AllowDynamicProperties]
class Neuron { class Neuron
{
/** /**
* __construct * Constructor
* *
* @param array $data * @param array $data Data to initialize the Neuron with. Can be an array or an object.
*/ */
public function __construct(...$data) public function __construct(...$data)
{ {
if (count($data) === 1 && if (
count($data) === 1 &&
isset($data[0]) && isset($data[0]) &&
(is_array($data[0]) || (is_array($data[0]) ||
is_object($data[0]))) is_object($data[0]))
) {
$data = $data[0]; $data = $data[0];
}
foreach($data as $key => $value) foreach ($data as $key => $value) {
$this->{$key} = $value; $this->{$key} = $value;
}
} }
/** /**
* __get * Magic method __get
* *
* @param string $index * This method is called when an undefined property is accessed.
* @return null * It returns null instead of triggering an E_NOTICE.
*
* @param string $index The name of the property being accessed.
* @return null Always returns null for undefined properties.
*/ */
public function __get(string $index): null public function __get(string $index)
{ {
return null; return null;
} }
} }
?>
+117 -58
View File
@@ -1,121 +1,180 @@
<?php <?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; namespace Libs;
class Request extends Neuron { use Exception;
/**
* Request - DuckBrain
*
* Complementary library to the Router library.
* Contains the basic body of the http request (POST, GET, JSON, etc).
*
* @author KJ
* @website https://kj2.me
* @license MIT
*/
class Request extends Neuron
{
public Neuron $get; public Neuron $get;
public Neuron $post; public Neuron $post;
public Neuron $put;
public Neuron $patch;
public Neuron $delete;
public Neuron $json; public Neuron $json;
public Neuron $params; public Neuron $params;
public string $path; public string $path;
public string $error; public string $error;
public array $next; public string $body;
/** /**
* __construct * __construct
*
* @param string $path Ruta actual tomando como raíz la instalación de DuckBrain.
*/ */
public function __construct() public function __construct()
{ {
$this->path = Router::currentPath(); $this->path = Router::currentPath();
$this->get = new Neuron($_GET); $this->get = new Neuron($_GET);
$this->post = new Neuron($_POST); $this->post = new Neuron($_POST);
$this->put = new Neuron();
$this->patch = new Neuron();
$this->delete = new Neuron();
$this->params = Router::$params ?? new Neuron();
$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"]) : '';
if ($contentType === "application/json") if ($contentType === "application/json") {
$this->json = new Neuron( $this->json = new Neuron(
(object) json_decode(trim(file_get_contents("php://input")), false) (object) json_decode(trim($this->body), false)
); );
else } else {
$this->json = new Neuron(); $this->json = new Neuron();
if (
$this->params = new Neuron(); in_array($_SERVER['REQUEST_METHOD'], ['PUT', 'PATCH', 'DELETE']) &&
} preg_match('/^[^;?\/:@&=+$,]{1,255}[=]/', $this->body, $matches)
) {
/** // With the regular expression, we verify that it is a valid
* Inicia la validación que se haya configurado. // http query string and avoid memory errors in case
* // the body contains something larger than that.
* @return mixed parse_str($this->body, $input_vars);
*/ $this->{strtolower($_SERVER['REQUEST_METHOD'])} = new Neuron($input_vars);
public function validate(): mixed }
{
if ($_SERVER['REQUEST_METHOD'] == 'GET')
$actual = $this->get;
else
$actual = $this->post;
if (Validator::validateList(static::paramRules(), $this->params) &&
Validator::validateList(static::getRules(), $this->get ) &&
Validator::validateList(static::rules(), $actual))
return Middleware::next($this);
if (isset(static::messages()[Validator::$lastFailed]))
$error = static::messages()[Validator::$lastFailed];
else {
$error = 'Error: validation failed of '.preg_replace('/\./', ' as ', Validator::$lastFailed, 1);
} }
return static::onInvalid($error); // Run configured validations
$this->validate();
} }
/** /**
* Reglas para el método actual. * Starts the configured validation.
*
* On failure the single error message is built with Validator::message()
* and handed to onInvalid(), which throws by default: failures travel up
* as exceptions instead of answering HTTP here.
*
* @return void
* @throws Exception When a configured rule fails.
*/
public function validate(): void
{
$actual = match ($_SERVER['REQUEST_METHOD']) {
'POST', 'PUT', 'PATCH', 'DELETE' => $this->{strtolower($_SERVER['REQUEST_METHOD'])},
default => $this->get
};
// Merge uploaded files ($_FILES) into the body data set so the file rules
// (file/image/mimes and the routed min/max/required...) can validate
// uploads through a Request. $this->post is left untouched.
$body = empty($_FILES)
? $actual
: new Neuron(array_merge(get_object_vars($actual), $_FILES));
if (
Validator::validateList(static::paramRules(), $this->params) &&
Validator::validateList(static::getRules(), $this->get) &&
Validator::validateList(static::rules(), $body)
) {
return;
}
$error = Validator::message(
Validator::$lastFailed,
static::messages(),
static::attributes()
);
static::onInvalid($error);
}
/**
* Rules for the current method.
* *
* @return array * @return array
*/ */
public static function rules(): array { public function rules(): array
{
return []; return [];
} }
/** /**
* Reglas para los parámetros por URL. * Rules for URL parameters.
* *
* @return array * @return array
*/ */
public static function paramRules(): array { public function paramRules(): array
{
return []; return [];
} }
/** /**
* Reglas para los parámetros GET. * Rules for GET parameters.
* *
* @return array * @return array
*/ */
public static function getRules(): array { public function getRules(): array
{
return []; return [];
} }
/** /**
* Mensajes de error en caso de fallar una validación. * Error messages in case a validation fails.
* *
* @return array * @return array
*/ */
public static function messages(): array { public function messages(): array
{
return []; return [];
} }
/** /**
* Función a ejecutar cuando se ha detectado un valor no válido. * Human-readable names for the fields.
*
* Forwarded to Validator::message() so the :attribute marker in the error
* text can be replaced with a friendlier name (e.g. "edad" => "la edad").
*
* @return array
*/
public function attributes(): array
{
return [];
}
/**
* Function to execute when an invalid value has been detected.
*
* The default implementation always throws a generic \Exception carrying
* the single error message and HTTP 422 as its code; the framework's
* exception boundary (Router::apply) renders the response. Override it to
* throw a more specific exception type instead. The never return type is
* the contract: an override that returned would let the request continue
* with invalid data, so PHP rejects such an override at compile time.
* *
* @param string $error * @param string $error
* *
* @return mixed * @return never
* @throws Exception
*/ */
protected function onInvalid(string $error): mixed public function onInvalid(string $error): never
{ {
print($error); throw new Exception($error, 422);
return null;
} }
} }
+269 -174
View File
@@ -1,57 +1,138 @@
<?php <?php
/**
* Router - DuckBrain
*
* Librería de Enrrutador.
* Depende de manera forzada de que la constante ROOT_DIR esté definida
* y de manera optativa de que la constante SITE_URL lo esté también.
*
* @author KJ
* @website https://kj2.me
* @licence MIT
*/
namespace Libs; namespace Libs;
class Router { /**
private static $get = []; * Router - DuckBrain
private static $post = []; *
private static $put = []; * Router library.
private static $patch = []; * It strictly depends on the ROOT_DIR constant being defined
private static $delete = []; * and optionally on the SITE_URL constant being defined.
private static $last; *
public static $notFoundCallback = 'Libs\Router::defaultNotFound'; * @author KJ
* @website https://kj2.me
* @license MIT
*/
class Router
{
private static array $get = [];
private static array $post = [];
private static array $put = [];
private static array $patch = [];
private static array $delete = [];
/** /**
* Función callback por defectio para cuando * Stores a temporary stack of middlewares to be applied to routes defined
* no se encuentra configurada la ruta. * within a specific scope, typically configured by `withMiddleware()`.
* These middlewares are active only during the execution of the scope's callback.
*
* @var array $withMiddlewares<callable>
*/
private static array $withMiddlewares = [];
/**
* Stores the method and index of the last configured route, e.g., ['get', 0].
* Used for chaining methods like middleware() or reconfigure().
*
* @var array $last
*/
private static array $last;
/**
* Stores the parameters extracted from the current matching route.
*
* @var Neuron $params
*/
public static Neuron $params;
/**
* The callback function to be executed when no route matches.
*
* @var callable $notFoundCallback
*/
public static $notFoundCallback = 'Libs\Router::defaultNotFound';
/**
* Default callback function for when
* the route is not found.
* *
* @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 - Page Not Found</h2>';
}
/**
* The callback function to be executed when the router's boundary
* catches an exception thrown anywhere in the matched route chain.
* It receives the exception as a named argument: the handler must
* declare its parameter as $exception (typeable as \Throwable).
*
* @var callable $exceptionCallback
*/
public static $exceptionCallback = 'Libs\Router::defaultException';
/**
* Default callback function for exception responses.
*
* The HTTP status comes from the exception's code only when it is an
* integer in the 400-599 range; anything else (0, arbitrary codes, the
* SQLSTATE string carried by PDOException) answers 500. The body is
* negotiated from the request's Accept header: JSON when the client asks
* for application/json, plain text otherwise. The trace is serialized
* from getTraceAsString() because json_encode() of a Throwable yields an
* empty object: its properties are protected.
*
* @param \Throwable $exception
*
* @return void
*/
public static function defaultException(\Throwable $exception): void
{
$code = $exception->getCode();
http_response_code(is_int($code) && $code >= 400 && $code <= 599 ? $code : 500);
$accept = $_SERVER['HTTP_ACCEPT'] ?? '';
if (str_contains($accept, 'application/json')) {
header('Content-Type: application/json');
print(json_encode([
'error' => $exception->getMessage(),
'exception' => get_class($exception),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => explode(PHP_EOL, $exception->getTraceAsString()),
]));
return;
}
print($exception->getMessage() . PHP_EOL . $exception->getTraceAsString());
} }
/** /**
* __construct * __construct
*/ */
private function __construct() {} private function __construct()
{
}
/** /**
* Parsea para deectar las pseudovariables (ej: {variable}) * Parses to detect pseudovariables (e.g., {variable})
* *
* @param string $path * @param string $path
* Ruta con pseudovariables. * Route with pseudovariables.
* *
* @param callable $callback * @param callable $callback
* Callback que será llamado cuando la ruta configurada en $path coincida. * Callback that will be called when the route configured in $path matches.
* *
* @return array * @return array
* Arreglo con 2 índices: * Array with 3 indices:
* path - Contiene la ruta con las pseudovariables reeplazadas por expresiones regulares. * path - Contains the route with pseudovariables replaced by regular expressions.
* callback - Contiene el callback en formato Namespace\Clase::Método. * callback - Contains the callback in Namespace\Class::Method format.
* paramNames - An array of parameter names found in the path.
*/ */
private static function parse(string $path, callable $callback): array private static function parse(string $path, callable $callback): array
{ {
@@ -62,72 +143,75 @@ class Router {
$path = preg_replace( $path = preg_replace(
['/\\\{\w+\\\}/s'], ['/\\\{\w+\\\}/s'],
['([^\/]+)'], ['([^\/]+)'],
$path); $path
);
return [ return [
'path' => $path, 'path' => $path,
'callback' => [$callback], 'callback' => [$callback],
'paramNames' => $paramNames 'paramNames' => $paramNames,
]; ];
} }
/** /**
* Devuelve el ruta base o raiz del proyecto sobre la que trabajará el router. * Returns the base or root path of the project on which the router will work.
* *
* Ej: Si la url del sistema está en "https://ejemplo.com/duckbrain" * Ex: If the system URL is "https://example.com/duckbrain"
* entonces la ruta base sería "/duckbrain" * then the base path would be "/duckbrain"
* *
* @return string * @return string
*/ */
public static function basePath(): string public static function basePath(): string
{ {
if (defined('SITE_URL') && !empty(SITE_URL)) if (defined('SITE_URL') && !empty(SITE_URL)) {
return rtrim(parse_url(SITE_URL, PHP_URL_PATH), '/').'/'; 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);
} }
/** /**
* Redirije a una ruta relativa interna. * Redirects to an internal relative path.
* *
* @param string $path * @param string $path
* La ruta relativa a la ruta base. * The path relative to the base path.
* *
* Ej: Si nuesto sistema está en "https://ejemplo.com/duckbrain" * Ex: If our system is at "https://example.com/duckbrain"
* llamamos a Router::redirect('/docs'), entonces seremos * and we call Router::redirect('/docs'), we will be
* redirigidos a "https://ejemplo.com/duckbrain/docs". * redirected to "https://example.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().ltrim($path, '/')); header('Location: ' . static::basePath() . ltrim($path, '/'));
exit;
} }
/** /**
* Añade un middleware a la última ruta usada. * Adds a middleware to the last used route.
* Solo se puede usar un middleware a la vez. * Only one middleware can be added at a time.
* *
* @param callable $callback * @param callable $callback
* @param int $prioriry * @param int|null $priority Optional priority for the middleware execution order.
* *
* @return static * @return static
* Devuelve la instancia actual. * Returns the current instance.
*/ */
public static function middleware(callable $callback, int $priority = null): static public static function middleware(callable $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 (isset($priority) && $priority <= 0) if (isset($priority) && $priority <= 0) {
$priority = 1; $priority = 1;
}
if (is_null($priority) || $priority >= count(static::$$method[$index]['callback'])) if (is_null($priority) || $priority >= count(static::$$method[$index]['callback'])) {
static::$$method[$index]['callback'][] = $callback; static::$$method[$index]['callback'][] = $callback;
else { } else {
static::$$method[$index]['callback'] = array_merge( static::$$method[$index]['callback'] = array_merge(
array_slice(static::$$method[$index]['callback'], 0, $priority), array_slice(static::$$method[$index]['callback'], 0, $priority),
[$callback], [$callback],
@@ -139,7 +223,29 @@ class Router {
} }
/** /**
* Reconfigura el callback final de la última ruta. * Temporarily applies a set of middlewares to routes defined within a given callback.
* The middlewares are only active for the duration of the callback execution
* and are reset afterwards.
*
* @param callable $middleware
* @param callable $callback
*
* @return static
*/
public static function withMiddleware(callable $middleware, callable $callback): static
{
$currentMiddlewares = static::$withMiddlewares;
array_unshift(static::$withMiddlewares, $middleware);
$callback();
static::$withMiddlewares = $currentMiddlewares; // Restore withMiddleware
return new static();
}
/**
* Reconfigures the final callback of the last route.
* *
* @param callable $callback * @param callable $callback
* *
@@ -147,8 +253,9 @@ class Router {
*/ */
public static function reconfigure(callable $callback): static public static function reconfigure(callable $callback): static
{ {
if (empty(static::$last)) if (empty(static::$last)) {
return new static(); return new static();
}
$method = static::$last[0]; $method = static::$last[0];
$index = static::$last[1]; $index = static::$last[1];
@@ -159,20 +266,20 @@ class Router {
} }
/** /**
* Configura calquier método para todas las rutas. * Configures any method for all routes.
* *
* En caso de no recibir un callback, busca la ruta actual * If no callback is received, it searches for the current route
* solo configura la ruta como la última configurada * and only sets the route as the last configured one
* siempre y cuando la misma haya sido configurada previamente. * provided it has been configured previously.
* *
* @param string $method * @param string $method
* Método http. * HTTP method.
* @param string $path * @param string $path
* Ruta con pseudovariables. * Route with pseudovariables.
* @param callable|null $callback * @param callable|null $callback
* *
* @return * @return static
* Devuelve la instancia actual. * Returns the current instance.
*/ */
public static function configure(string $method, string $path, ?callable $callback = null): static public static function configure(string $method, string $path, ?callable $callback = null): static
{ {
@@ -181,194 +288,182 @@ class Router {
$path = preg_replace( $path = preg_replace(
['/\\\{\w+\\\}/s'], ['/\\\{\w+\\\}/s'],
['([^\/]+)'], ['([^\/]+)'],
$path); $path
);
foreach(static::$$method as $index => $router) foreach (static::$$method as $index => $router) {
if ($router['path'] == $path) { if ($router['path'] == $path) {
static::$last = [$method, $index]; static::$last = [$method, $index];
break; break;
} }
}
return new static(); return new static();
} }
static::$$method[] = static::parse($path, $callback); static::$$method[] = static::parse($path, $callback);
static::$last = [$method, count(static::$$method)-1]; static::$last = [$method, count(static::$$method) - 1];
foreach (static::$withMiddlewares as $middleware) {
static::middleware($middleware);
}
return new static(); return new static();
} }
/** /**
* Define los routers para el método GET. * Defines routers for the GET method.
* *
* @param string $path * @param string $path
* Ruta con pseudovariables. * Route with pseudovariables.
* @param callable $callback * @param callable|null $callback
* Callback que será llamado cuando la ruta configurada en $path coincida. * Callback that will be called when the route configured in $path matches.
* *
* @return static * @return static
* Devuelve la instancia actual. * Returns the current instance.
*/ */
public static function get(string $path, callable $callback = null): static public static function get(string $path, ?callable $callback = null): static
{ {
return static::configure('get', $path, $callback); return static::configure('get', $path, $callback);
} }
/** /**
* Define los routers para el método POST. * Defines routers for the POST method.
* *
* @param string $path * @param string $path
* Ruta con pseudovariables. * Route with pseudovariables.
* @param callable $callback * @param callable|null $callback
* Callback que será llamado cuando la ruta configurada en $path coincida. * Callback that will be called when the route configured in $path matches.
* *
* @return static * @return static
* Devuelve la instancia actual. * Returns the current instance.
*/ */
public static function post(string $path, callable $callback = null): static public static function post(string $path, ?callable $callback = null): static
{ {
return static::configure('post', $path, $callback); return static::configure('post', $path, $callback);
} }
/** /**
* Define los routers para el método PUT. * Defines routers for the PUT method.
* *
* @param string $path * @param string $path
* Ruta con pseudovariables. * Route with pseudovariables.
* @param callable $callback * @param callable|null $callback
* Callback que será llamado cuando la ruta configurada en $path coincida. * Callback that will be called when the route configured in $path matches.
* *
* @return static * @return static
* Devuelve la instancia actual * Returns the current instance
*/ */
public static function put(string $path, callable $callback = null): static public static function put(string $path, ?callable $callback = null): static
{ {
return static::configure('put', $path, $callback); return static::configure('put', $path, $callback);
} }
/** /**
* Define los routers para el método PATCH. * Defines routers for the PATCH method.
* *
* @param string $path * @param string $path
* Ruta con pseudovariables. * Route with pseudovariables.
* @param callable $callback * @param callable|null $callback
* Callback que será llamado cuando la ruta configurada en $path coincida. * Callback that will be called when the route configured in $path matches.
* *
* @return static * @return static
* Devuelve la instancia actual * Returns the current instance
*/ */
public static function patch(string $path, callable $callback = null): static public static function patch(string $path, ?callable $callback = null): static
{ {
return static::configure('patch', $path, $callback); return static::configure('patch', $path, $callback);
} }
/** /**
* Define los routers para el método DELETE. * Defines routers for the DELETE method.
* *
* @param string $path * @param string $path
* Ruta con pseudovariables * Route with pseudovariables
* @param callable $callback * @param callable|null $callback
* Callback que será llamado cuando la ruta configurada en $path coincida. * Callback that will be called when the route configured in $path matches.
* *
* @return static * @return static
* Devuelve la instancia actual * Returns the current instance
*/ */
public static function delete(string $path, callable $callback = null): static public static function delete(string $path, ?callable $callback = null): static
{ {
return static::configure('delete', $path, $callback); return static::configure('delete', $path, $callback);
} }
/** /**
* Devuelve la ruta actual tomando como raíz la ruta de instalación de DuckBrain. * Returns the current path, taking the DuckBrain installation path as the root.
* *
* @return string * @return string
*/ */
public static function currentPath() : string public static function currentPath(): string
{ {
return preg_replace('/'.preg_quote(static::basePath(), '/').'/', return preg_replace(
'/', strtok($_SERVER['REQUEST_URI'], '?'), 1); '/' . preg_quote(static::basePath(), '/') . '/',
'/',
strtok($_SERVER['REQUEST_URI'], '?'),
1
);
} }
/** /**
* Aplica la configuración de rutas. * Applies the route configuration.
* *
* @param string $path (opcional) Ruta a usar. Si no se define, detecta la ruta actual. * This method is the framework's error boundary: any \Throwable thrown
* while running the matched route's callback chain, while printing the
* returned data, or while resolving the not-found callback is caught
* here and rendered through $exceptionCallback. A failing handler is
* deliberately left uncaught (double failure falls back to PHP itself).
*
* @param string|null $path (optional) Path to use. If not defined, it detects the current path.
* *
* @return void * @return void
*/ */
public static function apply(string $path = null): void public static function apply(?string $path = null): void
{ {
$path = $path ?? static::currentPath(); try {
$routers = match($_SERVER['REQUEST_METHOD']) { // Según el método selecciona un arreglo de routers $path = $path ?? static::currentPath();
'POST' => static::$post, $routers = match ($_SERVER['REQUEST_METHOD']) { // Selects an array of routers based on the method
'PUT' => static::$put, 'POST' => static::$post,
'PATCH' => static::$patch, 'PUT' => static::$put,
'DELETE' => static::$delete, 'PATCH' => static::$patch,
default => static::$get 'DELETE' => static::$delete,
}; default => static::$get
};
foreach ($routers as $router) { // revisa todos los routers para ver si coinciden con la ruta actual foreach ($routers as $router) { // Checks all routers to see if they match the current path
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]);
// Objtener un reflection del callback // Checking and storing the variable parameters of the route
$lastCallback = $router['callback'][0]; if (isset($matches[1])) {
if ($lastCallback instanceof \Closure) { // si es función anónima static::$params = new Neuron();
$reflectionCallback = new \ReflectionFunction($lastCallback); foreach ($matches as $index => $match) {
} else { $paramName = $router['paramNames'][$index - 1];
if (is_string($lastCallback)) static::$params->{$paramName} = urldecode($match[0]);
$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 // Processes the callback queue
if (isset($matches[1])) { foreach (array_reverse($router['callback']) as $callback) {
foreach ($matches as $index => $match) { $data = Synapsis::resolve($callback);
$paramName = $router['paramNames'][$index-1];
$request->params->$paramName = urldecode($match[0]);
} }
// By default, prints as JSON if something is returned
if (isset($data)) {
header('Content-Type: application/json');
print(json_encode($data));
}
return;
} }
// Llama a la validación y luego procesa la cola de callbacks
$request->next = $router['callback'];
$data = $request->validate($request);
// Por defecto imprime como JSON si se retorna algo
if (isset($data)) {
header('Content-Type: application/json');
print(json_encode($data));
}
return;
} }
}
// Si no hay router que coincida llamamos a $notFoundCallBack // If no router matches, call $notFoundCallBack
call_user_func_array(static::$notFoundCallback, [new Request]); Synapsis::resolve(static::$notFoundCallback);
} catch (\Throwable $exception) {
Synapsis::resolve(static::$exceptionCallback, ['exception' => $exception]);
}
} }
} }
+179
View File
@@ -0,0 +1,179 @@
<?php
namespace Libs;
use Closure;
use Exception;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
use ReflectionParameter;
/**
* Synapsis - DuckBrain
*
* Class responsible for dependency resolution and injection.
*
* @author KJ
* @website https://kj2.me
* @license MIT
*/
class Synapsis
{
/**
* @var array<class-string, class-string> $bindings
*/
private static array $bindings = [];
/**
* @var array<class-string, mixed> $instances
*/
private static array $instances = [];
/**
* Binds an interface to a class to instantiate it instead.
*
* @param class-string $interfaceName
* @param class-string $className
*
* @return void
* @throws Exception If the interface or class does not exist.
*/
public static function bind(string $interfaceName, string $className): void
{
if (interface_exists($interfaceName) && class_exists($className)) {
static::$bindings[$interfaceName] = $className;
return;
}
throw new Exception('Error at binding non existant Interface or ClassName.');
}
/**
* Binds multiple interfaces to multiple classes in bulk.
*
* @param array<class-string, class-string> $bindings
*
* @return void
*/
public static function bulkBind(array $bindings): void
{
foreach ($bindings as $interfaceName => $className) {
static::bind($interfaceName, $className);
}
}
/**
* Resolves and injects dependencies for a callable and returns its result.
*
* @param callable $action
* @param array<string, mixed> $named Associative array of values injected into the callable's
* parameters by exact name match. Consumed in resolveParameterValues().
*
* @return mixed
* @throws Exception If an unhandled callable type is provided.
*/
public static function resolve(callable $action, array $named = []): mixed
{
if ($action instanceof Closure) { // If it's an anonymous function
$reflectionCallback = new ReflectionFunction($action);
} else { // Array or string with the method
if (is_string($action)) {
$action = preg_split('/::/', $action);
}
// Check if it's a method or just a function
if (count($action) == 2) {
$reflectionCallback = new ReflectionMethod($action[0], $action[1]);
} else {
$reflectionCallback = new ReflectionFunction($action[0]);
}
}
// Get the parameters
return call_user_func_array(
$action,
static::resolveParameterValues($reflectionCallback->getParameters(), $named)
);
}
/**
* Resolves and injects dependencies for a class and returns its result.
*
* @param class-string $className
*
* @return mixed
* @throws Exception If a binding is missing for an interface or the class does not exist.
*/
public static function &resolveInstance(string $className): mixed
{
if (interface_exists($className)) {
if (isset(static::$bindings[$className])) {
$className = static::$bindings[$className];
} else {
throw new Exception("Missing binding for interface: {$className}");
}
}
if (!class_exists($className)) {
throw new Exception("Cannot resolve: {$className}");
}
if (!isset(static::$instances[$className])) {
// If the instance has not been resolved before, resolve it
$reflectionClass = new ReflectionClass($className);
$constructor = $reflectionClass->getConstructor();
static::$instances[$className] = new $className(
...static::resolveParameterValues($constructor?->getParameters() ?? [])
);
}
return static::$instances[$className];
}
/**
* Resolves parameter values by injecting dependencies.
*
* @param array<ReflectionParameter> $parameters
* @param array<string, mixed> $named
* Values injected into parameters whose name matches the key,
* taking precedence over optional defaults and DI resolution.
* Keys matching no parameter are ignored.
*
* @return array<mixed>
* @throws Exception If a primitive parameter does not have a default value.
*/
public static function resolveParameterValues(array $parameters, array $named = []): array
{
$values = [];
foreach ($parameters as $parameter) {
if ($parameter->isVariadic()) { // Skip variatic parameters
continue;
}
if (array_key_exists($parameter->getName(), $named)) { // Named values win over defaults and DI
$values[] = $named[$parameter->getName()];
continue;
}
if ($parameter->isOptional()) { // Always use the default value first
$values[] = $parameter->getDefaultValue();
continue;
}
$paramType = $parameter->getType();
if ($paramType === null) {
// If no type is declared, and it's not optional, we cannot resolve it.
throw new Exception(
"Untyped parameter '{$parameter}' expect at least a default value or must be optional."
);
}
if ($paramType->isBuiltin()) {
throw new Exception("Primitive parameter '{$parameter->getName()}' expects at least a default value.");
}
$values[] = static::resolveInstance($paramType->getName());
}
return $values;
}
}
+1142 -46
View File
File diff suppressed because it is too large Load Diff
+64 -64
View File
@@ -1,80 +1,80 @@
<?php <?php
/**
* View - DuckBrain
*
* Manejador de vistas simplificado.
*
* @author KJ
* @website https://kj2.me
* @licence MIT
*/
namespace Libs; namespace Libs;
class View extends Neuron { /**
* View - DuckBrain
*
* Simplified view handler.
*
* @author KJ
* @website https://kj2.me
* @license MIT
*/
class View extends Neuron
{
/** /**
* Incluye el archivo. * Includes the file.
* *
* @param string $viewName Ruta relativa y el nommbre sin extensión del archivo. * @param string $viewName Relative path and filename without extension.
* @param string $viewPath (opcional) Ruta donde se encuentra la vista. * @param string|null $viewPath (Optional) Path where the view file is located.
* @param string $extension (opcional) Extensión del archivo. * @param string $extension (Optional) File extension.
* *
* @return void * @return void
*/ */
private function include( protected function include(
string $viewName, string $viewName,
string $viewPath = null, ?string $viewPath = null,
string $extension = 'php' string $extension = 'php'
): void ): void {
{ if (
$view = $this; isset($viewPath) &&
file_exists("$viewPath$viewName.$extension")
if (isset($viewPath) && ) {
file_exists("$viewPath$viewName.$extension")) {
include("$viewPath$viewName.$extension"); include("$viewPath$viewName.$extension");
return; return;
} }
include(ROOT_CORE."/Views/$viewName.$extension"); include(ROOT_CORE . "/Views/$viewName.$extension");
} }
/** /**
* Función que "renderiza" las vistas * Function that "renders" views.
* *
* @param string $viewName Ruta relativa y el nommbre sin extensión del archivo. * @param string $viewName Relative path and filename without extension.
* @param array|Neuron $params (opcional) Arreglo que podrá ser usado en la vista mediante $view ($param['index'] se usaría así: $view->index) * @param array|Neuron $params (Optional) Array that can be used in the view
* @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/". * via $view ($param['index'] would be used as: $view->index).
* @param string $extension (opcional) Extensión del archivo. * @param string|null $viewPath (Optional) Path where the view is located. If the
* view is not found in this path, the default path "src/Views/" will be used.
* @param string $extension (Optional) File extension.
* *
* @return void * @return void
*/ */
public static function render( public static function render(
string $viewName, string $viewName,
array|Neuron $params = [], array|Neuron $params = [],
string $viewPath = null, ?string $viewPath = null,
string $extension = 'php' string $extension = 'php'
): void ): void {
{
$instance = new View($params); $instance = new View($params);
$instance->html($viewName, $viewPath); $instance->html($viewName, $viewPath, $extension);
} }
/** /**
* Renderiza las vistas HTML * Renders HTML views.
* *
* @param string $viewName Ruta relativa y el nommbre sin extensión del archivo ubicado en src/Views * @param string $viewName Relative path and filename without extension located in src/Views.
* @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/". * @param string|null $viewPath (Optional) Path where the view is located. If the view is not
* @param string $extension (opcional) Extensión del archivo. * found in this path, the default path "src/Views/" will be used.
* @param string $extension (Optional) File extension.
* *
* @return void * @return void
*/ */
public function html( public function html(
string $viewName, string $viewName,
string $viewPath = null, ?string $viewPath = null,
string $extension = 'php' string $extension = 'php'
): void ): void {
{
$this->include( $this->include(
$viewName, $viewName,
$viewPath, $viewPath,
@@ -83,47 +83,47 @@ class View extends Neuron {
} }
/** /**
* Renderiza código CSS. * Renders CSS code.
* *
* @param string $viewName Ruta relativa y el nommbre sin extensión del archivo ubicado en src/Views * @param string $viewName Relative path and filename without extension located in src/Views.
* @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/". * @param string|null $viewPath (Optional) Path where the view is located. If the view is not
* @param string $extension (opcional) Extensión del archivo. * found in this path, the default path "src/Views/" will be used.
* @param string $extension (Optional) File extension.
* *
* @return void * @return void
*/ */
public function css( public function css(
string $viewName, string $viewName,
string $viewPath = null, ?string $viewPath = null,
string $extension = 'css' string $extension = 'css'
): void ): void {
{
header("Content-type: text/css"); header("Content-type: text/css");
$this->include($viewName, $viewPath, $extension); $this->include($viewName, $viewPath, $extension);
} }
/** /**
* Renderiza código Javascript. * Renders Javascript code.
* *
* @param string $viewName Ruta relativa y el nommbre sin extensión del archivo ubicado en src/Views * @param string $viewName Relative path and filename without extension located in src/Views.
* @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/". * @param string|null $viewPath (Optional) Path where the view is located. If the view is not
* @param string $extension (opcional) Extensión del archivo. * found in this path, the default path "src/Views/" will be used.
* @param string $extension (Optional) File extension.
* *
* @return void * @return void
*/ */
public function js( public function js(
string $viewName, string $viewName,
string $viewPath = null, ?string $viewPath = null,
string $extension = 'js' string $extension = 'js'
): void ): void {
{
header("Content-type: application/javascript"); header("Content-type: application/javascript");
$this->include($viewName, $viewPath, $extension); $this->include($viewName, $viewPath, $extension);
} }
/** /**
* Imprime los datos en Json. * Prints data as JSON.
* *
* @param object|array $data Objeto o array que se imprimirá a JSON. * @param object|array $data Object or array to be printed as JSON.
* *
* @return void * @return void
*/ */
@@ -134,9 +134,9 @@ class View extends Neuron {
} }
/** /**
* Imprime los datos en texto plano * Prints data as plain text.
* *
* @param string $txt Contenido de texto. * @param string $txt Text content.
* *
* @return void * @return void
*/ */
@@ -147,18 +147,18 @@ class View extends Neuron {
} }
/** /**
* Intenta devolver la url absoluta a partir de una ruta relativa. * Attempts to return the absolute URL from a relative path.
* *
* @param string $path * @param string $path
* *
* @return string * @return string
*/ */
public function route(string $path = '/'): string public static function route(string $path = '/'): string
{ {
if (defined('SITE_URL') && !empty(SITE_URL)) if (defined('SITE_URL') && !empty(SITE_URL)) {
return rtrim(SITE_URL, '/').'/'.ltrim($path, '/'); return rtrim(SITE_URL, '/') . '/' . ltrim($path, '/');
}
return $path; return $path;
} }
} }
?>