Data Access Object in PHP, tecnica di astrazione del database
(Il codice è compatibile con PHP4)
Articoli da cui ho preso ispirazione e che costituiscono in ogni caso una lettura interessante:
-
Simplify Business Logic with PHP DataObjects by Darryl Patterson
per il "readOnlyResultSet" -
PHP Best practices
divisione DAO e model(solo rappresentazione delle tabelle) -
Using PHP Objects to access your Database Tables
spunti per generalizzare i metodi di accesso al DB del DAODefault - Tutte le idee si ispirano in ogni caso al pattern Java usato nelle tecnologie di Persistence
Limiti (importanti): query complesse (es. con join fra più tabelle); tabelle con chiavi primarie composte.
L'articolo tratterà i seguenti argomenti:
- Mappatura tabelle-classi: creazione dei ValueObjects o Entità
- Descrizione dei DAO (Data Access Object)
- Creazione dell'oggetto ResultSet
- Implementazione della BusinessLogic
- Esempio di utilizzo
Mappatura tabelle-classi: creazione dei ValueObjects (o Entità)
Inizio l'articolo con la creazione di quelle classi PHP che rappresentano una tupla di una tabella del DB.
Queste classi sono molto semplici e rispecchiano esattamente la struttura delle tabelle:
Per ogni tabella esistente, si crea una classe PHP con lo stesso nome della tabella (non necessariamente il nome deve essere uguale, ma è utile per mantenere coerenza nel codice).
Ogni classe deve contenere una variabile per ciascun campo della tabella, ed è importante che le variabili abbiano lo stesso nome del campo.
Le classi non devono contenere nient'altro che queste variabili, e non ci deve essere nessun costruttore e nessun valore di default.
Se per esempio volessimo mappare una tabella 'Persona' con i suoi campi "id, nome, cognome, telefono" dovremmo creare una classe fatta così:
Ciascuna istanza di questa classe è un entità che potrà essere resa "persistente" salvandola nella base dati, oppure potrà essere stata creata recuperando i dati dal database, ed in questo caso essa rappresenterà esattamente una tupla della tabella.
Descrizione dei DAO (Data Access Object)
A fianco delle entità (o Value Objects), dobbiamo creare gli oggetti che comunicano con il database (i DAO), e che hanno le informazioni necessarie al loro funzionamento.
Le informazioni necessarie sono:
- Il nome esatto della tabella mappata nel database
- Il nome esatto della chiave primaria della tabella
- L'informazione che mi consente di sapere quale entità corrisponde alla tabella mappata (userò un istanza dell'entità)
In pratica con un DAO metto in relazione un'entità (ad es. la classe Persona) con una tabella presente nel database, aggiungendo altre informazioni come la chiave primaria.
Vediamo com'è fatto il DAO per la tabella Persona:
Come si vede nel codice questa classe estende un DAODefault comune ai DAO di tutte le tabelle.
La classe 'DAODefault' è la classe più complicata e la più interessante, e per come l'ho scritta ci consente di aggiungere nuove entity e DAO in maniera molto semplice, senza dover
scrivere mai una riga di SQL per ogni tabella in più che si vuole mappare.
Vediamo quindi come è fatta la classe DAODefault:
Per prima cosa vogliamo creare i metodi di base per la comunicazione con il database attraverso le query SQL, le funzioni da implementare sono:
- get($id) - per recuperare una tupla da una tabella conoscendone la chiave primaria
- insert($object) - per inserire una nuova tupla in una tabella
- update($object) - per modificare i dati di una tupla esistente
- delete($object) - per cancellare una tupla presente in una tabella
Il problema principale è quello che tutti i metodi devono essere generali, non devono riferirsi a nessuna tabella in particolare,
e non possono conoscere a priori tutti i campi da modificare.
Iniziamo implementando il metodo get:
Si vede subito che il problema del nome della tabella viene risolto usando la variabile $tablename,
che viene inizializzata nel DAO specifico per ciascuna tabella che estenderà questa classe DAODefault.
Stessa cosa avviene per la chiave primaria ($id).
La parte interessante è il metodo getFromResult(), che ha lo scopo di riempire l'entity contenuta nel DAO con i dati recuperati dalla query:
Ma come fa a riempire l'entity ($vo) con i risultati del database senza sapere nulla sui campi della tabella?
Poichè l'entity rispecchia ESATTAMENTE la struttura della tabella, sarà sufficiente recuperare i nomi dei campi dal risultato della query,
quindi salvare nelle variabili di vo ($vo->$chiave) che hanno gli stessi nomi dei campi, i valori del risultato.
Passiamo al secondo metodo: insert($object).
In questo caso l'informazione sui campi presenti nella tabella da aggiornare viene presa dall'entity e dalle sue variabili.
L'elenco dei nomi delle variabili dell'entità da inserire nella tabella, ci consente di costruire la stringa SQL per aggiornare la tabella.
Vediamo il metodo update($object).
Anche qui il nome dei campi da aggiornare vengono presi dai nomi dalle variabili della entity ($vo).
Ed infine il metodo delete($object).
In questo caso la funzione è molto semplice e si limita a recuperare la chiave primaria dalla entità per eseguire un DELETE.
Con questo abbiamo concluso i metodi fondamentali.
Possiamo scrivere ora dei metodi per semplificare l'utilizzo del DAO, cominciando dal metodo "save($object)":
Questo semplice metodo ci consente di usare una sola funzione sia per inserire una nuova entità nel DB che per aggiornarne una già esistente
Ora dobbiamo creare un metodo che ci consenta di fare una ricerca che preveda la restituzione di più di una riga della tabella.
Per questo scopo è stato creato il metodo find($object).
Il parametro è un'entità con alcune delle sue variabili settate. I valori di queste variabili costituiscono il criterio di ricerca.
Per consentire una ricerca non soltanto per valori uguali (es. WHERE nome='john'), ho creato nel DAO la variabile $operator, che di default è settata a '='
ma che può essere impostata a piacimento, ad esempio nel caso si voglia trovare tutte le persone con più di 40 anni basterà impostare un ipotetica variabile eta dell'entità a 40,
e la variabile $operator = '>='.
Ho previsto anche la possibilità di dare un ordine ai risultati settando la variabile $orderby (es. $orderby='eta').
Per comodità è possibile creare un metodo stile "factory" per centralizzare la creazione dei DAO e per gestire le connessioni al DB:
Creazione dell'oggetto ReadOnlyResultSet
Una volta creata ed eseguita la query SQL nel metodo find, vogliamo poter gestire il risultato in maniera comoda, per questo viene creata la classe "ReadOnlyResultSet" con i metodi per
navigare tra i risultati.
Con un semplice ciclo "while($resultSet->hasNext())" è possibile ottenere un'entità per ogni riga del risultato ($entity = $resultSet->next()).
E' inoltre possibile resettare il contatore o indicare l'i-esima riga (offset) da cui prelevare le entità.
Implementazione della Business Logic
Fin'ora abbiamo creato uno strato software per astrarre la struttura del database, ora vogliamo creare i metodi utili alla nostra applicazione.
Creaiamo quindi una classe che contenga dei metodi per recuperare le informazioni che ci interessano.
Alcune funzioni possono sembrare ridondanti ma se vogliamo che la parte di Visualizzazione rimanga completamente isolata dalla parte di Modello (pattern MVC),
queste funzioni risulteranno comode e garantiranno una pulizia maggiore nel codice finale.
Esempio di utilizzo
Vediamo come si presenta il codice di una pagina php che utilizza quanto abbiamo creato fin'ora.
In questa pagina vogliamo elencare tutte le persone che hanno un età maggiore di 45 anni.