Programación (PHP) – Conexión a Bases de Datos con MySQLi parte 2

Aplicar Programación Orientada a Objetos (POO) para la conexión a bases de datos en PHP es una excelente práctica que vamos a empezar a implementar de ahora en adelante, esto nos va a ayudar a mejorar la estructura y el mantenimiento del código. Para este ejemplo vamos a usar la siguiente base de datos:

CREATE DATABASE cartas;
USE cartas;

CREATE TABLE Usuario (
IDUsuario INT AUTO_INCREMENT PRIMARY KEY,
Nombre VARCHAR (100)
);

CREATE TABLE Administrador (
IDUsuario INT PRIMARY KEY,
FOREIGN KEY (IDUsuario) REFERENCES Usuario (IDUsuario)
);

CREATE TABLE Jugador (
IDUsuario INT PRIMARY KEY,
Contra VARCHAR(255),
FOREIGN KEY (IDUsuario) REFERENCES Usuario (IDUsuario)
);

CREATE TABLE Partida (
IDPartida INT AUTO_INCREMENT PRIMARY KEY,
FechaHora DATETIME,
Ganador Varchar(100)
);

CREATE TABLE Juega (
IDUsuario INT,
IDPartida INT,
PRIMARY KEY (IDUsuario, IDPartida),
FOREIGN KEY (IDUsuario) REFERENCES Usuario (IDUsuario),
FOREIGN KEY (IDPartida) REFERENCES Partida (IDPartida)
);

CREATE TABLE Mano (
IDMano INT AUTO_INCREMENT PRIMARY KEY,
IDUsuario INT,
IDPartida INT,
FOREIGN KEY (IDUsuario, IDPartida) REFERENCES Juega (IDUsuario, IDPartida)
);

CREATE TABLE Carta (
IDCarta VARCHAR (3) PRIMARY KEY,
URL_Imagen VARCHAR (100),
Palo Varchar (10),
Numero TINYINT
);

CREATE TABLE Contiene (
IDCarta VARCHAR (3),
IDMano INT,
PRIMARY KEY (IDCarta, IDMano),
FOREIGN KEY (IDCarta) REFERENCES Carta (IDCarta),
FOREIGN KEY (IDMano) REFERENCES Mano (IDMano)
);

A continuación, les muestro cómo podríamos implementar una clase «Conexion" que maneje la conexión a la base de datos utilizando MySQLi.

<?php
class Conexion {
    private $host = 'localhost';
    private $usuario = 'root';
    private $password = 'root';
    private $baseDatos = 'cartas';
    private static $instancia = null;
    private $conexion;

    // Constructor privado para evitar la creación directa de la clase desde fuera
    private function __construct() {
        $this->conectar();
    }

    // Método para realizar la conexión a la base de datos
    private function conectar() {
        $this->conexion = new mysqli($this->host, $this->usuario, $this->password, $this->baseDatos);

        if ($this->conexion->connect_error) {
            die("Error de conexión: " . $this->conexion->connect_error);
        }
    }

    // Método público para obtener la instancia única (Singleton) de la clase
    public static function getInstancia() {
        if (self::$instancia === null) {
            self::$instancia = new self(); // Crear la instancia única si no existe
        }
        return self::$instancia;
    }

    // Método para obtener la conexión MySQLi
    public function getConexion() {
        return $this->conexion;
    }

    // Método para cerrar la conexión (opcional)
    public function cerrarConexion() {
        if ($this->conexion) {
            $this->conexion->close();
            self::$instancia = null; // Resetear la instancia si cerramos la conexión
        }
    }

    // Evitar que el objeto sea clonado (parte del patrón Singleton)
    private function __clone() {}

    // Evitar que se pueda deserializar (parte del patrón Singleton)
    private function __wakeup() {}
}
?>

Este código define una clase llamada Conexion que maneja la conexión a una base de datos MySQL utilizando la extensión mysqli de PHP.

Atributos de la clase:

