En capítulos anteriores habíamos visto como podíamos conectarnos a una base de datos, como podemos hacer consultas SQL, y hemos creado cierta cantidad de métodos que nos van a permitir hacer consultas a nuestra base de datos de manera más fácil.
En este capítulo hablaremos de un tema bastante importante y es la inyección SQL, el cual es un tipo de ciberataque en cubierto, en el cual un hacker inserta código propio en un sitio web con el fin de quebrantar las medidas de seguridad y acceder a datos protegidos. Este tipo de ataque se va a presentar cuando presentemos formularios en nuestra aplicación, formularios que le permita escribir código.
Una de las formas que tenemos de que no se inyecte código SQL es escapando del código que pueda insertar un hacker, otra manera es mediante las sentencias preparadas.
Para preparar nuestras consultas de posibles inyecciones SQL vamos a trabajar en nuestro archivo Model.php en los métodos where() y query(). Os dejamos el archivo con la nueva sintaxis:
<?php
namespace App\Models;
use mysqli;
class Model
{
// Propiedades que recogen los datos de acceso a la BBDD
protected $db_host = DB_HOST;
protected $db_user = DB_USER;
protected $db_pass = DB_PASS;
protected $db_name = DB_NAME;
// Propiedad que recoge la conexión a la base de datos
protected $connection;
// Propiedad que recoge la consulta a la BBDD
protected $query;
public function __construct(){
$this->connection();
}
// Método encargado de realizar la conexión
public function connection(){
// Conexión con mysqli
$this->connection = new mysqli($this->db_host, $this->db_user, $this->db_pass, $this->db_name);
if($this->connection->connect_error)
{
die('Error de conexión: ' . $this->connection->connect_error);
}
}
// Método que hace la consulta a la base de datos
public function query($sql, $data = [], $params = null){
if($data){
if($params == null){
$params = str_repeat('s', count($data));
}
// Creamos una sentencia preparada
$stmt = $this->connection->prepare($sql);
// Especificamos el tipo de elemento con 's' y especificamos el valor con $value
$stmt->bind_param($params, ...$data);
// Ejecutamos la sentencia
$stmt->execute();
// Accedemos al resultado de la consulta
// Lo guardamos en la variable $query
$this->query = $stmt->get_result();
} else {
$this->query = $this->connection->query($sql);
}
return $this;
}
// Método que recupera el primer registro de la consulta a la base de datos
public function first(){
return $this->query->fetch_assoc();
}
// Método que recupera todos los registros de la consulta a la base de datos
public function get(){
return $this->query->fetch_all(MYSQLI_ASSOC);
}
// Consultas
// Método que nos devolverá todos los registros de una determinada tabla
public function all(){
$sql = "SELECT * FROM {$this->table}";
return $this->query($sql)->get();
}
// Recuperar determinado registro por su id
public function find($id){
// SELECT * FROM contacts WHERE id = 1
$sql = "SELECT * FROM {$this->table} WHERE id = ?";
return $this->query($sql, [$id], 'i')->first();
}
// Método para filtrar registros
public function where($column, $operator, $value = null){
if($value == null){
$value = $operator;
$operator = '=';
}
// La sentencia comentada sirve para escapar de posibles ataques SQL
// $value = $this->connection->real_escape_string($value);
// SELECT * FROM contacts WHERE name = 'Francisco'
$sql = "SELECT * FROM {$this->table} WHERE {$column} {$operator} ?";
$this->query($sql, [$value], 's');
// Pedimos que retorne el objeto
return $this;
}
// Método para agregar registros
public function create($data){
// INSERT INTO contacts (name, email, phone) VALUES (? ? ?)
$columns = array_keys($data);
$columns = implode(', ', $columns);
$values = array_values($data);
$sql = "INSERT INTO {$this->table} ({$columns}) VALUES (" . str_repeat('?, ', count($values) - 1) . "?)";
$this->query($sql, $values);
$insert_id = $this->connection->insert_id;
return $this->find($insert_id);
}
public function update($id, $data){
// UPDATE contacts SET name = ?, email = ?, phone = ? WHERE id = 1
$fields = [];
foreach($data as $key => $value){
$fields[] = "{$key} = ?";
}
$fields = implode(', ', $fields);
$sql = "UPDATE {$this->table} SET {$fields} WHERE id = ?";
$values = array_values($data);
$values[] = $id;
$this->query($sql, $values);
return $this->find($id);
}
public function delete($id){
// DELETE FROM contacts WHERE id = 1
$sql = "DELETE FROM {$this->table} WHERE id = ?";
// EJECUTAMOS LA CONSULTA
// NO ES NECESARIO NADA MÁS, NO NECESITAMOS QUE RETORNE NADA
$this->query($sql, [$id], 'i');
}
}
Las consultas preparadas van a ser sobretodo útiles cuando dentro de nuestra consulta tenemos más de un valor.