Classi PHP e JSON

Abbiamo già visto qui un primo esempio introduttivo di utilizzo della OOP e le classi in PHP ma è decisamente da manuale e poco realistico. Vediamo invece come cominciare ad utilizzare la classi ed oggetti PHP per relazionarci col database e avvicinarci a parlare del modello MVC.

Classe base

Partiamo da un esempio classico sempre partendo dalla classe Persona che, per completezza riproponiamo epurata dei metodi che non vogliamo vedere nella nuova logica pseudo MVC.

class Persona
{
    private   $nome;
    private   $cognome;
    private   $indirizzo;
    private   $datadinascita;

    /**
     * Costruttore: occhio non è ammesso l'overloading
     * con più costruttori se non con un trucco assurdo
     */
    public function __construct($nome, $cognome,
                                $indirizzo, $datadinascita)
    {
        $this->nome = $nome;
        $this->cognome = $cognome;
        $this->indirizzo = $indirizzo;
        $this->datadinascita = $datadinascita;
    }

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

    public function setCognome($cognome)
    {
        $this->cognome= $cognome;
    }

    public function setIndirizzo($indirizzo)
    {
        $this->indirizzo= $indirizzo;
    }    

    public function setDatadinascita($data)
    {
        $this->datadinascita= $data;
    } 

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

    public function getNome()
    {
        return $this->nome;
    }

    public function getIndirizzo()
    {
        return $this->indirizzo;
    }
    
    public function getDatadinascita()
    {
        return $this->datsdinasita;
    }
}

Rispetto agli esempi precedenti abbiamo epurato il metodo stampa. Questo ha senso solo con lo scopo di debug ma non si dovrebbe delegare al codice PHP la facoltà di scrivere codice HTML da visualizzare se non per semplicità didattica. Le classi come quella che stiamo vedendo dovrebbero essere solo da modello dati e non occuparsi della visualizzazione.

Introduciamo un’altra versione della stessa classe, legata sempre a Persona. Questa volta però la arricchiamo di metodi che si collegano al database, interrogano una o più tabelle e restituiscono il risultato sotto forma di JSON. La scelta di restituire tutto attraverso JSON non è casuale. La nostra classe non deve occuparsi della visualizzazione ma restituire i dati di interesse in un formato utilizzabile con AJAX e JavaScript, i veri poi strumenti per realizzare la View o qualsiasi altro strumento simile.

Anche i messaggi di errore li esplicitiamo in JSON in modo da lasciare al frontend un elemento di debug che con AJAXe JS sarebbe invece complesso. Un’alternativa interessante è tornare comunque array associativi al posto di JSON. Sarebbe poi lasciato ad un altro strato software il compito di convertilo in JSON utile ad AJAX o XML utile ad altre tecnologie SOAP.

I metodi possono essere sia diretti riferiti all’oggetto, sia statici quando non si riferiscono allo stato dell’oggetto ma ad un insieme di sue istanze. Possiamo però tranquillamente notare come la maggior parte dei metodi, almeno per il nostro esercizio, avrà tipo statico e ritorno JSON. Lasciamo al lettore invece la possibilità di valorizzare come ritorno un oggetto di tipo persona.

Analizziamo gli elementi e funzioni interessanti.

La prima riga mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); serve essenzialmente a mostrare gli errori nei try/catch che andiamo ad inserire nelle nostre query.

Metodi toArray() e echoJSON()

Veniamo ai primi due metodi interessanti toArray() ed echoJSON(). Questi sono piuttosto semplici come strumenti ma nascondono un po’ la vera potenza della OOP applicata al PHP. L’idea di base sta nel creare oggetto interoperabili lato backend vs frontend. L’idea è quindi quella che un intero oggetto, una istanza di classe possa essere esposto facilmente o attraverso un array o meglio nella sua forma crossplatform JSON. Da implementare anche il metodo di ritorno da JSON ad oggetto classe. Per il metodo JSON non c’è nulla di misterioso. Come nelle altre esercitazioni AJAX, va esplicitamente definita l’intestazione header e quindi passata alla funzione json_encode la versione array dei dati del nostro oggetto, estraibile con il metodo appena commentato toArray().

Metodo show()

Ammetto che qui non c’è una sintassi chiara ed universale. Se i metodi di accessto sono set/get in quasi tutti i linguaggi di programmazione, come indicare universalmente la stampa di tutti i record di una tabella non c’è una versine unica condivisa o condivisibile. Tanto ci basta per dare senso al nome del nostro metodo.

