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.
Nell'articolo precedente abbiamo iniziato a discutere dell'implementazione del sistema di configurazione del framework Taste; abbiamo anche introdotto la struttura base di funzionamento accennando ai componenti che interagiscono nella gestione di una richiesta HTTP proveniente dal webserver ed instradata al file di bootstrap. In questo articolo continueremo da dove ci eravamo fermati precedentemente, discutendo prima l'implementazione di un registro statico globale in cui registrare delle informazioni che dovranno essere accessibili da qualunque punto del nostro codice e successivamente fornendo l'implementazione delle classi per la gestione delle richieste e delle risposte HTTP che passano per il file di bootstrap.
Il framework aggiornato con le ultime modifiche è possibile scaricarlo dal link download che trovate all'inizio dell'articolo.
Il registro globale
La classe Registry è la prima classe di supporto che andiamo ad implementare, che servirà per poterci permettere di condividere delle informazioni tra i vari oggetti senza la necessità di dover utilizzare variabili globali. La classe Registry è implementata utilizzando il pattern Singleton (che abbiamo discusso in un articolo precedente sui design patterns) ed una serie di metodi static pubblici che permettono di salvare, recuperare ed eliminare delle coppie chiave-valore. Il vantaggio dell'utilizzo di un registro globale implementate come un Singleton è dato dal fatto che si mantiene un'interfaccia ad oggetti senza dover necessariamente sacrificare la semplicità di utilizzo di un array globale e senza rischiare di intaccare altre porzioni di codice non strettamente legate al framework che potrebbero fare uso di array omonimi.
Il file sorgente (che potete trovare in taste/utils/Registry.php
) fornisce un'implementazione molto semplice di registro:
- Il metodo
getInstance
tipico del Singleton, che solamente viene reso pubblico, è invece privato in modo da forzare l'utilizzo degli altri metodi statici per lavorare sul registro; - Internamente i dati sono salvati all'interno dell'array
$cache
e quindi nel caso in cui venga assegnato un valore ad una chiave già presente nel registro, questo sovrascrive quello precedente; - i metodi
has
eset
lavorano sulla cache interna come farebbero rispettivamente il metodoisset
e l'operatore di assegnamento; - il metodo
get
ed il metododel
restituiscono entrambi un'eccezioneRegistryException
nel caso in cui la chiave specificata come primo parametro non esista all'interno del registro. È possibile comunque specificare come secondo parametro opzionale un valore di default per il metodo get in modo che l'eccezione non venga generata.
Con questa implementazione quindi sarà possibile interrogare il registro globalmente senza la necessità di ottenere delle istanze o fare riferimento a variabili globali:
<?php
require_once 'taste/utils/registry.php';
function test_set()
{
Registry::set('chiave', 'valore');
}
function test_get()
{
// Stampa: valore
echo Registry::get('chiave');
}
test_set();
test_get();
?>
Incapsulamento delle richieste HTTP
Dopo aver parlato del registro globale e di come è stato implementato, iniziamo la discussione riguardante la classe che si occuperà di incapsulare la richiesta HTTP proveniente dal web server in modo che possa essere gestita correttamente dallo script di bootstrap.
La classe che utilizzeremo è chiamata banalmente Request
(potete trovare l'implementazione nel file taste/Request.php
) e verrà istanziata una sola volta dallo script di bootstrap e salvata all'interno del registro in fase di costruzione, in modo da essere accessibile attraverso la chiave request in modo globale. La classe Request
si occupa fondamentalmente di rendere disponibile agli altri componenti del core il path richiesto dal web server; oltretutto permette anche di effettuare un reindirizzamento interno (quindi gestito sempre dalla stessa richiesta senza la necessità di doversi appoggiare ad un header HTTP) in modo trasparente. Vediamo l'implementazione del costruttore:
<?php
...
public function __construct()
{
if(DEBUG_MODE && isset($_GET['url']))
{
$url = $_GET['url'];
$qs = preg_replace("|url=([^&]+)&?|", "", $_SERVER['QUERY_STRING']);
if(strlen($qs) > 0)
$url .= "?".urldecode($qs);
}
else
$url = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : "/";
$this->path_info = current(explode("?", $url));
$this->redirect_path = null;
$this->redirect_queue = array($this->path_info);
Registry::set('request', $this);
}
...
?>
In primo luogo possiamo notare come il sistema gestisca in modo diverso l'assegnazione del path richiesto in base che ci si trovi in DEBUG_MODE
o no (la costante DEBUG_MODE
è definita nel file taste.php
automaticamente, in base al valore dell'opzione debug
della sezione Server
). Normalmente come path viene utilizzato l'URI richiesto dal browser, da cui viene rimossa la query string; nel caso in cui però ci si trovi in DEBUG_MODE
e nel caso in cui sia presente il parametro get url
, viene utilizzato il valore di quest'ultimo come path richiesto. Infine la richiesta viene salvata nel registro.
La classe Request mantiene una coda dei reindirizzamenti effettuati attraverso il metodo setRedirectPath
, che può essere successivamente recuperata attraverso il metodo getRedirectQueue
. Ogni qualvolta viene effettuato un nuovo reindirizzamento, l'ultimo path assegnato viene utilizzato come path corrente e quindi restituito dal metodo getPathInfo.
La classe Request non è da considerarsi completa, e potrebbe essere estesa con metodi che semplifichino l'accesso ai valori degli array globali POST
, GET
o FILE
.
Le risposte HTTP
Passiamo ora a trattare la classe che si occuperà di incapsulare le risposte che verranno generate dal codice eseguito dallo script di bootstrap in base alla richiesta effettuata dal web server. Ogni risposta sarà rappresentata da un'istanza della classe Response
che, oltre al contenuto da restituire al browser, si occuperà anche della gestione degli header e dell'eventuale generazione implicita ove fosse necessario.
L'implementazione della classe Response (che potete trovare all'interno del file taste/Response.php
) permette quindi:
- una gestione di un semplice buffer di output, gestito dai metodi
getContent
,setContent
edaddContent
che si occupano rispettivamente di recuperare, assegnare ed aggiungere del contenuto; - l'immagazzinamento di una lista di header, salvati attraverso i metodi
setHeader
esetHeaderIfUnset
; - la possibilità di assegnare semplicemente i valori di un cookie attraverso il metodo
setCookie
e di eliminare dei cookie salvati precedentemente attraversodelCookie
; - ed infine la renderizzazione dell'output.
Vediamo in dettaglio le poche righe di codice che si occupano della renderizzazione:
<?php
...
public function render()
{
$this->setHeader('Content-Lenght', strlen($this->content));
$this->setHeaderIfUnset('Content-Type', 'text/html');
foreach($this->headers as $name => $value)
header($name.": ".$value);
echo $this->content;
}
...
?>
Come possiamo notare viene utilizzato un semplice echo
sul contenuto immagazzinato, che viene comunque preceduto dall'assegnamento degli header Content-Type
e Content-Lenght
e dalla generazione della chiamata alla funzione header per ogni valore contenuto all'interno degli header salvati durante il processo di generazione della risposta. Il Content-Lenght
viene sempre generato in base alla lunghezza del buffer salvato, mentre al Content-Type
viene assegnato lo standard text/html
solamente nel caso in cui non sia stato specificato precedentemente.
La nostra implementazione di Response quindi si limita a gestire solamente i dati assegnati espressamente attraverso i metodi esposti dalla classe; i contenuti generati utilizzando le normali funzioni di output oppure gli header specificati esplicitamente non sono gestiti. Per ovviare a questo problema, che rischia comunque di rendere inconsistente il lavoro in alcune situazioni, si potrebbe estendere la classe facendo in modo che catturi ogni chiamata alle funzioni di output e la immagazzini automaticamente all'interno del buffer. Per fare ciò si potrebbe sia lavorare sullo stream di output di PHP che appoggiarsi alle funzioni ob_*
per il buffering dell'output, aggiungendo il contenuto bufferizzato al Response prima di ogni chiamata al metodo addContent e prima del rendering esplicito.
Conclusione
In questo articolo ci siamo avvicinati un po' di più a quello che sarà il cuore funzionante del nostro framework. Non siamo lontanissimi dall'avere a disposizione tutte le classi che ci permettano di implementare uno script di bootstrap funzionante, e nel prossimo articolo vedremo un altro set di classi che andranno a completare quelle necessarie. Nel caso abbiate qualche esigenza o necessitiate di qualche informazione, non esitate a contattarmi via email.