rework to change from MySQLi to PDO.

This commit is contained in:
kj 2022-07-14 21:41:48 -04:00
parent afb4c914a1
commit 0db6e4a021
4 changed files with 277 additions and 195 deletions

View File

@ -1,4 +1,4 @@
# DuckBrain - PHP Microframework * DuckBrain - PHP Microframework
Este microframework PHP tiene el objetivo de presentar un framework sencillo y potente que sea válido especialmente para proyectos pequeños o simples, sin limitar demasiado, ni depender de cosas que agranden innecesariamente proyectos. Este microframework PHP tiene el objetivo de presentar un framework sencillo y potente que sea válido especialmente para proyectos pequeños o simples, sin limitar demasiado, ni depender de cosas que agranden innecesariamente proyectos.
@ -12,10 +12,10 @@ Lo ideal sería mantener el código sencillo, lo suficiente como para que cualqu
El código no es perfecto, pero lo iré perfeccionando, ya que es algo que estoy usando actualmente para mis trabajos, de modo que puedo ir viendo que cosas se pueden ir mejorando. El código no es perfecto, pero lo iré perfeccionando, ya que es algo que estoy usando actualmente para mis trabajos, de modo que puedo ir viendo que cosas se pueden ir mejorando.
## Uso / Documentación * Uso / Documentación
Queda pendiente, si quieres usarlo ya mismo, puedes leer los comeentarios que he colocado en el código. Queda pendiente, si quieres usarlo ya mismo, puedes leer los comeentarios que he colocado en el código.
## Contacto * Contacto
Puedes encontrame en telegram como [@keyjay](https://telegram.me/keyjay) o contactarme mi correo: webmaster@outcontrol.net Puedes encontrame en telegram como [[https://telegram.me/keyjay][@keyjay]] o contactarme mi correo: webmaster@outcontrol.net

93
rework.org Normal file
View File

@ -0,0 +1,93 @@
* Sobre la actualización
Previamente, el sistema usaba [[https://www.php.net/mysqli][MySQLi]] para el Modelo/ORM, como actualización, se ha cambiado para funcionar con [[https://www.php.net/pdo][PDO]].
* Migración
Estos son detalles importantes a tomar en cuenta a la hora de migrar del anterior ModelMySQL (MySQLi) al actual Model (PDO).
+ El modelo base ha cambiado de nombre de =ModelMySQL= a =Model=.
+ Se han depurado los métodos =sql_calc_found_rows= y =found_rows=.
+ Se ha añadido la necesitad de la constante =DB_TYPE= para indicar el driver PDO a usar (ej. mysql, sqlite).
* Pruebas
En la siguiente tabla se encuentra la lista de estados de los SGBD que he probado (MySQL/MariaDB) o que planeo probar (sqlite3, postgreSQL) para asegurarme de que sean realmente compatibles.
*Entiéndase*:
+ *ok* como que ha sido probado y funciona.
+ En blanco como que no ha sido probado aún.
+ *error* como que fue probado, no funciona y no ha sido aún arreglado.
+ *not supported* como no soportado por el SGBD.
+ *fixed* para aquello que no existe, pero la librería lo traduce a un equivalente.
|------------------+---------------+---------+------------|
| method | MySQL/MariaDB | sqlite3 | postgreSQL |
|------------------+---------------+---------+------------|
| db | ok | | |
|------------------+---------------+---------+------------|
| query | ok | | |
|------------------+---------------+---------+------------|
| resetQuery | ok | | |
|------------------+---------------+---------+------------|
| buildQuery | ok | | |
|------------------+---------------+---------+------------|
| getInstance | ok | | |
|------------------+---------------+---------+------------|
| getVars | ok | | |
|------------------+---------------+---------+------------|
| className | ok | | |
|------------------+---------------+---------+------------|
| table | ok | | |
|------------------+---------------+---------+------------|
| update | ok | | |
|------------------+---------------+---------+------------|
| beginTransaction | ok | | |
|------------------+---------------+---------+------------|
| rollBack | ok | | |
|------------------+---------------+---------+------------|
| commit | ok | | |
|------------------+---------------+---------+------------|
| add | ok | | |
|------------------+---------------+---------+------------|
| save | ok | | |
|------------------+---------------+---------+------------|
| delete | ok | | |
|------------------+---------------+---------+------------|
| select | ok | | |
|------------------+---------------+---------+------------|
| from | ok | | |
|------------------+---------------+---------+------------|
| where | ok | | |
|------------------+---------------+---------+------------|
| where_in | ok | | |
|------------------+---------------+---------+------------|
| leftJoin | ok | | |
|------------------+---------------+---------+------------|
| rightJoin | ok | | |
|------------------+---------------+---------+------------|
| innerJoin | ok | | |
|------------------+---------------+---------+------------|
| and | ok | | |
|------------------+---------------+---------+------------|
| or | ok | | |
|------------------+---------------+---------+------------|
| groupBy | ok | | |
|------------------+---------------+---------+------------|
| limit | ok | | |
|------------------+---------------+---------+------------|
| orderBy | ok | | |
|------------------+---------------+---------+------------|
| count | ok | | |
|------------------+---------------+---------+------------|
| getById | ok | | |
|------------------+---------------+---------+------------|
| search | ok | | |
|------------------+---------------+---------+------------|
| get | ok | | |
|------------------+---------------+---------+------------|
| getFirst | ok | | |
|------------------+---------------+---------+------------|
| all | ok | | |
|------------------+---------------+---------+------------|
| setNull | ok | | |
|------------------+---------------+---------+------------|

View File

@ -2,9 +2,9 @@
/** /**
* Database - DuckBrain * Database - DuckBrain
* *
* Clase diseñada para crear y devolver una única instancia mysqli (database). * Clase diseñada para crear y devolver una única instancia PDO (database).
* Depende de manera forzada de que estén definidas las constantes: * Depende de manera forzada de que estén definidas las constantes:
* dbhost, dbname, dbpass y dbuser * DB_TYPE, DB_NAME, DB_HOST, DB_USER. DB_PASS
* *
* @author KJ * @author KJ
* @website https://kj2.me * @website https://kj2.me
@ -12,25 +12,33 @@
*/ */
namespace Libs; namespace Libs;
use mysqli;
class Database extends \mysqli { use PDO;
static private $db; use PDOException;
use Exception;
class Database extends PDO {
static private ?PDO $db = null;
private function __construct() {} private function __construct() {}
/** /**
* Devuelve una instancia homogénea (singlenton) a la base de datos. * Devuelve una instancia homogénea (singlenton) de la base de datos (PDO).
* *
* @return mysqli * @return PDO
*/ */
static public function getConnection() : mysqli { static public function getInstance() : PDO {
if (!isset(self::$db)) { if (is_null(self::$db)) {
self::$db = new mysqli(dbhost, dbuser, dbpass, dbname); $dsn = DB_TYPE.':dbname='.DB_NAME.';host='.DB_HOST;
if (self::$db->connect_errno) { try {
echo '<style>body{white-space: pre-line;}</style>'; self::$db = new PDO($dsn, DB_USER, DB_PASS);
throw new \Exception('No se ha podido conectar a la base de datos.'); } catch (PDOException $e) {
echo "<pre>";
throw new Exception(
'Error at connect to database: ' . $e->getMessage()
);
} }
self::$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} }
return self::$db; return self::$db;
} }

View File

@ -1,9 +1,9 @@
<?php <?php
/** /**
* ModelMysql - DuckBrain * Model - DuckBrain
* *
* Modelo ORM para objetos que hagan uso de una base de datos MySQL. * Modelo ORM para objetos que hagan uso de una base de datos.
* Depende de que exista Libs\Database para poder funcionar. * Depende de Libs\Database.
* *
* @author KJ * @author KJ
* @website https://kj2.me * @website https://kj2.me
@ -13,9 +13,11 @@
namespace Libs; namespace Libs;
use Libs\Database; use Libs\Database;
use mysqli; use PDO;
use PDOException;
use Exception;
class ModelMySQL { class Model {
public ?int $id = null; public ?int $id = null;
protected array $toNull = []; protected array $toNull = [];
@ -24,7 +26,8 @@ class ModelMySQL {
static protected array $forceSave = []; static protected array $forceSave = [];
static protected string $table; static protected string $table;
static protected string $tableSufix = 's'; static protected string $tableSufix = 's';
static protected ?mysqli $db = null; static protected ?PDO $db = null;
static protected array $queryVars = [];
static protected array $querySelect = [ static protected array $querySelect = [
'select' => ['*'], 'select' => ['*'],
'where' => '', 'where' => '',
@ -35,48 +38,93 @@ class ModelMySQL {
'AndOr' => '', 'AndOr' => '',
'orderBy' => '', 'orderBy' => '',
'groupBy' => '', 'groupBy' => '',
'limit' => '', 'limit' => ''
'sql_calc_found_rows' => false
]; ];
/** /**
* Sirve para obtener la instancia de la base de datos. * Sirve para obtener la instancia de la base de datos.
* *
* @return mysqli * @return Database
*/ */
protected static function db() : mysqli { protected static function db() : PDO {
if (is_null(static::$db)) if (is_null(static::$db))
static::$db = Database::getConnection(); static::$db = Database::getInstance();
return static::$db; return static::$db;
} }
/**
* Ejecuta PDO::beginTransaction para iniciar una transacción.
* Más info: https://www.php.net/manual/es/pdo.begintransaction.php
*
* @return bool
*/
public function beginTransaction() : bool {
return static::db()->beginTransaction();
}
/**
* Ejecuta PDO::rollBack para deshacher los cambios de una transacción.
* Más info: https://www.php.net/manual/es/pdo.rollback.php
*
* @return bool
*/
public function rollBack() : bool {
return static::db()->rollBack();
}
/**
* Ejecuta PDO::commit para consignar una transacción.
* Más info: https://www.php.net/manual/es/pdo.commit.php
*
* @return bool
*/
public function commit() : bool {
return static::db()->commit();
}
/** /**
* Ejecuta una sentencia SQL en la base de datos. * Ejecuta una sentencia SQL en la base de datos.
* *
* @param string $query * @param string $query
* Contiene la sentencia SQL que se desea ejecutar. * Contiene la sentencia SQL que se desea ejecutar.
* *
* @throws \Exception * @throws Exception
* En caso de que la sentencia SQL falle, devolverá un error en pantalla. * En caso de que la sentencia SQL falle, devolverá un error en pantalla
* y hará rolllback en caso de estar dentro de una transacción.
* *
* @return mixed * @param bool $resetQuery
* Contiene el resultado de la llamada SQL (mysqli_result o bool). * Indica si el query debe reiniciarse o no (por defecto es true).
*
* @return array
* Contiene el resultado de la llamada SQL .
*/ */
protected static function query($query) { protected static function query(string $query, bool $resetQuery = true) : array {
$db = static::db(); $db = static::db();
$result = $db->query($query); try {
if ($db->errno) { $prepared = $db->prepare($query);
echo '<style>body{white-space: pre-line;}</style>'; $prepared->execute(static::$queryVars);
throw new \Exception( } catch (PDOException $e) {
"\nFallo al consultar la base de datos\n" . if ($db->inTransaction())
"Errno: $db->errno\n" . $db->rollBack();
"Error: $db->error\n" .
"Query: $query\n" $vars = json_encode(static::$queryVars);
echo "<pre>";
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; return $result;
} }
@ -94,9 +142,9 @@ class ModelMySQL {
'AndOr' => '', 'AndOr' => '',
'orderBy' => '', 'orderBy' => '',
'groupBy' => '', 'groupBy' => '',
'limit' => '', 'limit' => ''
'sql_calc_found_rows' => false
]; ];
static::$queryVars = [];
} }
/** /**
@ -110,10 +158,7 @@ class ModelMySQL {
* Contiene la sentencia SQL. * Contiene la sentencia SQL.
*/ */
protected static function buildQuery(bool $resetQuery = true) : string { protected static function buildQuery(bool $resetQuery = true) : string {
if (static::$querySelect['sql_calc_found_rows']) $sql = 'SELECT '.join(', ', static::$querySelect['select']);
$sql = 'SELECT SQL_CALC_FOUND_ROWS '.join(', ', static::$querySelect['select']);
else
$sql = 'SELECT '.join(', ', static::$querySelect['select']);
if (static::$querySelect['from'] != '') { if (static::$querySelect['from'] != '') {
$sql .= ' FROM '.static::$querySelect['from']; $sql .= ' FROM '.static::$querySelect['from'];
@ -153,9 +198,6 @@ class ModelMySQL {
$sql .= ' LIMIT '.static::$querySelect['limit']; $sql .= ' LIMIT '.static::$querySelect['limit'];
} }
if ($resetQuery)
static::resetQuery();
return $sql; return $sql;
} }
@ -166,10 +208,10 @@ class ModelMySQL {
* Puede recibir un arreglo o un objeto que contiene los valores * Puede recibir un arreglo o un objeto que contiene los valores
* que tendrán sus atributos. * que tendrán sus atributos.
* *
* @return ModelMySQL * @return Model
* Retorna un objeto de la clase actual. * Retorna un objeto de la clase actual.
*/ */
protected static function getInstance(array $elem = []) : ModelMySQL { protected static function getInstance(array $elem = []) : Model {
$class = get_called_class(); $class = get_called_class();
$instance = new $class; $instance = new $class;
@ -243,11 +285,12 @@ class ModelMySQL {
foreach ($atts as $key => $value) { foreach ($atts as $key => $value) {
if (isset($value)) { if (isset($value)) {
$value = static::db()->real_escape_string($value);
if (in_array($key, $this->toNull)) if (in_array($key, $this->toNull))
$set[]="$key=NULL"; $set[]="$key=NULL";
else else {
$set[]="$key='$value'"; $set[]="$key=:$key";
static::$queryVars[':'.$key] = $value;
}
} else { } else {
if (in_array($key, $this->toNull)) if (in_array($key, $this->toNull))
$set[]="$key=NULL"; $set[]="$key=NULL";
@ -272,7 +315,8 @@ class ModelMySQL {
foreach ($atts as $key => $value) { foreach ($atts as $key => $value) {
if (isset($value)) { if (isset($value)) {
$into[] = "`$key`"; $into[] = "`$key`";
$values[] = "'".$db->real_escape_string($value)."'"; $values[] = ":$key";
static::$queryVars[":$key"] = $value;
} }
} }
@ -281,7 +325,7 @@ class ModelMySQL {
static::query($sql); static::query($sql);
$pk = static::$primaryKey; $pk = static::$primaryKey;
$this->$pk = $db->insert_id; $this->$pk = $db->lastInsertId();
} }
/** /**
@ -300,17 +344,11 @@ class ModelMySQL {
* Elimina el objeto actual de la base de datos. * Elimina el objeto actual de la base de datos.
*/ */
public function delete() { public function delete() {
$atts = $this->getVars();
foreach ($atts as $key => $value) {
$value = static::db()->real_escape_string($value);
$set[]="$key='$value'";
}
$table = static::table(); $table = static::table();
$pk = static::$primaryKey; $pk = static::$primaryKey;
$pkv = $this->$pk; $sql = "DELETE FROM $table WHERE $pk=:$pk";
$sql = "DELETE FROM $table WHERE $pk='$pkv'";
static::$queryVars[":$pk"] = $this->$pk;
static::query($sql); static::query($sql);
} }
@ -320,16 +358,10 @@ class ModelMySQL {
* @param array $columns * @param array $columns
* Columnas que se selecionarán en la consulta SQL. * Columnas que se selecionarán en la consulta SQL.
* *
* @return ModelMySQL * @return Model
*/ */
public static function select(array $columns) : ModelMySQL { public static function select(array $columns) : Model {
$db = static::db(); static::$querySelect['select'] = $columns;
$select = [];
foreach($columns as $column) {
$select[] = $db->real_escape_string($column);
}
static::$querySelect['select'] = $select;
return new static(); return new static();
} }
@ -340,16 +372,10 @@ class ModelMySQL {
* @param array $tables * @param array $tables
* Tablas que se selecionarán en la consulta SQL. * Tablas que se selecionarán en la consulta SQL.
* *
* @return ModelMySQL * @return Model
*/ */
public static function from(array $tables) : ModelMySQL { public static function from(array $tables) : Model {
$db = static::db(); static::$querySelect['from'] = join(', ', $tables);
$from = [];
foreach($tables as $table) {
$from[] = $db->real_escape_string($table);
}
static::$querySelect['from'] = join(', ', $from);
return new static(); return new static();
} }
@ -366,23 +392,23 @@ class ModelMySQL {
* @param string $value * @param string $value
* (opcional) El valor el valor a comparar en la columna. * (opcional) El valor el valor a comparar en la columna.
* *
* @param bool $no_quote * @param bool $no_filter
* (opcional) Se usa cuando $value es una columna o un valor que no requiere comillas. * (opcional) Se usa cuando $value es una columna o un valor que no requiere filtros contra ataques SQLI.
* *
* @return ModelMySQL * @return Model
*/ */
public static function where(string $column, string $operatorOrValue, string $value=null, bool $no_quote = false) : ModelMySQL { public static function where(string $column, string $operatorOrValue, string $value=null, bool $no_filter = false) : Model {
if (is_null($value)) { if (is_null($value)) {
$value = $operatorOrValue; $value = $operatorOrValue;
$operatorOrValue = '='; $operatorOrValue = '=';
} }
$value = static::db()->real_escape_string($value); if ($no_filter) {
static::$querySelect['where'] = "$column$operatorOrValue$value";
if ($no_quote) } else {
static::$querySelect['where'] = "$column$operatorOrValue$value"; static::$queryVars[":where_$column"] = $value;
else static::$querySelect['where'] = "$column$operatorOrValue:where_$column";
static::$querySelect['where'] = "$column$operatorOrValue'$value'"; }
return new static(); return new static();
} }
@ -399,17 +425,19 @@ class ModelMySQL {
* @param bool $in * @param bool $in
* Define si se tienen que comprobar negativa o positivamente. * Define si se tienen que comprobar negativa o positivamente.
* *
* @return ModelMySQL * @return Model
*/ */
public static function where_in(string $column, array $arr, bool $in = true) : ModelMySQL { public static function where_in(string $column, array $arr, bool $in = true) : Model {
$arrIn = [];
foreach($arr as $index => $value) { foreach($arr as $index => $value) {
$arr[$index] = static::db()->real_escape_string($value); $arrIn[] = ":on_$index";
static::$queryVars[":on_$index"] = $value;
} }
if ($in) if ($in)
static::$querySelect['where'] = "$column IN ('".join('\', \'',$arr)."')"; static::$querySelect['where'] = "$column IN (".join(', ', $arrIn).")";
else else
static::$querySelect['where'] = "$column NOT IN ('".join('\', \'',$arr)."')"; static::$querySelect['where'] = "$column NOT IN (".join(', ', $arrIn).")";
return new static(); return new static();
} }
@ -429,20 +457,16 @@ class ModelMySQL {
* @param string $columnB * @param string $columnB
* (opcional) Columna a comparar para hacer el join. * (opcional) Columna a comparar para hacer el join.
* *
* @return ModelMySQL * @return Model
*/ */
public static function leftJoin(string $table, string $columnA, string $operatorOrColumnB, string $columnB = null) : ModelMySQL { public static function leftJoin(string $table, string $columnA, string $operatorOrColumnB, string $columnB = null) : Model {
if (is_null($columnB)) { if (is_null($columnB)) {
$columnB = $operatorOrColumnB; $columnB = $operatorOrColumnB;
$operatorOrColumnB = '='; $operatorOrColumnB = '=';
} }
$columnA = static::db()->real_escape_string($columnA);
$columnB = static::db()->real_escape_string($columnB);
static::$querySelect['leftJoin'] .= ' LEFT JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB"; static::$querySelect['leftJoin'] .= ' LEFT JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB";
return new static(); return new static();
} }
@ -461,17 +485,14 @@ class ModelMySQL {
* @param string $columnB * @param string $columnB
* (opcional) Columna a comparar para hacer el join. * (opcional) Columna a comparar para hacer el join.
* *
* @return ModelMySQL * @return Model
*/ */
public static function rightJoin(string $table, string $columnA, string $operatorOrColumnB, string $columnB = null) : ModelMySQL { public static function rightJoin(string $table, string $columnA, string $operatorOrColumnB, string $columnB = null) : Model {
if (is_null($columnB)) { if (is_null($columnB)) {
$columnB = $operatorOrColumnB; $columnB = $operatorOrColumnB;
$operatorOrColumnB = '='; $operatorOrColumnB = '=';
} }
$columnA = static::db()->real_escape_string($columnA);
$columnB = static::db()->real_escape_string($columnB);
static::$querySelect['rightJoin'] .= ' RIGHT JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB"; static::$querySelect['rightJoin'] .= ' RIGHT JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB";
return new static(); return new static();
@ -492,17 +513,14 @@ class ModelMySQL {
* @param string $columnB * @param string $columnB
* (opcional) Columna a comparar para hacer el join. * (opcional) Columna a comparar para hacer el join.
* *
* @return ModelMySQL * @return Model
*/ */
public static function innerJoin(string $table, string $columnA, string $operatorOrColumnB, string $columnB = null) : ModelMySQL { public static function innerJoin(string $table, string $columnA, string $operatorOrColumnB, string $columnB = null) : Model {
if (is_null($columnB)) { if (is_null($columnB)) {
$columnB = $operatorOrColumnB; $columnB = $operatorOrColumnB;
$operatorOrColumnB = '='; $operatorOrColumnB = '=';
} }
$columnA = static::db()->real_escape_string($columnA);
$columnB = static::db()->real_escape_string($columnB);
static::$querySelect['innerJoin'] .= ' INNER JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB"; static::$querySelect['innerJoin'] .= ' INNER JOIN ' . $table . ' ON ' . "$columnA$operatorOrColumnB$columnB";
return new static(); return new static();
@ -520,23 +538,23 @@ class ModelMySQL {
* @param string $value * @param string $value
* (opcional) El valor el valor a comparar en la columna. * (opcional) El valor el valor a comparar en la columna.
* *
* @param bool $no_quote * @param bool $no_filter
* (opcional) Se usa cuando $value es una columna o un valor que no requiere comillas. * (opcional) Se usa cuando $value es una columna o un valor que no requiere filtros contra ataques SQLI.
* *
* @return ModelMySQL * @return Model
*/ */
public static function and(string $column, string $operatorOrValue, string $value=null, bool $no_quote = false) : ModelMySQL { public static function and(string $column, string $operatorOrValue, string $value=null, bool $no_filter = false) : Model {
if (is_null($value)) { if (is_null($value)) {
$value = $operatorOrValue; $value = $operatorOrValue;
$operatorOrValue = '='; $operatorOrValue = '=';
} }
$value = static::db()->real_escape_string($value); if ($no_filter)
if ($no_quote)
static::$querySelect['AndOr'] .= " AND $column$operatorOrValue$value"; static::$querySelect['AndOr'] .= " AND $column$operatorOrValue$value";
else else {
static::$querySelect['AndOr'] .= " AND $column$operatorOrValue'$value'"; static::$queryVars[":and_$column"] = $value;
static::$querySelect['AndOr'] .= " AND $column$operatorOrValue:and_$column";
}
return new static(); return new static();
} }
@ -553,23 +571,23 @@ class ModelMySQL {
* @param string $value * @param string $value
* (opcional) El valor el valor a comparar en la columna. * (opcional) El valor el valor a comparar en la columna.
* *
* @param bool $no_quote * @param bool $no_filter
* (opcional) Se usa cuando $value es una columna o un valor que no requiere comillas. * (opcional) Se usa cuando $value es una columna o un valor que no requiere filtros contra ataques SQLI.
* *
* @return ModelMySQL * @return Model
*/ */
public static function or(string $column, string $operatorOrValue, string $value=null, bool $no_quote = false) : ModelMySQL { public static function or(string $column, string $operatorOrValue, string $value=null, bool $no_filter = false) : Model {
if (is_null($value)) { if (is_null($value)) {
$value = $operatorOrValue; $value = $operatorOrValue;
$operatorOrValue = '='; $operatorOrValue = '=';
} }
$value = static::db()->real_escape_string($value); if ($no_filter)
if ($no_quote)
static::$querySelect['AndOr'] .= " OR $column$operatorOrValue$value"; static::$querySelect['AndOr'] .= " OR $column$operatorOrValue$value";
else else {
static::$querySelect['AndOr'] .= " OR $column$operatorOrValue'$value'"; static::$queryVars[":or_$column"] = $value;
static::$querySelect['AndOr'] .= " OR $column$operatorOrValue:or_$column";
}
return new static(); return new static();
} }
@ -580,9 +598,9 @@ class ModelMySQL {
* @param array $arr * @param array $arr
* Columnas por las que se agrupará. * Columnas por las que se agrupará.
* *
* @return ModelMySQL * @return Model
*/ */
public static function groupBy(array $arr) : ModelMySQL { public static function groupBy(array $arr) : Model {
static::$querySelect['groupBy'] = join(', ', $arr); static::$querySelect['groupBy'] = join(', ', $arr);
return new static(); return new static();
} }
@ -593,9 +611,9 @@ class ModelMySQL {
* @param int $initial * @param int $initial
* @param int $final * @param int $final
* *
* @return ModelMySQL * @return Model
*/ */
public static function limit(int $initial, int $final = 0) : ModelMySQL { public static function limit(int $initial, int $final = 0) : Model {
if ($final==0) if ($final==0)
static::$querySelect['limit'] = $initial; static::$querySelect['limit'] = $initial;
else else
@ -614,16 +632,14 @@ class ModelMySQL {
* (opcional) Define si el orden será de manera ascendente (ASC), * (opcional) Define si el orden será de manera ascendente (ASC),
* descendente (DESC) o aleatorio (RAND). * descendente (DESC) o aleatorio (RAND).
* *
* @return ModelMySQL * @return Model
*/ */
public static function orderBy(string $value, string $order = 'ASC') : ModelMySQL { public static function orderBy(string $value, string $order = 'ASC') : Model {
if ($value == "RAND") { if ($value == "RAND") {
static::$querySelect['orderBy'] = 'RAND()'; static::$querySelect['orderBy'] = 'RAND()';
return new static(); return new static();
} }
$value = static::db()->real_escape_string($value);
if (!(strtoupper($order) == 'ASC' || strtoupper($order) == 'DESC')) if (!(strtoupper($order) == 'ASC' || strtoupper($order) == 'DESC'))
$order = 'ASC'; $order = 'ASC';
@ -647,60 +663,36 @@ class ModelMySQL {
if (!$resetQuery) if (!$resetQuery)
$backup = [ $backup = [
'select' => static::$querySelect['select'], 'select' => static::$querySelect['select'],
'sql_calc_found_rows' => static::$querySelect['sql_calc_found_rows'],
'limit' => static::$querySelect['limit'], 'limit' => static::$querySelect['limit'],
'orderBy' => static::$querySelect['orderBy'] 'orderBy' => static::$querySelect['orderBy']
]; ];
if ($useLimit && static::$querySelect['limit'] != '') { if ($useLimit && static::$querySelect['limit'] != '') {
static::$querySelect['select'] = ['1']; static::$querySelect['select'] = ['1'];
static::$querySelect['sql_calc_found_rows'] = false; static::$querySelect['orderBy'] = '';
static::$querySelect['orderBy'] = '';
$sql = 'SELECT COUNT(1) AS quantity FROM ('.static::buildQuery($resetQuery).') AS counted'; $sql = 'SELECT COUNT(1) AS quantity FROM ('.static::buildQuery($resetQuery).') AS counted';
$queryResult = static::query($sql)->fetch_assoc(); $queryResult = static::query($sql);
$result = $queryResult['quantity']; $result = $queryResult[0]['quantity'];
} else { } else {
static::$querySelect['select'] = ['1']; static::$querySelect['select'] = ["COUNT(".static::$primaryKey.") as quantity"];
static::$querySelect['sql_calc_found_rows'] = true; static::$querySelect['limit'] = '1';
static::$querySelect['limit'] = '1'; static::$querySelect['orderBy'] = '';
static::$querySelect['orderBy'] = '';
$sql = static::buildQuery($resetQuery); $sql = static::buildQuery($resetQuery);
static::query($sql); $queryResult = static::query($sql);
$result = static::found_row(); $result = $queryResult[0]['quantity'];
} }
if (!$resetQuery) { if (!$resetQuery) {
static::$querySelect['select'] = $backup['select']; static::$querySelect['select'] = $backup['select'];
static::$querySelect['sql_calc_found_rows'] = $backup['sql_calc_found_rows']; static::$querySelect['limit'] = $backup['limit'];
static::$querySelect['limit'] = $backup['limit']; static::$querySelect['orderBy'] = $backup['orderBy'];
static::$querySelect['orderBy'] = $backup['orderBy'];
} }
return $result; return $result;
} }
/**
* Retorna las filas contadas en el último query.
*
* @return int
*/
public static function found_row() : int {
$result = static::query('SELECT FOUND_ROWS() AS quantity')->fetch_assoc();
return $result['quantity'];
}
/**
* Habilita el conteo de todos las coincidencias posibles incluso usando limit.
*
* @return ModelMySQL
*/
public static function sql_calc_found_rows() : ModelMySQL {
static::$querySelect['sql_calc_found_rows'] = true;
return new static();
}
/** /**
* Obtiene una instancia según su primary key (generalmente id). * Obtiene una instancia según su primary key (generalmente id).
* Si no encuentra una instancia, devuelve nulo. * Si no encuentra una instancia, devuelve nulo.
@ -721,9 +713,9 @@ class ModelMySQL {
* @param array $in * @param array $in
* (opcional) Columnas en las que se va a buscar (null para buscar en todas). * (opcional) Columnas en las que se va a buscar (null para buscar en todas).
* *
* @return ModelMySQL * @return Model
*/ */
public static function search(string $search, array $in = null) : ModelMySQL { public static function search(string $search, array $in = null) : Model {
if ($in == null) { if ($in == null) {
$className = get_called_class(); $className = get_called_class();
$in = array_keys((new $className())->getVars()); $in = array_keys((new $className())->getVars());
@ -731,12 +723,11 @@ class ModelMySQL {
$db = static::db(); $db = static::db();
$search = $db->real_escape_string($search); static::$queryVars[':search'] = $search;
$where = []; $where = [];
foreach($in as $row) { foreach($in as $row) {
$where[] = "$row LIKE '%$search%'"; $where[] = "$row LIKE CONCAT('%', :search, '%')";
} }
if (static::$querySelect['where']=='') if (static::$querySelect['where']=='')
@ -762,7 +753,7 @@ class ModelMySQL {
$instances = []; $instances = [];
while ($row = $result->fetch_assoc()) { foreach ($result as $row) {
$instances[] = static::getInstance($row); $instances[] = static::getInstance($row);
} }
@ -776,7 +767,7 @@ class ModelMySQL {
* (opcional) Indica si el query debe reiniciarse o no (por defecto es true). * (opcional) Indica si el query debe reiniciarse o no (por defecto es true).
* *
* @return mixed * @return mixed
* Puede retornar un objeto ModelMySQL o null. * Puede retornar un objeto Model o null.
*/ */
public static function getFirst(bool $resetQuery = true) { // Devuelve null si no encuentra nada. public static function getFirst(bool $resetQuery = true) { // Devuelve null si no encuentra nada.
static::limit(1); static::limit(1);
@ -792,14 +783,12 @@ class ModelMySQL {
*/ */
public static function all() : array { public static function all() : array {
$sql = 'SELECT * FROM '.static::table(); $sql = 'SELECT * FROM '.static::table();
$result = static::query($sql); $result = static::query($sql);
$instances = []; $instances = [];
while ($row = $result->fetch_assoc()) { foreach ($result as $row)
$instances[] = static::getInstance($row); $instances[] = static::getInstance($row);
}
return $instances; return $instances;
} }
@ -808,17 +797,9 @@ class ModelMySQL {
* Permite definir como nulo el valor de un atributo. * Permite definir como nulo el valor de un atributo.
* Sólo funciona para actualizar un elemento de la BD, no para insertar. * Sólo funciona para actualizar un elemento de la BD, no para insertar.
* *
* @trows \Exception
* Devolverá un error en caso de usarse en un insert.
*
* @param array $atts * @param array $atts
*/ */
public function setNull(array $atts) { public function setNull(array $atts) {
if (!isset($this->id))
throw new \Exception(
"\nEl método setNull sólo funciona para actualizar, no al insertar."
);
foreach ($atts as $att) { foreach ($atts as $att) {
if (!in_array($att, $this->toNull)) if (!in_array($att, $this->toNull))
$this->toNull[] = $att; $this->toNull[] = $att;