Compare commits
44
Commits
ece32b9bbe
..
v0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b6da45b01 | ||
|
|
b7260b5199 | ||
|
|
1f29622914 | ||
|
|
eb882db0c8 | ||
|
|
9ba43e3f25 | ||
|
|
54e96ba4bb | ||
|
|
9027f36748 | ||
|
|
2ae9ef39f3 | ||
|
|
ee0db0307e | ||
|
|
44cbe6bbdc | ||
|
|
31728af351 | ||
|
|
4845d14ef5 | ||
|
|
aca4e732ed | ||
|
|
f475c9125f | ||
|
|
4fe7314389 | ||
|
|
1f366830bc | ||
|
|
15df7c8d96 | ||
|
|
b8dd1fd0f6 | ||
|
|
83c80bd4ab | ||
|
|
b13c07d3c4 | ||
|
|
b95747bfe0 | ||
|
|
01c5eceeb4 | ||
|
|
ee9c109ed9 | ||
|
|
7eed69725d | ||
|
|
e37035eed8 | ||
|
|
5312e5060e | ||
|
|
3c9db3ac53 | ||
|
|
2b5d19ecd5 | ||
|
|
53e862d126 | ||
|
|
b6baeef32f | ||
|
|
159da01d5c | ||
|
|
3770dd6d9b | ||
|
|
76a69e5ef8 | ||
|
|
c5a4967ae4 | ||
|
|
c0b9f3851a | ||
|
|
78e199e300 | ||
|
|
8cd5e114dd | ||
|
|
ce03bdb27d | ||
|
|
b24598b118 | ||
|
|
5019b89cc0 | ||
|
|
d030e8d30e | ||
|
|
b2672ad92f | ||
|
|
4c57770b43 | ||
|
|
8e84450f95 |
@@ -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;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -3,24 +3,13 @@
|
|||||||
use Libs\Loader;
|
use Libs\Loader;
|
||||||
use Libs\Router;
|
use Libs\Router;
|
||||||
|
|
||||||
require_once('config.php');
|
require_once('autoload.php');
|
||||||
|
|
||||||
// Class autoloader
|
// Router autoload
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Router autoloader
|
|
||||||
Loader::load(ROOT_CORE . '/Routers/');
|
Loader::load(ROOT_CORE . '/Routers/');
|
||||||
|
|
||||||
// Other autoloaders
|
// Other autoloads
|
||||||
Loader::load(ROOT_CORE . '/Autoload/');
|
Loader::load(ROOT_CORE . '/Autoloads/');
|
||||||
|
|
||||||
// Run the routers
|
// Run the routers
|
||||||
Router::apply();
|
Router::apply();
|
||||||
|
|||||||
+41
-39
@@ -33,83 +33,85 @@ En la siguiente tabla se encuentra la lista de estados de los gestores de bases
|
|||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| 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 |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| whereIn | ok | ok | |
|
| whereIn | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| whereNotIn | ok | ok | |
|
| whereNotIn | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| whereNull | ok | ok | |
|
| whereNull | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| whereNotNull | ok | ok | |
|
| whereNotNull | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| whereExists | ok | ok | |
|
| whereExists | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| whereNotExists | ok | ok | |
|
| whereNotExists | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| leftJoin | ok | ok | |
|
| leftJoin | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| rightJoin | ok | fixed | |
|
| rightJoin | ok | fixed | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| innerJoin | ok | ok | |
|
| innerJoin | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| and | ok | ok | |
|
| crossJoin | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| or | ok | ok | |
|
| and | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| groupBy | ok | ok | |
|
| or | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| limit | ok | ok | |
|
| groupBy | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| orderBy | ok | ok | |
|
| limit | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| count | ok | ok | |
|
| orderBy | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| getById | ok | ok | |
|
| count | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| search | ok | ok | |
|
| getById | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| get | ok | ok | |
|
| search | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| getFirst | ok | ok | |
|
| get | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| all | ok | ok | |
|
| getFirst | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
| setNull | ok | ok | |
|
| all | ok | ok | ok |
|
||||||
|
|------------------+---------------+---------+------------|
|
||||||
|
| setNull | ok | ok | ok |
|
||||||
|------------------+---------------+---------+------------|
|
|------------------+---------------+---------+------------|
|
||||||
|
|
||||||
* Contacto
|
* Contacto
|
||||||
|
|||||||
+228
-171
@@ -23,40 +23,40 @@ use ReflectionProperty;
|
|||||||
#[AllowDynamicProperties]
|
#[AllowDynamicProperties]
|
||||||
class Model
|
class Model
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var array Attributes that should be set to NULL on update.
|
|
||||||
*/
|
|
||||||
protected array $toNull = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string The name of the primary key column.
|
* @var string The name of the primary key column.
|
||||||
*/
|
*/
|
||||||
protected static string $primaryKey = 'id';
|
protected static string $dbPrimaryKey = 'id';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array Attributes to ignore when saving to the database.
|
* @var array Attributes to ignore when saving to the database.
|
||||||
*/
|
*/
|
||||||
protected static array $ignoreSave = ['id'];
|
protected static array $dbIgnoreSave = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array Attributes that should be explicitly saved, even if private/protected.
|
* @var array Attributes that should be explicitly saved, even if private/protected.
|
||||||
*/
|
*/
|
||||||
protected static array $forceSave = [];
|
protected static array $dbForceSave = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool Attribute that persis if is current model is saved into the database
|
||||||
|
*/
|
||||||
|
protected bool $dbIsSaved = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string The database table name.
|
* @var string The database table name.
|
||||||
*/
|
*/
|
||||||
protected static string $table;
|
protected static string $dbTableName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array Variables for PDO prepared statements.
|
* @var array Variables for PDO prepared statements.
|
||||||
*/
|
*/
|
||||||
protected static array $queryVars = [];
|
protected static array $dbQueryVariables = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array Current SELECT query components.
|
* @var array Current SELECT query components.
|
||||||
*/
|
*/
|
||||||
protected static array $querySelect = [
|
protected static array $dbQuery = [
|
||||||
'select' => ['*'],
|
'select' => ['*'],
|
||||||
'where' => '',
|
'where' => '',
|
||||||
'from' => '',
|
'from' => '',
|
||||||
@@ -156,13 +156,29 @@ class Model
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$prepared = $db->prepare($query);
|
$prepared = $db->prepare($query);
|
||||||
$prepared->execute(static::$queryVars);
|
foreach (static::$dbQueryVariables as $key => $value) {
|
||||||
|
// Detect data type to choose the PDO constant
|
||||||
|
$type = match (gettype($value)) {
|
||||||
|
'integer' => PDO::PARAM_INT,
|
||||||
|
'boolean' => PDO::PARAM_BOOL,
|
||||||
|
'NULL' => PDO::PARAM_NULL,
|
||||||
|
default => PDO::PARAM_STR,
|
||||||
|
};
|
||||||
|
|
||||||
|
$prepared->bindValue($key, $value, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
$prepared->execute();
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
if ($db->inTransaction()) {
|
if ($db->inTransaction()) {
|
||||||
$db->rollBack();
|
$db->rollBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
$vars = json_encode(static::$queryVars);
|
$vars = json_encode(static::$dbQueryVariables);
|
||||||
|
|
||||||
|
if ($resetQuery) {
|
||||||
|
static::resetQuery();
|
||||||
|
}
|
||||||
|
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
"\nError at query to database.\n" .
|
"\nError at query to database.\n" .
|
||||||
@@ -187,7 +203,7 @@ class Model
|
|||||||
*/
|
*/
|
||||||
protected static function resetQuery(): void
|
protected static function resetQuery(): void
|
||||||
{
|
{
|
||||||
static::$querySelect = [
|
static::$dbQuery = [
|
||||||
'select' => ['*'],
|
'select' => ['*'],
|
||||||
'where' => '',
|
'where' => '',
|
||||||
'from' => '',
|
'from' => '',
|
||||||
@@ -199,7 +215,7 @@ class Model
|
|||||||
'groupBy' => '',
|
'groupBy' => '',
|
||||||
'limit' => '',
|
'limit' => '',
|
||||||
];
|
];
|
||||||
static::$queryVars = [];
|
static::$dbQueryVariables = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -211,44 +227,44 @@ class Model
|
|||||||
*/
|
*/
|
||||||
protected static function buildQuery(): string
|
protected static function buildQuery(): string
|
||||||
{
|
{
|
||||||
$sql = 'SELECT ' . join(', ', static::$querySelect['select']);
|
$sql = 'SELECT ' . join(', ', static::$dbQuery['select']);
|
||||||
|
|
||||||
if (static::$querySelect['from'] != '') {
|
if (static::$dbQuery['from'] != '') {
|
||||||
$sql .= ' FROM ' . static::$querySelect['from'];
|
$sql .= ' FROM ' . static::$dbQuery['from'];
|
||||||
} else {
|
} else {
|
||||||
$sql .= ' FROM ' . static::table();
|
$sql .= ' FROM ' . static::table();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static::$querySelect['crossJoin'] != '') {
|
if (static::$dbQuery['crossJoin'] != '') {
|
||||||
$sql .= static::$querySelect['crossJoin'];
|
$sql .= static::$dbQuery['crossJoin'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static::$querySelect['innerJoin'] != '') {
|
if (static::$dbQuery['innerJoin'] != '') {
|
||||||
$sql .= static::$querySelect['innerJoin'];
|
$sql .= static::$dbQuery['innerJoin'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static::$querySelect['leftJoin'] != '') {
|
if (static::$dbQuery['leftJoin'] != '') {
|
||||||
$sql .= static::$querySelect['leftJoin'];
|
$sql .= static::$dbQuery['leftJoin'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static::$querySelect['rightJoin'] != '') {
|
if (static::$dbQuery['rightJoin'] != '') {
|
||||||
$sql .= static::$querySelect['rightJoin'];
|
$sql .= static::$dbQuery['rightJoin'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static::$querySelect['where'] != '') {
|
if (static::$dbQuery['where'] != '') {
|
||||||
$sql .= ' WHERE ' . static::$querySelect['where'];
|
$sql .= ' WHERE ' . static::$dbQuery['where'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static::$querySelect['groupBy'] != '') {
|
if (static::$dbQuery['groupBy'] != '') {
|
||||||
$sql .= ' GROUP BY ' . static::$querySelect['groupBy'];
|
$sql .= ' GROUP BY ' . static::$dbQuery['groupBy'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static::$querySelect['orderBy'] != '') {
|
if (static::$dbQuery['orderBy'] != '') {
|
||||||
$sql .= ' ORDER BY ' . static::$querySelect['orderBy'];
|
$sql .= ' ORDER BY ' . static::$dbQuery['orderBy'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static::$querySelect['limit'] != '') {
|
if (static::$dbQuery['limit'] != '') {
|
||||||
$sql .= ' LIMIT ' . static::$querySelect['limit'];
|
$sql .= ' LIMIT ' . static::$dbQuery['limit'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $sql;
|
return $sql;
|
||||||
@@ -267,8 +283,8 @@ class Model
|
|||||||
*/
|
*/
|
||||||
public static function bind(string $value): string
|
public static function bind(string $value): string
|
||||||
{
|
{
|
||||||
$index = ':v_' . count(static::$queryVars);
|
$index = ':v_' . count(static::$dbQueryVariables);
|
||||||
static::$queryVars[$index] = $value;
|
static::$dbQueryVariables[$index] = $value;
|
||||||
return $index;
|
return $index;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,30 +300,65 @@ class Model
|
|||||||
*/
|
*/
|
||||||
protected static function getInstance(array $elem = []): static
|
protected static function getInstance(array $elem = []): static
|
||||||
{
|
{
|
||||||
$class = get_called_class();
|
$class = get_called_class();
|
||||||
$instance = new $class();
|
$instance = new $class();
|
||||||
$reflection = new ReflectionClass($instance);
|
$reflection = new ReflectionClass($instance);
|
||||||
$properties = $reflection->getProperties();
|
$properties = $reflection->getProperties();
|
||||||
$propertyNames = array_map(function ($property) {
|
|
||||||
return static::camelCaseToSnakeCase($property->name);
|
$map = [];
|
||||||
}, $properties);
|
foreach ($properties as $prop) {
|
||||||
|
$map[static::camelCaseToSnakeCase($prop->name)] = $prop;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($elem as $key => $value) {
|
foreach ($elem as $key => $value) {
|
||||||
$index = array_search($key, $propertyNames);
|
if (isset($map[$key])) {
|
||||||
if (is_numeric($index)) {
|
$property = $map[$key];
|
||||||
if (enum_exists($properties[$index]->getType()->getName())) {
|
$instance->{$property->name} = static::castValue($property, $value);
|
||||||
$instance->{$properties[$index]->name} = $properties[$index]->getType()->getName()::tryfrom($value);
|
|
||||||
} else {
|
|
||||||
$instance->{$properties[$index]->name} = $value;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
$instance->{static::snakeCaseToCamelCase($key)} = $value;
|
$instance->{static::snakeCaseToCamelCase($key)} = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$instance->markAsSaved();
|
||||||
|
|
||||||
return $instance;
|
return $instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles type casting for properties
|
||||||
|
*
|
||||||
|
* @param ReflectionProperty $property
|
||||||
|
* @param mixed $value
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
protected static function castValue(ReflectionProperty $property, mixed $value): mixed
|
||||||
|
{
|
||||||
|
$type = $property->getType();
|
||||||
|
if (!$type) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$typeName = $type->getName();
|
||||||
|
|
||||||
|
// Enum cast
|
||||||
|
if (enum_exists($typeName)) {
|
||||||
|
if (method_exists($typeName, 'tryFrom')) {
|
||||||
|
return $typeName::tryFrom($value);
|
||||||
|
}
|
||||||
|
if (in_array($value, array_column($typeName::cases(), 'name'))) {
|
||||||
|
return $typeName::{$value};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateTime cast
|
||||||
|
if (is_subclass_of($typeName, \DateTimeInterface::class)) {
|
||||||
|
return $value ? new $typeName($value) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the attributes to be saved for the current class.
|
* Returns the attributes to be saved for the current class.
|
||||||
* Attributes will be those that are public and not excluded in static::$ignoreSave,
|
* Attributes will be those that are public and not excluded in static::$ignoreSave,
|
||||||
@@ -323,30 +374,43 @@ class Model
|
|||||||
$result = [];
|
$result = [];
|
||||||
|
|
||||||
foreach ($properties as $property) {
|
foreach ($properties as $property) {
|
||||||
if (!in_array($property->name, static::$ignoreSave)) {
|
// Avoid save unset variables
|
||||||
$result[$this->camelCaseToSnakeCase($property->name)] = isset($this->{$property->name})
|
if ($property->getType() && !$property->isInitialized($this)) {
|
||||||
? $this->{$property->name} : null;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($property->name, static::$dbIgnoreSave)) {
|
||||||
|
$name = $this->camelCaseToSnakeCase($property->name);
|
||||||
|
$result[$name] = $this->castToDbValue($this->{$property->name});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (static::$forceSave as $value) {
|
foreach (static::$dbForceSave as $value) {
|
||||||
$result[$value] = isset($this->$value)
|
$result[$value] = $this->castToDbValue($this->$value);
|
||||||
? $this->$value : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($result as $i => $property) {
|
|
||||||
if (gettype($property) == 'boolean') {
|
|
||||||
$result[$i] = $property ? '1' : '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($property instanceof \UnitEnum) {
|
|
||||||
$result[$i] = $property->value ?? $property->name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a PHP value to a database-compatible value.
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
protected function castToDbValue(mixed $value): mixed
|
||||||
|
{
|
||||||
|
if ($value instanceof \UnitEnum) {
|
||||||
|
return $value->value ?? $value->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof \DateTimeInterface) {
|
||||||
|
return $value->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the current class, even if it's an extended class.
|
* Returns the name of the current class, even if it's an extended class.
|
||||||
*
|
*
|
||||||
@@ -370,8 +434,8 @@ class Model
|
|||||||
*/
|
*/
|
||||||
protected static function table(): string
|
protected static function table(): string
|
||||||
{
|
{
|
||||||
if (isset(static::$table)) {
|
if (isset(static::$dbTableName)) {
|
||||||
return static::$table;
|
return static::$dbTableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return static::camelCaseToSnakeCase(static::className()) . 's';
|
return static::camelCaseToSnakeCase(static::className()) . 's';
|
||||||
@@ -416,26 +480,20 @@ class Model
|
|||||||
protected function update(): void
|
protected function update(): void
|
||||||
{
|
{
|
||||||
$atts = $this->getVars();
|
$atts = $this->getVars();
|
||||||
|
static::$dbQueryVariables = [];
|
||||||
|
|
||||||
|
$set = [];
|
||||||
foreach ($atts as $key => $value) {
|
foreach ($atts as $key => $value) {
|
||||||
if (isset($value)) {
|
$set[] = "$key=:$key";
|
||||||
if (in_array($key, $this->toNull)) {
|
static::$dbQueryVariables[':' . $key] = $value;
|
||||||
$set[] = "$key=NULL";
|
|
||||||
} else {
|
|
||||||
$set[] = "$key=:$key";
|
|
||||||
static::$queryVars[':' . $key] = $value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (in_array($key, $this->toNull)) {
|
|
||||||
$set[] = "$key=NULL";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$table = static::table();
|
$table = static::table();
|
||||||
$pk = static::$primaryKey;
|
$pk = static::$dbPrimaryKey;
|
||||||
$pkv = $this->$pk;
|
$pkv = $this->$pk;
|
||||||
$sql = "UPDATE $table SET " . join(', ', $set) . " WHERE $pk='$pkv'";
|
$sql = "UPDATE $table SET " . join(', ', $set) . " WHERE $pk=:$pk";
|
||||||
|
static::$dbQueryVariables[":$pk"] = $pkv;
|
||||||
|
|
||||||
static::query($sql);
|
static::query($sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,38 +508,48 @@ class Model
|
|||||||
$atts = $this->getVars();
|
$atts = $this->getVars();
|
||||||
$into = [];
|
$into = [];
|
||||||
$values = [];
|
$values = [];
|
||||||
|
static::$dbQueryVariables = [];
|
||||||
|
|
||||||
foreach ($atts as $key => $value) {
|
foreach ($atts as $key => $value) {
|
||||||
if (isset($value)) {
|
$into[] = "$key";
|
||||||
$into[] = "`$key`";
|
$values[] = ":$key";
|
||||||
$values[] = ":$key";
|
static::$dbQueryVariables[":$key"] = $value;
|
||||||
static::$queryVars[":$key"] = $value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$table = static::table();
|
$table = static::table();
|
||||||
$sql = "INSERT INTO $table (" . join(', ', $into) . ") VALUES (" . join(', ', $values) . ")";
|
$sql = "INSERT INTO $table (" . join(', ', $into) . ") VALUES (" . join(', ', $values) . ")";
|
||||||
static::query($sql);
|
static::query($sql);
|
||||||
|
|
||||||
$pk = static::$primaryKey;
|
$pk = static::$dbPrimaryKey;
|
||||||
$this->$pk = $db->lastInsertId();
|
$this->$pk ??= $db->lastInsertId();
|
||||||
|
$this->markAsSaved();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the object to be saved is new or not, and based on the result,
|
* Checks if the object to be saved is new or not, and based on the result,
|
||||||
* calls `update` to update an existing row or `add` to insert a new row.
|
* calls `update` to update an existing row or `add` to insert a new row.
|
||||||
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function save(): void
|
public function save(): void
|
||||||
{
|
{
|
||||||
$pk = static::$primaryKey;
|
if ($this->dbIsSaved) {
|
||||||
if (isset($this->$pk)) {
|
|
||||||
$this->update();
|
$this->update();
|
||||||
} else {
|
} else {
|
||||||
$this->add();
|
$this->add();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the current instance as already saved
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function markAsSaved(): void
|
||||||
|
{
|
||||||
|
$this->dbIsSaved = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the current object from the database.
|
* Deletes the current object from the database.
|
||||||
* @return void
|
* @return void
|
||||||
@@ -489,11 +557,12 @@ class Model
|
|||||||
public function delete(): void
|
public function delete(): void
|
||||||
{
|
{
|
||||||
$table = static::table();
|
$table = static::table();
|
||||||
$pk = static::$primaryKey;
|
$pk = static::$dbPrimaryKey;
|
||||||
$sql = "DELETE FROM $table WHERE $pk=:$pk";
|
$sql = "DELETE FROM $table WHERE $pk=:$pk";
|
||||||
|
|
||||||
static::$queryVars[":$pk"] = $this->$pk;
|
static::$dbQueryVariables[":$pk"] = $this->$pk;
|
||||||
static::query($sql);
|
static::query($sql);
|
||||||
|
$this->dbIsSaved = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -506,7 +575,7 @@ class Model
|
|||||||
*/
|
*/
|
||||||
public static function select(string ...$columns): static
|
public static function select(string ...$columns): static
|
||||||
{
|
{
|
||||||
static::$querySelect['select'] = $columns;
|
static::$dbQuery['select'] = $columns;
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
}
|
}
|
||||||
@@ -521,7 +590,7 @@ class Model
|
|||||||
*/
|
*/
|
||||||
public static function from(...$tables): static
|
public static function from(...$tables): static
|
||||||
{
|
{
|
||||||
static::$querySelect['from'] = join(', ', $tables);
|
static::$dbQuery['from'] = join(', ', $tables);
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
}
|
}
|
||||||
@@ -591,10 +660,10 @@ class Model
|
|||||||
$value = static::bind($value);
|
$value = static::bind($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static::$querySelect['where'] == '') {
|
if (static::$dbQuery['where'] == '') {
|
||||||
static::$querySelect['where'] = "$column $operatorOrValue $value";
|
static::$dbQuery['where'] = "$column $operatorOrValue $value";
|
||||||
} else {
|
} else {
|
||||||
static::$querySelect['where'] .= " AND $column $operatorOrValue $value";
|
static::$dbQuery['where'] .= " AND $column $operatorOrValue $value";
|
||||||
}
|
}
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
@@ -633,10 +702,10 @@ class Model
|
|||||||
$value = static::bind($value);
|
$value = static::bind($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static::$querySelect['where'] == '') {
|
if (static::$dbQuery['where'] == '') {
|
||||||
static::$querySelect['where'] = "$column $operatorOrValue $value";
|
static::$dbQuery['where'] = "$column $operatorOrValue $value";
|
||||||
} else {
|
} else {
|
||||||
static::$querySelect['where'] .= " OR $column $operatorOrValue $value";
|
static::$dbQuery['where'] .= " OR $column $operatorOrValue $value";
|
||||||
}
|
}
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
@@ -663,10 +732,10 @@ class Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
$where = "$column IN (" . join(', ', $arrIn) . ")";
|
$where = "$column IN (" . join(', ', $arrIn) . ")";
|
||||||
if (static::$querySelect['where'] == '') {
|
if (static::$dbQuery['where'] == '') {
|
||||||
static::$querySelect['where'] = $where;
|
static::$dbQuery['where'] = $where;
|
||||||
} else {
|
} else {
|
||||||
static::$querySelect['where'] .= " AND $where";
|
static::$dbQuery['where'] .= " AND $where";
|
||||||
}
|
}
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
@@ -693,10 +762,10 @@ class Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
$where = "$column NOT IN (" . join(', ', $arrIn) . ")";
|
$where = "$column NOT IN (" . join(', ', $arrIn) . ")";
|
||||||
if (static::$querySelect['where'] == '') {
|
if (static::$dbQuery['where'] == '') {
|
||||||
static::$querySelect['where'] = $where;
|
static::$dbQuery['where'] = $where;
|
||||||
} else {
|
} else {
|
||||||
static::$querySelect['where'] .= " AND $where";
|
static::$dbQuery['where'] .= " AND $where";
|
||||||
}
|
}
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
@@ -787,7 +856,7 @@ class Model
|
|||||||
$operatorOrColumnB = '=';
|
$operatorOrColumnB = '=';
|
||||||
}
|
}
|
||||||
|
|
||||||
static::$querySelect['leftJoin'] .= ' LEFT JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB";
|
static::$dbQuery['leftJoin'] .= ' LEFT JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB";
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
}
|
}
|
||||||
@@ -822,10 +891,13 @@ class Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (static::db()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'sqlite') {
|
if (static::db()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'sqlite') {
|
||||||
return static::leftJoin($table, $columnB, $operatorOrColumnB, $columnA);
|
$currentTable = empty(static::$dbQuery['from']) ?
|
||||||
|
static::table() : static::$dbQuery['from'];
|
||||||
|
static::$dbQuery['from'] = $table;
|
||||||
|
return static::leftJoin($currentTable, $columnB, $operatorOrColumnB, $columnA);
|
||||||
}
|
}
|
||||||
|
|
||||||
static::$querySelect['rightJoin'] .= ' RIGHT JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB";
|
static::$dbQuery['rightJoin'] .= ' RIGHT JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB";
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
}
|
}
|
||||||
@@ -859,7 +931,7 @@ class Model
|
|||||||
$operatorOrColumnB = '=';
|
$operatorOrColumnB = '=';
|
||||||
}
|
}
|
||||||
|
|
||||||
static::$querySelect['innerJoin'] .= ' INNER JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB";
|
static::$dbQuery['innerJoin'] .= ' INNER JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB";
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
}
|
}
|
||||||
@@ -884,31 +956,22 @@ class Model
|
|||||||
*/
|
*/
|
||||||
public static function crossJoin(
|
public static function crossJoin(
|
||||||
string $table,
|
string $table,
|
||||||
string $columnA,
|
|
||||||
string $operatorOrColumnB,
|
|
||||||
?string $columnB = null
|
|
||||||
): static {
|
): static {
|
||||||
if (is_null($columnB)) {
|
static::$dbQuery['crossJoin'] .= ' CROSS JOIN ' . $table;
|
||||||
$columnB = $operatorOrColumnB;
|
|
||||||
$operatorOrColumnB = '=';
|
|
||||||
}
|
|
||||||
|
|
||||||
static::$querySelect['crossJoin'] .= ' CROSS JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB";
|
|
||||||
|
|
||||||
return new static();
|
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::$dbQuery['groupBy'] = join(', ', $columns);
|
||||||
return new static();
|
return new static();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -926,9 +989,9 @@ class Model
|
|||||||
public static function limit(int $offsetOrQuantity, ?int $quantity = null): static
|
public static function limit(int $offsetOrQuantity, ?int $quantity = null): static
|
||||||
{
|
{
|
||||||
if (is_null($quantity)) {
|
if (is_null($quantity)) {
|
||||||
static::$querySelect['limit'] = $offsetOrQuantity;
|
static::$dbQuery['limit'] = $offsetOrQuantity;
|
||||||
} else {
|
} else {
|
||||||
static::$querySelect['limit'] = $offsetOrQuantity . ', ' . $quantity;
|
static::$dbQuery['limit'] = $quantity . ' OFFSET ' . $offsetOrQuantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
@@ -949,7 +1012,9 @@ class Model
|
|||||||
public static function orderBy(string $value, string $order = 'ASC'): static
|
public static function orderBy(string $value, string $order = 'ASC'): static
|
||||||
{
|
{
|
||||||
if ($value == "RAND") {
|
if ($value == "RAND") {
|
||||||
static::$querySelect['orderBy'] = 'RAND()';
|
$random = static::db()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql' ? 'RAND()' : 'RANDOM()';
|
||||||
|
static::$dbQuery['orderBy'] = $random;
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -957,7 +1022,7 @@ class Model
|
|||||||
$order = 'ASC';
|
$order = 'ASC';
|
||||||
}
|
}
|
||||||
|
|
||||||
static::$querySelect['orderBy'] = $value . ' ' . $order;
|
static::$dbQuery['orderBy'] = $value . ' ' . $order;
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
}
|
}
|
||||||
@@ -978,25 +1043,25 @@ class Model
|
|||||||
{
|
{
|
||||||
if (!$resetQuery) {
|
if (!$resetQuery) {
|
||||||
$backup = [
|
$backup = [
|
||||||
'select' => static::$querySelect['select'],
|
'select' => static::$dbQuery['select'],
|
||||||
'limit' => static::$querySelect['limit'],
|
'limit' => static::$dbQuery['limit'],
|
||||||
'orderBy' => static::$querySelect['orderBy'],
|
'orderBy' => static::$dbQuery['orderBy'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($useLimit && static::$querySelect['limit'] != '') {
|
if ($useLimit && static::$dbQuery['limit'] != '') {
|
||||||
static::$querySelect['select'] = ['1'];
|
static::$dbQuery['select'] = ['1'];
|
||||||
static::$querySelect['orderBy'] = '';
|
static::$dbQuery['orderBy'] = '';
|
||||||
|
|
||||||
$sql = 'SELECT COUNT(1) AS quantity FROM (' . static::buildQuery() . ') AS counted';
|
$sql = 'SELECT COUNT(1) AS quantity FROM (' . static::buildQuery() . ') AS counted';
|
||||||
$queryResult = static::query($sql, $resetQuery);
|
$queryResult = static::query($sql, $resetQuery);
|
||||||
$result = $queryResult[0]['quantity'];
|
$result = $queryResult[0]['quantity'];
|
||||||
} else {
|
} else {
|
||||||
static::$querySelect['select'] = [
|
static::$dbQuery['select'] = [
|
||||||
"COUNT(" . static::table() . "." . static::$primaryKey . ") as quantity",
|
"COUNT(" . static::table() . "." . static::$dbPrimaryKey . ") as quantity",
|
||||||
];
|
];
|
||||||
static::$querySelect['limit'] = '1';
|
static::$dbQuery['limit'] = '1';
|
||||||
static::$querySelect['orderBy'] = '';
|
static::$dbQuery['orderBy'] = '';
|
||||||
|
|
||||||
$sql = static::buildQuery();
|
$sql = static::buildQuery();
|
||||||
$queryResult = static::query($sql, $resetQuery);
|
$queryResult = static::query($sql, $resetQuery);
|
||||||
@@ -1004,9 +1069,9 @@ class Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$resetQuery) {
|
if (!$resetQuery) {
|
||||||
static::$querySelect['select'] = $backup['select'];
|
static::$dbQuery['select'] = $backup['select'];
|
||||||
static::$querySelect['limit'] = $backup['limit'];
|
static::$dbQuery['limit'] = $backup['limit'];
|
||||||
static::$querySelect['orderBy'] = $backup['orderBy'];
|
static::$dbQuery['orderBy'] = $backup['orderBy'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int)$result;
|
return (int)$result;
|
||||||
@@ -1022,7 +1087,7 @@ class Model
|
|||||||
*/
|
*/
|
||||||
public static function getById(mixed $id): ?static
|
public static function getById(mixed $id): ?static
|
||||||
{
|
{
|
||||||
return static::where(static::$primaryKey, $id)->getFirst();
|
return static::where(static::$dbPrimaryKey, $id)->getFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1039,27 +1104,37 @@ class Model
|
|||||||
public static function search(string $search, ?array $in = null): static
|
public static function search(string $search, ?array $in = null): static
|
||||||
{
|
{
|
||||||
if ($in == null) {
|
if ($in == null) {
|
||||||
$className = get_called_class();
|
$in = [];
|
||||||
$in = array_keys((new $className())->getVars());
|
$reflection = new \ReflectionClass(get_called_class());
|
||||||
|
|
||||||
|
foreach ($reflection->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
|
||||||
|
if (!in_array($property->getName(), static::$dbIgnoreSave)) {
|
||||||
|
$in[] = static::camelCaseToSnakeCase($property->getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($in)) {
|
||||||
|
return new static();
|
||||||
}
|
}
|
||||||
|
|
||||||
$search = static::bind($search);
|
$search = static::bind($search);
|
||||||
$where = [];
|
$where = [];
|
||||||
|
|
||||||
if (static::db()->getAttribute(PDO::ATTR_DRIVER_NAME) == '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::$dbQuery['where'] == '') {
|
||||||
static::$querySelect['where'] = join(' OR ', $where);
|
static::$dbQuery['where'] = join(' OR ', $where);
|
||||||
} else {
|
} else {
|
||||||
static::$querySelect['where'] = static::$querySelect['where'] . ' AND (' . join(' OR ', $where) . ')';
|
static::$dbQuery['where'] = static::$dbQuery['where'] . ' AND (' . join(' OR ', $where) . ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
return new static();
|
return new static();
|
||||||
@@ -1123,22 +1198,4 @@ class Model
|
|||||||
|
|
||||||
return $instances;
|
return $instances;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows defining an attribute's value as null.
|
|
||||||
* Only works for updating an element in the DB, not for inserting.
|
|
||||||
*
|
|
||||||
* @param array $attributes
|
|
||||||
* Attribute or array of attributes that will be set to null.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setNull(string ...$attributes): void
|
|
||||||
{
|
|
||||||
foreach ($attributes as $att) {
|
|
||||||
if (!in_array($att, $this->toNull)) {
|
|
||||||
$this->toNull[] = $att;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -50,9 +50,9 @@ class Neuron
|
|||||||
* It returns null instead of triggering an E_NOTICE.
|
* It returns null instead of triggering an E_NOTICE.
|
||||||
*
|
*
|
||||||
* @param string $index The name of the property being accessed.
|
* @param string $index The name of the property being accessed.
|
||||||
* @return null Always returns null for undefined properties.
|
* @return null Always returns null for undefined properties.
|
||||||
*/
|
*/
|
||||||
public function __get(string $index): null
|
public function __get(string $index)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-17
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace Libs;
|
namespace Libs;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request - DuckBrain
|
* Request - DuckBrain
|
||||||
*
|
*
|
||||||
@@ -59,39 +61,48 @@ class Request extends Neuron
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run configured validations
|
// Run configured validations
|
||||||
if (!$this->validate()) {
|
$this->validate();
|
||||||
exit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the configured validation.
|
* Starts the configured validation.
|
||||||
*
|
*
|
||||||
* @return bool
|
* 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(): bool
|
public function validate(): void
|
||||||
{
|
{
|
||||||
$actual = match ($_SERVER['REQUEST_METHOD']) {
|
$actual = match ($_SERVER['REQUEST_METHOD']) {
|
||||||
'POST', 'PUT', 'PATCH', 'DELETE' => $this->{strtolower($_SERVER['REQUEST_METHOD'])},
|
'POST', 'PUT', 'PATCH', 'DELETE' => $this->{strtolower($_SERVER['REQUEST_METHOD'])},
|
||||||
default => $this->get
|
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 (
|
if (
|
||||||
Validator::validateList(static::paramRules(), $this->params) &&
|
Validator::validateList(static::paramRules(), $this->params) &&
|
||||||
Validator::validateList(static::getRules(), $this->get) &&
|
Validator::validateList(static::getRules(), $this->get) &&
|
||||||
Validator::validateList(static::rules(), $actual)
|
Validator::validateList(static::rules(), $body)
|
||||||
) {
|
) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset(static::messages()[Validator::$lastFailed])) {
|
$error = Validator::message(
|
||||||
$error = static::messages()[Validator::$lastFailed];
|
Validator::$lastFailed,
|
||||||
} else {
|
static::messages(),
|
||||||
$error = 'Error: validation failed of ' . preg_replace('/\./', ' as ', Validator::$lastFailed, 1);
|
static::attributes()
|
||||||
}
|
);
|
||||||
|
|
||||||
static::onInvalid($error);
|
static::onInvalid($error);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,16 +145,36 @@ class Request extends Neuron
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* 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 void
|
* @return never
|
||||||
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function onInvalid(string $error): void
|
public function onInvalid(string $error): never
|
||||||
{
|
{
|
||||||
http_response_code(422);
|
throw new Exception($error, 422);
|
||||||
print($error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+131
-35
@@ -20,6 +20,16 @@ class Router
|
|||||||
private static array $put = [];
|
private static array $put = [];
|
||||||
private static array $patch = [];
|
private static array $patch = [];
|
||||||
private static array $delete = [];
|
private static array $delete = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a temporary stack of middlewares to be applied to routes defined
|
||||||
|
* 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].
|
* 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().
|
||||||
@@ -27,18 +37,20 @@ class Router
|
|||||||
* @var array $last
|
* @var array $last
|
||||||
*/
|
*/
|
||||||
private static array $last;
|
private static array $last;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the parameters extracted from the current matching route.
|
* Stores the parameters extracted from the current matching route.
|
||||||
*
|
*
|
||||||
* @var Neuron $params
|
* @var Neuron $params
|
||||||
*/
|
*/
|
||||||
public static Neuron $params;
|
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 $notFoundCallback
|
* @var callable $notFoundCallback
|
||||||
*/
|
*/
|
||||||
public static $notFoundCallback = 'Libs\Router::defaultNotFound';
|
public static $notFoundCallback = 'Libs\Router::defaultNotFound';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default callback function for when
|
* Default callback function for when
|
||||||
@@ -52,6 +64,54 @@ class Router
|
|||||||
echo '<h2 style="text-align: center;margin: 25px 0px;">Error 404 - Page Not Found</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
|
||||||
*/
|
*/
|
||||||
@@ -124,7 +184,6 @@ class Router
|
|||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,7 +191,7 @@ class Router
|
|||||||
* Only one middleware can be added at a time.
|
* Only one middleware can be added at a time.
|
||||||
*
|
*
|
||||||
* @param callable $callback
|
* @param callable $callback
|
||||||
* @param int $priority Optional priority for the middleware execution order.
|
* @param int|null $priority Optional priority for the middleware execution order.
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
* Returns the current instance.
|
* Returns the current instance.
|
||||||
@@ -163,6 +222,28 @@ class Router
|
|||||||
return new static();
|
return new static();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Reconfigures the final callback of the last route.
|
||||||
*
|
*
|
||||||
@@ -198,7 +279,7 @@ class Router
|
|||||||
* @param callable|null $callback
|
* @param callable|null $callback
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
* Returns the current instance.
|
* 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
|
||||||
{
|
{
|
||||||
@@ -222,6 +303,11 @@ class Router
|
|||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,50 +410,60 @@ class Router
|
|||||||
/**
|
/**
|
||||||
* Applies the route configuration.
|
* Applies the route configuration.
|
||||||
*
|
*
|
||||||
|
* 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.
|
* @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']) { // Selects an array of routers based on the method
|
$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) { // Checks all routers to see if they match the current path
|
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]);
|
||||||
|
|
||||||
// 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();
|
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::$params->{$paramName} = urldecode($match[0]);
|
static::$params->{$paramName} = urldecode($match[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Processes the callback queue
|
// Processes the callback queue
|
||||||
foreach (array_reverse($router['callback']) as $callback) {
|
foreach (array_reverse($router['callback']) as $callback) {
|
||||||
$data = Synapsis::resolve($callback);
|
$data = Synapsis::resolve($callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// By default, prints as JSON if something is returned
|
// By default, prints as JSON if something is returned
|
||||||
if (isset($data)) {
|
if (isset($data)) {
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
print(json_encode($data));
|
print(json_encode($data));
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If no router matches, call $notFoundCallBack
|
// If no router matches, call $notFoundCallBack
|
||||||
Synapsis::resolve(static::$notFoundCallback);
|
Synapsis::resolve(static::$notFoundCallback);
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
Synapsis::resolve(static::$exceptionCallback, ['exception' => $exception]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-5
@@ -65,12 +65,14 @@ class Synapsis
|
|||||||
/**
|
/**
|
||||||
* Resolves and injects dependencies for a callable and returns its result.
|
* Resolves and injects dependencies for a callable and returns its result.
|
||||||
*
|
*
|
||||||
* @param callable $action
|
* @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
|
* @return mixed
|
||||||
* @throws Exception If an unhandled callable type is provided.
|
* @throws Exception If an unhandled callable type is provided.
|
||||||
*/
|
*/
|
||||||
public static function resolve(callable $action): mixed
|
public static function resolve(callable $action, array $named = []): mixed
|
||||||
{
|
{
|
||||||
if ($action instanceof Closure) { // If it's an anonymous function
|
if ($action instanceof Closure) { // If it's an anonymous function
|
||||||
$reflectionCallback = new ReflectionFunction($action);
|
$reflectionCallback = new ReflectionFunction($action);
|
||||||
@@ -90,7 +92,7 @@ class Synapsis
|
|||||||
// Get the parameters
|
// Get the parameters
|
||||||
return call_user_func_array(
|
return call_user_func_array(
|
||||||
$action,
|
$action,
|
||||||
static::resolveParameterValues($reflectionCallback->getParameters())
|
static::resolveParameterValues($reflectionCallback->getParameters(), $named)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,14 +134,27 @@ class Synapsis
|
|||||||
* Resolves parameter values by injecting dependencies.
|
* Resolves parameter values by injecting dependencies.
|
||||||
*
|
*
|
||||||
* @param array<ReflectionParameter> $parameters
|
* @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>
|
* @return array<mixed>
|
||||||
* @throws Exception If a primitive parameter does not have a default value.
|
* @throws Exception If a primitive parameter does not have a default value.
|
||||||
*/
|
*/
|
||||||
public static function resolveParameterValues(array $parameters): array
|
public static function resolveParameterValues(array $parameters, array $named = []): array
|
||||||
{
|
{
|
||||||
$values = [];
|
$values = [];
|
||||||
foreach ($parameters as $parameter) {
|
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
|
if ($parameter->isOptional()) { // Always use the default value first
|
||||||
$values[] = $parameter->getDefaultValue();
|
$values[] = $parameter->getDefaultValue();
|
||||||
continue;
|
continue;
|
||||||
@@ -148,7 +163,9 @@ class Synapsis
|
|||||||
$paramType = $parameter->getType();
|
$paramType = $parameter->getType();
|
||||||
if ($paramType === null) {
|
if ($paramType === null) {
|
||||||
// If no type is declared, and it's not optional, we cannot resolve it.
|
// If no type is declared, and it's not optional, we cannot resolve it.
|
||||||
throw new Exception('Untyped parameters expect at least a default value or must be optional.');
|
throw new Exception(
|
||||||
|
"Untyped parameter '{$parameter}' expect at least a default value or must be optional."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($paramType->isBuiltin()) {
|
if ($paramType->isBuiltin()) {
|
||||||
|
|||||||
+1075
-32
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user