$host: Almacena el nombre del servidor de base de datos, en este caso, ‘localhost’, lo que indica que la base de datos está en la misma máquina donde se ejecuta el script PHP.
$usuario: El nombre de usuario para conectarse a la base de datos, que en este caso es ‘root’, el usuario por defecto de MySQL.
$password: La contraseña asociada al usuario. En este caso tenemos asignado la contraseña «root» porque NO es un entorno de producción, lo cual es típico en configuraciones locales.
$baseDatos: Nombre de la base de datos a la cual el código intentará conectarse, que en este ejemplo es ‘ejemplo’.
$conexion: Este atributo almacena el objeto mysqli que representa la conexión a la base de datos.

Métodos de la clase:

Constructor: __construct()
Este método especial se ejecuta automáticamente cuando se instancia un objeto de la clase Conexión. En este caso, se llama al método conectar() para establecer la conexión a la base de datos.

Método conectar()
Es un método privado encargado de realizar la conexión a la base de datos MySQL.
Usa la función new mysqli() para crear la conexión utilizando los atributos previamente definidos ($host, $usuario, $password, $baseDatos).
Verifica si hubo algún error en la conexión mediante:
$this->conexion->connect_error.
Si hay un error, se interrumpe la ejecución con die(), mostrando el mensaje de error.

Método getConexion()
Es un método público que devuelve la conexión actual a la base de datos.
Esto es útil cuando se necesita acceder a la conexión desde fuera de la clase, por ejemplo, en otras clases que realicen consultas a la base de datos.

Método cerrarConexion()
Es un método público que cierra la conexión a la base de datos si está abierta, mediante el método close() de mysqli.

Para continuar trabajando orientado a objetos, necesitamos tener una clase que administre los métodos que vamos a trabajar para hacer las consultas. Les dejo un ejemplo con el PHPDoc armado para hacer las altas, bajas, consultas y modificaciones a la tabla Usuario->Jugador que están relacionadas entre ellas.

<?php
class JugadorCRUD
{
    private $conexion;

    public function __construct()
    {
        // Obtener la instancia de la conexión
        $this->conexion = Conexion::getInstancia()->getConexion();
    }

    /**
     * Crea un nuevo jugador en la base de datos.
     *
     * Esta función inserta un nuevo usuario en la tabla `Usuario` y almacena su información en la
     * tabla `Jugador`. La contraseña se encripta antes de ser almacenada por razones de seguridad.
     *
     * @param string $nombre El nombre del jugador que se va a crear.
     * @param string $contra La contraseña del jugador que se va a crear.
     * @return void
     */
    public function crearJugador(String $nombre, String $contra): void
    {
        // Encriptar la contraseña antes de almacenarla por seguridad
        $contraEncriptada = password_hash($contra, PASSWORD_DEFAULT);
        // Insertar el jugador en la tabla Usuario
        $sqlUsuario = "INSERT INTO Usuario (Nombre) VALUES (?)";
        $stmtUsuario = $this->conexion->prepare($sqlUsuario);
        $stmtUsuario->bind_param('s', $nombre);
        if ($stmtUsuario->execute()) {
            // Obtener el ID del nuevo usuario creado
            $idUsuario = $this->conexion->insert_id;
            // Insertar el ID y la contraseña en la tabla Jugador
            $sqlJugador = "INSERT INTO Jugador (IDUsuario, Contra) VALUES (?, ?)";
            $stmtJugador = $this->conexion->prepare($sqlJugador);
            $stmtJugador->bind_param('is', $idUsuario, $contraEncriptada);
            if ($stmtJugador->execute()) {
                echo "Jugador creado con éxito.";
            } else {
                echo "Error al crear jugador: " . $stmtJugador->error;
            }
            $stmtJugador->close();
        } else {
            echo "Error al crear usuario: " . $stmtUsuario->error;
        }
        $stmtUsuario->close();
    }

