Contenuti




PHP: guida completa dai fondamenti allo sviluppo web avanzato

Fondamenti, OOP, database, framework e best practices


PHP: guida completa dai fondamenti allo sviluppo web avanzato

PHP (PHP Hypertext Preprocessor) è uno dei linguaggi di programmazione più utilizzati per lo sviluppo web backend. Nato nel 1995, PHP alimenta oltre il 75% dei siti web nel mondo, inclusi giganti come Facebook, Wikipedia e WordPress.

In questo articolo
  • Facile da imparare: Sintassi intuitiva e curva di apprendimento graduale
  • Versatile: Perfetto per siti web, API, applicazioni CLI
  • Supporto database: Integrazione nativa con MySQL, PostgreSQL, MongoDB
  • Community: Vasta community e framework maturi (Laravel, Symfony, CodeIgniter)
  • Performance: PHP 8+ offre performance eccellenti con JIT compiler
  • Hosting: Supportato dalla maggior parte dei provider di hosting
Nota per ambienti reali
Usa sempre versioni aggiornate di PHP, estensioni compatibili e configurazioni separate per sviluppo e produzione.

Indice della Guida

  1. Introduzione e setup
  2. Fondamenti di PHP
  3. Controllo del Flusso
  4. Funzioni
  5. Array e Stringhe
  6. Programmazione Orientata agli Oggetti
  7. Database e MySQL
  8. Gestione dei Form
  9. Sessioni e Cookie
  10. Gestione File
  11. Framework e Composer
  12. Sicurezza
  13. Best Practices

Introduzione e setup

Cos’è PHP?

PHP è un linguaggio di scripting server-side interpretato, specificamente progettato per lo sviluppo web. Il codice PHP viene eseguito sul server e il risultato (tipicamente HTML) viene inviato al browser del cliente.

Installazione dell’ambiente di sviluppo

XAMPP (Windows/macOS/Linux)

  1. Scaricare XAMPP da apachefriends.org
  2. Installare e avviare Apache e MySQL
  3. Creare file PHP in htdocs/

LAMP Stack (Linux)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Ubuntu/Debian
sudo apt update
sudo apt install apache2 mysql-server php libapache2-mod-php php-mysql

# Avviare servizi
sudo systemctl start apache2
sudo systemctl start mysql

# Testare installazione
echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/info.php

Docker (tutti gli OS)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# docker-compose.yml
version: '3.8'
services:
  web:
    image: php:8.2-apache
    ports:
      - "8080:80"
    volumes:
      - ./src:/var/www/html

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: testdb
    ports:
      - "3306:3306"

Il tuo primo script PHP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php
// Primo script PHP
echo "Ciao, mondo!";

// Questo è un commento
/*
   Commento
   multi-linea
*/

// Informazioni sul PHP
phpinfo();
?>

Fondamenti di PHP

Sintassi Base

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
// I tag PHP iniziano sempre con <?php

// Variabili (iniziano con $)
$nome = "Gianluca";
$età = 30;
$altezza = 1.75;
$isStudent = true;

// Output
echo "Mi chiamo $nome e ho $età anni<br>";
echo 'Altezza: ' . $altezza . 'm<br>';
print("Studente: " . ($isStudent ? "Sì" : "No"));

// Fine tag PHP (opzionale se il file è solo PHP)
?>

Tipi di Dati

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
// Scalari
$integer = 42;
$float = 3.14159;
$string = "Ciao mondo";
$boolean = true;

// Composti
$array = [1, 2, 3, 4, 5];
$object = new stdClass();

// Speciali
$null_var = null;
$resource = fopen('file.txt', 'r');

// Controllo del tipo
var_dump($integer);
echo gettype($string);

// Conversioni di tipo
$num_string = "123";
$number = (int)$num_string;     // Cast esplicito
$auto_number = $num_string + 0; // Conversione automatica
?>

Operatori

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
$a = 10;
$b = 3;

// Aritmetici
echo $a + $b;  // 13 - Addizione
echo $a - $b;  // 7  - Sottrazione
echo $a * $b;  // 30 - Moltiplicazione
echo $a / $b;  // 3.333... - Divisione
echo $a % $b;  // 1  - Modulo
echo $a ** $b; // 1000 - Esponenziazione (PHP 5.6+)

// Assegnazione
$c = $a;       // Assegnazione semplice
$c += $b;      // $c = $c + $b
$c -= $b;      // $c = $c - $b
$c .= " testo"; // Concatenazione

// Confronto
var_dump($a == $b);   // false - Uguaglianza
var_dump($a === $b);  // false - Identità (tipo e valore)
var_dump($a != $b);   // true  - Disuguaglianza
var_dump($a !== $b);  // true  - Non identità
var_dump($a <=> $b);  // 1     - Spaceship operator (PHP 7+)

// Logici
$x = true;
$y = false;
var_dump($x && $y);   // false - AND
var_dump($x || $y);   // true  - OR
var_dump(!$x);        // false - NOT

// Incremento/Decremento
echo ++$a;  // Pre-incremento
echo $a++;  // Post-incremento
echo --$b;  // Pre-decremento
echo $b--;  // Post-decremento
?>

Costanti

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
// Definire costanti
define('PI', 3.14159);
const GRAVITY = 9.8;

// Costanti magiche
echo __FILE__;      // Percorso completo del file
echo __LINE__;      // Numero di linea corrente
echo __FUNCTION__; // Nome della funzione corrente
echo __CLASS__;    // Nome della classe corrente
echo __METHOD__;   // Nome del metodo corrente

// Usare costanti
echo "Il valore di PI è: " . PI;
echo "La gravità è: " . GRAVITY . " m/s²";
?>

Controllo del Flusso

Condizioni

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
$voto = 85;

// if-elseif-else
if ($voto >= 90) {
    $giudizio = "Eccellente";
} elseif ($voto >= 80) {
    $giudizio = "Buono";
} elseif ($voto >= 60) {
    $giudizio = "Sufficiente";
} else {
    $giudizio = "Insufficiente";
}

echo "Voto: $voto - Giudizio: $giudizio<br>";

// Operatore ternario
$status = ($voto >= 60) ? "Promosso" : "Bocciato";
echo "Status: $status<br>";

// Null coalescing operator (PHP 7+)
$nome = $_GET['nome'] ?? 'Guest';
echo "Benvenuto, $nome!<br>";

// Switch
$giorno = 3;
switch ($giorno) {
    case 1:
        echo "Lunedì";
        break;
    case 2:
        echo "Martedì";
        break;
    case 3:
        echo "Mercoledì";
        break;
    default:
        echo "Giorno non valido";
        break;
}
?>

Cicli

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?php
// For loop
echo "<h3>Tabellina del 5:</h3>";
for ($i = 1; $i <= 10; $i++) {
    echo "5 x $i = " . (5 * $i) . "<br>";
}

// While loop
echo "<h3>Conto alla rovescia:</h3>";
$countdown = 5;
while ($countdown > 0) {
    echo "$countdown...<br>";
    $countdown--;
}
echo "Via!<br>";

// Do-while loop
echo "<h3>Numero casuale:</h3>";
do {
    $numero = rand(1, 10);
    echo "Numero generato: $numero<br>";
} while ($numero != 7);

// Foreach (per array)
$frutti = ['mela', 'banana', 'arancia', 'kiwi'];
echo "<h3>Lista frutti:</h3>";
foreach ($frutti as $frutto) {
    echo "- $frutto<br>";
}

// Foreach con chiave e valore
$persona = [
    'nome' => 'Mario',
    'cognome' => 'Rossi',
    'età' => 30,
    'città' => 'Roma'
];

echo "<h3>Informazioni persona:</h3>";
foreach ($persona as $campo => $valore) {
    echo "$campo: $valore<br>";
}

// Controllo del flusso nei cicli
echo "<h3>Numeri pari da 1 a 20:</h3>";
for ($i = 1; $i <= 20; $i++) {
    if ($i % 2 != 0) {
        continue; // Salta i numeri dispari
    }

    if ($i > 15) {
        break; // Interrompe il ciclo
    }

    echo "$i ";
}
?>

Funzioni

Definire e Chiamare Funzioni

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
// Funzione semplice
function saluta() {
    return "Ciao!";
}

echo saluta(); // Output: Ciao!

// Funzione con parametri
function salutaPersona($nome, $cognome = "Sconosciuto") {
    return "Ciao, $nome $cognome!";
}

echo salutaPersona("Mario"); // Output: Ciao, Mario Sconosciuto!
echo salutaPersona("Mario", "Rossi"); // Output: Ciao, Mario Rossi!

// Funzione con parametri variabili
function somma(...$numeri) {
    $totale = 0;
    foreach ($numeri as $numero) {
        $totale += $numero;
    }
    return $totale;
}

echo somma(1, 2, 3, 4, 5); // Output: 15

// Funzione con tipo di ritorno (PHP 7+)
function calcola_area_cerchio(float $raggio): float {
    return 3.14159 * $raggio * $raggio;
}

echo calcola_area_cerchio(5.0); // Output: 78.53975

// Funzioni anonime (Closure)
$quadrato = function($x) {
    return $x * $x;
};

echo $quadrato(4); // Output: 16

