Tema 05

PHP + MySQL (solo SELECT) con Conexion + execute()

En esta clase NO creamos bases de datos ni tablas. Vamos a conectarnos a la BD que ya creamos antes y vamos a ejecutar consultas SELECT y mostrarlas en pantalla con PHP.

1) Qué vamos a construir hoy

Vamos a crear un espacio de trabajo mínimo (mini-proyecto) con estos objetivos:

Base ya existente: hoy asumimos que ya existe tu BD (por ejemplo curso_practica) y sus tablas (por ejemplo usuarios). Si no existe, se usa el script de SQL de clases anteriores.

2) Estructura de carpetas/archivos (mínima y ordenada)

Esto es lo que vamos a tener en el mini-proyecto (no frameworks, no MVC, solo orden):

/mini_php_mysql_select
  /config
    db.php
  /lib
    Conexion.php
    Functions.php
  /pages
    usuarios_list.php
  index.php
  /assets
    /css
      style.css
    /js
      copy.js

Qué hace cada cosa:

3) La clase Conexion (solo __construct)

Esta clase existe para que tú puedas hacer esto en cualquier parte del proyecto: $db = new Conexion(); y ya tengas una conexión lista.

Nota: la clase Conexion extiende mysqli. Eso significa que hereda métodos como: query(), insert_id, error, etc.

¿Por qué ponemos charset y timezone aquí?
Porque se ejecuta 1 sola vez al crear la conexión. Así todo el proyecto queda consistente.

4) La clase Functions (solo execute($layout))

Esta clase existe para centralizar la ejecución y estandarizar el resultado. El método recibe un texto SQL ($layout) y regresa un arreglo con información útil.

El método exacto que vamos a usar

public function execute($layout)
{
  $db = new Conexion();     // 1) abre conexión
  $sql = $layout;           // 2) el SQL recibido
  $query = $db->query($sql); // 3) ejecuta

  if ($query) {
    $result['query']  = $query;        // 4) el resultado (mysqli_result) si aplica
    $result['lastId'] = $db->insert_id; // 5) id insertado (hoy no lo usamos, pero ya queda estándar)
    $result['res']    = 'ok';          // 6) bandera ok
  } else {
    $result['res'] = 'error: ' . $db->error; // 7) error del motor
  }

  return $result;
}

Explicación parte por parte (sin saltos)

Ojo importante: la palabra query NO es “mágica”.
Es solo una llave del arreglo que nosotros decidimos usar para guardar el resultado. Pudiera llamarse data, result o como quieras, pero si la cambias, debes cambiarla en todo tu código.

5) Cómo se consume en PHP “crudo” (instanciar y ejecutar SELECT)

Paso 1: incluir clases y crear el objeto Functions.

require_once __DIR__ . '/../lib/Conexion.php';
require_once __DIR__ . '/../lib/Functions.php';

$functions = new Functions();

Paso 2: ejecutar el SELECT con execute().

$getUsers = $functions->execute("
  SELECT id, nombre, correo, rol, activo, created_at
  FROM usuarios
  ORDER BY id DESC
");

Paso 3: validar resultado.

if ($getUsers['res'] !== 'ok') {
  die($getUsers['res']); // imprime error del motor
}

Paso 4: usar num_rows para contar filas.

$totalFilas = $getUsers['query']->num_rows;
¿Qué es num_rows?
Cuando haces un SELECT, $getUsers['query'] es un mysqli_result. Ese objeto trae la propiedad num_rows que indica cuántas filas devolvió el SELECT.

Paso 5: recorrer filas con fetch_assoc().

while ($row = $getUsers['query']->fetch_assoc()) {
  // $row es un array asociativo: ['id'=>..., 'nombre'=>..., 'correo'=>...]
}
¿Qué hace fetch_assoc()?
Devuelve la siguiente fila como un array asociativo (llave = nombre de columna).
Cuando ya no hay filas, devuelve null y se termina el while.

6) El truco para no declarar variables una por una

En muchos proyectos (como el tuyo) se usa este patrón:

while ($row = $getUsers['query']->fetch_assoc()) {

  foreach ($row as $key => $value) {
    $$key = $value; // crea una variable con el nombre de la columna
  }

  // Ahora existen variables:
  // $id, $nombre, $correo, $rol, $activo, $created_at, etc.

  echo $nombre . " - " . $correo . "<br>";
}
¿Qué significa $$key?
$key contiene el nombre de la columna (por ejemplo "nombre").
Entonces $$key se vuelve $nombre y le asigna el valor correspondiente.
Precaución (para clase):
Este truco es útil, pero si en un SELECT llega una columna con el mismo nombre que una variable que ya usas, te la puede sobrescribir. Por eso se usa con orden y sabiendo qué columnas estás seleccionando.

7) Página de ejemplo: listar usuarios en tabla HTML

Esta página es exactamente lo que debe hacer pages/usuarios_list.php: ejecutar un SELECT y dibujar una tabla.

require_once __DIR__ . '/../lib/Conexion.php';
require_once __DIR__ . '/../lib/Functions.php';

$functions = new Functions();