    /**
     * Lee la información de un jugador por su ID.
     *
     * Esta función obtiene los datos del jugador (sin mostrar la contraseña) junto con el nombre del usuario
     * asociado y los imprime en pantalla. Si no se encuentra al jugador, muestra un mensaje de error.
     *
     * @param int $idUsuario El ID del jugador que se va a buscar.
     * @return void
     */
    public function leerJugador(int $idUsuario): void
    {
        // Obtener la información del jugador junto con el nombre del Usuario
        $sql = "SELECT u.IDUsuario, u.Nombre 
                FROM Jugador j
                JOIN Usuario u ON j.IDUsuario = u.IDUsuario
                WHERE j.IDUsuario = ?";
        $stmt = $this->conexion->prepare($sql);
        $stmt->bind_param('i', $idUsuario);
        $stmt->execute();
        $resultado = $stmt->get_result();
        if ($resultado->num_rows > 0) {
            $jugador = $resultado->fetch_assoc();
            echo "ID: " . $jugador['IDUsuario'] . " - Nombre: " . $jugador['Nombre'] . "<br>";
        } else {
            echo "Jugador no encontrado.";
        }
        $stmt->close();
    }

    /**
     * Permite leer todos los jugadores cargados en el sistema
     * 
     * Este método busca en la base de datos todos los jugadores cargados en el sistema y los retorna
     * en un formato de lista
     * @return void
     * 
     */
    public function leerJugadores(): void 
    {
        // Obtener la información del jugador junto con el nombre del Usuario
        $sql = "SELECT u.IDUsuario, u.Nombre FROM Jugador j JOIN Usuario u ON j.IDUsuario = u.IDUsuario";
        $stmt = $this->conexion->prepare($sql);
        $stmt->execute();
        $resultado = $stmt->get_result();
        if ($resultado->num_rows > 0) {
            // Mostrar los datos de cada fila
            while ($jugador = $resultado->fetch_assoc()) {
                echo "ID: " . $jugador['IDUsuario'] . " - Nombre: " . $jugador['Nombre'] . "<br>";
            }
        } else {
            echo "No hay jugadores cargados en el sistema.";
        }
    }
    

    /**
     * Actualiza la información del jugador en la base de datos.
     *
     * Esta función permite actualizar el nombre de un jugador y, opcionalmente, su contraseña.
     * Si se proporciona una nueva contraseña, se encripta antes de ser almacenada en la base de datos.
     *
     * @param int $idUsuario El ID del jugador que se va a actualizar.
     * @param string $nombre El nuevo nombre del jugador.
     * @param string|null $contra La nueva contraseña del jugador (opcional).
     * @return void
     */
    public function actualizarJugador(int $idUsuario, String $nombre, String $contra = null): void
    {
        // Actualizar el nombre en la tabla Usuario
        $sqlUsuario = "UPDATE Usuario SET Nombre = ? WHERE IDUsuario = ?";
        $stmtUsuario = $this->conexion->prepare($sqlUsuario);
        $stmtUsuario->bind_param('si', $nombre, $idUsuario);
        if ($stmtUsuario->execute()) {
            echo "Nombre actualizado con éxito.<br>";
        } else {
            echo "Error al actualizar el nombre: " . $stmtUsuario->error;
        }
        // Si se proporcionó una nueva contraseña, también la actualizamos
        if ($contra !== null) {
            $contraEncriptada = password_hash($contra, PASSWORD_DEFAULT);
            $sqlJugador = "UPDATE Jugador SET Contra = ? WHERE IDUsuario = ?";
            $stmtJugador = $this->conexion->prepare($sqlJugador);
            $stmtJugador->bind_param('si', $contraEncriptada, $idUsuario);
            if ($stmtJugador->execute()) {
                echo "Contraseña actualizada con éxito.";
            } else {
                echo "Error al actualizar la contraseña: " . $stmtJugador->error;
            }
            $stmtJugador->close();
        }
        $stmtUsuario->close();
    }