// Arrow functions (PHP 7.4+)
$cubo = fn($x) => $x * $x * $x;
echo $cubo(3); // Output: 27
?>

Scope delle Variabili

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
$globale = "Sono globale";

function testScope() {
    global $globale;
    $locale = "Sono locale";
    static $statica = 0;

    $statica++;
    echo "Globale: $globale<br>";
    echo "Locale: $locale<br>";
    echo "Statica: $statica<br>";
}

testScope(); // Statica: 1
testScope(); // Statica: 2
?>

Array e Stringhe

Array

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php
// Array indicizzati
$frutti = array('mela', 'banana', 'arancia');
$numeri = [1, 2, 3, 4, 5]; // Sintassi short (PHP 5.4+)

echo $frutti[0]; // Output: mela

// Array associativi
$persona = [
    'nome' => 'Mario',
    'età' => 30,
    'città' => 'Roma'
];

echo $persona['nome']; // Output: Mario

// Array multidimensionali
$studenti = [
    ['nome' => 'Mario', 'voto' => 85],
    ['nome' => 'Anna', 'voto' => 92],
    ['nome' => 'Luigi', 'voto' => 78]
];

echo $studenti[1]['nome']; // Output: Anna

// Funzioni per array
$numeri = [3, 1, 4, 1, 5, 9, 2, 6];

// Ordinamento
sort($numeri);          // Ordina crescente
rsort($numeri);         // Ordina decrescente
asort($persona);        // Ordina mantenendo chiavi
ksort($persona);        // Ordina per chiavi

// Ricerca e filtri
$cercato = array_search(4, $numeri);
$filtrati = array_filter($numeri, function($n) {
    return $n > 5;
});

// Trasformazioni
$quadrati = array_map(function($n) {
    return $n * $n;
}, $numeri);

// Riduzione
$somma = array_reduce($numeri, function($carry, $item) {
    return $carry + $item;
}, 0);

// Utility
echo count($numeri);           // Conta elementi
echo in_array(5, $numeri);     // Verifica presenza
$keys = array_keys($persona);  // Ottiene chiavi
$values = array_values($persona); // Ottiene valori

print_r($numeri); // Debug array
?>

Stringhe

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
$testo = "PHP è fantastico!";

// Informazioni sulla stringa
echo strlen($testo);        // Lunghezza: 18
echo str_word_count($testo); // Numero parole: 3

// Ricerca e sostituzione
$posizione = strpos($testo, "PHP");     // Trova posizione: 0
$sostituito = str_replace("PHP", "JavaScript", $testo);

// Maiuscole e minuscole
echo strtoupper($testo);    // PHP È FANTASTICO!
echo strtolower($testo);    // php è fantastico!
echo ucfirst($testo);       // Php è fantastico!
echo ucwords($testo);       // Php È Fantastico!

// Tagliare e estrarre
echo substr($testo, 0, 3);  // PHP
echo substr($testo, -11);   // fantastico!

// Dividere e unire
$parole = explode(" ", $testo);     // ['PHP', 'è', 'fantastico!']
$ricostruito = implode("-", $parole); // PHP-è-fantastico!

// Pulizia
$sporco = "  Testo con spazi  ";
echo trim($sporco);         // "Testo con spazi"
echo ltrim($sporco);        // "Testo con spazi  "
echo rtrim($sporco);        // "  Testo con spazi"

// Escape e validazione
$pericoloso = "<script>alert('xss')</script>";
echo htmlspecialchars($pericoloso); // &lt;script&gt;alert('xss')&lt;/script&gt;

// Espressioni regolari
$email = "test@example.com";
if (preg_match("/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/", $email)) {
    echo "Email valida";
}

// Formattazione
$prezzo = 1234.56;
echo number_format($prezzo, 2, ',', '.'); // 1.234,56

$data = date('d/m/Y H:i:s');
echo "Oggi è: $data";
?>

Programmazione Orientata agli Oggetti

Classi e Oggetti

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?php
// Definizione di una classe
class Persona {
    // Proprietà (attributi)
    private $nome;
    private $cognome;
    protected $età;
    public $email;

    // Costanti di classe
    const MAGGIORE_ETA = 18;

    // Costruttore
    public function __construct($nome, $cognome, $età = 0) {
        $this->nome = $nome;
        $this->cognome = $cognome;
        $this->età = $età;
    }

    // Metodi getter e setter
    public function getNome() {
        return $this->nome;
    }

    public function setNome($nome) {
        $this->nome = $nome;
    }

    public function getCognome() {
        return $this->cognome;
    }

    public function getEtà() {
        return $this->età;
    }

    public function setEtà($età) {
        if ($età >= 0 && $età <= 150) {
            $this->età = $età;
        } else {
            throw new InvalidArgumentException("Età non valida");
        }
    }

    // Metodo pubblico
    public function nomeCompleto() {
        return $this->nome . " " . $this->cognome;
    }

    public function èMaggiorenne() {
        return $this->età >= self::MAGGIORE_ETA;
    }

    // Metodo magico per la rappresentazione stringa
    public function __toString() {
        return $this->nomeCompleto() . " (" . $this->età . " anni)";
    }

    // Distruttore
    public function __destruct() {
        // Codice di cleanup se necessario
    }
}

// Creare oggetti
$persona1 = new Persona("Mario", "Rossi", 30);
$persona2 = new Persona("Anna", "Verdi", 25);

// Usare i metodi
echo $persona1->nomeCompleto(); // Mario Rossi
echo $persona1; // Mario Rossi (30 anni) - chiama __toString()

// Proprietà pubbliche
$persona1->email = "mario@email.com";
echo $persona1->email;

// Costanti di classe
echo Persona::MAGGIORE_ETA; // 18
?>

Ereditarietà

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?php
// Classe base
class Animale {
    protected $nome;
    protected $specie;

    public function __construct($nome, $specie) {
        $this->nome = $nome;
        $this->specie = $specie;
    }

    public function dormi() {
        return $this->nome . " sta dormendo";
    }

    public function mangia($cibo) {
        return $this->nome . " sta mangiando " . $cibo;
    }

    // Metodo che può essere sovrascritto
    public function versoCaratteristico() {
        return $this->nome . " fa un verso";
    }
}

// Classe derivata
class Cane extends Animale {
    private $razza;

    public function __construct($nome, $razza) {
        parent::__construct($nome, "Cane"); // Chiama costruttore padre
        $this->razza = $razza;
    }

    // Override del metodo padre
    public function versoCaratteristico() {
        return $this->nome . " abbaia: Bau bau!";
    }

    // Metodo specifico della classe
    public function riporta() {
        return $this->nome . " riporta la pallina";
    }

    public function getRazza() {
        return $this->razza;
    }
}

class Gatto extends Animale {
    public function __construct($nome) {
        parent::__construct($nome, "Gatto");
    }

    public function versoCaratteristico() {
        return $this->nome . " miagola: Miau!";
    }

    public function graffia() {
        return $this->nome . " sta graffiando";
    }
}

// Utilizzo
$cane = new Cane("Buddy", "Golden Retriever");
$gatto = new Gatto("Whiskers");

echo $cane->versoCaratteristico(); // Buddy abbaia: Bau bau!
echo $gatto->versoCaratteristico(); // Whiskers miagola: Miau!
echo $cane->riporta(); // Buddy riporta la pallina
?>

Classi Astratte e Interface

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<?php
// Classe astratta
abstract class Veicolo {
    protected $marca;
    protected $modello;

    public function __construct($marca, $modello) {
        $this->marca = $marca;
        $this->modello = $modello;
    }

    // Metodo concreto
    public function getInfo() {
        return $this->marca . " " . $this->modello;
    }

    // Metodo astratto - deve essere implementato dalle classi figlie
    abstract public function avvia();
    abstract public function ferma();
}

// Interface
interface Volabile {
    public function decolla();
    public function atterra();
}

interface Galleggiabile {
    public function naviga();
}

// Implementazione
class Auto extends Veicolo {
    private $carburante;

    public function __construct($marca, $modello, $carburante) {
        parent::__construct($marca, $modello);
        $this->carburante = $carburante;
    }

    public function avvia() {
        return "L'auto " . $this->getInfo() . " è in moto";
    }

    public function ferma() {
        return "L'auto " . $this->getInfo() . " è ferma";
    }
}

class Aereo extends Veicolo implements Volabile {
    public function avvia() {
        return "L'aereo " . $this->getInfo() . " ha acceso i motori";
    }

    public function ferma() {
        return "L'aereo " . $this->getInfo() . " ha spento i motori";
    }

    public function decolla() {
        return "L'aereo " . $this->getInfo() . " sta decollando";
    }

    public function atterra() {
        return "L'aereo " . $this->getInfo() . " sta atterrando";
    }
}

// Utilizzo
$auto = new Auto("Fiat", "500", "Benzina");
$aereo = new Aereo("Boeing", "747");

echo $auto->avvia();
echo $aereo->decolla();
?>

Trait e Namespace

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php
namespace App\Models;

// Trait per funzionalità condivise
trait Timestampable {
    private $created_at;
    private $updated_at;

    public function setCreatedAt($datetime) {
        $this->created_at = $datetime;
    }

    public function setUpdatedAt($datetime) {
        $this->updated_at = $datetime;
    }

    public function touch() {
        $this->updated_at = date('Y-m-d H:i:s');
    }
}

