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.
Con questo articolo iniziamo a parlare in modo anche pratico di come è strutturato un framework MVC e quali sono le pratiche comuni seguite durante il processo di implementazione. Per semplificare i titoli degli articoli e fare in modo che si capisca fin da subito se l'articolo che vi apprestate a leggere tratterà dello sviluppo del framework MVC, ho deciso di dare un nome al codice che andremo pian piano a sviluppare: Taste.
In questo articolo tratteremo le basi sulle quali andremo a sviluppare il core MVC e le utilità di supporto; nello specifico parleremo della struttura del filesystem, decideremo quale sarà il flusso di lavoro del framework e discuteremo l'implementazione di alcune delle classi principali.
La prima versione del codice potete scaricarla dal collegamento Download in alto in questa pagina. È ancora incompleta ma verrà arricchita man mano che avanzeremo con la serie di articoli.
Struttura ed organizzazione del core
Pratica comune della maggior parte dei framework MVC in circolazione è utilizzare un file di bootstrap (che nel nostro caso sarà bootstrap.php
) al quale vengono girate tutte le richieste HTTP indirizzate all'applicazione sviluppata con il framework. Il file di boot si occupa quindi di invocare la serie corretta di operazioni al fine di rispondere correttamente alla richiesta e restituire il risultato aspettato. Il path richiesto dal browser viene quindi interpretato diversamente da come verrebbe normalmente fatto: difatti non sempre questo si riferirà ad un file fisico presente su disco, ma rappresenterà in modo univoco un controller da eseguire per ottenere la vista corretta; sarà compito del framework identificare ed eseguire il controller corretto.
Date queste esigenze possiamo quindi identificare quattro componenti principali su cui si dovrà iniziare a sviluppare il framework:
- una classe che rappresenta la richiesta inviata al file di bootstrap;
- una classe che rappresenta la risposta alla richiesta inviata dal browser;
- una classe che rappresenti l'applicazione da eseguire per tradurre la richiesta in operazioni in grado di produrre una risposta adeguata;
- una classe che funga da collante e sia in grado di coordinare il funzionamento di quelle precedenti;
In aggiunta a questi componenti essenziali dovremo implementare un sistema per la gestione della configurazione del framework. Implementati questi componenti base potremmo quindi avere una struttura sulla quale costruire l'intero framework.
Il filesystem sarà organizzato nel modo seguente:
root/ taste/ static/ cache/ debug.conf controllers/ models/ views/ bootstrap.php taste.php
Il file bootstrap.php
sarà per l'appunto il nostro file di bootstrap e verrà completato successivamente, quando avremo un'applicazione d'esempio da eseguire.
Le cartelle models
, view
e controllers
sono anch'esse attualmente vuote ed andranno a contenere le implementazioni specifiche dei modelli, delle viste e dei controller che verranno affiancati al framework nello sviluppo di un'applicazione.
La cartella static
conterrà tutti i file statici che utilizzeremo nel framework come i file di configurazione, le pagine di default renderizzate in caso di errore ed i file temporanei di cache.
Infine abbiamo la cartella taste
che conterrà l'effettiva implementazione del framework e sulla quale inizieremo a lavorare tra poco; il file taste.php
includerà delle semplici opzioni di configurazione globale indipendenti dal sistema che andremo successivamente ad implementare.
Il sistema di configurazione
Per il sistema di configurazione ho deciso di seguire una strada differente da quella solitamente adottata quando si sviluppano applicazioni in PHP; anziché basarmi su degli array di configurazione oppure utilizzare un singleton globale dentro cui caricare la configurazione contenuta in un file di testo, ho optato per un approccio ad oggetti: ogni oggetto che vogliamo risulti configurabile dall'utente, dovrà estendere la classe astratta Configurable che si occuperà in automatico di caricare da un file di configurazione le direttive specifiche in base al nome della classe stessa. Le direttive potranno poi essere accedute attraverso la proprietà pubblica config sia come proprietà che attraverso il metodo get che permette di specificare anche un'eventuale valore di default.
Il sistema di configurazione, che si trova all'interno della cartella taste/config, si compone di due classi principali e due classi di supporto:
- la classe
ConfigException
, utilizzata come eccezione generata in caso di malfunzionamenti imprevisti durante l'utilizzo del sistema; - la classe
DummyConfigurable
che, come vedremo tra poco, verrà utilizzata dalla nostra classeConfigurable
per accedere alle proprietà di configurazione di un oggetto specifico, indipendentemente dal contesto e senza dover passare per la sua proprietà config (e quindi senza richiederne un'istanza); - la classe
Config
che rappresenta l'oggetto esposto daConfigurable
attraverso la proprietàconfig
; - ed infine la classe astratta
Configurable
con l'implementazione reale del sistema;
Il file di configurazione da cui Configurable
estrarrà le informazioni verrà posizionato nella cartella static/
ed avrà un formato molto semplice; ogni riga vuota o preceduta dal carattere sharp (#) verrà ignorata dal parser, mentre le direttive saranno rappresentate da delle normali espressioni di assegnamento.
Le righe verranno lette in successione e le direttive verranno assegnate automaticamente alla sezione Default; per cambiare la sezione, e di conseguenza specificare l'oggetto al quale assegnare alcune direttive, sarà necessario includere il nome dell'oggetto stesso all'interno di parentesi quadre. Vediamo un esempio:
# Questo è un commento
valore1 = Variabile assegnata alla sezione Default
[MioOggetto]
valore2 = Variabile assegnata a MioOggetto
[AltroMioOggetto]
valore3 = Variabile assegnata ad AltroMioOggetto
Passiamo quindi ai dettagli implementativi: la classe Config
(contenuta nel file taste/config/Config.php
insieme ad alcuni commenti) accetta come parametro del costruttore un array di dati formato da coppie chiave-valore; ogni chiave rappresenta una proprietà esposta dall'oggetto configurabile all'interno del quale Config verrà istanziata.
Le proprietà potranno essere accedute sia attraverso l'overload del metodo __get che utilizzando il metodo get; nel primo caso verrà generata una ConfigException
nel caso in cui la proprietà non fosse presente, mentre nel secondo caso potremo specificare come secondo parametro opzionale il valore di default da applicare per mascherare l'eccezione.
La classe astratta Configurable
(contenuta nel file taste/config/Configurable.php
insieme ad alcuni commenti) è il cuore del sistema; per ottimizzare le prestazioni il file di configurazione viene caricato una volta sola e salvato in una variabile statica privata, che verrà interrogata da tutte le istanze di Configurable
per ricevere le informazioni desiderate.
Il metodo privato loadConfiguration
si occupa di questa operazione, effettuando il parsing lineare del file di configurazione specificato nella costante CONFIGURATION_FILE
e salvando i dati nella variabile $configuration
. Ad ogni oggetto configurabile vengono assegnate le sezioni che corrispondono al nome della sua classe ed al nome delle classi da cui deriva; in questo modo potremo impostare delle configurazioni generiche per delle classi di oggetti e specificare dei valori specifici solo per alcune sottoclassi di questi:
[Base] prova = Valore di Base [Figlio1] prova = Valore di Figlio1 --- class Base extends Configurable { ... } class Figlio1 extends Base { ... } class Figlio2 extends Base { ... } $base = new Base(); echo $base->config->prova; // Stampa Valore di Base $f1 = new Figlio1(); echo $f1->config->prova; // Stampa Valore di Figlio1 $f2 = new Figlio2(); echo $f2->config->prova; // Stampa Valore di Base
Infine per rendere possibile l'accesso ai valori di configurazione di un oggetto senza necessariamente doverne avere un'istanza, viene implementato il metodo statico queryConfiguration
: appoggiandosi ad un oggetto configurabile standard (DummyConfigurable
) viene caricato il file di configurazione in memoria e successivamente viene interrogata manualmente la variabile statica $configuration
per recuperare il valore richiesto per la sezione specificata.
Conclusioni
In questo articolo abbiamo introdotto la struttura base del framework e descritto l'implementazione del sistema di configurazione; settimana prossima passeremo a descrivere in modo dettagliato le classi che gestiscono le richieste, le risposte e l'applicazione.