Accés a Bases de Dades

MySQL amb PHP

Connexions, CRUD i Transaccions

Unitat 05 - DWES

Introducció

L'accés a bases de dades és la base de quasi tots els sistemes de gestió actuals

Objetius del Tema

  • ✓ Connectar amb una BD MySQL des de PHP
  • ✓ Realitzar operacions CRUD (Create, Read, Update, Delete)
  • ✓ Gestionar errors i transaccions
  • ✓ Entendre constraints d'integritat
  • ✓ Implementar bones pràctiques

Connectors de Bases de Dades

Sistemes per interconnectar codi amb BD:

  • PDO (PHP Data Objects) - Multiple SGBDs
  • MySQLi (MySQL Improved) - Només MySQL
  • JDBC - Java
  • ADO - Microsoft

En aquest tema usarem MySQLi

Estats en l'Execució de SQL

Cicle de vida d'una operació BD:

1. Configurar paràmetres de connexió
2. Connectar a la BD
3. Executar operació SQL
4. Tractar resultats (si cal)
5. Desconnectar

Operacions Possibles

Escritura (Immediat)

  • INSERT: Afegir dades
  • UPDATE: Modificar dades
  • DELETE: Esborrar dades

Lectura (Cursor)

  • SELECT: Consultar dades
  • Resultat per processament
  • Múltiples files

Transaccions

Transacció: Conjunt d'instruccions SQL que s'executen totes juntes. Si una falla, es desfan totes (ROLLBACK).

États possibles:

  • COMMIT: Confirmar transacció
  • ROLLBACK: Desfer transacció

Exemple: Transferència bancària (resta + suma)

Paràmetres de Connexió

Informació Necessària

  • Servidor: localhost o IP del servidor
  • Usuari: Credencials d'accés
  • Contrasenya: Password de l'usuari
  • Base de dades: Nom de la BD a connectar

Codi PHP - Configuració

// Paràmetres de connexió
$servidor = "localhost";
$usuari = "root";
$contrasenya = "";
$bd = "bdprova";

// Realització de la connexió
$con = mysqli_connect($servidor, $usuari, $contrasenya, $bd);

// Comprovar connexió
if (!$con) {
    die("Error: " . mysqli_connect_error());
}
echo "Connectat correctament!";
                    

Verificació de Connexió

✓ Connexió exitosa:
Variable $con conté la connexió activa
✗ Error de connexió:
Variable $con és FALSE
Usar mysqli_connect_error() per obtenir detalls

Charset - Caràcters Especials

Per evitar problemes amb accents, dièresis, etc:

// Establir charset (UTF-8)
mysqli_set_charset($con, "utf8mb4");

// O dins la connexió
$con = mysqli_connect(
    $servidor, $usuari, $contrasenya, $bd
);
mysqli_set_charset($con, "utf8mb4");
                    

Operacions CRUD

Create • Read • Update • Delete

CREATE - Inserció de Dades

// Instrucció SQL INSERT
$sql = "INSERT INTO estudiant 
        VALUES ('52666666F', 'Toni Blanco', 23, '2GM')";

// Executar
$res = mysqli_query($con, $sql);

// Comprovar
if (mysqli_errno($con) != 0) {
    die("Error inserció: " . mysqli_error($con));
}
echo "Dada inserida correctament!";
echo "ID: " . mysqli_insert_id($con);
                    

READ - Lectura de Dades (Fila Única)

// SELECT simple
$sql = "SELECT nom, edat FROM estudiant 
        WHERE dni = '52222222B'";

$res = mysqli_query($con, $sql);

if (mysqli_errno($con) != 0) {
    die("Error consulta: " . mysqli_error($con));
}

// Obtenir fila
$fila = mysqli_fetch_assoc($res);

if ($fila) {
    echo "Nom: " . $fila['nom'];
    echo "Edat: " . $fila['edat'];
} else {
    echo "No trobat";
}
                    

READ - Lectura de Múltiples Files

$sql = "SELECT nom, edat FROM estudiant";
$res = mysqli_query($con, $sql);

// Recórrer resultats amb cursor
while ($fila = mysqli_fetch_assoc($res)) {
    $nom = $fila['nom'];
    $edat = $fila['edat'];
    echo "Nom: $nom | Edat: $edat <br>";
}

// Alliberar resultats
mysqli_free_result($res);
                    

UPDATE - Modificació de Dades