trait Sluggable {
    public function generateSlug($text) {
        return strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $text)));
    }
}

// Classe che usa i trait
class Article {
    use Timestampable, Sluggable;

    private $title;
    private $content;
    private $slug;

    public function __construct($title, $content) {
        $this->title = $title;
        $this->content = $content;
        $this->slug = $this->generateSlug($title);
        $this->setCreatedAt(date('Y-m-d H:i:s'));
        $this->setUpdatedAt(date('Y-m-d H:i:s'));
    }

    public function getTitle() {
        return $this->title;
    }

    public function getSlug() {
        return $this->slug;
    }
}

// Utilizzo
$article = new Article("La Guida Completa a PHP", "Contenuto dell'articolo...");
echo $article->getSlug(); // la-guida-completa-a-php
?>

Database e MySQL

Nota database
Preferisci PDO con query preparate e imposta sempre il charset a utf8mb4 per evitare problemi di encoding.

Connessione con MySQLi

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
// Parametri di connessione
$host = 'localhost';
$username = 'root';
$password = '';
$database = 'test_db';

// Connessione procedurale
$connection = mysqli_connect($host, $username, $password, $database);

if (!$connection) {
    die("Connessione fallita: " . mysqli_connect_error());
}

// Query di selezione
$query = "SELECT id, nome, email FROM utenti";
$result = mysqli_query($connection, $query);

if (mysqli_num_rows($result) > 0) {
    echo "<table border='1'>";
    echo "<tr><th>ID</th><th>Nome</th><th>Email</th></tr>";

    while ($row = mysqli_fetch_assoc($result)) {
        echo "<tr>";
        echo "<td>" . $row['id'] . "</td>";
        echo "<td>" . $row['nome'] . "</td>";
        echo "<td>" . $row['email'] . "</td>";
        echo "</tr>";
    }
    echo "</table>";
} else {
    echo "Nessun risultato trovato";
}

// Chiudere connessione
mysqli_close($connection);
?>

PDO (PHP Data Objects)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
<?php
class DatabaseManager {
    private $pdo;

    public function __construct($host, $dbname, $username, $password) {
        try {
            $dsn = "mysql:host=$host;dbname=$dbname;charset=utf8mb4";
            $options = [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => false,
            ];

            $this->pdo = new PDO($dsn, $username, $password, $options);
        } catch (PDOException $e) {
            throw new Exception("Errore di connessione: " . $e->getMessage());
        }
    }

    // INSERT
    public function insertUser($nome, $email, $password) {
        $sql = "INSERT INTO utenti (nome, email, password) VALUES (?, ?, ?)";
        $stmt = $this->pdo->prepare($sql);

        $hashed_password = password_hash($password, PASSWORD_DEFAULT);

        return $stmt->execute([$nome, $email, $hashed_password]);
    }

    // SELECT
    public function getUserById($id) {
        $sql = "SELECT * FROM utenti WHERE id = ?";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$id]);

        return $stmt->fetch();
    }

    public function getAllUsers() {
        $sql = "SELECT id, nome, email, created_at FROM utenti ORDER BY nome";
        $stmt = $this->pdo->query($sql);

        return $stmt->fetchAll();
    }

    // UPDATE
    public function updateUser($id, $nome, $email) {
        $sql = "UPDATE utenti SET nome = ?, email = ? WHERE id = ?";
        $stmt = $this->pdo->prepare($sql);

        return $stmt->execute([$nome, $email, $id]);
    }

    // DELETE
    public function deleteUser($id) {
        $sql = "DELETE FROM utenti WHERE id = ?";
        $stmt = $this->pdo->prepare($sql);

        return $stmt->execute([$id]);
    }

    // Transazioni
    public function transferFunds($from_account, $to_account, $amount) {
        try {
            $this->pdo->beginTransaction();

            // Debita dal conto di partenza
            $sql1 = "UPDATE conti SET saldo = saldo - ? WHERE id = ?";
            $stmt1 = $this->pdo->prepare($sql1);
            $stmt1->execute([$amount, $from_account]);

            // Accredita al conto di destinazione
            $sql2 = "UPDATE conti SET saldo = saldo + ? WHERE id = ?";
            $stmt2 = $this->pdo->prepare($sql2);
            $stmt2->execute([$amount, $to_account]);

            $this->pdo->commit();
            return true;
        } catch (Exception $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }
}

// Utilizzo
try {
    $db = new DatabaseManager('localhost', 'test_db', 'root', '');

    // Inserire nuovo utente
    $db->insertUser('Mario Rossi', 'mario@email.com', 'password123');

    // Ottenere tutti gli utenti
    $users = $db->getAllUsers();
    foreach ($users as $user) {
        echo $user['nome'] . " - " . $user['email'] . "<br>";
    }

} catch (Exception $e) {
    echo "Errore: " . $e->getMessage();
}
?>

Gestione dei Form

Nota sui form
Valida sempre lato server: i controlli client-side non sono sufficienti.

Form HTML e Validazione

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<?php
// form.html
?>
<!DOCTYPE html>
<html>
<head>
    <title>Form di Registrazione</title>
</head>
<body>
    <form action="process_form.php" method="POST">
        <label for="nome">Nome:</label>
        <input type="text" id="nome" name="nome" required><br><br>

        <label for="email">Email:</label>
        <input type="email" id="email" name="email" required><br><br>

        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required><br><br>

        <label for="età">Età:</label>
        <input type="number" id="età" name="età" min="18" max="100"><br><br>

        <label for="bio">Biografia:</label>
        <textarea id="bio" name="bio" rows="4" cols="50"></textarea><br><br>

        <input type="submit" value="Registrati">
    </form>
</body>
</html>

<?php
// process_form.php
class FormValidator {
    private $errors = [];

    public function validateName($name) {
        if (empty(trim($name))) {
            $this->errors['nome'] = "Il nome è obbligatorio";
            return false;
        }

        if (strlen($name) < 2) {
            $this->errors['nome'] = "Il nome deve avere almeno 2 caratteri";
            return false;
        }

        return true;
    }

    public function validateEmail($email) {
        if (empty($email)) {
            $this->errors['email'] = "L'email è obbligatoria";
            return false;
        }

        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $this->errors['email'] = "Formato email non valido";
            return false;
        }

        return true;
    }

    public function validatePassword($password) {
        if (empty($password)) {
            $this->errors['password'] = "La password è obbligatoria";
            return false;
        }

        if (strlen($password) < 8) {
            $this->errors['password'] = "La password deve avere almeno 8 caratteri";
            return false;
        }

        if (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/', $password)) {
            $this->errors['password'] = "La password deve contenere almeno una minuscola, una maiuscola e un numero";
            return false;
        }

        return true;
    }

    public function validateAge($age) {
        if (!empty($age)) {
            if (!is_numeric($age) || $age < 18 || $age > 100) {
                $this->errors['età'] = "L'età deve essere compresa tra 18 e 100 anni";
                return false;
            }
        }

        return true;
    }

    public function getErrors() {
        return $this->errors;
    }

    public function hasErrors() {
        return !empty($this->errors);
    }
}

// Processare il form
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $validator = new FormValidator();

    // Sanitize input
    $nome = htmlspecialchars(trim($_POST['nome'] ?? ''));
    $email = filter_var($_POST['email'] ?? '', FILTER_SANITIZE_EMAIL);
    $password = $_POST['password'] ?? '';
    $età = filter_var($_POST['età'] ?? '', FILTER_SANITIZE_NUMBER_INT);
    $bio = htmlspecialchars(trim($_POST['bio'] ?? ''));

    // Validazione
    $validator->validateName($nome);
    $validator->validateEmail($email);
    $validator->validatePassword($password);
    $validator->validateAge($età);

    if ($validator->hasErrors()) {
        // Mostra errori
        echo "<h2>Errori di validazione:</h2>";
        foreach ($validator->getErrors() as $field => $error) {
            echo "<p><strong>$field:</strong> $error</p>";
        }
        echo '<a href="form.html">Torna al form</a>';
    } else {
        // Salva nel database
        try {
            $db = new DatabaseManager('localhost', 'test_db', 'root', '');
            $success = $db->insertUser($nome, $email, $password);

            if ($success) {
                echo "<h2>Registrazione completata con successo!</h2>";
                echo "<p>Benvenuto, $nome!</p>";
            } else {
                echo "<h2>Errore durante la registrazione</h2>";
            }
        } catch (Exception $e) {
            echo "<h2>Errore del database:</h2>";
            echo "<p>" . $e->getMessage() . "</p>";
        }
    }
}
?>

Upload File

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
<?php
class FileUploader {
    private $upload_dir = 'uploads/';
    private $allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
    private $max_size = 5242880; // 5MB in bytes

    public function __construct($upload_dir = null) {
        if ($upload_dir) {
            $this->upload_dir = $upload_dir;
        }

        // Crea directory se non esiste
        if (!is_dir($this->upload_dir)) {
            mkdir($this->upload_dir, 0755, true);
        }
    }

