Programació Web¶
Variables de servidor¶
PHP emmagatzema la informació del servidor i de les peticions HTTP a sis vectors globals:
$_ENV: informació sobre les variables d'entorn$_GET: paràmetres enviats a la petició GET$_POST: paràmetres enviats a l'enviament POST$_COOKIE: conté les cookies de la petició, les claus de l'array són els noms de les galetes$_SERVER: informació sobre el servidor$_FILES: informació sobre els fitxers carregats via upload
Si ens centrem a l'array $_SERVER podem consultar les propietats següents:
PHP_SELF: nom de l'script executat, relatiu al document root (p.ex:/tienda/carrito.php)SERVER_SOFTWARE: (p. ex.: Apache)SERVER_NAME: domini, àlies DNS (per exemple:www.elche.es)REQUEST_METHOD·LICITUD: GETREQUEST_URI: URI, sense el dominiQUERY_STRING: tot el que va després de?a l'URL (per exemple:heroe=Batman&nom=Bruce)
Més informació a https://www.php.net/manual/es/reserved.variables.server.php
<?php
echo $_SERVER["PHP_SELF"]."<br>"; ///u4/401server.php
echo $_SERVER["SERVER_SOFTWARE"]."<br>"; // Apache/2.4.46 (Win64) OpenSSL/1.1.1g PHP/7.4.9
echo $_SERVER["REQUEST_METHOD"]."<br>"; // GET
echo $_SERVER["REQUEST_URI"]."<br>"; // /u4/401server.php?heroe=Batman
echo $_SERVER["QUERY_STRING"]."<br>"; // heroe=Batman
Altres propietats relacionades:
PATH_INFO: ruta extra després de la petició. Si la URL éshttp://www.php.com/php/pathInfo.php/algo/cosa?foo=bar, llavors$_SERVER['PATH_INFO']serà/alguna_cosa/cosa.REMOTE_HOST: hostname que va fer la peticióREMOTE_ADDR: IP del clientAUTH_TYPE: tipus d'autenticació (p.ex: Basic)REMOTE_USER: nom de l'usuari autenticat
Apache crea una clau per a cada capçalera HTTP, en majúscules i substituint els guions per subratllats:
HTTP_USER_AGENTagent (navegador)HTTP_REFERER: pàgina des de la qual es va fer la petició
<?php
echo $_SERVER["HTTP_USER_AGENT"]."<br>";
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15
Formularis¶
A l'hora d'enviar un formulari, hem de tenir clar quan fer servir GET o POST
- GET: els paràmetres es passen a la URL
- <2048 caràcters, només ASCII
- Permet emmagatzemar l'adreça completa (marcador/historial)
- Idempotent: dues trucades amb les mateixes dades sempre han de donar el mateix resultat
-
El navegador pot escorcollar les trucades
-
POST: paràmetres ocults (no encriptats)
-
Sense límit de dades, permet dades binàries.
- No es poden escorcollar
- No idempotent → actualitzar la BBDD
Així doncs, per recollir les dades accedirem a l'array depenent del mètode del formulari que ens ha invocat:
<?php
$par = $_GET["parametro"]
$par = $_POST["parametro"]
Per als següents apartats ens basarem en el següent exemple:
<form action="formulario.php" method="GET">
<p><label for="nombre">Nombre del alumno:</label>
<input type="text" name="nombre" id="nombre" value="" />
</p>
<p><input type="checkbox" name="modulos[]" id="modulosDWES" value="DWES" />
<label for="modulosDWES">Desarrollo web en entorno servidor</label>
</p>
<p><input type="checkbox" name="modulos[]" id="modulosDWEC" value="DWEC" />
<label for="modulosDWEC">Desarrollo web en entorno cliente</label>
</p>
<input type="submit" value="Enviar" name="enviar" />
</form>
Validació¶
Pel que fa a la validació, és convenient sempre fer validació doble:
- Al client mitjançant JS
- A servidor, abans de trucar a negoci, és convenient tornar a validar les dades.
<?php
if (isset($_GET["parametro"])) {
$par = $_GET["parametro"];
// comprobar si $par tiene el formato adecuado, su valor, etc...
}
Llibreries de validació
Hi ha diverses llibreries que faciliten la validació dels formularis, com són respect/validation o particle/validador. En Laravel es treballa en la validació de manera declarativa.
Paràmetre multivalor¶
Hi ha elements HTML que envien diversos valors:
select multipleode selecció múltiplecheckboxocaselles de selecció
Per recollir les dades, el nom de l'element ha de ser un matriu.
<select name="lenguajes[]" multiple="true">
<option value="c">C</option>
<option value="java">Java</option>
<option value="php">PHP</option>
<option value="python">Python</option>
</select>
<input type="checkbox" name="lenguajes[]" value="c" /> C<br/>
<input type="checkbox" name="lenguajes[]" value="java" /> Java<br/>
<input type="checkbox" name="lenguajes[]" value="php" /> Php<br/>
<input type="checkbox" name="lenguajes[]" value="python" /> Python<br/>
De manera que després en recollir les dades:
<?php
$lenguajes = $_GET["lenguajes"];
foreach ($lenguajes as $lenguaje) {
echo "$lenguaje <br/>";
}
Tornant a emplenar un formulari¶
Un sticky form és un formulari que recorda els valors. Per això, hem d'emplenar els atributs value dels elements HTML amb la informació que contenien:
<?php
if (!empty($_POST['modulos']) && !empty($_POST['nombre'])) {
// Aquí se incluye el código a ejecutar cuando los datos son correctos
} else {
// Generamos el formulario
$nombre = $_POST['nombre'] ?? "";
$modulos = $_POST['modulos'] ?? [];
?>
<form action="<?php echo $_SERVER['PHP_SELF'];?>" method="POST">
<p><label for="nombre">Nombre del alumno:</label>
<input type="text" name="nombre" id="nombre" value="<?= $nombre ?>" />
</p>
<p><input type="checkbox" name="modulos[]" id="modulosDWES" value="DWES"
<?php if(in_array("DWES",$modulos)) echo 'checked="checked"'; ?> />
<label for="modulosDWES">Desarrollo web en entorno servidor</label>
</p>
<p><input type="checkbox" name="modulos[]" id="modulosDWEC" value="DWEC"
<?php if(in_array("DWEC",$modulos)) echo 'checked="checked"'; ?> />
<label for="modulosDWEC">Desarrollo web en entorno cliente</label>
</p>
<input type="submit" value="Enviar" name="enviar"/>
</form>
<?php } ?>
Pujant fitxers¶
S'emmagatzemen al servidor a l'array $_FILES amb el nom del camp del tipus file del formulari.
<form enctype="multipart/form-data" action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST">
Archivo: <input name="archivoEnviado" type="file" />
<br/>
<input type="submit" name="btnSubir" value="Subir" />
</form>
Configuració a php.ini
file_uploads: encès / apagatupload_max_filesize: 2Mupload_tmp_dir: directori temporal. No cal configurar-lo, agafarà el predeterminat del sistemapost_max_size: mida màxima de les dades POST. Ha de ser més gran a upload_max_filesize.max_file_uploads: nombre màxim de fitxers que es poden carregar alhora.max_input_time: temps màxim emprat a la càrrega (GET/POST i upload → normalment es configura a 60)memory_limit: 128Mmax_execution_time: temps d'execució d'un script (no té en compte l'upload)
Per carregar els fitxers, accedim a l'array $_FILES:
<?php
if (isset($_POST['btnSubir']) && $_POST['btnSubir'] == 'Subir') {
if (is_uploaded_file($_FILES['archivoEnviado']['tmp_name'])) {
// subido con éxito
$nombre = $_FILES['archivoEnviado']['name'];
move_uploaded_file($_FILES['archivoEnviado']['tmp_name'], "./uploads/{$nombre}");
echo "<p>Archivo $nombre subido con éxito</p>";
}
}
Cada fitxer carregat a $_FILES té:
name: nomtmp_name: ruta temporalsize: mida en bytestype: tipus MIMEerror: si hi ha error, conté un missatge. Si ok → 0.
Capçaleres de resposta¶
Ha de ser el primer a tornar. Es retornen mitjançant la funció header(cadena). Mitjançant les capçaleres podem configurar el tipus de contingut, el temps d'expiració, redireccionar el navegador, especificar errors HTTP, etc.
<?php header("Content-Type: text/plain"); ?>
<?php header("Location: https://docencia.xaviersastre.cat/index.html");
exit();
Es pot comprovar a les eines del desenvolupador dels navegadors web mitjançant Eines de desenvolupador → Xarxa → Capçaleres.
És molt comú configurar les capçaleres per evitar consultes a la memòria cau o provocar-ne la renovació:
<?php
header("Expires: Sun, 31 Jan 2021 23:59:59 GMT");
// tres hores
$now = time();
$horas3 = gmdate("D, d M Y H:i:s", $now + 60 * 60 * 3) . " GMT";
header("Expires: {$horas3}");
// un any
$now = time();
$anyo1 = gmdate("D, d M Y H:i:s", $now + 365 * 86400) . " GMT"; // corregit també l'error: 86440 → 86400
header("Expires: {$anyo1}");
// se marca com expirat (data en el passat)
$pasado = gmdate("D, d M Y H:i:s") . " GMT";
header("Expires: {$pasado}");
// evitem caché de navegador i/o proxy
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
Gestió de l'estat¶
HTTP és un protocol stateless, sense estat. Per això, se simula l'estat mitjançant l'ús de galetes, tokens o la sessió. L'estat és necessari per a processos com ara el carret de la compra, operacions associades a un usuari, etc... El mecanisme de PHP per gestionar la sessió utilitza cookies de forma interna. Les galetes s'emmagatzemen al navegador, i la sessió al servidor web.
Galetes¶
Les galetes s'emmagatzemen a l'array global $_COOKIE. El que col·loquem dins de l'array, es guardarà al client. Cal tenir present que el client pot no voler emmagatzemar-les.
Hi ha una limitació de 20 cookies per domini i 300 en total al navegador.
A PHP, per crear una galeta s'utilitza la funció setcookie:
<?php
setcookie(nombre [, valor [, expira [, ruta [, dominio [, seguro [, httponly ]]]]]]);
setcookie(nombre [, valor = "" [, opciones = [] ]] )
?>
Cal destacar que el nom no pot contenir espais ni el caràcter ;. Pel que fa al contingut de la galeta, no pot superar els 4 KB.
Per exemple, mitjançant cookies podem comprovar la quantitat de visites diferents que realitza un usuari:
<?php
$accesosPagina = 1; // Comença en 1 pel primer accès
if (isset($_COOKIE['accesos'])) {
$accesosPagina = $_COOKIE['accesos'] + 1; // Suma un a l'últim valor
}
// Opcional: cookie per a 30 dies
setcookie('accesos', $accesosPagina, time() + (86400 * 30), "/");
// Mostrar valor actual
echo "Has visitat aquesta pàgina $accesosPagina cops.";
?>
Inspeccionant les cookies
Si volem veure que contenen les cookies que tenim emmagatzemades al navegador, es pot comprovar el seu valor a Eines de desenvolupament → Aplicació → Emmagatzematge
El temps de vida de les galetes pot ser tan llarg com el lloc web on resideixen. Elles seguiran allà, fins i tot si el navegador està tancat o obert.
Per esborrar una galeta es pot posar que expirin en el passat:
<?php
setcookie(nombre, "", 1) // pasado
O que caduquin dins un període de temps determinat:
<?php
setcookie(nombre, valor, time() + 3600) // Caducan dentro de una hora
S'utilitzen per a:
- Recordar els inicis de sessió
- Emmagatzemar valors temporals d'usuari
- Si un usuari està navegant per una llista paginada darticles, ordenats de certa manera, podem emmagatzemar lajustament de la classificació.
L'alternativa al client per emmagatzemar informació al navegador és l'objecte LocalStorage.
Sessió¶
La sessió afegeix la gestió de l'estat a HTTP, emmagatzemant en aquest cas la informació al servidor. Cada visitant té un ID de sessió únic, el qual per defecte s'emmagatzema en una galeta anomenada SESSIONS DE PHP. Si el client no té les galetes actives, l'ID es propaga a cada URL dins del mateix domini. Cada sessió té associat un magatzem de dades mitjançant l'array global $_SESSION, en el qual podem emmagatzemar i recuperar informació.
La sessió comença en executar un script PHP. Es genera un nou ID i es carreguen les dades del magatzem:
Les operacions que podem fer amb la sessió són:
<?php
session_start(); // carga la sesión
session_id() // devuelve el id
$_SESSION[clave] = valor; // inserción
session_destroy(); // destruye la sesión
unset($_SESSION[clave]; // borrado
Vegem mitjançant un exemple com podem inserir en una pàgina dades a la sessió per posteriorment a una altra pàgina accedir a aquestes dades. Per exemple, a sessió1.php tindríem
<?php
session_start(); // inicializamos
$_SESSION["ies"] = "IES Severo Ochoa"; // asignación
$instituto = $_SESSION["ies"]; // recuperación
echo "Estamos en el $instituto ";
?>
<br/>
<a href="sesion2.php">Y luego</a>
I posteriorment podem accedir a la sessió a sesion2.php:
<?php
session_start();
$instituto = $_SESSION["ies"]; // recuperación
echo "Otra vez, en el $instituto ";
?>
Configurant la sessió a php.ini
Les següents propietats de php.ini permeten configurar alguns aspectes de la sessió:
-
session.save_handler: controlador que gestiona com s'emmagatzema (files) -
session.save_path: ruta on s'emmagatzemen els fitxers amb les dades (si tenim un clúster, podríem utilitzar/mnt/sessionsa tots els servidors de manera que apunten a una carpeta compartida) -
session.name: nom de la sessió (PHSESSID) -
session.auto_start: Es pot fer que s'autocarregui amb cada script. Per defecte està deshabilitat -
session.cookie_lifetime: temps de vida per defecte
Més informació a la documentació oficial.
Autenticació d'usuaris¶
Una sessió estableix una relació anònima amb un usuari particular, de manera que podem saber si és el mateix usuari entre dues peticions diferents. Si preparem un sistema de login, podrem saber qui utilitza la nostra aplicació.
Per això, preparem un senzill sistema d'autenticació:
- Mostra el formulari login/password
- Comprovar les dades enviades
- Afegir el login a la sessió
- Comprovar el login a la sessió per realitzar tasques específiques de l'usuari
- Eliminar el login de la sessió quan lusuari la tanca.
Veurem en codi cada pas del procés. Comencem amb l'arxiu index.php:
<form action='login.php' method='post'>
<fieldset>
<legend>Login</legend>
<div><span class='error'><?php echo $error; ?></span></div>
<div class='fila'>
<label for='usuario'>Usuario:</label><br/>
<input type='text' name='inputUsuario' id='usuario' maxlength="50" /><br/>
</div>
<div class='fila'>
<label for='password'>Contraseña:</label><br/>
<input type='password' name='inputPassword' id='password' maxlength="50" /><br/>
</div>
<div class='fila'>
<input type='submit' name='enviar' value='Enviar' />
</div>
</fieldset>
</form>
En fer submit ens porta a login.php, el qual fa de controlador:
<?php
// Comprobamos si ya se ha enviado el formulario
if (isset($_POST['enviar'])) {
$usuario = $_POST['inputUsuario'];
$password = $_POST['inputPassword'];
// validamos que recibimos ambos parámetros
if (empty($usuario) || empty($password)) {
$error = "Debes introducir un usuario y contraseña";
include "index.php";
} else {
if ($usuario == "admin" && $password == "admin") {
// almacenamos el usuario en la sesión
session_start();
$_SESSION['usuario'] = $usuario;
// cargamos la página principal
include "main.php";
} else {
// Si las credenciales no son válidas, se vuelven a pedir
$error = "Usuario o contraseña no válidos!";
include "index.php";
}
}
}
Depenent de l'usuari que s'hagi llogat, anirem a una vista oa una altra. Per exemple, a main.php tindríem:
<?php
// Recuperamos la información de la sesión
if(!isset($_SESSION)) {
session_start();
}
// Y comprobamos que el usuario se haya autentificado
if (!isset($_SESSION['usuario'])) {
die("Error - debe <a href='index.php'>identificarse</a>.<br/>");
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Listado de productos</title>
</head>
<body>
<h1>Bienvenido <?= $_SESSION['usuario'] ?></h1>
<p>Pulse <a href="logout.php">aquí</a> para salir</p>
<p>Volver al <a href="main.php">inicio</a></p>
<h2>Listado de productos</h2>
<ul>
<li>Producto 1</li>
<li>Producto 2</li>
<li>Producto 3</li>
</ul>
</body>
</html>
Finalment, necessitem l'opció de tancar la sessió que col·loquem a logout.php:
<?php
// Recuperamos la información de la sesión
session_start();
// Y la destruimos
session_destroy();
header("Location: index.php");
?>
Autenticació en producció
Actualment l'autenticació d'usuari no es realitza gestionant la sessió directament, sinó que es realitza mitjançant altres mecanismes que veurem d'aquí poc.