// Instrucció UPDATE
$sql = "UPDATE estudiant 
        SET edat = 25 
        WHERE dni = '52666666F'";

$res = mysqli_query($con, $sql);

// Comprovar
if (mysqli_errno($con) != 0) {
    die("Error modificació: " . mysqli_error($con));
}

// Mostrar files afectades
$files_afectades = mysqli_affected_rows($con);
echo "Files modificades: " . $files_afectades;
                    

DELETE - Eliminació de Dades

// Instrucció DELETE
$sql = "DELETE FROM estudiant 
        WHERE dni = '52666666F'";

$res = mysqli_query($con, $sql);

// Comprovar
if (mysqli_errno($con) != 0) {
    die("Error eliminació: " . mysqli_error($con));
}

// Mostrar files eliminades
$files_eliminades = mysqli_affected_rows($con);
echo "Files eliminades: " . $files_eliminades;
                    

⚠️ DELETE - Precaucions Importants

Sempre usar WHERE!
DELETE sense WHERE elimina TOTA la taula

Bon:

DELETE FROM estudiant WHERE dni = '52666666F';

Dolent (PERILLÓS!):

DELETE FROM estudiant;  // Elimina TOTS!

Cursors de Dades

Concepte de Cursor

Cursor: Apuntador que navega pels resultats

Permet accedir a dades fila a fila
Necessari quan hi ha múltiples resultats
Funcions:
mysqli_fetch_assoc() - Obtenir fila associativa
mysqli_fetch_row() - Obtenir fila indexada
mysqli_fetch_object() - Obtenir com objecte

Fetch Assoc - Array Associatiu

$sql = "SELECT dni, nom, edat FROM estudiant";
$res = mysqli_query($con, $sql);

// Accés per nom de camp
$fila = mysqli_fetch_assoc($res);
echo $fila['nom'];      // Array associatiu
echo $fila['edat'];
                    

Avantatge: Codi més llegible

Fetch Row - Array Indexat

$sql = "SELECT dni, nom, edat FROM estudiant";
$res = mysqli_query($con, $sql);

// Accés per posició
$fila = mysqli_fetch_row($res);
echo $fila[0];      // dni
echo $fila[1];      // nom
echo $fila[2];      // edat
                    

Avantatge: Lleugerament més ràpid

Búcles de Lectura

// Patterns comuns
$sql = "SELECT * FROM estudiant";
$res = mysqli_query($con, $sql);

// Pattern 1: While amb fetch_assoc
while ($fila = mysqli_fetch_assoc($res)) {
    echo $fila['nom'] . "<br>";
}

// Pattern 2: Contar resultats
$num_resultats = mysqli_num_rows($res);
echo "Total: $num_resultats";

// Alliberar memoria
mysqli_free_result($res);
                    

Transaccions en MySQL

Estructura de Transacció

// Iniciar transacció
mysqli_query($con, "BEGIN");