    public function upload($file_input_name) {
        // Verifica se il file è stato caricato
        if (!isset($_FILES[$file_input_name]) || $_FILES[$file_input_name]['error'] !== UPLOAD_ERR_OK) {
            throw new Exception('Errore durante il caricamento del file');
        }

        $file = $_FILES[$file_input_name];

        // Validazioni
        $this->validateFile($file);

        // Genera nome file sicuro
        $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
        $filename = uniqid() . '_' . time() . '.' . $extension;
        $filepath = $this->upload_dir . $filename;

        // Sposta il file
        if (!move_uploaded_file($file['tmp_name'], $filepath)) {
            throw new Exception('Impossibile spostare il file caricato');
        }

        return [
            'filename' => $filename,
            'filepath' => $filepath,
            'original_name' => $file['name'],
            'size' => $file['size']
        ];
    }

    private function validateFile($file) {
        // Controlla dimensione
        if ($file['size'] > $this->max_size) {
            throw new Exception('Il file è troppo grande. Dimensione massima: ' . ($this->max_size / 1024 / 1024) . 'MB');
        }

        // Controlla tipo MIME
        if (!in_array($file['type'], $this->allowed_types)) {
            throw new Exception('Tipo di file non consentito. Tipi consentiti: ' . implode(', ', $this->allowed_types));
        }

        // Controllo aggiuntivo del tipo file
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mime_type = finfo_file($finfo, $file['tmp_name']);
        finfo_close($finfo);

        if (!in_array($mime_type, $this->allowed_types)) {
            throw new Exception('Tipo di file non valido');
        }
    }
}

// Form HTML per upload
?>
<!DOCTYPE html>
<html>
<head>
    <title>Upload File</title>
</head>
<body>
    <form action="" method="POST" enctype="multipart/form-data">
        <label for="image">Seleziona immagine:</label>
        <input type="file" id="image" name="image" accept="image/*" required><br><br>

        <input type="submit" name="upload" value="Carica File">
    </form>
</body>
</html>

<?php
// Processare upload
if (isset($_POST['upload'])) {
    try {
        $uploader = new FileUploader();
        $result = $uploader->upload('image');

        echo "<h2>File caricato con successo!</h2>";
        echo "<p><strong>Nome file:</strong> " . $result['filename'] . "</p>";
        echo "<p><strong>Dimensione:</strong> " . number_format($result['size'] / 1024, 2) . " KB</p>";
        echo "<img src='" . $result['filepath'] . "' alt='Uploaded image' style='max-width: 300px;'>";

    } catch (Exception $e) {
        echo "<h2>Errore:</h2>";
        echo "<p>" . $e->getMessage() . "</p>";
    }
}
?>

Gestione Sessioni

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<?php
// login.php
session_start();

class SessionManager {
    public static function login($user_id, $username, $role = 'user') {
        // Rigenera ID sessione per sicurezza
        session_regenerate_id(true);

        $_SESSION['user_id'] = $user_id;
        $_SESSION['username'] = $username;
        $_SESSION['role'] = $role;
        $_SESSION['login_time'] = time();
        $_SESSION['last_activity'] = time();

        return true;
    }

    public static function logout() {
        // Rimuovi tutte le variabili di sessione
        session_unset();

        // Distruggi la sessione
        session_destroy();

        // Rimuovi il cookie di sessione
        if (isset($_COOKIE[session_name()])) {
            setcookie(session_name(), '', time() - 3600, '/');
        }

        return true;
    }

    public static function isLoggedIn() {
        return isset($_SESSION['user_id']);
    }

    public static function requireLogin() {
        if (!self::isLoggedIn()) {
            header('Location: login.php');
            exit;
        }

        // Aggiorna ultima attività
        $_SESSION['last_activity'] = time();
    }

    public static function checkTimeout($timeout = 1800) { // 30 minuti
        if (isset($_SESSION['last_activity'])) {
            if (time() - $_SESSION['last_activity'] > $timeout) {
                self::logout();
                return false;
            }
        }

        return true;
    }

    public static function getUserId() {
        return $_SESSION['user_id'] ?? null;
    }

    public static function getUsername() {
        return $_SESSION['username'] ?? null;
    }

    public static function hasRole($required_role) {
        $user_role = $_SESSION['role'] ?? 'guest';
        return $user_role === $required_role || $user_role === 'admin';
    }
}

// Form di login
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = $_POST['username'] ?? '';
    $password = $_POST['password'] ?? '';

    // Validazione credenziali (esempio semplificato)
    if ($username === 'admin' && $password === 'password123') {
        SessionManager::login(1, $username, 'admin');
        header('Location: dashboard.php');
        exit;
    } else {
        $error = "Credenziali non valide";
    }
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
</head>
<body>
    <h2>Login</h2>

    <?php if (isset($error)): ?>
        <p style="color: red;"><?php echo $error; ?></p>
    <?php endif; ?>

    <form method="POST">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required><br><br>

        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required><br><br>

        <input type="submit" value="Login">
    </form>
</body>
</html>

<?php
// dashboard.php
session_start();

SessionManager::requireLogin();
SessionManager::checkTimeout();

echo "<h1>Benvenuto, " . SessionManager::getUsername() . "!</h1>";
echo "<p>User ID: " . SessionManager::getUserId() . "</p>";
echo "<a href='logout.php'>Logout</a>";
?>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<?php
class CookieManager {
    public static function set($name, $value, $days = 30, $secure = false, $httponly = true) {
        $expire = time() + ($days * 24 * 60 * 60);

        return setcookie(
            $name,
            $value,
            $expire,
            '/',
            '',
            $secure,
            $httponly
        );
    }

    public static function get($name, $default = null) {
        return $_COOKIE[$name] ?? $default;
    }

    public static function delete($name) {
        if (isset($_COOKIE[$name])) {
            setcookie($name, '', time() - 3600, '/');
            unset($_COOKIE[$name]);
        }
    }

    public static function exists($name) {
        return isset($_COOKIE[$name]);
    }
}

// Esempio: Sistema "Ricordami"
if ($_POST['remember_me'] ?? false) {
    // Genera token sicuro
    $token = bin2hex(random_bytes(32));

    // Salva token nel database associato all'utente
    $db->saveRememberToken($user_id, $token);

    // Imposta cookie
    CookieManager::set('remember_token', $token, 30, true, true);
}

// Controllo token di ricordo
if (!SessionManager::isLoggedIn() && CookieManager::exists('remember_token')) {
    $token = CookieManager::get('remember_token');
    $user = $db->getUserByRememberToken($token);

    if ($user) {
        SessionManager::login($user['id'], $user['username'], $user['role']);

        // Rigenera token per sicurezza
        $new_token = bin2hex(random_bytes(32));
        $db->updateRememberToken($user['id'], $new_token);
        CookieManager::set('remember_token', $new_token, 30, true, true);
    }
}

// Preferences utente
class UserPreferences {
    public static function setTheme($theme) {
        CookieManager::set('user_theme', $theme, 365);
    }

    public static function getTheme() {
        return CookieManager::get('user_theme', 'light');
    }

    public static function setLanguage($language) {
        CookieManager::set('user_language', $language, 365);
    }

    public static function getLanguage() {
        return CookieManager::get('user_language', 'it');
    }
}

// Utilizzo
$theme = UserPreferences::getTheme();
echo "<body class='theme-$theme'>";
?>

Gestione File

Operazioni sui File

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
<?php
class FileManager {
    public static function read($filepath) {
        if (!file_exists($filepath)) {
            throw new Exception("File non trovato: $filepath");
        }

        $content = file_get_contents($filepath);
        if ($content === false) {
            throw new Exception("Impossibile leggere il file: $filepath");
        }

        return $content;
    }

    public static function write($filepath, $content, $append = false) {
        $flags = $append ? FILE_APPEND | LOCK_EX : LOCK_EX;

        $result = file_put_contents($filepath, $content, $flags);
        if ($result === false) {
            throw new Exception("Impossibile scrivere il file: $filepath");
        }

        return $result;
    }

    public static function readLines($filepath) {
        if (!file_exists($filepath)) {
            throw new Exception("File non trovato: $filepath");
        }

        return file($filepath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    }

    public static function copy($source, $destination) {
        if (!file_exists($source)) {
            throw new Exception("File sorgente non trovato: $source");
        }

        if (!copy($source, $destination)) {
            throw new Exception("Impossibile copiare il file");
        }

        return true;
    }

    public static function delete($filepath) {
        if (file_exists($filepath)) {
            return unlink($filepath);
        }

        return false;
    }

    public static function getInfo($filepath) {
        if (!file_exists($filepath)) {
            throw new Exception("File non trovato: $filepath");
        }

        return [
            'name' => basename($filepath),
            'size' => filesize($filepath),
            'type' => mime_content_type($filepath),
            'modified' => filemtime($filepath),
            'permissions' => fileperms($filepath),
            'is_readable' => is_readable($filepath),
            'is_writable' => is_writable($filepath)
        ];
    }
}

// CSV Handler
class CSVHandler {
    public static function read($filepath, $delimiter = ',', $has_header = true) {
        if (!file_exists($filepath)) {
            throw new Exception("File CSV non trovato");
        }

        $data = [];
        $headers = [];

        if (($handle = fopen($filepath, 'r')) !== false) {
            $row_number = 0;

            while (($row = fgetcsv($handle, 1000, $delimiter)) !== false) {
                if ($row_number === 0 && $has_header) {
                    $headers = $row;
                } else {
                    if ($has_header && !empty($headers)) {
                        $data[] = array_combine($headers, $row);
                    } else {
                        $data[] = $row;
                    }
                }
                $row_number++;
            }

            fclose($handle);
        }

        return $data;
    }

