Desarrollo Web en Entorno Servidor en DAW: PHP, PDO y sesiones con ejercicios de examen resueltos

Desarrollo Web en Entorno Servidor (DWES) es el módulo de segundo de DAW en el que programas la parte de una web que se ejecuta en el servidor: recibir los datos de un formulario, validarlos, guardarlos en una base de datos, recordar quién ha iniciado sesión y devolver HTML o JSON. En la mayoría de centros se da en PHP con MySQL. Para aprobarlo necesitas dominar cinco ejercicios tipo: procesar y validar un formulario, hacer un CRUD con PDO y sentencias preparadas, montar un login con sesiones y password_hash, usar cookies y crear una API sencilla que devuelva JSON.
Lo difícil de DWES no es la sintaxis de PHP, que se aprende rápido si vienes de Java. Es que cada página es una petición nueva, el servidor no recuerda nada de la anterior y los errores no siempre se ven: a veces solo tienes una página en blanco. Aquí tienes qué entra, cinco ejercicios resueltos y explicados, los errores que más vemos en clase y cómo prepararlo.
Qué entra en Desarrollo Web en Entorno Servidor
DWES es el módulo 0613 del título de DAW (Real Decreto 686/2010) y se da en segundo curso. Las horas, el orden de los temas y el lenguaje cambian según la comunidad y el centro. Lo más habitual es PHP con MySQL o MariaDB, pero hay centros que lo dan en Java (Servlets, JSP o Spring) y algunos en Node.js o Python. Los bloques, sea cual sea el lenguaje, son casi siempre estos:
| Bloque | Qué te piden | Lo que más falla |
|---|---|---|
| Arquitectura y fundamentos | Cliente-servidor, HTTP, sintaxis de PHP, arrays asociativos, funciones, include y require |
Rutas relativas en los include |
| Formularios | Leer $_GET y $_POST, validar, volver a mostrar el formulario con los errores |
Mostrar en la página lo que escribe el usuario sin escaparlo |
| Sesiones y cookies | session_start, $_SESSION, setcookie, login y logout |
El error headers already sent |
| Acceso a datos | Conectar con PDO, CRUD, sentencias preparadas, transacciones | Concatenar variables en el SQL |
| Separar lógica y presentación | Programación orientada a objetos en PHP, patrón MVC, plantillas; en algunos centros, Laravel o Symfony | Un único fichero con SQL, lógica y HTML mezclados |
| Servicios web | API REST que devuelve JSON, consumir una API externa | No indicar el tipo de contenido ni el código HTTP |
| Páginas interactivas y aplicaciones híbridas | Peticiones fetch desde JavaScript al servidor, combinar datos de varias fuentes |
No saber si el fallo está en el cliente o en el servidor |
Los cuatro primeros bloques son la base de casi todas las prácticas y del examen. Los ejercicios de abajo los cubren, y el último entra en servicios web.
Cómo funciona una petición en PHP: lo que hay que entender primero
Cuando el navegador pide alumnos.php, el servidor web le pasa el fichero a PHP, PHP lo ejecuta de arriba abajo y lo que el script imprime (HTML o JSON) es lo que recibe el navegador. Al terminar, PHP olvida todas las variables. La siguiente petición, aunque sea del mismo usuario un segundo después, empieza de cero. Por eso se dice que HTTP no tiene estado.
Esa frase explica medio módulo. Los datos de un formulario llegan en $_POST y solo existen durante esa petición. Para recordar algo entre páginas hay tres opciones: guardarlo en el navegador (cookies), en el servidor asociado a ese usuario (sesiones) o en la base de datos.
Cinco ejercicios típicos resueltos en PHP
Los enunciados cambian según el profesor, pero casi todos los exámenes de PHP en DAW son una variación de estos. El código es para PHP 8.1 o superior. Intenta hacer cada uno antes de mirar la solución.
1. Procesar y validar un formulario con POST
Enunciado: un formulario pide nombre, email y edad. Al enviarlo, se valida en el servidor: nombre obligatorio de hasta 50 caracteres, email válido y edad entre 16 y 99. Si hay errores, el formulario se vuelve a mostrar con los datos escritos y un mensaje junto a cada campo. Si todo está bien, redirige a gracias.php.
<?php
function e(string $texto): string
{
return htmlspecialchars($texto, ENT_QUOTES, 'UTF-8');
}
$errores = [];
$nombre = '';
$email = '';
$edad = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$nombre = trim($_POST['nombre'] ?? '');
$email = trim($_POST['email'] ?? '');
$edad = trim($_POST['edad'] ?? '');
if ($nombre === '' || mb_strlen($nombre) > 50) {
$errores['nombre'] = 'El nombre es obligatorio (máximo 50 caracteres).';
}
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
$errores['email'] = 'El email no es válido.';
}
$opcionesEdad = ['options' => ['min_range' => 16, 'max_range' => 99]];
if (filter_var($edad, FILTER_VALIDATE_INT, $opcionesEdad) === false) {
$errores['edad'] = 'La edad debe ser un número entre 16 y 99.';
}
if (empty($errores)) {
// Aquí se guardarían los datos (ejercicio 2)
header('Location: gracias.php');
exit;
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Alta de alumno</title>
</head>
<body>
<form method="post">
<label>Nombre <input name="nombre" value="<?= e($nombre) ?>"></label>
<?php if (isset($errores['nombre'])): ?><p><?= e($errores['nombre']) ?></p><?php endif; ?>
<label>Email <input name="email" value="<?= e($email) ?>"></label>
<?php if (isset($errores['email'])): ?><p><?= e($errores['email']) ?></p><?php endif; ?>
<label>Edad <input name="edad" value="<?= e($edad) ?>"></label>
<?php if (isset($errores['edad'])): ?><p><?= e($errores['edad']) ?></p><?php endif; ?>
<button type="submit">Enviar</button>
</form>
</body>
</html>
Lo que hay que entender: la lógica va arriba y el HTML abajo, porque header('Location: ...') tiene que ejecutarse antes de que salga cualquier carácter hacia el navegador. Sin el exit, el script sigue ejecutándose después de redirigir. El ?? evita el aviso de clave no definida cuando la página se abre por primera vez.
La trampa está en dos sitios. Primero, filter_var devuelve el valor convertido o false si no es válido, y un valor válido como 0 también es "falso" en una comparación floja: compara siempre con === false. filter_input(INPUT_POST, 'edad', FILTER_VALIDATE_INT) hace lo mismo leyendo directamente de la petición, con una diferencia: devuelve null si el campo no ha llegado. Segundo, todo lo que viene del usuario y vuelve a la página pasa por htmlspecialchars. Si no, alguien escribe <script> en el nombre y se ejecuta en el navegador: un XSS, que se penaliza aunque el formulario funcione.
2. CRUD con PDO y sentencias preparadas
Enunciado: en la base de datos instituto existe la tabla alumno (id, nombre, nota). Haz una página que liste los alumnos con nota mayor o igual que la indicada en la URL (?minima=5), ordenados de mayor a menor, y que permita añadir, modificar la nota y borrar alumnos.
conexion.php:
<?php
function conectar(): PDO
{
$dsn = 'mysql:host=localhost;dbname=instituto;charset=utf8mb4';
return new PDO($dsn, 'root', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
}
alumnos.php:
<?php
require __DIR__ . '/conexion.php';
$pdo = conectar();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$accion = $_POST['accion'] ?? '';
if ($accion === 'crear') {
$stmt = $pdo->prepare('INSERT INTO alumno (nombre, nota) VALUES (:nombre, :nota)');
$stmt->execute([
'nombre' => trim($_POST['nombre'] ?? ''),
'nota' => (float) ($_POST['nota'] ?? 0),
]);
} elseif ($accion === 'actualizar') {
$stmt = $pdo->prepare('UPDATE alumno SET nota = :nota WHERE id = :id');
$stmt->execute([
'nota' => (float) ($_POST['nota'] ?? 0),
'id' => (int) ($_POST['id'] ?? 0),
]);
} elseif ($accion === 'borrar') {
$stmt = $pdo->prepare('DELETE FROM alumno WHERE id = ?');
$stmt->execute([(int) ($_POST['id'] ?? 0)]);
}
header('Location: alumnos.php'); // evita que F5 repita la operación
exit;
}
$minima = filter_input(INPUT_GET, 'minima', FILTER_VALIDATE_FLOAT) ?: 0.0;
$stmt = $pdo->prepare('SELECT id, nombre, nota FROM alumno WHERE nota >= ? ORDER BY nota DESC');
$stmt->execute([$minima]);
$alumnos = $stmt->fetchAll();
?>
<table>
<?php foreach ($alumnos as $a): ?>
<tr>
<td><?= htmlspecialchars($a['nombre']) ?></td>
<td>
<form method="post">
<input type="hidden" name="accion" value="actualizar">
<input type="hidden" name="id" value="<?= $a['id'] ?>">
<input name="nota" value="<?= $a['nota'] ?>">
<button type="submit">Guardar</button>
</form>
</td>
<td>
<form method="post">
<input type="hidden" name="accion" value="borrar">
<input type="hidden" name="id" value="<?= $a['id'] ?>">
<button type="submit">Borrar</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</table>
<form method="post">
<input type="hidden" name="accion" value="crear">
<input name="nombre" placeholder="Nombre" required>
<input name="nota" placeholder="Nota" required>
<button type="submit">Añadir</button>
</form>
Lo que hay que entender:
- Sentencias preparadas siempre que haya datos.
prepareenvía la consulta con huecos (?o:nombre) yexecuteenvía los valores por separado. El valor nunca se mezcla con el texto SQL, así que no hay inyección SQL ni problemas con comillas. Escribir"... WHERE id = $id"funciona en tus pruebas y es el error que más penaliza el profesor. ERRMODE_EXCEPTIONhace que un error de SQL lance una excepción con el mensaje de MySQL. Desde PHP 8 es el valor por defecto, pero conviene ponerlo explícito.charset=utf8mb4en el DSN evita las tildes rotas.- Redirigir después de un POST. Si no rediriges y el usuario pulsa F5, el navegador reenvía el formulario y el alumno se inserta dos veces. Es el patrón POST/Redirect/GET.
?: 0.0cubre los dos casos raros defilter_input: que el parámetro no venga (null) y que no sea un número (false).require __DIR__ . '/conexion.php'busca el fichero en la carpeta del script actual, no en la carpeta desde la que se ejecuta PHP. Con rutas como../conexion.php, elrequirefalla en cuanto el fichero se incluye desde otra carpeta.
Si el SQL todavía te cuesta (JOIN, GROUP BY, claves ajenas), repásalo antes con cómo aprobar SQL y bases de datos en DAM y DAW: en DWES se da por hecho.
3. Login con sesiones y password_hash
Enunciado: la tabla usuario (id, email, hash) guarda los usuarios registrados. Haz un login que compruebe email y contraseña, una página privada a la que solo se pueda entrar con sesión iniciada y un logout.
Al registrar al usuario, la contraseña nunca se guarda tal cual:
$stmt = $pdo->prepare('INSERT INTO usuario (email, hash) VALUES (?, ?)');
$stmt->execute([$email, password_hash($clave, PASSWORD_DEFAULT)]);
login.php:
<?php
session_start();
require __DIR__ . '/conexion.php';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = trim($_POST['email'] ?? '');
$clave = $_POST['clave'] ?? '';
$stmt = conectar()->prepare('SELECT id, email, hash FROM usuario WHERE email = ?');
$stmt->execute([$email]);
$usuario = $stmt->fetch();
if ($usuario && password_verify($clave, $usuario['hash'])) {
session_regenerate_id(true);
$_SESSION['usuario_id'] = $usuario['id'];
$_SESSION['email'] = $usuario['email'];
header('Location: privada.php');
exit;
}
$error = 'Email o contraseña incorrectos.';
}
?>
<form method="post">
<input type="email" name="email" required>
<input type="password" name="clave" required>
<button type="submit">Entrar</button>
<p><?= htmlspecialchars($error) ?></p>
</form>
privada.php:
<?php
session_start();
if (!isset($_SESSION['usuario_id'])) {
header('Location: login.php');
exit;
}
?>
<h1>Hola, <?= htmlspecialchars($_SESSION['email']) ?></h1>
<a href="logout.php">Cerrar sesión</a>
logout.php:
<?php
session_start();
$_SESSION = [];
session_destroy();
header('Location: login.php');
exit;
Lo que hay que entender: la sesión guarda los datos en el servidor y el navegador solo tiene una cookie con un identificador. session_start() va al principio de cada página que use $_SESSION, también en el logout.
password_hash genera un hash distinto cada vez, aunque la contraseña sea la misma, porque añade un valor aleatorio (la sal). Por eso no puedes buscar al usuario con WHERE hash = ?: buscas por email y comparas con password_verify. La columna hash debe ser VARCHAR(255), porque el algoritmo por defecto puede cambiar en futuras versiones y producir hashes más largos. md5 o sha1 no sirven: son tan rápidos que facilitan probar millones de contraseñas por segundo.
Dos detalles que suben nota: el mensaje de error no dice si lo que falla es el email o la contraseña, para no dar pistas, y session_regenerate_id(true) cambia el identificador de sesión al iniciar sesión, lo que evita que alguien reutilice un identificador anterior (fijación de sesión).
4. Recordar una preferencia con cookies
Enunciado: la página saluda en el idioma elegido (español, inglés o francés). El idioma se elige con un enlace (?idioma=en) y se recuerda durante 30 días. Además, muestra cuántas veces ha visitado la página ese navegador.
<?php
$saludos = ['es' => 'Hola', 'en' => 'Hello', 'fr' => 'Bonjour'];
$elegido = $_GET['idioma'] ?? null;
if (in_array($elegido, array_keys($saludos), true)) {
setcookie('idioma', $elegido, [
'expires' => time() + 60 * 60 * 24 * 30,
'path' => '/',
'httponly' => true,
'samesite' => 'Lax',
]);
$idioma = $elegido; // $_COOKIE no tendrá el valor nuevo hasta la próxima petición
} else {
$idioma = $_COOKIE['idioma'] ?? 'es';
if (!in_array($idioma, array_keys($saludos), true)) {
$idioma = 'es';
}
}
$visitas = (int) ($_COOKIE['visitas'] ?? 0) + 1;
setcookie('visitas', (string) $visitas, time() + 60 * 60 * 24 * 365);
?>
<h1><?= $saludos[$idioma] ?></h1>
<p>Visitas desde este navegador: <?= $visitas ?></p>
<p>
<a href="?idioma=es">Español</a> |
<a href="?idioma=en">English</a> |
<a href="?idioma=fr">Français</a>
</p>
Lo que hay que entender: setcookie no cambia $_COOKIE. Lo que hace es añadir una cabecera a la respuesta pidiendo al navegador que guarde la cookie, y el navegador la enviará de vuelta en la siguiente petición. Si después de setcookie lees $_COOKIE['idioma'], tienes el valor antiguo. Por eso el ejemplo usa la variable $idioma. Esta es la pregunta trampa del tema.
Igual que header, setcookie va antes de cualquier salida. Y la cookie la controla el usuario, que puede borrarla o cambiarla: por eso se comprueba el idioma y por eso algo como "es administrador" nunca va en una cookie, sino en la sesión.
5. Una API sencilla que devuelve JSON
Enunciado: crea api/alumnos.php. Con GET devuelve la lista de alumnos en JSON, o un solo alumno si se pasa ?id=. Con POST recibe un JSON con nombre y nota y lo inserta. Usa los códigos HTTP adecuados.
<?php
require __DIR__ . '/../conexion.php';
header('Content-Type: application/json; charset=utf-8');
function responder(int $codigo, array $datos): never
{
http_response_code($codigo);
echo json_encode($datos, JSON_UNESCAPED_UNICODE);
exit;
}
$pdo = conectar();
$metodo = $_SERVER['REQUEST_METHOD'];
if ($metodo === 'GET') {
if (!isset($_GET['id'])) {
$alumnos = $pdo->query('SELECT id, nombre, nota FROM alumno ORDER BY nombre')->fetchAll();
responder(200, $alumnos);
}
$id = filter_var($_GET['id'], FILTER_VALIDATE_INT);
if ($id === false) {
responder(400, ['error' => 'El id debe ser un número entero']);
}
$stmt = $pdo->prepare('SELECT id, nombre, nota FROM alumno WHERE id = ?');
$stmt->execute([$id]);
$alumno = $stmt->fetch();
if ($alumno === false) {
responder(404, ['error' => 'Alumno no encontrado']);
}
responder(200, $alumno);
}
if ($metodo === 'POST') {
$datos = json_decode(file_get_contents('php://input'), true);
if (!is_array($datos)) {
responder(400, ['error' => 'El cuerpo no es un JSON válido']);
}
$nombre = trim((string) ($datos['nombre'] ?? ''));
$nota = filter_var($datos['nota'] ?? null, FILTER_VALIDATE_FLOAT);
if ($nombre === '' || $nota === false) {
responder(400, ['error' => 'Faltan el nombre o la nota']);
}
$stmt = $pdo->prepare('INSERT INTO alumno (nombre, nota) VALUES (?, ?)');
$stmt->execute([$nombre, $nota]);
responder(201, ['id' => (int) $pdo->lastInsertId()]);
}
responder(405, ['error' => 'Método no permitido']);
Lo que hay que entender: una API devuelve datos, no HTML, y la cabecera Content-Type: application/json y el código HTTP importan tanto como el contenido: 200 si va bien, 201 si se ha creado algo, 400 si la petición es incorrecta, 404 si no existe y 405 si el método no está permitido. Cuando el cliente envía JSON, los datos no llegan a $_POST: $_POST solo se rellena con formularios. Hay que leer el cuerpo con file_get_contents('php://input') y convertirlo con json_decode(..., true) para obtener un array asociativo.
Desde el otro lado, esta API se consume con fetch en JavaScript. Si te pierdes con las promesas, lo tienes en cómo aprobar Desarrollo Web en Entorno Cliente. Cuando una petición falle, abre la pestaña Red del navegador: ves la URL, el código HTTP y la respuesta exacta, y sabes en qué lado está el problema.
Los errores que más suspenden en DWES
"Cannot modify header information - headers already sent". Aparece al llamar a header, setcookie o session_start cuando el script ya ha enviado algo: un echo, HTML o incluso una línea en blanco antes de <?php. El mensaje indica dónde empezó la salida ("output started at"). En los ficheros que solo tienen PHP, no pongas el ?> de cierre.
Concatenar variables en el SQL. "SELECT * FROM usuario WHERE email = '$email' AND clave = '$clave'" es inyección SQL. Si alguien escribe ' OR '1'='1 en el campo de la contraseña, la condición se cumple para todas las filas y el login le deja entrar. Usa siempre prepare y execute.
No escapar la salida. Imprimir $_POST['nombre'] o un dato de la base de datos sin htmlspecialchars permite inyectar JavaScript. Se escapa al imprimir en HTML, no al guardar.
Contraseñas en claro o con md5. Es de lo primero que mira el profesor en la práctica del login. Usa password_hash y password_verify.
Olvidar session_start(). Sin él, $_SESSION está vacío y la página privada te devuelve al login una y otra vez.
Validar solo en el cliente. El atributo required y la validación en JavaScript se saltan enviando la petición a mano. La validación que cuenta es la del servidor.
Rutas mal construidas en include y require. Una ruta como ../conexion.php se resuelve desde el directorio de trabajo de PHP, no desde la carpeta del fichero que hace el include, y deja de funcionar en cuanto incluyes desde otra carpeta. Construye las rutas con __DIR__ y te ahorras el "failed to open stream".
Trabajar con los errores ocultos. Una página en blanco casi siempre es un error fatal que no se muestra. En desarrollo activa display_errors y error_reporting = E_ALL en el php.ini, o mira el log del servidor. En producción, al revés: errores al log, nunca a la pantalla. Para buscar el fallo con método, sigue estos pasos para depurar código.
Mezclarlo todo en un fichero. Un index.php de 600 líneas con conexión, consultas, validación y HTML funciona, pero es justo lo que el módulo enseña a evitar. Separa al menos el acceso a datos, la lógica y las vistas.
Cómo prepararlo
Monta el entorno el primer día. XAMPP, Laragon, Docker o lo que use tu centro, con PHP y MySQL o MariaDB funcionando. Comprueba que ves los errores y que conexion.php conecta. Mucha gente pierde media hora del examen con la configuración.
Escribe de memoria las cinco plantillas. Formulario con validación y redirección, conexión PDO con consulta preparada, login con sesión, cookie con setcookie y respuesta JSON con código HTTP. Si las escribes sin mirar, el examen consiste en adaptarlas al enunciado.
Haz una aplicación pequeña de principio a fin. Una gestión de alumnos o de una biblioteca: registro, login, CRUD, página privada y una API. Te obliga a juntar todos los bloques, como las prácticas grandes y el proyecto final de DAW.
Repasa la orientación a objetos. En MVC y en cualquier framework trabajarás con clases, interfaces y espacios de nombres. Si en Java ya te costaba, repasa la programación orientada a objetos explicada: en PHP las ideas son las mismas.
Prueba como un atacante. Antes de entregar, escribe <b>hola</b> y una comilla simple en cada campo, recarga después de cada POST, entra en la página privada sin sesión y llama a la API con datos que falten. Si algo se rompe, es lo mismo que va a probar quien corrige.
Entiende dónde se va a ejecutar. En el mismo curso tienes Despliegue de Aplicaciones Web, donde subes a un servidor real lo que programas aquí: tienes una guía en cómo aprobar Despliegue de Aplicaciones Web. Para las últimas semanas, mira cómo preparar un examen práctico de programación.
Cuándo pedir ayuda
Si haces los ejercicios, entiendes por qué falla una página cuando falla y sabes explicar la diferencia entre sesión y cookie, vas bien. Merece la pena buscar ayuda cuando:
- Tu página sale en blanco o con headers already sent y no sabes por dónde empezar a buscar.
- El login de los apuntes funciona, pero no sabrías explicar qué hacen
session_startnipassword_verify. - La práctica es un único fichero enorme y no sabes cómo separarla en modelo, vista y controlador.
- Tienes DWES pendiente y necesitas aprobarlo para hacer las prácticas en empresa o presentar el proyecto.
En nuestras clases de refuerzo para DAM y DAW trabajamos con tus enunciados y en tu propio proyecto: lo ejecutamos juntos, buscamos dónde se rompe y lo arreglas tú, para que en el examen no dependas de nadie. Si lo que te frena son las consultas, también damos clases de SQL online. Son clases online uno a uno a 25 € la hora, sin matrícula ni permanencia.
Preguntas frecuentes
¿Qué es Desarrollo Web en Entorno Servidor en DAW?
Desarrollo Web en Entorno Servidor (DWES) es el módulo 0613 del ciclo de Desarrollo de Aplicaciones Web y se cursa en segundo. Enseña a programar la parte de una aplicación web que se ejecuta en el servidor: procesar formularios, gestionar sesiones y cookies, acceder a bases de datos, separar la lógica de la presentación con MVC y crear servicios web que devuelven JSON.
¿DWES se da en PHP, en Java o con Spring?
Lo más habitual es PHP con MySQL o MariaDB. Algunos centros lo dan en Java, con Servlets y JSP o con Spring Boot, y otros en Node.js o Python. Los conceptos son los mismos en todos (peticiones HTTP, formularios, sesiones, acceso a datos, MVC y API REST) y solo cambian la sintaxis y las herramientas.
¿Es difícil Desarrollo Web en Entorno Servidor?
Es exigente porque junta programación, bases de datos, HTTP y seguridad, y muchos errores no se ven en pantalla. PHP en sí es sencillo si vienes de Java. Con SQL y orientación a objetos bien asentados y los ejercicios tipo practicados, se aprueba; si arrastras módulos de primero, cuesta bastante más.
¿Qué diferencia hay entre sesiones y cookies en PHP?
Una cookie se guarda en el navegador y viaja en cada petición, así que el usuario puede verla y modificarla. Una sesión guarda los datos en el servidor y el navegador solo tiene una cookie con el identificador de sesión. Para preferencias sin importancia sirve una cookie; para saber quién ha iniciado sesión o qué permisos tiene, se usa la sesión.
¿PDO o mysqli en DWES?
Las dos conectan con MySQL y admiten sentencias preparadas. PDO funciona además con otros sistemas gestores (PostgreSQL, SQLite) casi sin cambiar el código y permite parámetros con nombre como :nombre. Usa la que se dé en tu centro; si puedes elegir, PDO es la opción más extendida.
¿Qué apuntes y herramientas necesito para practicar DWES?
Necesitas un servidor con PHP y MySQL o MariaDB: XAMPP o Laragon son lo más sencillo, y PHP también trae un servidor de pruebas que se arranca con php -S localhost:8000. Como referencia, además de los apuntes de tu profesor, usa el manual oficial de php.net, que explica cada función con ejemplos.