try {
    // Operació 1
    mysqli_query($con, "UPDATE cuentas SET saldo = saldo - 1000 
                        WHERE id = 1");
    
    // Operació 2
    mysqli_query($con, "UPDATE cuentas SET saldo = saldo + 1000 
                        WHERE id = 2");
    
    // Si tot va bé: COMMIT
    mysqli_query($con, "COMMIT");
    echo "Transferència realizada!";
    
} catch (Exception $e) {
    // Si error: ROLLBACK
    mysqli_query($con, "ROLLBACK");
    echo "Error: " . $e->getMessage();
}
                    

COMMIT vs ROLLBACK

COMMIT

  • Confirma els canvis
  • Es fan permanents
  • No es pot desfer

ROLLBACK

  • Desfà els canvis
  • BD torna a l'estat inicial
  • Seguretat de dades

Exemple Pràctic - Transacció

// Simular eliminació amb rollback
mysqli_query($con, "BEGIN");

$sql = "DELETE FROM estudiant WHERE dni = '52666666F'";
mysqli_query($con, $sql);

// Verificar eliminació
$sql2 = "SELECT COUNT(*) as quants FROM estudiant 
         WHERE dni = '52666666F'";
$res = mysqli_query($con, $sql2);
$fila = mysqli_fetch_assoc($res);
echo "Després eliminar: " . $fila['quants']; // 0

// Desfer els canvis
mysqli_query($con, "ROLLBACK");

// Verificar recuperació
$sql3 = "SELECT COUNT(*) as quants FROM estudiant 
         WHERE dni = '52666666F'";
$res = mysqli_query($con, $sql3);
$fila = mysqli_fetch_assoc($res);
echo "Després rollback: " . $fila['quants']; // 1
                    

Restriccions d'Integritat

Claus Primàries

Restricció d'Identitat:
• No pot ser NULL
• Ha de ser únic (sense duplicats)
• Identifica cada fila

Operacions:

  • INSERT: Verificar clau no existeix
  • DELETE: Sense restriccions
  • UPDATE: Verificar noves claus

Claus Foranes - Integritat Referencial

-- Taula CURS
CREATE TABLE CURS (
    nom VARCHAR(20) PRIMARY KEY,
    hores INT
);

-- Taula ESTUDIANT amb clau forana
CREATE TABLE ESTUDIANT (
    dni VARCHAR(9) PRIMARY KEY,
    nom VARCHAR(50),
    curs VARCHAR(20),
    FOREIGN KEY (curs) REFERENCES CURS(nom)
);
                    

Estratègies de Claus Foranes

Quando s'elimina una fila referenciada:

  • RESTRICT: Impedir eliminació (error)
  • SET NULL: Posar NULL els foranes
  • CASCADE: Eliminar en cascada
-- Exemple CASCADE
ALTER TABLE ESTUDIANT 
ADD FOREIGN KEY (curs) REFERENCES CURS(nom) 
ON DELETE CASCADE 
ON UPDATE CASCADE;
                    

Validar abans d'INSERT

// Validar clau forana ANTES de INSERT
$curs_voler = "1GInf";

$sql_check = "SELECT COUNT(*) as num 
              FROM CURS WHERE nom = '$curs_voler'";
$res = mysqli_query($con, $sql_check);
$fila = mysqli_fetch_assoc($res);

if ($fila['num'] > 0) {
    // Curs existeix - es pot insertar
    $sql_insert = "INSERT INTO ESTUDIANT 
                   VALUES ('52222222B', 'Joan', '1GInf')";
    mysqli_query($con, $sql_insert);
} else {
    echo "Error: Curs no existeix!";
}
                    

Gestió d'Errors

Funcions d'Error

// Obtenir número d'error (0 = sense error)
$error_num = mysqli_errno($con);

// Obtenir descripció d'error
$error_msg = mysqli_error($con);

// Comprovar si hi ha error
if (mysqli_errno($con) != 0) {
    echo "Error: " . mysqli_error($con);
    die();
}

// Exemple complet
$sql = "SELECT * FROM estudiant WHERE dni = '52222222B'";
$res = mysqli_query($con, $sql);

if (!$res) {
    printf("Error: %s\n", mysqli_error($con));
    exit();
}
                    

Informació de Resultats

// Nombre de files afectades (UPDATE, DELETE, INSERT)
$files_afectadas = mysqli_affected_rows($con);

// Nombre de files en resultat SELECT
$num_resultats = mysqli_num_rows($res);

// Nombre de camps en resultat
$num_camps = mysqli_num_fields($res);

// ID de l'últim INSERT (si autoincrement)
$ultimo_id = mysqli_insert_id($con);

// Exemples
$sql = "UPDATE estudiant SET edat = 25 WHERE edat < 20";
mysqli_query($con, $sql);
echo "Actualitzats: " . mysqli_affected_rows($con);
                    

Prepared Statements - Prevenir SQL Injection

// EVITAR (vulnerable a injection)
$dni = $_GET['dni'];
$sql = "SELECT * FROM estudiant WHERE dni = '$dni'";

// USAR (segur)
$dni = $_GET['dni'];
$sql = "SELECT * FROM estudiant WHERE dni = ?";
$stmt = mysqli_prepare($con, $sql);
mysqli_stmt_bind_param($stmt, "s", $dni);
mysqli_stmt_execute($stmt);
$res = mysqli_stmt_get_result($stmt);
$fila = mysqli_fetch_assoc($res);
                    

Bones Pràctiques

Seguretat

  • ✓ Usar prepared statements per entrada externa
  • ✓ Validar i sanititzar dades
  • ✓ Usar contrasenya forta (no root sense password)
  • ✓ Limitar permisos de BD per usuari
  • ✓ Nunca mostrar errors BD a usuari final

Rendiment

  • ✓ Usar índexs en camps que es consulten freqüentment
  • ✓ Limitar SELECT a camps necessaris (no SELECT *)
  • ✓ Usar LIMIT per paginació
  • ✓ Tancar cursors explícitament
  • ✓ Usar connexió pool si múltiples connexions

Mantenibilitat

  • ✓ Separar codi SQL en fitxers o classes
  • ✓ Usar constants per paràmetres de BD
  • ✓ Comentar queries complexes
  • ✓ Usar transaccions per operacions múltiples
  • ✓ Fazer còpies de seguretat regulars

Checklist de Connexió

// ✓ Checklist recomana
✓ Verificar conexió
✓ Establir charset (utf8mb4)
✓ Usar prepared statements
✓ Comprovar errors (mysqli_errno)
✓ Alliberar resultats (mysqli_free_result)
✓ Desconnectar (mysqli_close)
✓ Mantenir secrets seguros (.env)
✓ No fer queries N+1
✓ Loguear errors per debugging
✓ Testejar amb dades reals
                    

Exemple Complet

CRUD d'Estudiants amb Totes les Bones Pràctiques

Estructura del Projecte

proyecto/
├── config/
│   └── db.php          ← Connexió
├── crud/
│   ├── create.php
│   ├── read.php
│   ├── update.php
│   └── delete.php
├── index.php
└── estudiantes.sql     ← Dump BD
                    

config/db.php

<?php
// Paràmetres de connexió
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'academica');

// Connexió
$con = mysqli_connect(
    DB_HOST, DB_USER, DB_PASS, DB_NAME
);

// Verificar
if (!$con) {
    die("Error: " . mysqli_connect_error());
}

// Charset
mysqli_set_charset($con, "utf8mb4");
?>
                    

crud/create.php

<?php
require '../config/db.php';

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $dni = $_POST['dni'] ?? '';
    $nom = $_POST['nom'] ?? '';
    $edat = $_POST['edat'] ?? '';
    $curs = $_POST['curs'] ?? '';
    
    // Validar
    if (empty($dni) || empty($nom)) {
        echo "Error: Camps obligatoris";
        exit;
    }
    
    // Prepared statement
    $sql = "INSERT INTO estudiant (dni, nom, edat, curs) 
            VALUES (?, ?, ?, ?)";
    $stmt = mysqli_prepare($con, $sql);
    mysqli_stmt_bind_param($stmt, "ssis", $dni, $nom, $edat, $curs);
    
    if (mysqli_stmt_execute($stmt)) {
        echo "Estudiant creat!";
    } else {
        echo "Error: " . mysqli_error($con);
    }
    
    mysqli_stmt_close($stmt);
}
?>
                    

