From 0db6e4a0216088b012c8bc499050e7b9d1967db5 Mon Sep 17 00:00:00 2001 From: kj Date: Thu, 14 Jul 2022 21:41:48 -0400 Subject: [PATCH] rework to change from MySQLi to PDO. --- readme.md => readme.org | 8 +- rework.org | 93 +++++++ src/Libs/Database.php | 34 ++- src/Libs/{ModelMySQL.php => Model.php} | 337 ++++++++++++------------- 4 files changed, 277 insertions(+), 195 deletions(-) rename readme.md => readme.org (90%) create mode 100644 rework.org rename src/Libs/{ModelMySQL.php => Model.php} (73%) diff --git a/readme.md b/readme.org similarity index 90% rename from readme.md rename to readme.org index 1bf7760..ec5e090 100644 --- a/readme.md +++ b/readme.org @@ -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. @@ -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. -## Uso / Documentación +* Uso / Documentación 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 diff --git a/rework.org b/rework.org new file mode 100644 index 0000000..e0acc1c --- /dev/null +++ b/rework.org @@ -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 | | | +|------------------+---------------+---------+------------| diff --git a/src/Libs/Database.php b/src/Libs/Database.php index 307ba58..2afddf1 100644 --- a/src/Libs/Database.php +++ b/src/Libs/Database.php @@ -2,9 +2,9 @@ /** * 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: - * dbhost, dbname, dbpass y dbuser + * DB_TYPE, DB_NAME, DB_HOST, DB_USER. DB_PASS * * @author KJ * @website https://kj2.me @@ -12,25 +12,33 @@ */ namespace Libs; -use mysqli; -class Database extends \mysqli { - static private $db; +use PDO; +use PDOException; +use Exception; + +class Database extends PDO { + static private ?PDO $db = null; 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 { - if (!isset(self::$db)) { - self::$db = new mysqli(dbhost, dbuser, dbpass, dbname); - if (self::$db->connect_errno) { - echo ''; - throw new \Exception('No se ha podido conectar a la base de datos.'); + static public function getInstance() : PDO { + if (is_null(self::$db)) { + $dsn = DB_TYPE.':dbname='.DB_NAME.';host='.DB_HOST; + try { + self::$db = new PDO($dsn, DB_USER, DB_PASS); + } catch (PDOException $e) { + echo "
";
+                throw new Exception(
+                    'Error at connect to database: ' . $e->getMessage()
+                );
             }
+            self::$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
         }
         return self::$db;
     }
diff --git a/src/Libs/ModelMySQL.php b/src/Libs/Model.php
similarity index 73%
rename from src/Libs/ModelMySQL.php
rename to src/Libs/Model.php
index 482a22a..a6094e7 100644
--- a/src/Libs/ModelMySQL.php
+++ b/src/Libs/Model.php
@@ -1,9 +1,9 @@
  ['*'],
         'where'               => '',