E’ un metodo statico, quindi non prevede di essere lanciato direttamente da una istanza della classe a cui appartiene, anzi. La sintassi per i metodi statici è banale Classe::metodo() ovvero qui Persona::show().

Veniamo alla query. Qui ci complichiamo un po’ la vita rispetto alle query classiche viste negli altri esercizi ad esempio basati su AJAX. Vogliamo infatti usare una “query preparata“. In questo caso, non c’è una differenza profonda con la versione “normale”. Andamo ad effettuare una SELECT senza una clausola WHERE parametrizzata del resto. Vedremo invece che le query preparate nel caso di parametri arrivanti via PHP da form o altreporzioni di codice, sono molto interesanti poichè precompilando la query senza parametri, rende la query stessa immune alle SQL Injection.

Osserviamo come la connessione diventi un oggetto,la query, un oggetto, idem risultato e righe. Tutti con i propri metodi. In quest’ultimo caso ci semplifichiamo ulteriormente la vita, attenzione soprattutto l’alunno in vista dell’esame di stato, che con una riga precompila un array di array facilmente restituibile come JSON.

Tutto il codice è inserito nel try/catch, costrutto che gli alunni conoscono dal Java e C# e che in PHP non ci sorprende. L’unico dettagli oche vi segnalo è che anche l’errore viene restituito con un JSON in modo che l’eventuale interfaccia Javascript può sempre processare questo formato sia con esito positivo che negativo della query, semplicemente discriminando un messaggio distinguibile come KO.

    public static function show()
    {
        try
        {
            $connessione = new mysqli("localhost","root", "root","test");
            $query = $connessione->prepare("SELECT * FROM Persona");
            $query->execute();
            $risultato = $query->get_result();
            $righe = $risultato->fetch_all(MYSQLI_ASSOC);

            header("Content-type: application/json");
            echo json_encode($righe);
        }
        catch(Exception $e)
        {
            header("Content-type: application/json");
            echo json_encode(array("KO", $e->getMessage()));
        }
    }

Metodo insert()

    public function insert()
    {
        try
        {
            $connessione = new mysqli("localhost","root", "root","test");
            $query = $connessione->prepare("INSERT INTO Persona (nome, cognome,indirizzo, datanascita, sesso) VALUES (?,?,?,?,?)");
            $datemysql = date('Y-m-d', strtotime(str_replace('-', '/', $this->datanascita))); //date php diverse da mysql, converto
            $query->bind_param("sssss",$this->nome, $this->cognome, $this->indirizzo, $datemysql, $this->sesso);
            $query->execute();

            header("Content-type: application/json");
            echo json_encode(array("OK", "Inserimento corretto"));
        }
        catch(Exception $e)
        {
            header("Content-type: application/json");
            echo json_encode(array("KO", $e->getMessage()));
        }
    }

Metodo get()

Restitutisce un record della tabella usando un parametro come chiave. Anche qui metodo statico e query preparata. Il fetch_all per semplificare la traduzione del risultato in array associativo, la sua esposizione conseguente in JSON

public static function get($id)
    {
        try
        {
            $connessione = new mysqli("localhost","root", "root","test");
            $query = $connessione->prepare("SELECT * FROM Persona WHERE id = ?");
            $query->bind_param("s",$id);
            $query->execute();

            $risultato = $query->get_result();
            $righe = $risultato->fetch_all(MYSQLI_ASSOC);

            header("Content-type: application/json");
            echo json_encode($righe);
        }
        catch(Exception $e)
        {
            header("Content-type: application/json");
            echo json_encode(array("KO", $e->getMessage()));
        }
    }    

Elimina

Diciamo l verità, il metodo precedente get era un po’ la scusa per preparare il metodo di cancellazione dal db di un record. Infatti ricalca molto un mix del metodo get con i messaggi del metodo insert. Non lo commentiamo oltremodo perché non presenta particolari difficoltà rispetto al codice già commentato.

    public static function delete($id)
    {
        try
        {
            $connessione = new mysqli("localhost","root", "","test");
            $query = $connessione->prepare("DELETE FROM Persona WHERE id=?");
            $query->bind_param("s",$id);
            $query->execute();
            
            header("Content-type: application/json");
            echo json_encode(array("OK", "Cancellazione corretta"));           
        }
        catch(Exception $e)
        {
            header("Content-type: application/json");
            echo json_encode(array("KO", $e->getMessage()));
        }
    }   

