Gestire dati JSON in MySQL e MariaDB con PHP

by theArchitect
SHARE
Gestire dati JSON in MySQL e MariaDB con PHP
© Guida-HTML5.it

Introduzione

MySQL e MariaDB permettono di archiviare dati strutturati in formato JSON. Questa soluzione è utile quando una parte delle informazioni può cambiare nel tempo oppure quando non è conveniente creare subito una nuova tabella per ogni proprietà.

Un esempio concreto è un catalogo di prodotti. Ogni prodotto può avere caratteristiche diverse: un notebook può avere RAM e dimensione dello schermo, mentre una scarpa può avere taglia e materiale. Salvare queste proprietà in una colonna JSON consente di mantenere una struttura flessibile senza rinunciare alla possibilità di eseguire ricerche.

In questo tutorial utilizzeremo PDO per collegarci al database e impareremo a:

  • creare una tabella con una colonna JSON;
  • inserire dati JSON da PHP;
  • leggere e decodificare i dati;
  • cercare valori all´interno del JSON;
  • modificare una singola proprietà;
  • gestire correttamente validazione ed errori.

La sintassi JSON è generalmente compatibile tra MySQL e MariaDB, anche se l´implementazione interna può essere diversa. In particolare, MySQL dispone di un tipo JSON nativo, mentre in molte versioni di MariaDB JSON è gestito come alias di LONGTEXT. Per questo motivo è importante validare sempre i dati anche nell´applicazione PHP.

Codice completo

Il seguente esempio crea una tabella di prodotti, inserisce un prodotto con caratteristiche variabili, legge il JSON, cerca prodotti in base a una proprietà e aggiorna una singola voce.

<?php
declare(strict_types=1);

$dsn = ´mysql:host=localhost;dbname=negozio;charset=utf8mb4´;
$username = ´app_user´;
$password = ´password_sicura´;

try {
    $pdo = new PDO($dsn, $username, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false,
    ]);

    // Creazione della tabella.
    // In MySQL JSON è un tipo nativo; in MariaDB può essere un alias
    // di LONGTEXT con validazione JSON a seconda della versione.
    $pdo->exec(
        ´CREATE TABLE IF NOT EXISTS prodotti (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            nome VARCHAR(150) NOT NULL,
            prezzo DECIMAL(10, 2) NOT NULL,
            caratteristiche JSON NOT NULL,
            creato_il TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        ) ENGINE=InnoDB´
    );

    // Dati strutturati in PHP.
    $caratteristiche = [
        ´marca´ => ´TechPro´,
        ´ram_gb´ => 16,
        ´schermo_pollici´ => 15.6,
        ´colori´ => [´nero´, ´grigio´],
        ´disponibile´ => true
    ];

    // json_encode converte l´array PHP in una stringa JSON.
    $json = json_encode(
        $caratteristiche,
        JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE
    );

    $insert = $pdo->prepare(
        ´INSERT INTO prodotti (nome, prezzo, caratteristiche)
         VALUES (:nome, :prezzo, :caratteristiche)´
    );

    $insert->execute([
        ´nome´ => ´Notebook professionale´,
        ´prezzo´ => 1299.90,
        ´caratteristiche´ => $json
    ]);

    $idProdotto = (int) $pdo->lastInsertId();

    // Lettura di un prodotto.
    $select = $pdo->prepare(
        ´SELECT id, nome, prezzo, caratteristiche
         FROM prodotti
         WHERE id = :id´
    );
    $select->execute([´id´ => $idProdotto]);

    $prodotto = $select->fetch();

    if ($prodotto === false) {
        throw new RuntimeException(´Prodotto non trovato.´);
    }

    // Conversione dal JSON a un array PHP.
    $prodotto[´caratteristiche´] = json_decode(
        $prodotto[´caratteristiche´],
        true,
        512,
        JSON_THROW_ON_ERROR
    );

    echo ´<h2>´ . htmlspecialchars($prodotto[´nome´], ENT_QUOTES, ´UTF-8´) . ´</h2>´;
    echo ´<p>RAM: ´ .
        (int) $prodotto[´caratteristiche´][´ram_gb´] .
        ´ GB</p>´;

    // Ricerca di prodotti con almeno 16 GB di RAM.
    // JSON_EXTRACT estrae un valore usando il percorso JSON.
    $search = $pdo->prepare(
        ´SELECT id, nome, prezzo
         FROM prodotti
         WHERE CAST(JSON_UNQUOTE(
             JSON_EXTRACT(caratteristiche, ´$.ram_gb´)
         ) AS UNSIGNED) >= :ram_minima´
    );
    $search->execute([´ram_minima´ => 16]);

    foreach ($search as $riga) {
        echo ´<p>´ .
            htmlspecialchars($riga[´nome´], ENT_QUOTES, ´UTF-8´) .
            ´ - € ´ . number_format((float) $riga[´prezzo´], 2, ´,´, ´.´) .
            ´</p>´;
    }

    // Aggiornamento di una sola proprietà del documento JSON.
    $update = $pdo->prepare(
        ´UPDATE prodotti
         SET caratteristiche = JSON_SET(
             caratteristiche,
             ´$.disponibile´,
             :disponibile
         )
         WHERE id = :id´
    );

    $update->execute([
        ´disponibile´ => 0,
        ´id´ => $idProdotto
    ]);

} catch (JsonException $e) {
    echo ´Errore nel formato JSON: ´ .
        htmlspecialchars($e->getMessage(), ENT_QUOTES, ´UTF-8´);
} catch (PDOException $e) {
    echo ´Errore del database.´;
}
?>