    public static function write($filepath, $data, $headers = null, $delimiter = ',') {
        if (($handle = fopen($filepath, 'w')) === false) {
            throw new Exception("Impossibile aprire il file per scrittura");
        }

        // Scrivi intestazioni
        if ($headers) {
            fputcsv($handle, $headers, $delimiter);
        }

        // Scrivi dati
        foreach ($data as $row) {
            fputcsv($handle, $row, $delimiter);
        }

        fclose($handle);
        return true;
    }
}

// Logger
class Logger {
    private $log_file;

    public function __construct($log_file = 'app.log') {
        $this->log_file = $log_file;
    }

    public function log($level, $message, $context = []) {
        $timestamp = date('Y-m-d H:i:s');
        $context_str = !empty($context) ? json_encode($context) : '';

        $log_entry = "[$timestamp] $level: $message $context_str" . PHP_EOL;

        FileManager::write($this->log_file, $log_entry, true);
    }

    public function info($message, $context = []) {
        $this->log('INFO', $message, $context);
    }

    public function warning($message, $context = []) {
        $this->log('WARNING', $message, $context);
    }

    public function error($message, $context = []) {
        $this->log('ERROR', $message, $context);
    }
}

// Esempi di utilizzo
try {
    // Lettura file
    $content = FileManager::read('config.txt');
    echo "Contenuto file: $content";

    // Scrittura file
    FileManager::write('output.txt', "Hello, World!\n");

    // Info file
    $info = FileManager::getInfo('output.txt');
    print_r($info);

    // CSV
    $csv_data = [
        ['Nome', 'Età', 'Email'],
        ['Mario', 30, 'mario@email.com'],
        ['Anna', 25, 'anna@email.com']
    ];

    CSVHandler::write('users.csv', array_slice($csv_data, 1), $csv_data[0]);
    $loaded_data = CSVHandler::read('users.csv');
    print_r($loaded_data);

    // Logging
    $logger = new Logger();
    $logger->info('Applicazione avviata');
    $logger->error('Errore di connessione', ['host' => 'localhost']);

} catch (Exception $e) {
    echo "Errore: " . $e->getMessage();
}
?>

Directory e Path

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<?php
class DirectoryManager {
    public static function create($path, $permissions = 0755, $recursive = true) {
        if (!is_dir($path)) {
            return mkdir($path, $permissions, $recursive);
        }

        return true;
    }

    public static function delete($path, $recursive = false) {
        if (!is_dir($path)) {
            return false;
        }

        if ($recursive) {
            $files = array_diff(scandir($path), ['.', '..']);

            foreach ($files as $file) {
                $file_path = $path . DIRECTORY_SEPARATOR . $file;

                if (is_dir($file_path)) {
                    self::delete($file_path, true);
                } else {
                    unlink($file_path);
                }
            }
        }

        return rmdir($path);
    }

    public static function listFiles($path, $filter = '*') {
        if (!is_dir($path)) {
            throw new Exception("Directory non trovata: $path");
        }

        $pattern = $path . DIRECTORY_SEPARATOR . $filter;
        return glob($pattern);
    }

    public static function size($path) {
        $size = 0;

        if (is_file($path)) {
            return filesize($path);
        }

        if (is_dir($path)) {
            $files = array_diff(scandir($path), ['.', '..']);

            foreach ($files as $file) {
                $file_path = $path . DIRECTORY_SEPARATOR . $file;
                $size += self::size($file_path);
            }
        }

        return $size;
    }

    public static function copy($source, $destination) {
        if (is_file($source)) {
            return copy($source, $destination);
        }

        if (is_dir($source)) {
            self::create($destination);

            $files = array_diff(scandir($source), ['.', '..']);

            foreach ($files as $file) {
                $source_path = $source . DIRECTORY_SEPARATOR . $file;
                $dest_path = $destination . DIRECTORY_SEPARATOR . $file;

                self::copy($source_path, $dest_path);
            }

            return true;
        }

        return false;
    }
}

// Path Utilities
class PathUtils {
    public static function join(...$parts) {
        return implode(DIRECTORY_SEPARATOR, array_filter($parts, 'strlen'));
    }

    public static function normalize($path) {
        return realpath($path);
    }

    public static function relativePath($from, $to) {
        $from = explode(DIRECTORY_SEPARATOR, $from);
        $to = explode(DIRECTORY_SEPARATOR, $to);

        while (count($from) && count($to) && $from[0] === $to[0]) {
            array_shift($from);
            array_shift($to);
        }

        return str_repeat('..'.DIRECTORY_SEPARATOR, count($from)) . implode(DIRECTORY_SEPARATOR, $to);
    }

    public static function extension($path) {
        return pathinfo($path, PATHINFO_EXTENSION);
    }

    public static function filename($path, $include_extension = true) {
        return $include_extension
            ? pathinfo($path, PATHINFO_BASENAME)
            : pathinfo($path, PATHINFO_FILENAME);
    }

    public static function directory($path) {
        return pathinfo($path, PATHINFO_DIRNAME);
    }
}

// Esempi
try {
    // Creare directory
    DirectoryManager::create('test/subdir');

    // Listar file
    $files = DirectoryManager::listFiles('.', '*.php');
    echo "File PHP trovati: " . count($files) . "\n";

    // Dimensione directory
    $size = DirectoryManager::size('.');
    echo "Dimensione directory: " . number_format($size) . " bytes\n";

    // Path operations
    $path = PathUtils::join('var', 'www', 'html', 'index.php');
    echo "Path: $path\n";

    echo "Extension: " . PathUtils::extension($path) . "\n";
    echo "Filename: " . PathUtils::filename($path) . "\n";
    echo "Directory: " . PathUtils::directory($path) . "\n";

} catch (Exception $e) {
    echo "Errore: " . $e->getMessage() . "\n";
}
?>

Framework e Composer

Introduzione a Composer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# Installare Composer globalmente
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer

# Creare nuovo progetto
mkdir my-php-project
cd my-php-project

# Inizializzare Composer
composer init

# Installare pacchetti
composer require monolog/monolog
composer require guzzlehttp/guzzle
composer require --dev phpunit/phpunit

# Aggiornare dipendenze
composer update

# Installare dipendenze da composer.json esistente
composer install

composer.json Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
    "name": "mycompany/my-project",
    "description": "Un progetto PHP di esempio",
    "type": "project",
    "require": {
        "php": ">=8.0",
        "monolog/monolog": "^3.0",
        "guzzlehttp/guzzle": "^7.0",
        "doctrine/dbal": "^3.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^10.0",
        "squizlabs/php_codesniffer": "^3.0"
    },
    "autoload": {
        "psr-4": {
            "MyProject\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "MyProject\\Tests\\": "tests/"
        }
    },
    "scripts": {
        "test": "phpunit",
        "lint": "phpcs src/ --standard=PSR12",
        "fix": "phpcbf src/ --standard=PSR12"
    }
}

Autoloading PSR-4

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?php
// composer.json configurato per PSR-4 autoloading

// src/Models/User.php
namespace MyProject\Models;

class User {
    private $id;
    private $name;
    private $email;

    public function __construct($id, $name, $email) {
        $this->id = $id;
        $this->name = $name;
        $this->email = $email;
    }

    public function getId() {
        return $this->id;
    }

    public function getName() {
        return $this->name;
    }

    public function getEmail() {
        return $this->email;
    }
}

// src/Services/UserService.php
namespace MyProject\Services;

use MyProject\Models\User;
use MyProject\Database\DatabaseConnection;

class UserService {
    private $db;

    public function __construct(DatabaseConnection $db) {
        $this->db = $db;
    }

    public function createUser($name, $email) {
        // Logica per creare utente
        $id = $this->db->insert('users', [
            'name' => $name,
            'email' => $email
        ]);

        return new User($id, $name, $email);
    }

    public function getUserById($id) {
        $data = $this->db->select('users', ['id' => $id]);

        if ($data) {
            return new User($data['id'], $data['name'], $data['email']);
        }

        return null;
    }
}

// index.php - Entry point
<?php
require_once 'vendor/autoload.php';

use MyProject\Services\UserService;
use MyProject\Database\DatabaseConnection;

// Dependency Injection semplice
$db = new DatabaseConnection('localhost', 'mydb', 'user', 'pass');
$userService = new UserService($db);

// Utilizzo
$user = $userService->createUser('Mario Rossi', 'mario@email.com');
echo "Utente creato: " . $user->getName();
?>

Utilizzo di Librerie Esterne

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
<?php
require_once 'vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

// Logging con Monolog
class ApplicationLogger {
    private $logger;

    public function __construct($name = 'MyApp') {
        $this->logger = new Logger($name);

        // Handler per file
        $this->logger->pushHandler(new StreamHandler('logs/app.log', Logger::DEBUG));

        // Handler per errori critici
        $this->logger->pushHandler(new StreamHandler('logs/error.log', Logger::ERROR));
    }

    public function info($message, $context = []) {
        $this->logger->info($message, $context);
    }

    public function error($message, $context = []) {
        $this->logger->error($message, $context);
    }