Modifica

La modifica è un po’ più critica. Può essere fatta in diversi modi. Il metodo statico è il più semplice e goloso per un esercizio semplificato, passando i parametri con i dati da modificare e la chiave per selezionare il record di interesse. La versione più interessante sarebbe utilizzare un metodo di classe a patto che nell’attributo chiave, qui l’id, ci sia stato reperito ed inserito il valore corretto, cosa che non abbiamo fatto in queste righe al momento e che lasciamo al lettore come problema di facile soluzione in realtà.

    public function modifica($nome, $cognome, $indirizzo, $data, $sesso)
    {
         try
        {
            $connessione = new mysqli("localhost","root", "root","test");
            $query = $connessione->prepare("UPDATE Persona SET nome = ?, cognome= ?, indirizzo = ?, datanascita = ?, sesso = ?   WHERE id = ?");
            $query->bind_param("ssssss",$nome, $cognome, $indirizzo, $data, $sesso, $this->id);
            $query->execute();

            header("Content-type: application/json");
            echo json_encode(array("OK", "Aggiornamento corretto"));  
        }
        catch(Exception $e)
        {
            header("Content-type: application/json");
            echo json_encode(array("KO", $e->getMessage()));
        }
    }

Listato completo

<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

class Persona
{
    private   $nome;
    private   $cognome;
    private   $indirizzo;
    private   $datanascita;
    private   $sesso;

    /**
     * Costruttore: occhio non è ammesso l'overloading
     * con più costruttori se non con un trucco assurdo
     */
    public function __construct($nome, $cognome,
                                $indirizzo, $datanascita, $sesso)
    {                                
        $this->nome = $nome;
        $this->cognome = $cognome;
        $this->indirizzo = $indirizzo;
        $this->datanascita = $datanascita;
        $this->sesso = $sesso;
    }

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

    public function setCognome($cognome)
    {
        $this->cognome= $cognome;
    }

    public function setIndirizzo($indirizzo)
    {
        $this->indirizzo= $indirizzo;
    }    

    public function setDatadinascita($data)
    {
        $this->datanascita= $data;
    } 

    public function setSesso($sesso)
    {
        $this->sesso= $sesso;
    }

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

    public function getNome()
    {
        return $this->nome;
    }

    public function getIndirizzo()
    {
        return $this->indirizzo;
    }
    
    public function getDatanascita()
    {
        return $this->datanascita;
    }

    public function getSesso()
    {
        return $this->sesso;
    }
    /**
     * Una funzione di comodo ma quasi necessaria serve ad
     * esportare i dati dell'oggetto in array per favorire
     * la codifica in json
     */
    public function toArray()
    {
        //return (array)$this;
        //equivalente a questo
        return array("nome"=>$this->nome,
                     "cognome"=>$this->cognome,
                     "indirizzo"=>$this->indirizzo,
                     "datanascita"=>$this->datanascita,
                     "sesso"=>$this->sesso);
    }

    public function echoJSON()
    {
        header("Content-type: application/json");
        echo json_encode($this->toArray());
    }

    /**
     * Qui inserisco tutti i metodi che interagiscono con il DB SQL
     * Tecnica diffusa è separare la parte di classe e modellazione SQL 
     * in due file (persona.class.php e persona.model.php). 
     * Qui semplifichiamo in un file solo.
     */
 
    public static function show()
    {
        try
        {
            $connessione = new mysqli("localhost","root", "root","test");
            $query = $connessione->prepare("SELECT * FROM Persona");
            $query->execute();
            $risultato = $query->get_result();
            $righe = $risultato->fetch_all(MYSQLI_ASSOC);

            header("Content-type: application/json");
            echo json_encode($righe);
        }
        catch(Exception $e)
        {
            header("Content-type: application/json");
            echo json_encode(array("KO", $e->getMessage()));
        }
    }

    public function insert()
    {
        try
        {
            $connessione = new mysqli("localhost","root", "root","test");
            $query = $connessione->prepare("INSERT INTO Persona (nome, cognome,indirizzo, datanascita, sesso) VALUES (?,?,?,?,?)");
            $datemysql = date('Y-m-d', strtotime(str_replace('-', '/', $this->datanascita))); //date php diverse da mysql, converto
            $query->bind_param("sssss",$this->nome, $this->cognome, $this->indirizzo, $datemysql, $this->sesso);
            $query->execute();

            header("Content-type: application/json");
            echo json_encode(array("OK", "Inserimento corretto"));
        }
        catch(Exception $e)
        {
            header("Content-type: application/json");
            echo json_encode(array("KO", $e->getMessage()));
        }
    }

