Questo articolo fa parte di una serie dedicata alla programmazione di un framework MVC personalizzato in PHP. L'autore ha chiamato questo framework "Taste". Gli altri articoli della serie sono disponibili nella categoria Taste framework di php.html.it.
Ci stiamo avvicinando alla fine della serie riguardante lo sviluppo di un semplice framework MVC in PHP. Dopo aver parlato del core, dei controller e delle viste, non ci rimane che trattare gli argomenti riguardanti i modelli. Quando ho iniziato a scrivere questa serie di articoli, pensavo anche di trattare in modo dettagliato l'argomento della persistenza degli oggetti su database, dato che si lega molto al concetto di modello ed oltretutto è uno strumento molto utilizzato attualmente nello sviluppo web.
Purtroppo, sviluppando la parte di framework che si sarebbe occupata della persistenza, ho notato che il codice risultava troppo complesso per poter essere trattato su articoli di questo taglio (sarebbe potuto risultare noioso a molti); ho quindi preferito lasciarlo da parte e dedicare questo articolo alla teoria che sta dietro i modelli e la persistenza, lasciando la parte implementativa a librerie esterne, salvo qualche semplice linea di codice che permetta di rendere le idee illustrate.
Cosa si intende per modello
Nella programmazione di applicazioni si ha sempre a che fare con l'implementazione di logica di programmazione che elabora delle fonti di dati attraverso l'input dell'utente. Queste fonti di dati possono avere sorgenti diversi (possono essere file XML, file di testo, database relazionali e molto altro) ma tutte hanno come scopo quello di fornire dati all'applicazione ed eseguire le operazioni di modifica richieste.
Un modello non è altro che un concetto astratto che rappresenta in modo uniforme una sorgente di dati, e che contiene la parte logica necessaria a fornire al codice dell'applicazione le API necessarie all'interazione.
Unificare l'accesso a differenti fonti di dato attraverso un'unica interfaccia è spesso un'operazione complessa che spesso non porta a raggiungere i risultati sperati. Per questo motivo gran parte delle implementazioni di framework MVC hanno deciso di implementare modelli che si occupano di interagire con le fonti di dato più comuni nel caso di applicazioni web: i database relazionali. Spesso i modelli sono implementati utilizzando un meccanismo di persistenza che permette di fornire delle API di interazione con le fonti di dato che si integrano perfettamente con il modello ad oggetti con cui spesso sono implementati i framework MVC.
La persistenza su database
Il concetto di persistenza su database è ormai all'ordine del giorno quando si parla di sviluppo web, soprattutto se si ha a che fare con framework MVC o applicazioni leggermente più complesse di quelle di routine.
La persistenza su database permette di salvare e recuperare in modo quasi trasparente degli oggetti all'interno del database, e poterli successivamente recuperare attraverso delle API di interrogazione. La persistenza su database viene solitamente implementata utilizzando due concetti differenti:
- Il framework di persistenza può fornire delle funzionalità che permettono di rimappare dati relazionali esistenti su delle classi create appositamente per adattarsi alle strutture presenti. In questo caso spesso si forniscono delle specifiche in XML che identificano come si deve comportare il framework per interagire con il database;
- Il framework di persistenza può invece fornire delle funzionalità basate sul concetto inverso rispetto a quello identificato precedentemente: difatti potrebbe fornire delle funzionalità che permettono di salvare su database un oggetto qualunque e di poterlo recuperare, senza la necessità di fornire strumenti per rimappare gli oggetti alle tabelle relazionali. Per implementare questo ultimo sistema spesso si forniscono strumenti aggiuntivi che generano una tabella relazionale da una definizione di classe. In rari casi (come nel caso di db4o) il framework implementa un sistema di storage ed interrogazione alternativo non basato su database relazionali.
In entrambi i casi, con un minimo di lavoro aggiuntivo, si può lasciare da parte il codice SQL ed interagire con le fonti di dato come se fossero dei repository di oggetti che possono essere interrogati e filtrati per ottenere quello che desideriamo.
Il processo di interrogazione avviene anche in questo caso attraverso due diversi sistemi:
- Alcuni framework (come Hybernate per Java) forniscono un linguaggio simile ad SQL che permette di interrogare la fonte di dato ed ottenere delle collezioni di oggetti che rappresentano il risultato generato dalla query. In questo caso si perde un po' di astrazione a favore di codice più snello ed un controllo più dettagliato sull'esecuzione delle interrogazioni;
- Altri framework invece (come Propel per PHP) preferiscono fornire delle API di interrogazione basate sul linguaggio stesso attraverso dei metodi e degli oggetti che si occupano di generare internamente il codice SQL necessario a recuperare correttamente i dati;
Ultimamente sta nascendo anche un terzo approccio che ritengo molto interessante (attualmente db40 implementa tra le sue API questa tipologia di interrogazione): si tratta delle così dette query native. Una query nativa non è altro che un'interrogazione fatta in modo implicito sul database attraverso del normale codice di programmazione, come se si stesse lavorando direttamente con quegli oggetti che si deve filtrare.
Il framework analizza le parti di codice relative a delle interrogazioni native e le compila internamente in linguaggio SQL in modo che vengano eseguite molto più velocemente sulla fonte di dato. In questo modo non si hanno della API specifiche per l'interrogazione, ma si ha semplicemente a che fare con il proprio linguaggio e si lavora come se gli oggetti fossero presenti in memoria, iterandoli e controllandoli uno per uno al fine di filtrarli, ordinandoli con funzioni di sorting ed aggiornandoli con semplici assegnamenti.
Qualunque sia l'approccio utilizzato è buona abitudine degli sviluppatori di framework fornire sempre strumenti che permettono la gestione praticamente automatica delle relazioni tra tabelle.
I modelli e la persistenza in Taste
Come accennato all'inizio dell'articolo ho deciso di non discutere del framework di persistenza che inizialmente ho sviluppato per Taste. Dopo aver fatto questa lunga introduzione sui modelli e sulla persistenza, abbiamo sicuramente qualche base teorica in più che ci permette di comprendere il concetto di modello anche senza la necessità di implementare esplicitamente un framework che se ne occupi.
Se non si ha a che fare direttamente con persistenza o altri meccanismi particolari, un modello può essere identificato come una semplice classe che contiene alcune funzioni di interrogazione della fonte di dati che rappresenta, e sistemi che permettono di salvare e recuperare collezioni di dati omogenei.
Un'implementazione molto semplice di modello consiste in una classe astratta che implementa le seguenti funzionalità:
- Analisi, in fase di costruzione, delle proprietà pubbliche definite in una sottoclasse in modo che possano essere utilizzate come nomi delle colonne da salvare nel database. Eventualmente si possono implementare sistemi di caching per evitare di analizzare più volte le stesse sottoclassi.
- Metodo che si occupa di controllare inserire l'oggetto nel database (nel caso sia nuovo) oppure di aggiornare il record corrispondente (nel caso l'oggetto provenga da una selezione). E' opportuno che nel caso avvenga un inserimento sia tracciato in qualche modo all'interno dell'oggetto l'eventuale chiave primaria generata automaticamente dal database. Nel caso il valore di una proprietà sia un altro modello è possibile salvarlo e successivamente utilizzare come valore della colonna corrispondente la chiave primaria autogenerata.
- Metodo che si occupa di rimuovere l'oggetto dal database.
Le sottoclassi non dovranno far altro che fornire le proprietà pubbliche da rimappare come colonne ed implementare eventualmente dei metodi che permettano una veloce elaborazione del modello. Spesso si implementano anche dei metodi statici che permettono di recuperare collezioni di oggetti in base a determinati criteri. Seguendo questo sistema ci assicuriamo di separare il codice relativo alle fonti di dato dal resto dell'applicazione, potendo eventualmente estenderlo con funzionalità più avanzate di persistenza se necessario.
Conclusione
Abbiamo terminato questa breve ma, spero, completa illustrazione dei concetti relativi ai modelli. Ho preferito adottare un approccio descrittivo e teorico, sperando di riuscire a toccare tutti i punti relativi ai modelli ed alla persistenza così da descriverli e stimolare la voglia di qualcuno nell'apprendere i concetti in modo completo. Nel prossimo articolo, che sarà l'ultimo della serie dedicata a Taste, riassumeremo i concetti fino a qui acquisiti con una semplice applicazione di esempio.