Zend Framework è uno dei framework open source più utilizzati dai programmatori PHP. Con oltre 15 milioni di download la versione 1.x ha avuto un notevole successo ed è stata scelta come piattaforma di base in molti progetti business critical e in molti prodotti software famosi, come il sistema di e-commerce Magento.
Le novità introdotte dalla versione 2.0 (attualmente è disponibile la versione 2.0.0beta4) sono tante ed importanti, le principali sono:
- il pieno supporto al PHP 5.3 con le ultime novità del linguaggio: namespace, funzioni lambda e closure, late static binding, etc;
- una nuova architettura MVC, basata sulla gestione di eventi;
- performance migliorate, grazie, e non solo, ad un nuovo sistema di autoloading delle classi;
- utilizzo di nuovi design pattern come la Dependency Injection, l'Event Manager ed il Service Locator;
- gestione nativa dei moduli all'interno dell'architettura MVC;
- una nuova gestione delle viste tramite un modello ad oggetti gerarchico;
- un nuovo sistema di packaging basato su pyrus composer
Possiamo tranquillamente affermare che anche se la versione 2.0 è un'evoluzione della versione 1.0, è di fatto un nuovo progetto.
Le idee principali sono rimaste quelle della versione precedente ma l'architettura interna è stata completamente ridisegnata. Visto che si è deciso di supportare le novità della versione 5.3 del PHP la retrocompatibilità non poteva essere garantita con la versione precedente, perchè basata su PHP 5.2. Per questo motivo si è optato per una riscrittura completa dell'architettura del progetto, al fine di garantire una maggiore solidità e migliori performance.
Oltre alle novità tecniche, il progetto Zend Framework 2 presenta anche alcune novità nella sua gestione. In particolare è stata semplificata la procedura di collaborazione al progetto. Non è più necessario sottoscrivere il documento Contributor License Agreement (CLA). Inoltre la gestione dei sorgenti avviene tramite github, ciò significa che chi vuole collaborare al progetto può farlo tramite una semplice pull request (una richiesta di aggiunta o modifica al progetto), tutto qui.
Dopo aver parlato in generale delle novità vediamo in dettaglio alcune di queste, iniziando dal nuovo sistema di autoloading delle classi.
Il nuovo sistema di autoloading
Zend Framework 2 offre un nuovo sistema di autoloading delle classi basato su tre opzioni:
- autoloading PSR-0 in stile Zend Framework 1;
- autoloading basato su Namespace o Prefissi di classe;
- autoloading tramite classmap.
Una novità importante del progetto Zend Framework 2 è la totale assenza di require_once
Il primo sistema di autoloading è simile a quello utilizzato nel progetto Zend Framework 1. Un esempio di utilizzo è riportato di seguito:
require_once 'Zend/Loader/StandardAutoloader.php';
$loader = new ZendLoaderStandardAutoloader(array(
'fallback_autoloader' => true,
));
$loader->register();
Questo sistema consente di caricare automaticamente le classi seguendo lo standard PSR-0
ZendConfigConfig
Zend/Config/Config.php
La classe ZendLoaderStandardAutoloader
../../StandardAutoloader
stream_resolve_include_path
La seconda modalità di autoloading offerta da Zend Framework 2 consente di specificare il percorso di inclusione dei file PHP tramite namespace o tramite un prefisso prestabilito. Ad esempio è possibile specificare il percorso per il namespace "Foo" e per tutte le classi che iniziano con il prefisso "Bar_" nel modo seguente:
require_once 'Zend/Loader/StandardAutoloader.php';
$loader = new ZendLoaderStandardAutoloader();
$loader->registerNamespace('Foo', __DIR__ . '/../library/Foo')
->registerPrefix('Bar_', __DIR__ . '/../library/Bar');
$loader->register();
La terza modalità offerta da Zend Framework 2 per l'autoloading delle classi è basata sul concetto di class map. La class map è una mappa che associa ogni classe PHP il suo rispettivo file tramite percorso assoluto. In Zend Framework 2 una tipica class map è costituita da un file PHP (di solito denominato .classmap.php
<?php
return array(
'MyFooBar' => __DIR__ . '/Foo/Bar.php',
);
Questo file viene utilizzato per configurare il componente ZendLoaderClassMapAutoloader nel modo seguente:
require_once 'Zend/Loader/ClassMapAutoloader.php';
$loader = new ZendLoaderClassMapAutoloader();
$loader->registerAutoloadMap(__DIR__ . '/../library/.classmap.php');
$loader->register();
Attraverso il file .classmap.php
Il punto di forza di questo metodo è anche quello dolente, ossia la creazione della class map. Dover aggiornare a mano la class map è sicuramente un'operazione poco agevole. Per questo motivo abbiamo creato uno script (classmap_generator.php
È sufficiente eseguire lo script da linea di comando, posizionandosi nella directory root del vostro progetto e specificando il parametro -w
$ cd your/library $ php /path/to/classmap_generator.php -w
Lo script genererà il file .classmap.php
L'utilizzo di questo script automatico è particolarmente indicato per il deploy di applicazioni PHP in ambienti di produzione. Infatti, solitamente il passaggio in produzione di un'applicazione web è gestito tramite un processo automatizzato di deploy. Inserendo l'esecuzione dello script classmap_generator.php
Giusto per darvi un'idea delle differenze di performance tra i vari sistemi di autoloading appena esposti abbiamo riscontrato che utilizzando l'autoloader basato su class map si possono ottenere incrementi di performance attorno al 20% rispetto allo standard autoloader. Utilizzando un acceleratore di codice, come ad esempio APC
.classmap.php
Il nuovo sistema di autoloading di Zend Framework 2 è stato adattato anche nel branch della versione precedente e sarà disponibile con la prossima versione 1.12, prevista nei prossimi giorni.
Dependency Injection
Durante lo sviluppo di Zend Framework 2 abbiamo utilizzato diversi design pattern cercando di seguire il principio dell'Inversion of Control (IoC):
"Per Inversion of Control (IOC - inversione di controllo) si intende un pattern di programmazione, secondo il quale si tende a tener disaccoppiati i singoli componenti di un sistema, in cui le eventuali dipendenze non vengono scritte all'interno del componente stesso, ma gli vengono iniettate dall'esterno". (Wikipedia)
In particolare abbiamo cercato di disaccoppiare il codice utilizzando la dependency injection, ossia una tecnica che consiste nell'iniettare le dipendenze tra classi attraverso il passaggio di oggetti. In questo modo le dipendenze possono essere definite, ad esempio, tramite dei file di configurazione e gestite quindi durante il run-time dell'applicazione.
Facciamo un esempio:
class Foo {
protected $bar;
public function __construct() {
$this->bar= new Bar();
}
}
In questo esempio, la classe Foo
Bar
Foo
Bar
Foo
Un modo più efficace per gestire la dipendenza è quello di iniettare un oggetto della classe Bar
Foo
class Foo {
protected $bar;
public function __construct(Bar $bar) {
$this->bar = $bar;
}
}
In questo modo la dipendenza può essere gestita in fase di runtime ed è possibile cambiare l'oggetto Bar
Foo
Questo è un esempio di dependency injection. L'oggetto $bar
poteva anche essere passato tramite un metodo setBar($bar)
all'interno della classe Foo
:
class Foo
{
protected $bar;
public function setBar(Bar $bar) {
$this->bar = $bar;
}
}
In generale, ci sono tre modalità per gestire la dependency injection:
- in fase di inizializzazione della classe (come parametro del metodo __construct);
- tramite un setter (setBar($bar) dell'esempio precedente);
- tramite un'interfaccia (dove si utilizza un'interfaccia come type hinting al posto della classe).
L'ultimo metodo è quello più generico possibile perchè rende possibile la modifica della tipologia di classe da iniettare nel codice (a patto che implementi l'interfaccia richiesta).
Immaginate di dover gestire la dipendenza tra componenti in un'applicazione web che utilizza un centinaio di classi. La gestione manuale delle dipendenze, anche utilizzando il principio della dependency injection, potrebbe rilevarsi particolarmente complessa. Per questo motivo molto spesso si utilizza un Dependency Injection Container, ossia un componente in grado di gestire le dipendenze tra classi a runtime tramite l'utilizzo di un file di configurazione.
In Zend Framework 2 abbiamo sviluppato questo Di Container ZendDi
Ecco un esempio basato sulla classe Foo precedentemente esposta.
Di seguito è riportato il file di configurazione (di-config.php
<?php
return array(
'Foo' => array(
'setBar' => array(
'bar' => array(
'type' => 'Bar',
'required' => true,
),
),
),
);
In questo array viene definita la dipendenza della classe Foo
Bar
setBar
'required' => true
use ZendDiDi,
ZendDiConfiguration;
$config = require 'di-config.php';
$di = new Di;
$config = new Configuration(array(
'definition' => array('class' => $config)
));
$config->configure($di);
$foo = $di->get('Foo'); // contiene l'oggetto della classe Bar
Utilizzando il metodo get()
Foo
setBar
Per modificare la dipendenza tra la classe Foo
Bar
di-config.php
Oltre alla gestione tramite un array di configurazione è possibile utilizzare anche un sistema di annotazione che consente di esplicitare le dipendenze tra classi direttamente nel codice sorgente tramite appositi tag, in standard phpdoc, presenti nei commenti.
Di seguito è riportato un esempio:
use ZendDiDefinitionAnnotation as Di;
class Foo
{
protected $bar;
/**
* @DiInject()
*/
public function setBar(Bar $bar) {
$this->bar = $bar;
}
}
La dipendenza è gestita attraverso la notazione @DiInject()
ZendDiDefinitionCompilerDefinition
$compiler = new ZendDiDefinitionCompilerDefinition();
$compiler->addDirectory('Percorso della classe Foo');
$compiler->compile();
$definitions = new ZendDiDefinitionList($compiler);
$di = new ZendDiDi($definitions);
$bar = $di->get('Foo'); // contiene l'oggetto della classe Bar
Il vantaggio dell'utilizzo dell'annotazione è evidente, non c'è bisogno di utilizzare un file di configurazione esterno, è possibile specificare la dipendenza come commento, direttamente nel codice sorgente della classe. Lo svantaggio è dato dalla fase di compilazione che rende meno performante l'esecuzione dello script PHP. Quest'ultimo punto è facilmente risolvibile memorizzando l'output del processo di compilazione in un sistema di cache (ad esempio memorizzando la variabile $compiler dell'esempio precedente).
Un altro componente nuovo del progetto Zend Framework 2 è l'Event Manager
Il nuovo MVC di Zend Framework 2 è completamente basato su eventi. Fondamentalmente, il flusso di un'applicazione è guidato da tre step principali:
- bootstrap
- route
- dispatch

Ad ognuna di queste tra fasi sono associati degli eventi predefiniti attraverso un componente di gestione dell'applicazione ZendServiceManager
ZendApplication
È possibile aggiungere, eliminare o modificare un evento in qualsiasi punto dell'applicazione, ciò significa che è possibile cambiare il flusso di un'applicazione in qualsiasi momento. Questa architettura rende molto flessibile la gestione di un'applicazione web e da la possibilità di gestire il flusso in contesti differenti con nuovi interessanti casi d'uso.
Di seguito è illustrato un esempio su come creare ed eseguire un evento tramite il componente ZendEventManager.
use ZendEventManagerEventManager;
$events = new EventManager();
$events->attach('do', function($e) {
$event = $e->getName();
$params = $e->getParams();
printf('Handled event "%s", with parameters %s',
$event,
json_encode($params));
}, 100);
$params = array('foo' => 'bar', 'baz' => 'bat');
$events->trigger('do', null, $params);
Gli eventi vengono aggiunti all'EventManager attraverso l'utilizzo del metodo attach()
Nel nostro esempio abbiamo creato un evento denominato "do" che esegue, tramite una funzione anonima, una stampa del nome dell'evento eseguito e dei suoi parametri.
L'evento viene eseguito richiamando il metodo trigger() dell'EventManager. I parametri di questo metodo prevedono il nome dell'evento da eseguire, il contesto nel quale eseguire l'evento (tipicamente l'istanza dell'oggetto contenente l'evento; nel nostro caso nessuna poiché l'evento è creato tramite una funzione anonima) ed infine i parametri da passare all'evento.
Sistema di packaging
Con la nuova versione di Zend Framework è stato introdotto un nuovo sistema di packaging per la distribuzione e l'installazione del framework.
La distribuzione del framework può avvenire in tre modalità:
- tramite il classico download dal sito
http://packages.zendframework.com/
- tramite l'utilizzo del modulo PEAR2_Pyrus,
http://pear2.php.net/PEAR2_Pyrus
- tramite composer,
http://getcomposer.org/
Utilizzando PEAR2_Pyrus si possono installare anche singoli componenti del framework. Ad esempio, nel caso si voglia utilizzare soltanto il componente ZendHttp di Zend Framework 2, si devono eseguire i seguenti comandi (in un ambiente GNU/Linux):
Installazione di pyrus:
Installazione del componente ZendHttp
$ pyrus.phar . install zf2/Zend_Http-beta
Le prime tre istruzioni devono essere eseguite soltanto una volta per l'installazione di pyrus. L'ultima istruzione è quella che effettua l'installazione del componente Zend_Http-beta
Oltre al sistema pyrus, Zend Framework 2 supporta anche il recente progetto composer. Composer
JSON
Ad esempio, utilizzando il seguente file di configurazione composer.json
{
"require": {
"zendframework/zendframework": "2.0.0beta4"
}
}
È possibile installare Zend Framework 2 nella sua versione 2.0.0beta4 tramite i seguenti comandi (in ambiente GNU/Linux):
Il primo comando effettua il download di composer (nel formato phar) e il secondo comando esegue l'installazione dello Zend Framework 2 leggendo il file di configurazione precedentemente creato, composer.json.
Composer installerà la libreria all'interno della directory vendor. Inoltre, viene creato il file vendor/autoload.php per l'autoloading delle librerie scaricate. È sufficiente includere questo file di autoload nel proprio progetto per iniziare ad utilizzare Zend Framework 2.
Conclusioni
Il progetto Zend Framework 2 presenta numerose novità rispetto alla versione 1.0. L'architettura MVC è stata completamente riscritta ed è basata interamente su eventi. L'utilizzo di nuovi design pattern come la Dependency Injection, l'Event Manager, ed il Service Locator, hanno consentito di creare componenti disaccoppiati che possono essere facilmente utilizzati come componenti stand-alone. Il nuovo sistema di autoloading consente di ottenere un notevole aumento delle performance. Inoltre, grazie al nuovo sistema di packaging è più semplice distribuire ed installare l'intero framework o singoli componenti.
La versione 2.0 stabile di Zend Framework verrà rilasciata entro l'estate 2012. Attualmente è disponibile la versione 2.0.0beta4. Vi invito a provare questa versione beta scaricando ad esempio l'applicazione dimostrativa Zend Skeleton Application dal scaricandola da github
Questa applicazione è lo scheletro di partenza per sviluppare un'applicazione web basata su Zend Framework 2.