$getUsers = $functions->execute("
  SELECT id, nombre, correo, rol, activo, created_at
  FROM usuarios
  ORDER BY id DESC
");

if ($getUsers['res'] !== 'ok') die($getUsers['res']);

?>

<table border="1" cellpadding="6">
  <tr>
    <th>ID</th>
    <th>Nombre</th>
    <th>Correo</th>
    <th>Rol</th>
    <th>Activo</th>
  </tr>

  <?php while ($row = $getUsers['query']->fetch_assoc()) { ?>
    <?php foreach ($row as $key => $value) { $$key = $value; } ?>

    <tr>
      <td><?php echo $id; ?></td>
      <td><?php echo $nombre; ?></td>
      <td><?php echo $correo; ?></td>
      <td><?php echo $rol; ?></td>
      <td><?php echo $activo; ?></td>
    </tr>

  <?php } ?>
</table>

8) PROMPTS (muy detallados) para generar el mini-proyecto SOLO SELECT

Estos prompts están diseñados para que el resultado genere EXACTAMENTE las carpetas y archivos que estamos explicando. Ejecuta en orden.

Prompt 1 — Solo estructura (sin código)

Actúa como desarrollador PHP (procedural) para un mini proyecto de clase.

Objetivo del mini proyecto:
- Conectar PHP a MySQL con mysqli.
- Usar EXACTAMENTE este patrón:
  1) class Conexion extends mysqli (solo __construct)
  2) class Functions (solo execute($layout))
- Por ahora SOLO haremos SELECT y mostrar datos en HTML.
- No uses frameworks ni routers.

REQUISITO DE ESTA RESPUESTA:
No escribas código. SOLO entrega el árbol de carpetas y archivos con rutas.

Estructura requerida (debe ser exactamente esta):
/mini_php_mysql_select
  /config
    db.php
  /lib
    Conexion.php
    Functions.php
  /pages
    usuarios_list.php
  index.php
  /assets
    /css
      style.css
    /js
      copy.js

La BD ya existe (por ejemplo: curso_practica) y la tabla usuarios ya existe.
En tu árbol no incluyas scripts de create database o create table.

Prompt 2 — Código archivo por archivo (sin INSERT/UPDATE/DELETE)

Ahora genera TODO el código del proyecto usando el árbol exacto.

Reglas:
- Entrega archivo por archivo con este formato exacto:
  === ruta/archivo.ext ===
  (código completo)
- No omitas archivos.
- No agregues archivos extra.

Requisitos por archivo:

1) config/db.php
- Debe retornar un arreglo (return [ ... ]) con:
  host, user, pass, db, port
- Valores de ejemplo (editables):
  host: 127.0.0.1
  user: root
  pass: (vacío o 'root123' de ejemplo)
  db: curso_practica
  port: 3306

2) lib/Conexion.php
- Debe contener: class Conexion extends mysqli
- SOLO método: public function __construct()
- Debe leer config/db.php con require
- Debe llamar:
  parent::__construct(host, user, pass, db, port)
- Debe ejecutar:
  $this->set_charset('utf8mb4');
  $this->query("SET SESSION time_zone = '-06:00'");
- Si hay error:
  die("Error en la conexión: " . $this->connect_error);

3) lib/Functions.php
- Debe contener: class Functions
- SOLO método: public function execute($layout)
- Debe ser EXACTAMENTE así (estructura):
  $db = new Conexion();
  $sql = $layout;
  $query = $db->query($sql);

  if ($query) {
    $result['query'] = $query;
    $result['lastId'] = $db->insert_id;
    $result['res'] = 'ok';
  } else {
    $result['res'] = 'error: ' . $db->error;
  }

  return $result;

4) index.php
- Landing simple con Bootstrap 5 CDN
- Un botón o link a pages/usuarios_list.php

5) pages/usuarios_list.php
- Debe incluir require_once de lib/Conexion.php y lib/Functions.php
- Debe crear:
  $functions = new Functions();
- Debe ejecutar un SELECT:
  SELECT id,nombre,correo,rol,activo,created_at FROM usuarios ORDER BY id DESC
- Debe validar:
  if ($getUsers['res'] !== 'ok') die($getUsers['res']);
- Debe mostrar tabla HTML con resultados
- Debe usar el patrón:
  while ($row = $getUsers['query']->fetch_assoc()) {
    foreach ($row as $key => $value) { $$key = $value; }
    ... imprimir $id, $nombre, $correo, $rol, $activo ...
  }

6) assets/css/style.css
- Estilos mínimos para que se vea limpio.

7) assets/js/copy.js
- Script simple para copiar bloques de código (opcional, mínimo).

IMPORTANTE:
- NO incluyas CREATE DATABASE ni CREATE TABLE.
- NO incluyas INSERT/UPDATE/DELETE.
- SOLO SELECT.

Prompt 3 — Ejercicios de SELECT (solo enunciados)

Genera 10 ejercicios SOLO de SELECT para la tabla usuarios (id,nombre,correo,rol,activo,created_at),
de nivel principiante a intermedio, incluyendo:
- WHERE con activo
- ORDER BY
- LIMIT
- LIKE para nombre/correo
- AS (alias)
NO incluyas respuestas. Solo enunciados.
En la siguiente clase, sobre este mismo mini-proyecto, agregaremos INSERT/UPDATE/DELETE con formularios.

9) Ejercicios para el pizarrón (5)

  1. SELECT: listar usuarios activos, ordenar por nombre A→Z.
  2. SELECT: buscar usuarios cuyo correo contenga “mail” usando LIKE.
  3. SELECT: mostrar solo 5 usuarios más recientes (ORDER BY id DESC + LIMIT).
  4. Explica: ¿de dónde sale $getUsers['query'] y qué tipo de objeto es?
  5. Explica: ¿qué hace fetch_assoc() y qué hace num_rows?