Spiegazione

Conversione tra PHP e JSON

In PHP un oggetto JSON viene normalmente rappresentato da un array associativo. La funzione json_encode() trasforma l´array in una stringa JSON pronta per essere salvata nel database. L´opzione JSON_THROW_ON_ERROR è consigliata perché genera un´eccezione se i dati non possono essere convertiti.

Per eseguire l´operazione inversa si utilizza json_decode(). Il secondo parametro impostato a true restituisce un array associativo invece di un oggetto PHP. Anche in questo caso JSON_THROW_ON_ERROR consente di intercettare dati corrotti o non validi.

Percorsi JSON

Un percorso come $.ram_gb indica la proprietà ram_gb al livello principale del documento. Per accedere a dati annidati si possono usare percorsi come $.specifiche.batteria.cicli. Per un elemento di un array si può utilizzare un indice, ad esempio $.colori[0].

JSON_EXTRACT() restituisce il valore individuato. JSON_UNQUOTE() rimuove le virgolette da una stringa JSON. Quando il valore deve essere confrontato numericamente, è opportuno usare anche CAST(), come nell´esempio della RAM.

Aggiornare un valore

JSON_SET() modifica una proprietà esistente oppure la crea se non è presente. Questa tecnica evita di leggere l´intero documento in PHP, modificarlo e riscriverlo completamente. È quindi utile quando il JSON è grande o quando più processi possono lavorare sugli stessi dati.

Best practice

  • Valida i dati in PHP: controlla tipo, intervallo e presenza delle proprietà prima di chiamare json_encode().
  • Usa sempre query preparate: anche i valori utilizzati nelle ricerche JSON devono essere passati tramite parametri.
  • Non inserire nomi di percorsi JSON direttamente dall´utente: i percorsi non possono essere trattati come normali parametri in ogni funzione SQL. Usa una lista di percorsi consentiti.
  • Non usare JSON per tutto: dati frequentemente filtrati, ordinati o collegati ad altre tabelle dovrebbero normalmente avere colonne relazionali tradizionali.
  • Considera gli indici: se una proprietà JSON viene cercata spesso, valuta una colonna generata o una colonna separata indicizzata. La sintassi esatta può variare tra MySQL e MariaDB.
  • Limita la dimensione dei documenti: un JSON enorme rende più difficili manutenzione, validazione e prestazioni.
  • Proteggi l´output HTML: usa htmlspecialchars() quando mostri valori provenienti dal database in una pagina web.
  • Gestisci i valori booleani: JSON distingue tra true, false, stringhe e numeri. Evita confronti ambigui e converti esplicitamente i tipi.

Riepilogo

Le colonne JSON sono una soluzione efficace per dati flessibili, come attributi personalizzati, preferenze utente o configurazioni di prodotto. Con PHP si lavora principalmente attraverso json_encode() e json_decode(), mentre MySQL e MariaDB offrono funzioni come JSON_EXTRACT() e JSON_SET() per cercare e aggiornare i contenuti direttamente nel database.

La flessibilità non deve però trasformarsi in disordine: i dati importanti per relazioni, filtri frequenti e vincoli di integrità dovrebbero restare in colonne o tabelle tradizionali. Il formato JSON è più efficace quando viene utilizzato per la parte variabile del modello, non come sostituto completo del modello relazionale.

Approfondisci con risorse ufficiali

SHARE