    public function debug($message, $context = []) {
        $this->logger->debug($message, $context);
    }
}

// HTTP Client con Guzzle
class ApiClient {
    private $client;
    private $logger;

    public function __construct($base_uri, ApplicationLogger $logger) {
        $this->client = new Client(['base_uri' => $base_uri]);
        $this->logger = $logger;
    }

    public function get($endpoint, $params = []) {
        try {
            $this->logger->info("API Request", ['endpoint' => $endpoint, 'params' => $params]);

            $response = $this->client->request('GET', $endpoint, [
                'query' => $params,
                'timeout' => 30
            ]);

            $data = json_decode($response->getBody(), true);

            $this->logger->info("API Response", ['status' => $response->getStatusCode()]);

            return $data;

        } catch (RequestException $e) {
            $this->logger->error("API Request Failed", [
                'endpoint' => $endpoint,
                'error' => $e->getMessage()
            ]);

            throw $e;
        }
    }

    public function post($endpoint, $data) {
        try {
            $response = $this->client->request('POST', $endpoint, [
                'json' => $data,
                'timeout' => 30
            ]);

            return json_decode($response->getBody(), true);

        } catch (RequestException $e) {
            $this->logger->error("API POST Failed", [
                'endpoint' => $endpoint,
                'error' => $e->getMessage()
            ]);

            throw $e;
        }
    }
}

// Utilizzo
$logger = new ApplicationLogger();
$apiClient = new ApiClient('https://jsonplaceholder.typicode.com/', $logger);

try {
    // GET request
    $posts = $apiClient->get('posts', ['userId' => 1]);
    echo "Trovati " . count($posts) . " post\n";

    // POST request
    $newPost = $apiClient->post('posts', [
        'title' => 'Il mio nuovo post',
        'body' => 'Contenuto del post...',
        'userId' => 1
    ]);

    $logger->info("Nuovo post creato", ['id' => $newPost['id']]);

} catch (Exception $e) {
    echo "Errore: " . $e->getMessage() . "\n";
}
?>

Framework Laravel (Introduzione)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Installare Laravel via Composer
composer global require laravel/installer

# Creare nuovo progetto Laravel
laravel new my-laravel-app
# oppure
composer create-project laravel/laravel my-laravel-app

# Navigare nel progetto
cd my-laravel-app

# Avviare server di sviluppo
php artisan serve
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?php
// routes/web.php - Definire rotte
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserController;

Route::get('/', function () {
    return view('welcome');
});

Route::get('/users', [UserController::class, 'index']);
Route::get('/users/{id}', [UserController::class, 'show']);
Route::post('/users', [UserController::class, 'store']);

// app/Http/Controllers/UserController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;

class UserController extends Controller {
    public function index() {
        $users = User::all();
        return response()->json($users);
    }

    public function show($id) {
        $user = User::findOrFail($id);
        return response()->json($user);
    }

    public function store(Request $request) {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users'
        ]);

        $user = User::create($validated);
        return response()->json($user, 201);
    }
}

// app/Models/User.php - Eloquent Model
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model {
    protected $fillable = ['name', 'email'];

    protected $hidden = ['password'];

    public function posts() {
        return $this->hasMany(Post::class);
    }
}

// resources/views/users/index.blade.php - Blade Template
@extends('layouts.app')

@section('content')
<div class="container">
    <h1>Users</h1>

    <div class="row">
        @foreach($users as $user)
        <div class="col-md-4">
            <div class="card">
                <div class="card-body">
                    <h5 class="card-title">{{ $user->name }}</h5>
                    <p class="card-text">{{ $user->email }}</p>
                    <a href="{{ route('users.show', $user->id) }}" class="btn btn-primary">View</a>
                </div>
            </div>
        </div>
        @endforeach
    </div>
</div>
@endsection
?>

Sicurezza

Nota sulla sicurezza
Non concatenare input nelle query SQL e non stampare input utente senza sanitizzazione. Usa query preparate e htmlspecialchars().

Prevenzione delle Vulnerabilità Comuni

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
<?php
class SecurityHelper {

    // 1. Prevenzione SQL Injection
    public static function safeQuery($pdo, $sql, $params = []) {
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt;
    }

    // 2. Prevenzione XSS
    public static function sanitizeOutput($string) {
        return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }

    public static function sanitizeInput($input) {
        return filter_var(trim($input), FILTER_SANITIZE_STRING);
    }

    // 3. Validazione Input
    public static function validateEmail($email) {
        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    }

    public static function validateInteger($int, $min = null, $max = null) {
        $options = [];
        if ($min !== null || $max !== null) {
            $options['options'] = [];
            if ($min !== null) $options['options']['min_range'] = $min;
            if ($max !== null) $options['options']['max_range'] = $max;
        }

        return filter_var($int, FILTER_VALIDATE_INT, $options) !== false;
    }

    // 4. CSRF Protection
    public static function generateCSRFToken() {
        if (!isset($_SESSION['csrf_token'])) {
            $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
        }
        return $_SESSION['csrf_token'];
    }

    public static function validateCSRFToken($token) {
        return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
    }

    // 5. Password Security
    public static function hashPassword($password) {
        return password_hash($password, PASSWORD_DEFAULT);
    }

    public static function verifyPassword($password, $hash) {
        return password_verify($password, $hash);
    }

    public static function validatePasswordStrength($password) {
        $errors = [];

        if (strlen($password) < 8) {
            $errors[] = "Password deve essere almeno 8 caratteri";
        }

        if (!preg_match('/[A-Z]/', $password)) {
            $errors[] = "Password deve contenere almeno una maiuscola";
        }

        if (!preg_match('/[a-z]/', $password)) {
            $errors[] = "Password deve contenere almeno una minuscola";
        }

        if (!preg_match('/[0-9]/', $password)) {
            $errors[] = "Password deve contenere almeno un numero";
        }

        if (!preg_match('/[^a-zA-Z0-9]/', $password)) {
            $errors[] = "Password deve contenere almeno un carattere speciale";
        }

        return $errors;
    }

    // 6. File Upload Security
    public static function validateUploadedFile($file, $allowed_types = [], $max_size = 5242880) {
        $errors = [];

        // Controllo errori upload
        if ($file['error'] !== UPLOAD_ERR_OK) {
            $errors[] = "Errore durante l'upload del file";
            return $errors;
        }

        // Controllo dimensione
        if ($file['size'] > $max_size) {
            $errors[] = "File troppo grande. Massimo: " . ($max_size / 1024 / 1024) . "MB";
        }

        // Controllo tipo MIME
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mime_type = finfo_file($finfo, $file['tmp_name']);
        finfo_close($finfo);

        if (!empty($allowed_types) && !in_array($mime_type, $allowed_types)) {
            $errors[] = "Tipo di file non consentito";
        }

        // Controllo estensione
        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        $dangerous_extensions = ['php', 'phtml', 'php3', 'php4', 'php5', 'pl', 'py', 'jsp', 'asp', 'sh', 'cgi'];

        if (in_array($extension, $dangerous_extensions)) {
            $errors[] = "Estensione file non sicura";
        }

        return $errors;
    }

    // 7. Rate Limiting
    public static function rateLimitCheck($identifier, $max_attempts = 5, $window_minutes = 15) {
        $cache_key = "rate_limit_" . md5($identifier);
        $attempts = $_SESSION[$cache_key] ?? [];

        // Rimuovi tentativi vecchi
        $cutoff = time() - ($window_minutes * 60);
        $attempts = array_filter($attempts, function($timestamp) use ($cutoff) {
            return $timestamp > $cutoff;
        });

        if (count($attempts) >= $max_attempts) {
            return false; // Rate limit exceeded
        }

        // Aggiungi tentativo corrente
        $attempts[] = time();
        $_SESSION[$cache_key] = $attempts;

        return true;
    }
}

// Esempio di form sicuro
class SecureLoginForm {
    private $pdo;

    public function __construct($pdo) {
        $this->pdo = $pdo;
    }

    public function processLogin($username, $password, $csrf_token) {
        $errors = [];

        // 1. Validazione CSRF
        if (!SecurityHelper::validateCSRFToken($csrf_token)) {
            $errors[] = "Token di sicurezza non valido";
            return $errors;
        }

        // 2. Rate limiting
        $client_ip = $_SERVER['REMOTE_ADDR'];
        if (!SecurityHelper::rateLimitCheck($client_ip, 5, 15)) {
            $errors[] = "Troppi tentativi di login. Riprova tra 15 minuti.";
            return $errors;
        }

        // 3. Sanitizzazione input
        $username = SecurityHelper::sanitizeInput($username);

        if (empty($username) || empty($password)) {
            $errors[] = "Username e password sono obbligatori";
            return $errors;
        }

        // 4. Query sicura
        try {
            $stmt = SecurityHelper::safeQuery(
                $this->pdo,
                "SELECT id, username, password_hash FROM users WHERE username = ? LIMIT 1",
                [$username]
            );

            $user = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$user || !SecurityHelper::verifyPassword($password, $user['password_hash'])) {
                $errors[] = "Credenziali non valide";
                return $errors;
            }

            // 5. Login riuscito - avvia sessione sicura
            session_regenerate_id(true);
            $_SESSION['user_id'] = $user['id'];
            $_SESSION['username'] = $user['username'];
            $_SESSION['login_time'] = time();

            return []; // Nessun errore

        } catch (PDOException $e) {
            error_log("Database error in login: " . $e->getMessage());
            $errors[] = "Errore interno del sistema";
            return $errors;
        }
    }
}