crud/read.php

<?php
require '../config/db.php';

// Obtenir tots els estudiants
$sql = "SELECT * FROM estudiant ORDER BY nom";
$res = mysqli_query($con, $sql);

if (!$res) {
    die("Error: " . mysqli_error($con));
}

echo "<table border='1'>";
echo "<tr><th>DNI</th><th>Nom</th><th>Edat</th><th>Curs</th></tr>";

while ($fila = mysqli_fetch_assoc($res)) {
    echo "<tr>";
    echo "<td>" . $fila['dni'] . "</td>";
    echo "<td>" . $fila['nom'] . "</td>";
    echo "<td>" . $fila['edat'] . "</td>";
    echo "<td>" . $fila['curs'] . "</td>";
    echo "</tr>";
}
echo "</table>";

mysqli_free_result($res);
mysqli_close($con);
?>
                    

Resum del Tema

  • Connexió: mysqli_connect() amb paràmetres
  • CRUD: INSERT, SELECT, UPDATE, DELETE
  • Cursors: mysqli_fetch_assoc() per resultats
  • Transaccions: BEGIN, COMMIT, ROLLBACK
  • Integritat: Claus primàries i foranes
  • Errors: mysqli_errno(), mysqli_error()
  • Seguretat: Prepared statements
  • Bones pràctiques: Validació, charset, closes

Recursos i Enllaços

  • MySQL: https://dev.mysql.com/doc/
  • MySQLi: https://www.php.net/manual/mysqli
  • PDO: https://www.php.net/manual/pdo
  • SQL Injection: OWASP.org
  • Best Practices: PHP-FIG.org

¿Preguntes?

Accés a Bases de Dades amb PHP i MySQL

Presentació creada amb Reveal.js