1139 lines
32 KiB
PHP
1139 lines
32 KiB
PHP
<?php
|
|
|
|
namespace Libs;
|
|
|
|
use AllowDynamicProperties;
|
|
use Exception;
|
|
use PDO;
|
|
use PDOException;
|
|
use ReflectionClass;
|
|
use ReflectionProperty;
|
|
|
|
/**
|
|
* Model - DuckBrain
|
|
*
|
|
* ORM Model for database-backed objects.
|
|
* Depends on Libs\Database and uses constants
|
|
* DB_TYPE, DB_HOST, DB_NAME, DB_USER, and DB_PASS.
|
|
*
|
|
* @author KJ
|
|
* @website https://kj2.me
|
|
* @license MIT
|
|
*/
|
|
#[AllowDynamicProperties]
|
|
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.
|
|
*/
|
|
protected static string $primaryKey = 'id';
|
|
|
|
/**
|
|
* @var array Attributes to ignore when saving to the database.
|
|
*/
|
|
protected static array $ignoreSave = ['id'];
|
|
|
|
/**
|
|
* @var array Attributes that should be explicitly saved, even if private/protected.
|
|
*/
|
|
protected static array $forceSave = [];
|
|
|
|
/**
|
|
* @var string The database table name.
|
|
*/
|
|
protected static string $table;
|
|
|
|
/**
|
|
* @var array Variables for PDO prepared statements.
|
|
*/
|
|
protected static array $queryVars = [];
|
|
|
|
/**
|
|
* @var array Current SELECT query components.
|
|
*/
|
|
protected static array $querySelect = [
|
|
'select' => ['*'],
|
|
'where' => '',
|
|
'from' => '',
|
|
'leftJoin' => '',
|
|
'rightJoin' => '',
|
|
'innerJoin' => '',
|
|
'crossJoin' => '',
|
|
'orderBy' => '',
|
|
'groupBy' => '',
|
|
'limit' => '',
|
|
];
|
|
|
|
/**
|
|
* Retrieves the database instance.
|
|
*
|
|
* @return PDO
|
|
*/
|
|
protected static function db(): PDO
|
|
{
|
|
if (DB_TYPE == 'sqlite') {
|
|
return Database::getInstance(
|
|
type: DB_TYPE,
|
|
name: DB_NAME
|
|
);
|
|
} else {
|
|
return Database::getInstance(
|
|
DB_TYPE,
|
|
DB_HOST,
|
|
DB_NAME,
|
|
DB_USER,
|
|
DB_PASS
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Executes PDO::beginTransaction to start a transaction.
|
|
* More info: https://www.php.net/manual/es/pdo.begintransaction.php (Spanish, will keep for context)
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function beginTransaction(): bool
|
|
{
|
|
return static::db()->beginTransaction();
|
|
}
|
|
|
|
/**
|
|
* Executes PDO::rollBack to undo changes in a transaction.
|
|
* More info: https://www.php.net/manual/es/pdo.rollback.php (Spanish, will keep for context)
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function rollBack(): bool
|
|
{
|
|
if (static::db()->inTransaction()) {
|
|
return static::db()->rollBack();
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Executes PDO::commit to commit a transaction.
|
|
* More info: https://www.php.net/manual/es/pdo.commit.php (Spanish, will keep for context)
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function commit(): bool
|
|
{
|
|
if (static::db()->inTransaction()) {
|
|
return static::db()->commit();
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Executes an SQL statement in the database.
|
|
*
|
|
* @param string $query
|
|
* Contains the SQL statement to be executed.
|
|
*
|
|
* @throws Exception
|
|
* If the SQL statement fails, it will throw an error
|
|
* and perform a rollback if currently within a transaction
|
|
* (see beginTransaction method).
|
|
*
|
|
* @param bool $resetQuery
|
|
* Indicates whether the query should be reset (defaults to true).
|
|
*
|
|
* @return array
|
|
* Contains the result of the SQL call.
|
|
*/
|
|
protected static function query(string $query, bool $resetQuery = true): array
|
|
{
|
|
$db = static::db();
|
|
|
|
try {
|
|
$prepared = $db->prepare($query);
|
|
$prepared->execute(static::$queryVars);
|
|
} catch (PDOException $e) {
|
|
if ($db->inTransaction()) {
|
|
$db->rollBack();
|
|
}
|
|
|
|
$vars = json_encode(static::$queryVars);
|
|
|
|
throw new Exception(
|
|
"\nError at query to database.\n" .
|
|
"Query: $query\n" .
|
|
"Vars: $vars\n" .
|
|
"Error:\n" . $e->getMessage()
|
|
);
|
|
}
|
|
|
|
$result = $prepared->fetchAll();
|
|
|
|
if ($resetQuery) {
|
|
static::resetQuery();
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Resets the SQL query configuration.
|
|
* @return void
|
|
*/
|
|
protected static function resetQuery(): void
|
|
{
|
|
static::$querySelect = [
|
|
'select' => ['*'],
|
|
'where' => '',
|
|
'from' => '',
|
|
'leftJoin' => '',
|
|
'rightJoin' => '',
|
|
'innerJoin' => '',
|
|
'crossJoin' => '',
|
|
'orderBy' => '',
|
|
'groupBy' => '',
|
|
'limit' => '',
|
|
];
|
|
static::$queryVars = [];
|
|
}
|
|
|
|
/**
|
|
* Builds the SQL statement from static::$querySelect and, once
|
|
* built, calls resetQuery.
|
|
*
|
|
* @return string
|
|
* Contains the SQL statement.
|
|
*/
|
|
protected static function buildQuery(): string
|
|
{
|
|
$sql = 'SELECT ' . join(', ', static::$querySelect['select']);
|
|
|
|
if (static::$querySelect['from'] != '') {
|
|
$sql .= ' FROM ' . static::$querySelect['from'];
|
|
} else {
|
|
$sql .= ' FROM ' . static::table();
|
|
}
|
|
|
|
if (static::$querySelect['crossJoin'] != '') {
|
|
$sql .= static::$querySelect['crossJoin'];
|
|
}
|
|
|
|
if (static::$querySelect['innerJoin'] != '') {
|
|
$sql .= static::$querySelect['innerJoin'];
|
|
}
|
|
|
|
if (static::$querySelect['leftJoin'] != '') {
|
|
$sql .= static::$querySelect['leftJoin'];
|
|
}
|
|
|
|
if (static::$querySelect['rightJoin'] != '') {
|
|
$sql .= static::$querySelect['rightJoin'];
|
|
}
|
|
|
|
if (static::$querySelect['where'] != '') {
|
|
$sql .= ' WHERE ' . static::$querySelect['where'];
|
|
}
|
|
|
|
if (static::$querySelect['groupBy'] != '') {
|
|
$sql .= ' GROUP BY ' . static::$querySelect['groupBy'];
|
|
}
|
|
|
|
if (static::$querySelect['orderBy'] != '') {
|
|
$sql .= ' ORDER BY ' . static::$querySelect['orderBy'];
|
|
}
|
|
|
|
if (static::$querySelect['limit'] != '') {
|
|
$sql .= ' LIMIT ' . static::$querySelect['limit'];
|
|
}
|
|
|
|
return $sql;
|
|
}
|
|
|
|
|
|
/**
|
|
* Configures $queryVars to bind a value to a
|
|
* substitution parameter and returns the latter.
|
|
*
|
|
* @param string $value
|
|
* Value to bind.
|
|
*
|
|
* @return string
|
|
* Substitution parameter.
|
|
*/
|
|
public static function bind(string $value): string
|
|
{
|
|
$index = ':v_' . count(static::$queryVars);
|
|
static::$queryVars[$index] = $value;
|
|
return $index;
|
|
}
|
|
|
|
/**
|
|
* Creates an instance of the current object from an array.
|
|
*
|
|
* @param mixed $elem
|
|
* Can receive an array or an object containing the values
|
|
* that its attributes will have.
|
|
*
|
|
* @return static
|
|
* Returns an object of the current class.
|
|
*/
|
|
protected static function getInstance(array $elem = []): static
|
|
{
|
|
$class = get_called_class();
|
|
$instance = new $class();
|
|
$reflection = new ReflectionClass($instance);
|
|
$properties = $reflection->getProperties();
|
|
$propertyNames = array_map(function ($property) {
|
|
return static::camelCaseToSnakeCase($property->name);
|
|
}, $properties);
|
|
|
|
foreach ($elem as $key => $value) {
|
|
$index = array_search($key, $propertyNames);
|
|
if (is_numeric($index)) {
|
|
if (enum_exists($properties[$index]->getType()->getName())) {
|
|
$instance->{$properties[$index]->name} = $properties[$index]->getType()->getName()::tryfrom($value);
|
|
} else {
|
|
$instance->{$properties[$index]->name} = $value;
|
|
}
|
|
} else {
|
|
$instance->{static::snakeCaseToCamelCase($key)} = $value;
|
|
}
|
|
}
|
|
|
|
return $instance;
|
|
}
|
|
|
|
/**
|
|
* Returns the attributes to be saved for the current class.
|
|
* Attributes will be those that are public and not excluded in static::$ignoreSave,
|
|
* and those that are private or protected but are in static::$forceSave.
|
|
*
|
|
* @return array
|
|
* Contains the indexed attributes of the current object.
|
|
*/
|
|
protected function getVars(): array
|
|
{
|
|
$reflection = new ReflectionClass($this);
|
|
$properties = $reflection->getProperties(ReflectionProperty::IS_PUBLIC);
|
|
$result = [];
|
|
|
|
foreach ($properties as $property) {
|
|
if (!in_array($property->name, static::$ignoreSave)) {
|
|
$result[$this->camelCaseToSnakeCase($property->name)] = isset($this->{$property->name})
|
|
? $this->{$property->name} : null;
|
|
}
|
|
}
|
|
|
|
foreach (static::$forceSave as $value) {
|
|
$result[$value] = isset($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;
|
|
}
|
|
|
|
/**
|
|
* Returns the name of the current class, even if it's an extended class.
|
|
*
|
|
* @return string
|
|
*
|
|
*/
|
|
public static function className(): string
|
|
{
|
|
return substr(
|
|
strrchr(get_called_class(), '\\'),
|
|
1
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Constructs (from the class name and the suffix in static::$tableSufix)
|
|
* and/or returns the name of the DB table where the current object will be
|
|
* or is stored.
|
|
*
|
|
* @return string
|
|
*/
|
|
protected static function table(): string
|
|
{
|
|
if (isset(static::$table)) {
|
|
return static::$table;
|
|
}
|
|
|
|
return static::camelCaseToSnakeCase(static::className()) . 's';
|
|
}
|
|
|
|
/**
|
|
* Converts from lowerCamelCase to snake_case.
|
|
*
|
|
* @param string $string
|
|
*
|
|
* @return string
|
|
*/
|
|
protected static function camelCaseToSnakeCase(string $string): string
|
|
{
|
|
return strtolower(
|
|
preg_replace(
|
|
'/(?<!^)[A-Z]/',
|
|
'_$0',
|
|
$string
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Converts from snake_case to lowerCamelCase.
|
|
*
|
|
* @param string $string
|
|
*
|
|
* @return string
|
|
*/
|
|
protected static function snakeCaseToCamelCase(string $string): string
|
|
{
|
|
return preg_replace_callback('/_([a-z])/', function ($matches) {
|
|
return strtoupper($matches[1]);
|
|
}, $string);
|
|
}
|
|
|
|
/**
|
|
* Updates the values in the database with the current object's values.
|
|
* @return void
|
|
*/
|
|
protected function update(): void
|
|
{
|
|
$atts = $this->getVars();
|
|
|
|
foreach ($atts as $key => $value) {
|
|
if (isset($value)) {
|
|
if (in_array($key, $this->toNull)) {
|
|
$set[] = "$key=NULL";
|
|
} else {
|
|
$set[] = "$key=:$key";
|
|
static::$queryVars[':' . $key] = $value;
|
|
}
|
|
} else {
|
|
if (in_array($key, $this->toNull)) {
|
|
$set[] = "$key=NULL";
|
|
}
|
|
}
|
|
}
|
|
|
|
$table = static::table();
|
|
$pk = static::$primaryKey;
|
|
$pkv = $this->$pk;
|
|
$sql = "UPDATE $table SET " . join(', ', $set) . " WHERE $pk='$pkv'";
|
|
static::query($sql);
|
|
}
|
|
|
|
/**
|
|
* Inserts a new row into the database from the
|
|
* current object.
|
|
* @return void
|
|
*/
|
|
protected function add(): void
|
|
{
|
|
$db = static::db();
|
|
$atts = $this->getVars();
|
|
$into = [];
|
|
$values = [];
|
|
|
|
foreach ($atts as $key => $value) {
|
|
if (isset($value)) {
|
|
$into[] = "$key";
|
|
$values[] = ":$key";
|
|
static::$queryVars[":$key"] = $value;
|
|
}
|
|
}
|
|
|
|
$table = static::table();
|
|
$sql = "INSERT INTO $table (" . join(', ', $into) . ") VALUES (" . join(', ', $values) . ")";
|
|
static::query($sql);
|
|
|
|
$pk = static::$primaryKey;
|
|
$this->$pk = $db->lastInsertId();
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @return void
|
|
*/
|
|
public function save(): void
|
|
{
|
|
$pk = static::$primaryKey;
|
|
if (isset($this->$pk)) {
|
|
$this->update();
|
|
} else {
|
|
$this->add();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes the current object from the database.
|
|
* @return void
|
|
*/
|
|
public function delete(): void
|
|
{
|
|
$table = static::table();
|
|
$pk = static::$primaryKey;
|
|
$sql = "DELETE FROM $table WHERE $pk=:$pk";
|
|
|
|
static::$queryVars[":$pk"] = $this->$pk;
|
|
static::query($sql);
|
|
}
|
|
|
|
/**
|
|
* Defines SELECT in the SQL statement.
|
|
*
|
|
* @param array<string> $columns
|
|
* Columns to be selected in the SQL query.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function select(string ...$columns): static
|
|
{
|
|
static::$querySelect['select'] = $columns;
|
|
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines FROM in the SQL statement.
|
|
*
|
|
* @param array $tables
|
|
* Tables to be selected in the SQL query.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function from(...$tables): static
|
|
{
|
|
static::$querySelect['from'] = join(', ', $tables);
|
|
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines the WHERE clause in the SQL statement.
|
|
*
|
|
* @param string $column
|
|
* The column to compare.
|
|
*
|
|
* @param string $operatorOrValue
|
|
* The operator or the value to compare as equal if $value is not defined.
|
|
*
|
|
* @param string|null $value
|
|
* (Optional) The value to compare against the column.
|
|
*
|
|
* @param bool $no_filter
|
|
* (Optional) Used when $value is a column or a value that does not require
|
|
* filtering against SQL injection attacks (defaults to false).
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function where(
|
|
string $column,
|
|
string $operatorOrValue,
|
|
?string $value = null,
|
|
bool $no_filter = false
|
|
): static {
|
|
return static::and(
|
|
$column,
|
|
$operatorOrValue,
|
|
$value,
|
|
$no_filter
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Defines AND in the SQL statement (can be nested).
|
|
*
|
|
* @param string $column
|
|
* The column to compare.
|
|
*
|
|
* @param string $operatorOrValue
|
|
* The operator or the value to compare as equal if $value is not defined.
|
|
*
|
|
* @param string|null $value
|
|
* (Optional) The value to compare against the column.
|
|
*
|
|
* @param bool $no_filter
|
|
* (Optional) Used when $value is a column or a value that does not require
|
|
* filtering against SQL injection attacks (defaults to false).
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function and(
|
|
string $column,
|
|
string $operatorOrValue,
|
|
?string $value = null,
|
|
bool $no_filter = false
|
|
): static {
|
|
if (is_null($value)) {
|
|
$value = $operatorOrValue;
|
|
$operatorOrValue = '=';
|
|
}
|
|
|
|
if (!$no_filter) {
|
|
$value = static::bind($value);
|
|
}
|
|
|
|
if (static::$querySelect['where'] == '') {
|
|
static::$querySelect['where'] = "$column $operatorOrValue $value";
|
|
} else {
|
|
static::$querySelect['where'] .= " AND $column $operatorOrValue $value";
|
|
}
|
|
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines OR in the SQL statement (can be nested).
|
|
*
|
|
* @param string $column
|
|
* The column to compare.
|
|
*
|
|
* @param string $operatorOrValue
|
|
* The operator or the value to compare as equal if $value is not defined.
|
|
*
|
|
* @param string|null $value
|
|
* (Optional) The value to compare against the column.
|
|
*
|
|
* @param bool $no_filter
|
|
* (Optional) Used when $value is a column or a value that does not require
|
|
* filtering against SQL injection attacks (defaults to false).
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function or(
|
|
string $column,
|
|
string $operatorOrValue,
|
|
?string $value = null,
|
|
bool $no_filter = false
|
|
): static {
|
|
if (is_null($value)) {
|
|
$value = $operatorOrValue;
|
|
$operatorOrValue = '=';
|
|
}
|
|
|
|
if (!$no_filter) {
|
|
$value = static::bind($value);
|
|
}
|
|
|
|
if (static::$querySelect['where'] == '') {
|
|
static::$querySelect['where'] = "$column $operatorOrValue $value";
|
|
} else {
|
|
static::$querySelect['where'] .= " OR $column $operatorOrValue $value";
|
|
}
|
|
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines WHERE using IN in the SQL statement.
|
|
*
|
|
* @param string $column
|
|
* The column to compare.
|
|
*
|
|
* @param array $arr
|
|
* Array with all values to compare against the column.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function whereIn(
|
|
string $column,
|
|
array $arr,
|
|
): static {
|
|
$arrIn = [];
|
|
foreach ($arr as $value) {
|
|
$arrIn[] = static::bind($value);
|
|
}
|
|
|
|
$where = "$column IN (" . join(', ', $arrIn) . ")";
|
|
if (static::$querySelect['where'] == '') {
|
|
static::$querySelect['where'] = $where;
|
|
} else {
|
|
static::$querySelect['where'] .= " AND $where";
|
|
}
|
|
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines WHERE using NOT IN in the SQL statement.
|
|
*
|
|
* @param string $column
|
|
* The column to compare.
|
|
*
|
|
* @param array $arr
|
|
* Array with all values to compare against the column.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function whereNotIn(
|
|
string $column,
|
|
array $arr,
|
|
): static {
|
|
$arrIn = [];
|
|
foreach ($arr as $value) {
|
|
$arrIn[] = static::bind($value);
|
|
}
|
|
|
|
$where = "$column NOT IN (" . join(', ', $arrIn) . ")";
|
|
if (static::$querySelect['where'] == '') {
|
|
static::$querySelect['where'] = $where;
|
|
} else {
|
|
static::$querySelect['where'] .= " AND $where";
|
|
}
|
|
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines WHERE using IS NULL in the SQL statement.
|
|
*
|
|
* @param string $column
|
|
* The column to compare.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function whereNull(string $column): static
|
|
{
|
|
static::where($column, 'IS', 'NULL', true);
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines WHERE using IS NOT NULL in the SQL statement.
|
|
*
|
|
* @param string $column
|
|
* The column to compare.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function whereNotNull(string $column): static
|
|
{
|
|
static::where($column, 'IS NOT', 'NULL', true);
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines WHERE using EXISTS in the SQL statement.
|
|
*
|
|
* @param string $query
|
|
* SQL query.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function whereExists(string $query): static
|
|
{
|
|
static::where('', 'EXISTS', "($query)", true);
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines WHERE using NOT EXISTS in the SQL statement.
|
|
*
|
|
* @param string $query
|
|
* SQL query.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function whereNotExists(string $query): static
|
|
{
|
|
static::where('', 'NOT EXISTS', "($query)", true);
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines LEFT JOIN in the SQL statement.
|
|
*
|
|
* @param string $table
|
|
* Table to join with the current object's table.
|
|
*
|
|
* @param string $columnA
|
|
* Column to compare for the join.
|
|
*
|
|
* @param string $operatorOrColumnB
|
|
* Operator or column to compare as equal for the join
|
|
* if $columnB is not defined.
|
|
*
|
|
* @param string|null $columnB
|
|
* (Optional) Column to compare for the join.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function leftJoin(
|
|
string $table,
|
|
string $columnA,
|
|
string $operatorOrColumnB,
|
|
?string $columnB = null
|
|
): static {
|
|
if (is_null($columnB)) {
|
|
$columnB = $operatorOrColumnB;
|
|
$operatorOrColumnB = '=';
|
|
}
|
|
|
|
static::$querySelect['leftJoin'] .= ' LEFT JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB";
|
|
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines RIGHT JOIN in the SQL statement.
|
|
*
|
|
* @param string $table
|
|
* Table to join with the current object's table.
|
|
*
|
|
* @param string $columnA
|
|
* Column to compare for the join.
|
|
*
|
|
* @param string $operatorOrColumnB
|
|
* Operator or column to compare as equal for the join
|
|
* if $columnB is not defined.
|
|
*
|
|
* @param string|null $columnB
|
|
* (Optional) Column to compare for the join.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function rightJoin(
|
|
string $table,
|
|
string $columnA,
|
|
string $operatorOrColumnB,
|
|
?string $columnB = null
|
|
): static {
|
|
if (is_null($columnB)) {
|
|
$columnB = $operatorOrColumnB;
|
|
$operatorOrColumnB = '=';
|
|
}
|
|
|
|
if (static::db()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'sqlite') {
|
|
$currentTable = empty(static::$querySelect['from']) ?
|
|
static::table() : static::$querySelect['from'];
|
|
static::$querySelect['from'] = $table;
|
|
return static::leftJoin($currentTable, $columnB, $operatorOrColumnB, $columnA);
|
|
}
|
|
|
|
static::$querySelect['rightJoin'] .= ' RIGHT JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB";
|
|
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines INNER JOIN in the SQL statement.
|
|
*
|
|
* @param string $table
|
|
* Table to join with the current object's table.
|
|
*
|
|
* @param string $columnA
|
|
* Column to compare for the join.
|
|
*
|
|
* @param string $operatorOrColumnB
|
|
* Operator or column to compare as equal for the join
|
|
* if $columnB is not defined.
|
|
*
|
|
* @param string|null $columnB
|
|
* (Optional) Column to compare for the join.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function innerJoin(
|
|
string $table,
|
|
string $columnA,
|
|
string $operatorOrColumnB,
|
|
?string $columnB = null
|
|
): static {
|
|
if (is_null($columnB)) {
|
|
$columnB = $operatorOrColumnB;
|
|
$operatorOrColumnB = '=';
|
|
}
|
|
|
|
static::$querySelect['innerJoin'] .= ' INNER JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB";
|
|
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines CROSS JOIN in the SQL statement.
|
|
*
|
|
* @param string $table
|
|
* Table to join with the current object's table.
|
|
*
|
|
* @param string $columnA
|
|
* Column to compare for the join.
|
|
*
|
|
* @param string $operatorOrColumnB
|
|
* Operator or column to compare as equal for the join
|
|
* if $columnB is not defined.
|
|
*
|
|
* @param string|null $columnB
|
|
* (Optional) Column to compare for the join.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function crossJoin(
|
|
string $table,
|
|
): static {
|
|
static::$querySelect['crossJoin'] .= ' CROSS JOIN ' . $table;
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines GROUP BY in the SQL statement.
|
|
*
|
|
* @param array $columns
|
|
* Columns to group by.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function groupBy(string ...$columns): static
|
|
{
|
|
static::$querySelect['groupBy'] = join(', ', $columns);
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines LIMIT in the SQL statement.
|
|
*
|
|
* @param int $offsetOrQuantity
|
|
* Defines the rows to skip or the quantity to take
|
|
* if $quantity is not defined.
|
|
* @param int|null $quantity
|
|
* (Optional) Defines the maximum number of rows to take.
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function limit(int $offsetOrQuantity, ?int $quantity = null): static
|
|
{
|
|
if (is_null($quantity)) {
|
|
static::$querySelect['limit'] = $offsetOrQuantity;
|
|
} else {
|
|
static::$querySelect['limit'] = $quantity . ' OFFSET ' . $offsetOrQuantity;
|
|
}
|
|
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Defines ORDER BY in the SQL statement.
|
|
*
|
|
* @param string $value
|
|
* Column to order by.
|
|
*
|
|
* @param string $order
|
|
* (Optional) Defines whether the order will be ascending (ASC),
|
|
* descending (DESC), or random (RAND).
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function orderBy(string $value, string $order = 'ASC'): static
|
|
{
|
|
if ($value == "RAND") {
|
|
static::$querySelect['orderBy'] = 'RAND()';
|
|
return new static();
|
|
}
|
|
|
|
if (!(strtoupper($order) == 'ASC' || strtoupper($order) == 'DESC')) {
|
|
$order = 'ASC';
|
|
}
|
|
|
|
static::$querySelect['orderBy'] = $value . ' ' . $order;
|
|
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Returns the number of rows in a query.
|
|
*
|
|
* @param bool $resetQuery
|
|
* (Optional) Indicates whether the query should be reset (defaults to true).
|
|
*
|
|
* @param bool $useLimit
|
|
* (Optional) Allows using limit to establish an initial and final maximum for counting.
|
|
* Requires the limit to have been defined beforehand (defaults to false).
|
|
*
|
|
* @return int
|
|
*/
|
|
public static function count(bool $resetQuery = true, bool $useLimit = false): int
|
|
{
|
|
if (!$resetQuery) {
|
|
$backup = [
|
|
'select' => static::$querySelect['select'],
|
|
'limit' => static::$querySelect['limit'],
|
|
'orderBy' => static::$querySelect['orderBy'],
|
|
];
|
|
}
|
|
|
|
if ($useLimit && static::$querySelect['limit'] != '') {
|
|
static::$querySelect['select'] = ['1'];
|
|
static::$querySelect['orderBy'] = '';
|
|
|
|
$sql = 'SELECT COUNT(1) AS quantity FROM (' . static::buildQuery() . ') AS counted';
|
|
$queryResult = static::query($sql, $resetQuery);
|
|
$result = $queryResult[0]['quantity'];
|
|
} else {
|
|
static::$querySelect['select'] = [
|
|
"COUNT(" . static::table() . "." . static::$primaryKey . ") as quantity",
|
|
];
|
|
static::$querySelect['limit'] = '1';
|
|
static::$querySelect['orderBy'] = '';
|
|
|
|
$sql = static::buildQuery();
|
|
$queryResult = static::query($sql, $resetQuery);
|
|
$result = $queryResult[0]['quantity'];
|
|
}
|
|
|
|
if (!$resetQuery) {
|
|
static::$querySelect['select'] = $backup['select'];
|
|
static::$querySelect['limit'] = $backup['limit'];
|
|
static::$querySelect['orderBy'] = $backup['orderBy'];
|
|
}
|
|
|
|
return (int)$result;
|
|
}
|
|
|
|
/**
|
|
* Retrieves an instance by its primary key (usually 'id').
|
|
* If no instance is found, returns null.
|
|
*
|
|
* @param mixed $id
|
|
*
|
|
* @return static|null
|
|
*/
|
|
public static function getById(mixed $id): ?static
|
|
{
|
|
return static::where(static::$primaryKey, $id)->getFirst();
|
|
}
|
|
|
|
/**
|
|
* Performs a search in the current instance's table.
|
|
*
|
|
* @param string $search
|
|
* Content to search for.
|
|
*
|
|
* @param array|null $in
|
|
* (Optional) Columns to search within (null to search all).
|
|
*
|
|
* @return static
|
|
*/
|
|
public static function search(string $search, ?array $in = null): static
|
|
{
|
|
if ($in == null) {
|
|
$className = get_called_class();
|
|
$in = array_keys((new $className())->getVars());
|
|
}
|
|
|
|
$search = static::bind($search);
|
|
$where = [];
|
|
|
|
if (static::db()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'sqlite') {
|
|
foreach ($in as $row) {
|
|
$where[] = "$row LIKE '%' || $search || '%'";
|
|
}
|
|
} else {
|
|
foreach ($in as $row) {
|
|
$where[] = "$row LIKE CONCAT('%', $search, '%')";
|
|
}
|
|
}
|
|
|
|
if (static::$querySelect['where'] == '') {
|
|
static::$querySelect['where'] = join(' OR ', $where);
|
|
} else {
|
|
static::$querySelect['where'] = static::$querySelect['where'] . ' AND (' . join(' OR ', $where) . ')';
|
|
}
|
|
|
|
return new static();
|
|
}
|
|
|
|
/**
|
|
* Retrieves the results of the SQL query.
|
|
*
|
|
* @param bool $resetQuery
|
|
* (Optional) Indicates whether the query should be reset (defaults to true).
|
|
*
|
|
* @return array<static>
|
|
* Array with instances of the current class resulting from the query.
|
|
*/
|
|
public static function get(bool $resetQuery = true): array
|
|
{
|
|
$sql = static::buildQuery();
|
|
$result = static::query($sql, $resetQuery);
|
|
|
|
$instances = [];
|
|
|
|
foreach ($result as $row) {
|
|
$instances[] = static::getInstance($row);
|
|
}
|
|
|
|
return $instances;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the first element from the SQL query result.
|
|
*
|
|
* @param bool $resetQuery
|
|
* (Optional) Indicates whether the query should be reset (defaults to true).
|
|
*
|
|
* @return static|null
|
|
* Can return an instance of the current class or null.
|
|
*/
|
|
public static function getFirst(bool $resetQuery = true): ?static
|
|
{
|
|
static::limit(1);
|
|
$instances = static::get($resetQuery);
|
|
return empty($instances) ? null : $instances[0];
|
|
}
|
|
|
|
/**
|
|
* Retrieves all elements from the current instance's table.
|
|
*
|
|
* @return array<static>
|
|
* Contains an array of instances of the current class.
|
|
*/
|
|
public static function all(): array
|
|
{
|
|
$sql = 'SELECT * FROM ' . static::table();
|
|
$result = static::query($sql);
|
|
|
|
$instances = [];
|
|
|
|
foreach ($result as $row) {
|
|
$instances[] = static::getInstance($row);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|