// Template sicuro
function renderSecureForm() {
    $csrf_token = SecurityHelper::generateCSRFToken();
    ?>
    <form method="POST" action="login.php">
        <input type="hidden" name="csrf_token" value="<?= SecurityHelper::sanitizeOutput($csrf_token) ?>">

        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required maxlength="50">

        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required>

        <input type="submit" value="Login">
    </form>
    <?php
}

// Configurazione sicura della sessione
function configureSecureSession() {
    // Configurazioni di sicurezza
    ini_set('session.cookie_httponly', 1);
    ini_set('session.cookie_secure', 1); // Solo HTTPS
    ini_set('session.use_strict_mode', 1);
    ini_set('session.cookie_samesite', 'Strict');

    session_start();
}

// Headers di sicurezza
function setSecurityHeaders() {
    // Prevent clickjacking
    header('X-Frame-Options: DENY');

    // Prevent MIME sniffing
    header('X-Content-Type-Options: nosniff');

    // XSS Protection
    header('X-XSS-Protection: 1; mode=block');

    // HSTS (solo su HTTPS)
    if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
        header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
    }

    // Content Security Policy
    header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';");
}
?>

Best Practices

Coding Standards e Convenzioni

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
<?php
/**
 * PSR-12 Coding Standard Example
 *
 * @author Gianluca Occhetto
 * @version 1.0
 */

namespace MyProject\Services;

use MyProject\Models\User;
use MyProject\Exceptions\ValidationException;
use MyProject\Interfaces\UserRepositoryInterface;

/**
 * Service per la gestione degli utenti
 */
class UserService
{
    private UserRepositoryInterface $userRepository;
    private array $config;

    public function __construct(UserRepositoryInterface $userRepository, array $config = [])
    {
        $this->userRepository = $userRepository;
        $this->config = array_merge($this->getDefaultConfig(), $config);
    }

    /**
     * Crea un nuovo utente
     *
     * @param array $userData Dati dell'utente
     * @return User
     * @throws ValidationException
     */
    public function createUser(array $userData): User
    {
        $this->validateUserData($userData);

        $user = new User(
            name: $userData['name'],
            email: $userData['email'],
            password: $this->hashPassword($userData['password'])
        );

        return $this->userRepository->save($user);
    }

    /**
     * Ottiene utente per ID
     *
     * @param int $id
     * @return User|null
     */
    public function getUserById(int $id): ?User
    {
        if ($id <= 0) {
            throw new \InvalidArgumentException('ID deve essere positivo');
        }

        return $this->userRepository->findById($id);
    }

    /**
     * Aggiorna utente esistente
     *
     * @param int $id
     * @param array $userData
     * @return User
     * @throws ValidationException
     */
    public function updateUser(int $id, array $userData): User
    {
        $user = $this->getUserById($id);

        if (!$user) {
            throw new \RuntimeException("Utente con ID $id non trovato");
        }

        $this->validateUserData($userData, $id);

        $user->updateFromArray($userData);

        return $this->userRepository->save($user);
    }

    /**
     * Lista utenti con filtri e paginazione
     *
     * @param array $filters
     * @param int $page
     * @param int $perPage
     * @return array
     */
    public function listUsers(
        array $filters = [],
        int $page = 1,
        int $perPage = 20
    ): array {
        $this->validatePaginationParams($page, $perPage);

        $offset = ($page - 1) * $perPage;

        return $this->userRepository->findWithFilters(
            filters: $filters,
            limit: $perPage,
            offset: $offset
        );
    }

    /**
     * Valida i dati dell'utente
     *
     * @param array $data
     * @param int|null $excludeId ID da escludere per controlli di unicità
     * @throws ValidationException
     */
    private function validateUserData(array $data, ?int $excludeId = null): void
    {
        $errors = [];

        // Nome
        if (empty($data['name']) || !is_string($data['name'])) {
            $errors['name'] = 'Nome è obbligatorio e deve essere una stringa';
        } elseif (strlen($data['name']) < 2 || strlen($data['name']) > 100) {
            $errors['name'] = 'Nome deve essere tra 2 e 100 caratteri';
        }

        // Email
        if (empty($data['email'])) {
            $errors['email'] = 'Email è obbligatoria';
        } elseif (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
            $errors['email'] = 'Email non valida';
        } elseif ($this->emailExists($data['email'], $excludeId)) {
            $errors['email'] = 'Email già esistente';
        }

        // Password (solo per nuovi utenti o se fornita)
        if (isset($data['password'])) {
            $passwordErrors = $this->validatePassword($data['password']);
            if (!empty($passwordErrors)) {
                $errors['password'] = implode(', ', $passwordErrors);
            }
        } elseif ($excludeId === null) {
            $errors['password'] = 'Password è obbligatoria per nuovi utenti';
        }

        if (!empty($errors)) {
            throw new ValidationException('Errori di validazione', $errors);
        }
    }

    private function validatePassword(string $password): array
    {
        $errors = [];

        if (strlen($password) < 8) {
            $errors[] = 'Password deve essere almeno 8 caratteri';
        }

        if (!preg_match('/[A-Z]/', $password)) {
            $errors[] = 'Password deve contenere almeno una maiuscola';
        }

        if (!preg_match('/[a-z]/', $password)) {
            $errors[] = 'Password deve contenere almeno una minuscola';
        }

        if (!preg_match('/[0-9]/', $password)) {
            $errors[] = 'Password deve contenere almeno un numero';
        }

        return $errors;
    }

    private function validatePaginationParams(int $page, int $perPage): void
    {
        if ($page < 1) {
            throw new \InvalidArgumentException('Page deve essere almeno 1');
        }

        if ($perPage < 1 || $perPage > 100) {
            throw new \InvalidArgumentException('PerPage deve essere tra 1 e 100');
        }
    }

    private function emailExists(string $email, ?int $excludeId = null): bool
    {
        $user = $this->userRepository->findByEmail($email);

        if (!$user) {
            return false;
        }

        return $excludeId === null || $user->getId() !== $excludeId;
    }

    private function hashPassword(string $password): string
    {
        return password_hash($password, PASSWORD_DEFAULT);
    }

    private function getDefaultConfig(): array
    {
        return [
            'password_min_length' => 8,
            'name_max_length' => 100,
            'max_per_page' => 100
        ];
    }
}

// Exception personalizzata
class ValidationException extends \Exception
{
    private array $errors;

    public function __construct(string $message, array $errors = [], int $code = 0, ?\Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
        $this->errors = $errors;
    }

    public function getErrors(): array
    {
        return $this->errors;
    }

    public function hasErrors(): bool
    {
        return !empty($this->errors);
    }
}

// Interface per repository pattern
interface UserRepositoryInterface
{
    public function save(User $user): User;
    public function findById(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function findWithFilters(array $filters = [], int $limit = 20, int $offset = 0): array;
    public function delete(User $user): bool;
}
?>

Performance e Ottimizzazione

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
<?php
/**
 * Tecniche di ottimizzazione per PHP
 */

// 1. Caching
interface CacheInterface
{
    public function get(string $key);
    public function set(string $key, $value, int $ttl = 3600): bool;
    public function delete(string $key): bool;
    public function clear(): bool;
}

class FileCache implements CacheInterface
{
    private string $cacheDir;

    public function __construct(string $cacheDir = './cache/')
    {
        $this->cacheDir = rtrim($cacheDir, '/') . '/';

        if (!is_dir($this->cacheDir)) {
            mkdir($this->cacheDir, 0755, true);
        }
    }

    public function get(string $key)
    {
        $filename = $this->getFilename($key);

        if (!file_exists($filename)) {
            return null;
        }

        $data = unserialize(file_get_contents($filename));

        if ($data['expires'] < time()) {
            $this->delete($key);
            return null;
        }

        return $data['value'];
    }

    public function set(string $key, $value, int $ttl = 3600): bool
    {
        $filename = $this->getFilename($key);

        $data = [
            'value' => $value,
            'expires' => time() + $ttl
        ];

        return file_put_contents($filename, serialize($data), LOCK_EX) !== false;
    }

    public function delete(string $key): bool
    {
        $filename = $this->getFilename($key);

        if (file_exists($filename)) {
            return unlink($filename);
        }

        return true;
    }

    public function clear(): bool
    {
        $files = glob($this->cacheDir . '*.cache');

        foreach ($files as $file) {
            unlink($file);
        }

        return true;
    }

    private function getFilename(string $key): string
    {
        return $this->cacheDir . md5($key) . '.cache';
    }
}

// 2. Database Query Optimization
class OptimizedUserService
{
    private PDO $pdo;
    private CacheInterface $cache;

    public function __construct(PDO $pdo, CacheInterface $cache)
    {
        $this->pdo = $pdo;
        $this->cache = $cache;
    }