@@ -35,48 +38,93 @@ class ModelMySQL {
         'AndOr'               => '',
         'orderBy'             => '',
         'groupBy'             => '',
-        'limit'               => '',
-        'sql_calc_found_rows' => false
+        'limit'               => ''
     ];
 
     /**
      * 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))
-            static::$db = Database::getConnection();
+            static::$db = Database::getInstance();
 
         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.
      *
      * @param string $query
      *   Contiene la sentencia SQL que se desea ejecutar.
      *
-     * @throws \Exception
-     *   En caso de que la sentencia SQL falle, devolverá un error en pantalla.
+     * @throws Exception
+     *   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
-     *   Contiene el resultado de la llamada SQL (mysqli_result o bool).
+     * @param bool $resetQuery
+     *   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();
 
-        $result = $db->query($query);
-        if ($db->errno) {
-            echo '';
-            throw new \Exception(
-                "\nFallo al consultar la base de datos\n" .
-                "Errno: $db->errno\n" .
-                "Error: $db->error\n" .
-                "Query: $query\n"
+        try {
+            $prepared = $db->prepare($query);
+            $prepared->execute(static::$queryVars);
+        } catch (PDOException $e) {
+            if ($db->inTransaction())
+                $db->rollBack();
+
+            $vars = json_encode(static::$queryVars);
+
+            echo "
";
+            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;
     }
 
@@ -94,9 +142,9 @@ class ModelMySQL {
             'AndOr'               => '',
             'orderBy'             => '',
             'groupBy'             => '',
-            'limit'               => '',
-            'sql_calc_found_rows' => false
+            'limit'               => ''
         ];
+        static::$queryVars  = [];
     }
 
     /**
@@ -110,10 +158,7 @@ class ModelMySQL {
      *   Contiene la sentencia SQL.
      */
     protected static function buildQuery(bool $resetQuery = true) : string {
-        if (static::$querySelect['sql_calc_found_rows'])
-            $sql = 'SELECT SQL_CALC_FOUND_ROWS '.join(', ', static::$querySelect['select']);
-        else
-            $sql = 'SELECT '.join(', ', static::$querySelect['select']);
+        $sql = 'SELECT '.join(', ', static::$querySelect['select']);
 
         if (static::$querySelect['from'] != '') {
             $sql .= ' FROM '.static::$querySelect['from'];
@@ -153,9 +198,6 @@ class ModelMySQL {
             $sql .= ' LIMIT '.static::$querySelect['limit'];
         }
 
-        if ($resetQuery)
-            static::resetQuery();
-
         return $sql;
     }
 
@@ -166,10 +208,10 @@ class ModelMySQL {
      *   Puede recibir un arreglo o un objeto que contiene los valores
      *   que tendrán sus atributos.
      *
-     * @return ModelMySQL
+     * @return Model
      *   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();
         $instance = new $class;
 
@@ -243,11 +285,12 @@ class ModelMySQL {
 
         foreach ($atts as $key => $value) {
             if (isset($value)) {
-                $value = static::db()->real_escape_string($value);
                 if (in_array($key, $this->toNull))
                     $set[]="$key=NULL";
-                else
-                    $set[]="$key='$value'";
+                else {
+                    $set[]="$key=:$key";
+                    static::$queryVars[':'.$key] = $value;
+                }
             } else {
                 if (in_array($key, $this->toNull))
                     $set[]="$key=NULL";
@@ -272,7 +315,8 @@ class ModelMySQL {
         foreach ($atts as $key => $value) {
             if (isset($value)) {
                 $into[] = "`$key`";
-                $values[] = "'".$db->real_escape_string($value)."'";
+                $values[] = ":$key";
+                static::$queryVars[":$key"] = $value;
             }
         }
 
@@ -281,7 +325,7 @@ class ModelMySQL {
         static::query($sql);
 
         $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.
      */
     public function delete() {
-        $atts = $this->getVars();
-
-        foreach ($atts as $key => $value) {
-            $value = static::db()->real_escape_string($value);
-            $set[]="$key='$value'";
-        }
-
         $table = static::table();
         $pk = static::$primaryKey;
-        $pkv = $this->$pk;
-        $sql = "DELETE FROM $table WHERE $pk='$pkv'";
+        $sql = "DELETE FROM $table WHERE $pk=:$pk";
+
+        static::$queryVars[":$pk"] = $this->$pk;
         static::query($sql);
     }
 
@@ -320,16 +358,10 @@ class ModelMySQL {
      * @param array $columns
      *   Columnas que se selecionarán en la consulta SQL.
      *
-     * @return ModelMySQL
+     * @return Model
      */
-    public static function select(array $columns) : ModelMySQL {
-        $db = static::db();
-        $select = [];
-        foreach($columns as $column) {
-            $select[] = $db->real_escape_string($column);
-        }
-
-        static::$querySelect['select'] = $select;
+    public static function select(array $columns) : Model {
+        static::$querySelect['select'] = $columns;
 
         return new static();
     }
@@ -340,16 +372,10 @@ class ModelMySQL {
      * @param array $tables
      *   Tablas que se selecionarán en la consulta SQL.
      *
-     * @return ModelMySQL
+     * @return Model
      */
-    public static function from(array $tables) : ModelMySQL {
-        $db = static::db();
-        $from = [];
-        foreach($tables as $table) {
-            $from[] = $db->real_escape_string($table);
-        }
-
-        static::$querySelect['from'] = join(', ', $from);
+    public static function from(array $tables) : Model {
+        static::$querySelect['from'] = join(', ', $tables);
 
         return new static();
     }
@@ -366,23 +392,23 @@ class ModelMySQL {
      * @param string $value
      *   (opcional) El valor el valor a comparar en la columna.
      *
-     * @param bool $no_quote
-     *   (opcional) Se usa cuando $value es una columna o un valor que no requiere comillas.
+     * @param bool $no_filter
+     *   (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)) {
             $value = $operatorOrValue;
             $operatorOrValue = '=';
         }
 
-        $value = static::db()->real_escape_string($value);
-
-        if ($no_quote)
-            static::$querySelect['where'] = "$column$operatorOrValue$value";
-        else
-            static::$querySelect['where'] = "$column$operatorOrValue'$value'";
+        if ($no_filter) {
+            static::$querySelect['where']   = "$column$operatorOrValue$value";
+        } else {
+            static::$queryVars[":where_$column"] = $value;
+            static::$querySelect['where']   = "$column$operatorOrValue:where_$column";
+        }
 
         return new static();
     }
@@ -399,17 +425,19 @@ class ModelMySQL {
      * @param bool $in
      *   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) {
-            $arr[$index] = static::db()->real_escape_string($value);
+            $arrIn[] = ":on_$index";
+            static::$queryVars[":on_$index"] = $value;
         }
 
         if ($in)
-            static::$querySelect['where'] = "$column IN ('".join('\', \'',$arr)."')";
+            static::$querySelect['where'] = "$column IN (".join(', ', $arrIn).")";
         else
-            static::$querySelect['where'] = "$column NOT IN ('".join('\', \'',$arr)."')";
+            static::$querySelect['where'] = "$column NOT IN (".join(', ', $arrIn).")";
 
         return new static();
     }
@@ -429,20 +457,16 @@ class ModelMySQL {
      * @param string $columnB
      *   (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)) {
             $columnB = $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";
 
-
         return new static();
     }
 
@@ -461,17 +485,14 @@ class ModelMySQL {
      * @param string $columnB
      *   (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)) {
             $columnB = $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";
 
         return new static();
@@ -492,17 +513,14 @@ class ModelMySQL {
      * @param string $columnB
      *   (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)) {
             $columnB = $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";
 
         return new static();
@@ -520,23 +538,23 @@ class ModelMySQL {
      * @param string $value
      *   (opcional) El valor el valor a comparar en la columna.
      *
-     * @param bool $no_quote
-     *   (opcional) Se usa cuando $value es una columna o un valor que no requiere comillas.
+     * @param bool $no_filter
+     *   (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)) {
             $value = $operatorOrValue;
             $operatorOrValue = '=';
         }
 
-        $value = static::db()->real_escape_string($value);
-
-        if ($no_quote)
+        if ($no_filter)
             static::$querySelect['AndOr'] .= " AND $column$operatorOrValue$value";
-        else
-            static::$querySelect['AndOr'] .= " AND $column$operatorOrValue'$value'";
+        else {
+            static::$queryVars[":and_$column"] = $value;
+            static::$querySelect['AndOr'] .= " AND $column$operatorOrValue:and_$column";
+        }
 
         return new static();
     }
@@ -553,23 +571,23 @@ class ModelMySQL {
      * @param string $value
      *   (opcional) El valor el valor a comparar en la columna.
      *
-     * @param bool $no_quote
-     *   (opcional) Se usa cuando $value es una columna o un valor que no requiere comillas.
+     * @param bool $no_filter
+     *   (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)) {
             $value = $operatorOrValue;
             $operatorOrValue = '=';
         }
 
-        $value = static::db()->real_escape_string($value);
-
-        if ($no_quote)
+        if ($no_filter)
             static::$querySelect['AndOr'] .= " OR $column$operatorOrValue$value";
-        else
-            static::$querySelect['AndOr'] .= " OR $column$operatorOrValue'$value'";
+        else {
+            static::$queryVars[":or_$column"] = $value;
+            static::$querySelect['AndOr'] .= " OR $column$operatorOrValue:or_$column";
+        }
 
         return new static();
     }
@@ -580,9 +598,9 @@ class ModelMySQL {
      * @param array $arr
      *   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);
         return new static();
     }
@@ -593,9 +611,9 @@ class ModelMySQL {
      * @param int $initial
      * @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)
             static::$querySelect['limit'] = $initial;
         else
@@ -614,16 +632,14 @@ class ModelMySQL {
      *   (opcional) Define si el orden será de manera ascendente (ASC),
      *   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") {
             static::$querySelect['orderBy'] = 'RAND()';
             return new static();
         }
 
-        $value = static::db()->real_escape_string($value);
-
         if (!(strtoupper($order) == 'ASC' || strtoupper($order) == 'DESC'))
             $order = 'ASC';
 
@@ -647,60 +663,36 @@ class ModelMySQL {
         if (!$resetQuery)
             $backup = [
                 'select'              => static::$querySelect['select'],
-                'sql_calc_found_rows' => static::$querySelect['sql_calc_found_rows'],
                 'limit'               => static::$querySelect['limit'],
                 'orderBy'             => static::$querySelect['orderBy']
             ];
 
         if ($useLimit && static::$querySelect['limit'] != '') {
-            static::$querySelect['select']              = ['1'];
-            static::$querySelect['sql_calc_found_rows'] = false;
-            static::$querySelect['orderBy']             = '';
+            static::$querySelect['select']  = ['1'];
+            static::$querySelect['orderBy'] = '';
 
             $sql         = 'SELECT COUNT(1) AS quantity FROM ('.static::buildQuery($resetQuery).') AS counted';
-            $queryResult = static::query($sql)->fetch_assoc();
-            $result      = $queryResult['quantity'];
+            $queryResult = static::query($sql);
+            $result      = $queryResult[0]['quantity'];
         } else {
-            static::$querySelect['select']              = ['1'];
-            static::$querySelect['sql_calc_found_rows'] = true;
-            static::$querySelect['limit']               = '1';
-            static::$querySelect['orderBy']             = '';
+            static::$querySelect['select']  = ["COUNT(".static::$primaryKey.") as quantity"];
+            static::$querySelect['limit']   = '1';
+            static::$querySelect['orderBy'] = '';
 
             $sql = static::buildQuery($resetQuery);
-            static::query($sql);
-            $result = static::found_row();
+            $queryResult = static::query($sql);
+            $result      = $queryResult[0]['quantity'];
         }
 
         if (!$resetQuery) {
-            static::$querySelect['select']              = $backup['select'];
-            static::$querySelect['sql_calc_found_rows'] = $backup['sql_calc_found_rows'];
-            static::$querySelect['limit']               = $backup['limit'];
-            static::$querySelect['orderBy']             = $backup['orderBy'];
+            static::$querySelect['select']  = $backup['select'];
+            static::$querySelect['limit']   = $backup['limit'];
+            static::$querySelect['orderBy'] = $backup['orderBy'];
         }
 
         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).
      * Si no encuentra una instancia, devuelve nulo.
@@ -721,9 +713,9 @@ class ModelMySQL {
      * @param array $in
      *   (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) {
             $className = get_called_class();
             $in = array_keys((new $className())->getVars());
@@ -731,12 +723,11 @@ class ModelMySQL {
 
         $db = static::db();
 
-        $search = $db->real_escape_string($search);
-
+        static::$queryVars[':search'] = $search;
         $where = [];
 
         foreach($in as $row) {
-            $where[] = "$row LIKE '%$search%'";
+            $where[] = "$row LIKE CONCAT('%', :search, '%')";
         }
 
         if (static::$querySelect['where']=='')
@@ -762,7 +753,7 @@ class ModelMySQL {
 
         $instances = [];
 
-        while ($row = $result->fetch_assoc()) {
+        foreach ($result as $row) {
             $instances[] = static::getInstance($row);
         }
 
@@ -776,7 +767,7 @@ class ModelMySQL {
      *   (opcional) Indica si el query debe reiniciarse o no (por defecto es true).
      *
      * @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.
         static::limit(1);
@@ -792,14 +783,12 @@ class ModelMySQL {
      */
     public static function all() : array {
         $sql = 'SELECT * FROM '.static::table();
-
         $result = static::query($sql);
 
         $instances = [];
 
-        while ($row = $result->fetch_assoc()) {
+        foreach ($result as $row)
             $instances[] = static::getInstance($row);
-        }
 
         return $instances;
     }
@@ -808,17 +797,9 @@ class ModelMySQL {
      * Permite definir como nulo el valor de un atributo.
      * 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
      */
     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) {
             if (!in_array($att, $this->toNull))
                 $this->toNull[] = $att;