    /**
     * Elimina un jugador de la base de datos.
     *
     * Esta función elimina primero el registro del jugador de la tabla Jugador
     * y luego de la tabla Usuario. Si no se encuentra el jugador, se mostrará
     * un mensaje de error correspondiente.
     *
     * @param int $idUsuario El ID del jugador que se va a eliminar.
     * @return void
     */
    public function eliminarJugador(int $idUsuario): void
    {
        // Primero eliminar de la tabla Jugador
        $sqlJugador = "DELETE FROM Jugador WHERE IDUsuario = ?";
        $stmtJugador = $this->conexion->prepare($sqlJugador);
        $stmtJugador->bind_param('i', $idUsuario);
        if ($stmtJugador->execute()) {
            // Luego eliminar de la tabla Usuario
            $sqlUsuario = "DELETE FROM Usuario WHERE IDUsuario = ?";
            $stmtUsuario = $this->conexion->prepare($sqlUsuario);
            $stmtUsuario->bind_param('i', $idUsuario);
            if ($stmtUsuario->execute()) {
                echo "Jugador eliminado con éxito.";
            } else {
                echo "Error al eliminar usuario: " . $stmtUsuario->error;
            }
            $stmtUsuario->close();
        } else {
            echo "Error al eliminar jugador: " . $stmtJugador->error;
        }
        $stmtJugador->close();
    }

    /**
     * Verifica la contraseña de un jugador.
     *
     * Esta función comprueba si la contraseña ingresada por el jugador
     * coincide con la contraseña almacenada en la base de datos.
     * Si el jugador no existe, se muestra un mensaje correspondiente.
     *
     * @param int $idUsuario El ID del jugador cuyo contraseña se desea verificar.
     * @param string $contraIngresada La contraseña ingresada por el jugador para la verificación.
     * @return void
     */
    public function verificarContraseña(int $idUsuario, String $contraIngresada): void
    {
        // Obtener la contraseña almacenada en la base de datos
        $sql = "SELECT Contra FROM Jugador WHERE IDUsuario = ?";
        $stmt = $this->conexion->prepare($sql);
        $stmt->bind_param('i', $idUsuario);
        $stmt->execute();
        $resultado = $stmt->get_result();
        if ($resultado->num_rows > 0) {
            $jugador = $resultado->fetch_assoc();
            $contraseñaAlmacenada = $jugador['Contra'];
            // Verificar si la contraseña ingresada coincide con la almacenada
            if (password_verify($contraIngresada, $contraseñaAlmacenada)) {
                echo "Contraseña correcta.";
            } else {
                echo "Contraseña incorrecta.";
            }
        } else {
            echo "Jugador no encontrado.";
        }
        $stmt->close();
    }
}

¡Desafió!
Si bien esta clase es 100% funcional, tiene unos pequeños errores de concepto. Siempre que trabajamos con un método lo ideal es retornar un valor, en vez de mostrar un texto. Veamos un ejemplo:

    /**
     * Verifica la contraseña de un jugador.
     *
     * Esta función comprueba si la contraseña ingresada por el jugador
     * coincide con la contraseña almacenada en la base de datos.
     * Si el jugador no existe, se muestra un mensaje correspondiente.
     *
     * @param int $idUsuario El ID del jugador cuyo contraseña se desea verificar.
     * @param string $contraIngresada La contraseña ingresada por el jugador para la verificación.
     * @return bool
     */
    public function verificarContra(int $idUsuario, String $contraIngresada): bool
    {
        // Obtener la contraseña almacenada en la base de datos
        $existe = false;
        $sql = "SELECT Contra FROM Jugador WHERE IDUsuario = ?";
        $stmt = $this->conexion->prepare($sql);
        $stmt->bind_param('i', $idUsuario);
        $stmt->execute();
        $resultado = $stmt->get_result();
        if ($resultado->num_rows > 0) {
            $jugador = $resultado->fetch_assoc();
            $contraseñaAlmacenada = $jugador['Contra'];
            // Verificar si la contraseña ingresada coincide con la almacenada
            if (password_verify($contraIngresada, $contraseñaAlmacenada)) {
                $existe = true; 
            } else {
                $existe = false;
            }
        } else {
            return false;
        }
        $stmt->close();
        return $existe;
    }

Deja un comentario

I’m Luis E. Fagúndez

Bienvenidos a mi página web personal. Mi nombre es Luis, me gusta enseñar, programar y tomar litros y litros de café.

En esta web vas a encontrar materiales sobre educación, programación, Gnu/Linux, software libre y mucho más.

Esta web busca brindar información sobre las asignaturas que imparto en DGETP-UTU, así como proyectos personales y otras cosas.

Gracias por ser parte de esta hermosa comunidad.

Formas de contacto