    /**
     * Ottimizzazione: Query con prepared statements e caching
     */
    public function getUserWithPosts(int $userId): ?array
    {
        $cacheKey = "user_with_posts_$userId";

        // Controlla cache
        $cached = $this->cache->get($cacheKey);
        if ($cached !== null) {
            return $cached;
        }

        // Query ottimizzata con JOIN invece di query multiple
        $sql = "
            SELECT
                u.id, u.name, u.email,
                p.id as post_id, p.title, p.content, p.created_at as post_date
            FROM users u
            LEFT JOIN posts p ON u.id = p.user_id
            WHERE u.id = ?
            ORDER BY p.created_at DESC
        ";

        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$userId]);
        $results = $stmt->fetchAll(PDO::FETCH_ASSOC);

        if (empty($results)) {
            return null;
        }

        // Organizza risultati
        $user = [
            'id' => $results[0]['id'],
            'name' => $results[0]['name'],
            'email' => $results[0]['email'],
            'posts' => []
        ];

        foreach ($results as $row) {
            if ($row['post_id']) {
                $user['posts'][] = [
                    'id' => $row['post_id'],
                    'title' => $row['title'],
                    'content' => $row['content'],
                    'created_at' => $row['post_date']
                ];
            }
        }

        // Cache per 30 minuti
        $this->cache->set($cacheKey, $user, 1800);

        return $user;
    }

    /**
     * Batch operations per evitare N+1 queries
     */
    public function getUsersWithPostCounts(array $userIds): array
    {
        if (empty($userIds)) {
            return [];
        }

        $placeholders = implode(',', array_fill(0, count($userIds), '?'));

        $sql = "
            SELECT
                u.id, u.name, u.email,
                COUNT(p.id) as post_count
            FROM users u
            LEFT JOIN posts p ON u.id = p.user_id
            WHERE u.id IN ($placeholders)
            GROUP BY u.id, u.name, u.email
        ";

        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($userIds);

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

// 3. Memory Management
class MemoryEfficientProcessor
{
    /**
     * Processa file grandi senza caricare tutto in memoria
     */
    public function processLargeFile(string $filename, callable $processor): void
    {
        $handle = fopen($filename, 'r');

        if (!$handle) {
            throw new \RuntimeException("Cannot open file: $filename");
        }

        try {
            while (($line = fgets($handle)) !== false) {
                $processor(trim($line));

                // Libera memoria periodicamente
                if (memory_get_usage() > 50 * 1024 * 1024) { // 50MB
                    gc_collect_cycles();
                }
            }
        } finally {
            fclose($handle);
        }
    }

    /**
     * Generator per iterare su grandi dataset
     */
    public function getUsers(): \Generator
    {
        $sql = "SELECT * FROM users ORDER BY id";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute();

        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            yield new User($row['id'], $row['name'], $row['email']);
        }
    }
}

// 4. Lazy Loading
class LazyUser
{
    private int $id;
    private ?string $name = null;
    private ?string $email = null;
    private ?array $posts = null;
    private UserRepositoryInterface $repository;

    public function __construct(int $id, UserRepositoryInterface $repository)
    {
        $this->id = $id;
        $this->repository = $repository;
    }

    public function getName(): string
    {
        if ($this->name === null) {
            $this->loadBasicInfo();
        }

        return $this->name;
    }

    public function getEmail(): string
    {
        if ($this->email === null) {
            $this->loadBasicInfo();
        }

        return $this->email;
    }

    public function getPosts(): array
    {
        if ($this->posts === null) {
            $this->posts = $this->repository->getPostsByUserId($this->id);
        }

        return $this->posts;
    }

    private function loadBasicInfo(): void
    {
        $data = $this->repository->getBasicUserInfo($this->id);

        $this->name = $data['name'];
        $this->email = $data['email'];
    }
}

// 5. Configuration e Environment
class Config
{
    private static ?self $instance = null;
    private array $config = [];

    private function __construct()
    {
        $this->loadConfig();
    }

    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    public function get(string $key, $default = null)
    {
        return $this->config[$key] ?? $default;
    }

    public function set(string $key, $value): void
    {
        $this->config[$key] = $value;
    }

    private function loadConfig(): void
    {
        // Carica da file .env
        if (file_exists('.env')) {
            $lines = file('.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);

            foreach ($lines as $line) {
                if (strpos($line, '=') !== false && !str_starts_with($line, '#')) {
                    [$key, $value] = explode('=', $line, 2);
                    $this->config[trim($key)] = trim($value);
                }
            }
        }

        // Override con variabili d'ambiente
        foreach ($_ENV as $key => $value) {
            $this->config[$key] = $value;
        }
    }
}

// Utilizzo delle ottimizzazioni
$cache = new FileCache('./cache/');
$config = Config::getInstance();

$dsn = "mysql:host={$config->get('DB_HOST', 'localhost')};dbname={$config->get('DB_NAME')}";
$pdo = new PDO($dsn, $config->get('DB_USER'), $config->get('DB_PASS'));

$userService = new OptimizedUserService($pdo, $cache);

// Esempio di processing efficiente
$processor = new MemoryEfficientProcessor();

$processor->processLargeFile('large_data.csv', function($line) {
    // Processa ogni linea individualmente
    $data = str_getcsv($line);
    // ... elabora $data
});

// Generator per grandi dataset
foreach ($processor->getUsers() as $user) {
    echo $user->getName() . "\n";
    // Memoria utilizzata rimane costante
}
?>

Conclusioni e Prossimi Passi

PHP è un linguaggio potente e versatile che continua ad evolversi. Con PHP 8+, il linguaggio ha introdotto molte funzionalità moderne che lo rendono competitivo con altri linguaggi di programmazione.

Cosa Abbiamo Imparato

In questa guida abbiamo coperto:

Fondamenti: Sintassi, variabili, controllo di flusso ✅ Programmazione Avanzata: OOP, namespace, trait ✅ Database: PDO, sicurezza, transazioni ✅ Web Development: Form, sessioni, cookie, upload ✅ Sicurezza: Prevenzione vulnerabilità, validazione, autenticazione ✅ Best Practices: PSR standards, performance, architettura ✅ Ecosystem: Composer, framework, librerie

Prossimi Passi Consigliati

1. Pratica Hands-on

  • Crea un progetto completo (es. blog, e-commerce, gestionale)
  • Contribuisci a progetti open source PHP su GitHub
  • Risolvi problemi su piattaforme come Codewars

2. Framework Moderni

  • Laravel: Framework full-stack più popolare
  • Symfony: Framework enterprise con componenti riutilizzabili
  • CodeIgniter: Framework leggero e semplice
  • CakePHP: Convention over configuration
  • Phalcon: High-performance C-extension framework

3. Specializzazioni

  • API Development: REST, GraphQL, microservizi
  • Testing: PHPUnit, Pest, Behat per TDD/BDD
  • DevOps: Docker, CI/CD, deployment automation
  • Performance: Profiling, caching avanzato, ottimizzazione

4. Tecnologie Complementari

  • Frontend: JavaScript, Vue.js, React per full-stack
  • Database: Redis, MongoDB, Elasticsearch per casi specifici
  • Cloud: AWS, Google Cloud, Azure per scalabilità
  • Message Queues: RabbitMQ, Redis per processing asincrono

Certificazioni e Carriera

Certificazioni PHP

  • Zend Certified Engineer: Certificazione ufficiale PHP
  • Laravel Certification: Per specialisti Laravel
  • Symfony Certification: Per expertise Symfony

Percorsi di Carriera

  • Backend Developer: Focus su API, microservizi, architettura
  • Full-Stack Developer: PHP + frontend technologies
  • DevOps Engineer: Automation, deployment, infrastruttura
  • Solutions Architect: Design di sistemi scalabili

Risorse per Continuare l’Apprendimento

Risorse Essenziali

Documentazione e Reference:

Community e News:

Learning Platforms:

Tool e Ambiente di Sviluppo

IDE Consigliati

  • PhpStorm: IDE professionale JetBrains (a pagamento)
  • Visual Studio Code: Leggero con ottime estensioni PHP
  • Sublime Text: Veloce ed estensibile
  • Vim/Neovim: Per sviluppatori avanzati

Estensioni VS Code Essenziali

  • PHP Intelephense
  • PHP Debug
  • PHP CS Fixer
  • GitLens
  • Docker

Tendenze Future di PHP

PHP 8.3+ Features

  • Performance improvements: JIT compiler ottimizzato
  • Type system: Union types, intersection types
  • Syntax improvements: Match expressions, nullsafe operator
  • Async support: Fibers per programmazione asincrona

Ecosystem Evolution

  • Modern frameworks: Focus su performance e developer experience
  • Microservices: PHP in architetture distribuite
  • Serverless: PHP su AWS Lambda, Google Cloud Functions
  • Real-time applications: con ReactPHP, Swoole

PHP rimane uno dei linguaggi più richiesti nel mercato del lavoro, specialmente per sviluppo web. La sua facilità di apprendimento, combinata con la potenza dei framework moderni, lo rende una scelta eccellente per chi inizia nel web development o per chi vuole specializzarsi nel backend.

Continua a praticare, sperimenta con progetti reali e rimani aggiornato sulle novità dell’ecosistema PHP. Il viaggio di apprendimento non finisce mai, ma con le basi solide di questa guida sei pronto ad affrontare qualsiasi sfida PHP! 🚀

Buon coding e in bocca al lupo per i tuoi progetti PHP! 🐘💻