    public static function get($id)
    {
        try
        {
            $connessione = new mysqli("localhost","root", "root","test");
            $query = $connessione->prepare("SELECT * FROM Persona WHERE id = ?");
            $query->bind_param("s",$id);
            $query->execute();

            $risultato = $query->get_result();
            $righe = $risultato->fetch_all(MYSQLI_ASSOC);

            header("Content-type: application/json");
            echo json_encode($righe);
        }
        catch(Exception $e)
        {
            header("Content-type: application/json");
            echo json_encode(array("KO", $e->getMessage()));
        }
    }    

}

Alternativa al fech_all()

Se l’alunno non ha familiarità col metodo fetch_all, può sempre sviscerare il risultato della query con la costruzione classica del vettore associtivo come segue

//se ho partiolari esigenze faccio il loop altrimenti fetch_all automatico
            $array = array();
            
            while($r = $risultato->fetch_object())
            {
                array_push($array, array("id" => $r->id,
                                            "nome" => $r->nome,
                                            "cognome" => $r->cognome,
                                            "indirizzo" => $r->indirizzo,
                                            "datadinascita" => $r->datadinascita));
            }

Parametrizzazione dei dati di login Mysql

Da sempre siamo abituati a paramtrizzare i dati di login al db in un file a parte. Non ci siamo impazziti in questo esercizio ma semplicemente all’interno della classe non posso fare un semplice include della varibili note. Se programmo ad OOP, anche questi dati andrebbero messi in una classe che poi portei far interagire o per ereditarità o composizione. Soluzine ntrmedia pur sempre valida è usare, con parsimoni, le varibili superglobali php $GLOBALS che andrebbero valorizzate in un main o script antecedente, poi usate nella classe come segue. I valori contenuti in queste varibili sono un po’ come i cokie, visibili in tutte le pagine del nostro script.

  public function insert()
  {
      $connessione = new mysqli($GLOBALS["mysql_host"],
                                $GLOBALS["mysql_user"],
                                $GLOBALS["mysql_pass"],
                                $GLOBALS["mysql_db"]);
      
      if ($connessione->connect_errno)
        return array("KO", $connessione->error);

      $query = $connessione->prepare("INSERT INTO Persona (id, nome, cognome, sesso) VALUES (?,?,?,?)");
      $query->execute([null,$this->nome, $this->cognome,$this->sesso]);

  }

Script di test

Non avendo ancora gli strumenti completi per la creazione di un modello MVC funzionante, ci limitiamo a creare uno script che sia una sorta di main per testare il potenziale della nostra classe. Commentate o scommentate le righe di interesse per vedere come le chiamate JSON restituiscano o meno gli elementi di interesse.

<?php
ini_set ( 'display_errors', 1 );
error_reporting ( E_ALL );

include "persona.class.php";

$alfy= new Persona("Alfredo", "Centinaro", "via villa mosca 47b", date("1/2/1982"),"M");
$alfy->echoJSON();
$alfy->insert();
Persona::show();
Persona::get(1);

Il codice SQL

Per completezza e testing, riportiamo il codice della tabella Persona, nulla di straordinariamente complesso ma utile per facilitare la comprensione pratica e ulteriori test. Nel nostro caso abbiamo inserito la tabella in un db denominato test

CREATE TABLE `Persona` (
  `id` int NOT NULL,
  `nome` varchar(60) NOT NULL,
  `cognome` varchar(60) NOT NULL,
  `indirizzo` varchar(120) NOT NULL,
  `datanascita` date DEFAULT NULL,
  `sesso` varchar(1) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


INSERT INTO `Persona` (`id`, `nome`, `cognome`, `indirizzo`, `datanascita`, `sesso`) VALUES
(1, 'Mario', 'Rossi', 'via garibaldi, 57', '1977-07-01', 'M'),
(2, 'Alfredo', 'Centinaro', 'via villa mosca 47b', '1982-01-02', 'M');

ALTER TABLE `Persona`
  ADD PRIMARY KEY (`id`);

ALTER TABLE `Persona`
  MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
COMMIT;

Ultima modifica 2 Maggio 2023