Démarrer avec Zend Framework 2

| Rob Allen (auteur) | Philippe Peso (traducteur) |

Traducteur

philippe.peso

Ce tutoriel a été rédigé pour vous présenter Zend Framework 2 en créant une application basée sur une base de données basique et utilisant une conception Modèle-Vue-Contrôleur. Après la lecture de ce document, vous aurez une application ZF2 fonctionnelle et vous pourrez examiner le code pour en comprendre le fonctionnement et les mécanismes.

Note : ce tutoriel est basé sur la version beta 4 de Zend Framework 2. Il peut fonctionner sur une version plus récente, mais ne fonctionnera pas sur une version antérieure.

Cet article a été publié avec l'aimable autorisation de Rob Allen. L'article original peut être lu sur son site : http://akrabat.com/zend-framework-2-tutorial/.

Commentez

- Titre : Démarrer avec Zend Framework 2

- Auteurs : Rob Allen (auteur), Philippe Peso (traducteur)

- Traducteur : philippe.peso

- Parution : 20 juin 2012

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ®  Rob Allen (auteur), Philippe Peso (traduction française). Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Prérequis

- L'application du tutoriel

- Démarrage : l'application squelette

- Démarrer avec un fichier zip

- Virtual host

- Modules

- Configuration du module Album

- Autochargement des fichiers

- Configuration

- Informer l'application du nouveau module

- Les pages du site

- Créer le contrôleur

- Initialiser les vues

- La base de données

- Les fichiers du modèle

- Utiliser le ServiceManager pour configurer les identifiants de la base de données et le passer au contrôleur

- Lister les albums

- Les styles

- Ajouter de nouveaux albums

- Éditer un album

- Supprimer un album

- Faire en sorte que la page d'accueil affiche la liste des albums

- Conclusion et remerciements

Prérequis

Ce tutoriel part du principe que vous utilisez PHP 5.3.10, avec un serveur Web Apache et une base de données MySQL, accessible par l'extension PDO. Vous devez avoir installé et activé l'extension mod_rewrite.

Vous devez également vous assurer qu'Apache est configuré pour supporter les fichiers .htaccess. Pour cela vous pouvez changer la directive

AllowOverride None

en

AllowOverride All

dans votre fichier httpd.conf. Consultez la documentation de votre serveur Web pour plus de renseignements. Vous ne pourrez naviguer sur aucune page, à part la page d'accueil, si vous n'avez pas activé le mod_rewrite et bien complété le fichier .htaccess.

L'application du tutoriel

Nous allons créer une application qui affichera un système de gestion de nos albums. La page principale listera notre collection d'albums et nous permettra d'en ajouter, de les éditer et de supprimer des CD. Nous aurons besoin de quatre pages sur le site :

Liste des albums

Cette page affichera la liste des albums, avec des liens pour les éditer et les supprimer, et pour en ajouter un nouveau.

Ajout d'un nouvel album

Cette page affichera un formulaire d'ajout d'un nouvel album.

Édition d'un album

Cette page affichera un formulaire d'édition d'un album.

Suppression d'un album

Cette page permettra la confirmation de la suppression d'un album et fera cette suppression.

Nous aurons également besoin de stocker les données dans une base de données. Nous n'aurons besoin que d'une table, avec ces colonnes :

Nom du champ

Type

Null ?

Notes

id

integer

Non

Clé primaire, auto-incrément

artist

varchar(100)

Non

title

varchar(100)

Non

Démarrage : l'application squelette

Il y a deux façons de démarrer. Si vous êtes a l'aise avec git, utilisez-le, sinon lisez la section Démarrer avec un fichier zip.

Démarrer avec un fichier zip

Afin de créer notre application, nous allons télécharger le ZendSkeletonApplication, disponible sur github. Allez à https://github.com/zendframework/ZendSkeletonApplication et cliquez sur le bouton "Zip". Vous téléchargerez un fichier nommé zendframework-ZendSkeletonApplication-zfrelease-2.0.0beta4-7-g1dad749.zip ou un nom ressemblant.

Dézippez ce fichier dans le répertoire défini dans le vhost et renommez le répertoire obtenu en zf2-tutorial.

Le ZendSkeletonApplication utilise Composer pour trouver les dépendances, dans notre cas, la dépendance est Zend Framework 2 lui-même.

Pour installer Zend Framework 2 dans votre application, vous devez simplement taper :

php composer.phar install

depuis le répertoire zf2-tutorial. Cela peut prendre un certain temps. Vous obtiendrez un résultat ressemblant à ceci :

Installing dependencies from lock file
- Installing zendframework/zendframework (dev-master)
Cloning cef2f9c90a9dc3fe3018b624ff9113666a397b73

Generating autoload files

Nous pouvons désormais passer au virtual host.

Virtual host

Nous allons créer un virtual host Apache pour l'application, en éditant le fichier hosts pour que l'adresse http://zf2-tutorial.localhost pointe sur le fichier index.php du répertoire zf2-tutorial/public.

La configuration du virtual host est généralement faite dans le fichier httpd.conf ou extra/httpd-vhosts.conf (si vous utilisez httpd-vhosts.conf, assurez-vous que ce fichier est chargé par le fichier principal httpd.conf).

Assurez-vous également que NameVirtualHost est défini à "*:80" ou une configuration similaire, ensuite définissez le virtual host ainsi :

<VirtualHost *:80>
ServerName zf2-tutorial.localhost
DocumentRoot /path/to/zf-2tutorial/public
SetEnv APPLICATION_ENV "development"
<Directory /path/to/zf2-tutorial/public>
DirectoryIndex index.php
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost>

Pensez à mettre à jour le fichier /etc/hosts ou c:\windows\system32\drivers\etc\hosts avec la relation zf2-tutorial.localhost associée à 127.0.0.1. Le site est alors accessible à l'adresse http://zf2-tutorial.localhost.

Si vous n'avez pas fait d'erreur, vous devriez voir une page ressemblant à celle-ci :

Pour vérifier que le fichier .htaccess fonctionne bien, allez à la page http://zf2-tutorial.localhost/1234, vous devriez voir ceci :

Si vous voyez l'erreur Apache 404, vous devez corriger le fichier .htaccess avant de poursuivre.

Vous avez désormais une application squelette fonctionnelle, nous allons pouvoir nous occuper de notre application.

Modules

Zend Framework 2 utilise un système de modules, où vous placez un code spécifique au module. Le module Application du squelette est utilisé pour fournir le bootstrap, la configuration des erreurs et du routage, à toute l'application. Il est généralement utilisé pour fournir les contrôleurs de l'application, comme sa page d'accueil, mais nous n'allons pas utiliser celui qui est fourni par défaut dans l'application. Notre page listant les albums aura son propre module.

Nous allons mettre tout notre code dans le module Album, qui contiendra nos contrôleurs, nos modèles, nos formulaires, nos vues et les fichiers de configuration. Commençons par les répertoires.

Configuration du module Album

Commencez par créer un répertoire nommé Album en respectant cette arborescence pour organiser les fichiers du module :

zf2-tutorial/
/module
/Album
/config
/src
/Album
/Controller
/Form
/Model
/view
/album

Comme vous pouvez le voir, le module Album a des répertoires pour les différents types de fichiers. Les fichiers PHP qui contiennent les classes du namespace Album sont dans le répertoire src/Album ce qui permet d'avoir plusieurs espaces de nom dans ce module. Le répertoire view a également un sous-répertoire nommé album pour les vues du module.

Afin de charger et configurer un module, Zend Framework 2 a un ModuleManager. Celui-ci cherchera le fichier Module.php à la racine du répertoire du module et s'attend à y trouver une classe nommée Album\Module. La classe d'un module donné aura l'espace de nom et le nom du module, cela sera également le nom du répertoire du module.

Créez le fichier Module.php dans le module Album :

module/Album/Module.php

<?php

namespace Album;

class Module
{
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php',
),
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}

public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
}

Le ModuleManager appellera getAutoloaderConfig() et getConfig() automatiquement pour nous.

Autochargement des fichiers

Notre méthode getAutoloaderConfig() retourne un tableau, ce qui est compatible avec l'AutoloaderFactory de ZF2. Nous allons le configurer, en ajoutant un fichier de classe générale (class map) au ClassmapAutoloader et ajouter l'espace de nom du module au StandardAutoloader. L'autoloader standard a besoin d'un espace de nom et du chemin nécessaire pour trouver les fichiers de cet espace. Ceci est conforme au standard PSR-0, avec les classes qui correspondent directement aux fichiers des règles PSR-0. Voir ce site pour plus d'information : https://gist.github.com/1293323.

Comme nous sommes en environnement de développement, nous n'avons pas besoin de charger ces fichiers par la carte de classe. Nous pouvons donc mettre un tableau vide pour l'autoloader. Créez un fichier autoload_classmap.php en le remplissant ainsi :

module/Album/autoload_classmap.php

<?php
return array();

Avec un tableau vide, à chaque fois que l'autoloader cherche une classe dans l'espace de nom Album, il retombera sur le StandardAutoloader.

Notez qu'en utilisant Composer en tant qu'alternative, vous ne pouvez pas implémenter getAutoloaderConfig() et ajouter à la place "Application": "module/Application/src" à la clé psr-0 du composer.json. Si vous préférez cette façon de faire, vous devez lancer php composer.phar update pour mettre à jour les fichiers d'autochargement du composer.

Configuration

Après avoir configuré l'autoloader, jetons un œil à la méthode getConfig() dans Album\Module. Cette méthode charge seulement le fichier config/module.config.php.

Créez ce fichier de configuration dans le module Album :

module/Album/config/module.config.php

<?php
return array(
'controller' => array(
'classes' => array(
'album/album' => 'Album\Controller\AlbumController',
),
),
'view_manager' => array(
'template_path_stack' => array(
__DIR__ . '/../view',
),
),
);

Les informations de configuration sont transmises aux composants appropriés par le ServiceManager. Nous avons besoin de deux sections initiales : controller et view_manager. La section controller fournit une liste de tous les contrôleurs appelés par le module. Nous n'aurons besoin que d'un contrôleur, AlbumController, que nous nommons album/album. Cette clé doit être unique dans tous les modules, c'est pourquoi nous la préfixons avec le nom du module.

Dans la section view_manager, nous ajoutons le répertoire de la vue à la clé TemplatePathStack. Ceci permettra de trouver les scripts de vue du module Album qui sont stockés dans le répertoire views/album.

Informer l'application du nouveau module

Nous allons maintenant informer le ModuleManager de l'existence du nouveau module en modifiant le fichier config/application.config.php fourni par le squelette de l'application. Ajoutez dans la section modulesl'entrée Album pour que votre fichier ressemble à ceci :

config/application.config.php

<?php
return array(
'modules' => array(
'Application',
'Album',
),
'module_listener_options' => array(
'config_glob_paths' => array(
'config/autoload/{,*.}{global,local}.php',
),
'config_cache_enabled' => false,
'cache_dir' => 'data/cache',
'module_paths' => array(
'./module',
'./vendor',
),
),
'service_manager' => array(
'use_defaults' => true,
'factories' => array(
),
),
);

Comme vous pouvez le voir, nous avons ajouté notre module Album dans la liste des modules, après le module Application. Nous pouvons désormais créer notre code pour le module.

Les pages du site

Nous allons construire un système basique d'affichage de notre collection d'albums. La page d'accueil listera cette collecte et nous permettra d'ajouter, d'éditer et de supprimer des albums, avec ces pages :

Accueil

Affichera la liste des albums avec des liens pour les éditer et les supprimer, ainsi qu'un lien pour ajouter un nouvel album.

Ajouter un nouvel album

Affichera un formulaire pour ajouter un nouvel album.

Éditer un album

Affichera un formulaire pour supprimer un album.

Supprimer un album

Permettra de confirmer et d'effectuer la suppression d'un album.

Avant de créer ces fichiers, il faut bien comprendre comment le framework gère l'organisation des pages. Chaque page de l'application représente une action et les actions sont regroupées dans les contrôleurs, eux-mêmes dans des modules. Par exemple, un contrôleur actualites aurait des actions actuelles, archivees et vues

Nous avons donc quatre pages pour gérer nos albums, nous allons les regrouper dans un seul contrôleur nommé AlbumController, lui-même dans le module Album. Ce contrôleur aura quatre actions, qui seront :

Page

Contrôleur

Action

Accueil

AlbumController

index

Ajouter un nouvel album

AlbumController

add

Éditer un album

AlbumController

edit

Supprimer un album

AlbumController

delete

La création de l'URL vers une action particulière est effectuée en utilisant les routes définies dans le fichier module.config.php du module. Nous allons ajouter une route pour toutes nos actions. Voici le fichier mis à jour :

module/Album/config/module.config.php

<?php
return array(
'controller' => array(
'classes' => array(
'album/album' => 'Album\Controller\AlbumController',
),
),
'router' => array(
'routes' => array(
'album' => array(
'type' => 'segment',
'options' => array(
'route' => '/album[/:action][/:id]',
'constraints' => array(
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'id' => '[0-9]+',
),
'defaults' => array(
'controller' => 'album',
'action' => 'index',
),
),
),
),
),
'view_manager' => array(
'template_path_stack' => array(
__DIR__ . '/../view',
),
),
);

Le nom de la route est album et est formé de segments, qui nous permettent de spécifier des zones de paramètres dans l'URL, qui devront correspondre avec le nom des paramètres de la route. Dans notre cas, la route est /album[/:action][/:id] et correspond à n'importe quelle URL démarrant par /album. Le segment suivant sera le nom facultatif de l'action et le dernier segment correspond à l'id, également facultatif. Les crochets indiquent que le segment est facultatif. Ces contraintes nous assurent que les caractères des segments correspondent à un format attendu. Nous forçons le nom de l'action à démarrer par une lettre, suivie de caractères alphanumériques, de tirets bas (« _ ») ou de tirets hauts (« - ») uniquement. Pour l'id, ce doit être un nombre.

Cette route nous permet d'obtenir les URL suivantes :

URL

Page

Action

/album

Page d'accueil (liste des albums)

index

/album/add

Ajout d'un nouvel album

add

/album/edit/2

Edition de l'album ayant l'id 2

edit

/album/delete/4

Suppression de l'album ayant l'id 4

delete

Créer le contrôleur

Nous sommes maintenant prêts à compléter le contrôleur. Avec Zend Framework 2, le contrôleur est une classe qui est généralement appelée {Controller name}Controller. Notez que {Controller name} doit commencer par une majuscule. Cette classe doit être placée dans un fichier nommé {Controller name}Controller.php et être dans le répertoire Controller du module. Dans notre cas le répertoire est module/Album/src/Album/Controller. Chaque action du contrôleur doit être une méthode publique et doit être nommée {action name}Action. {action name} doit commencer par une minuscule.

Ces règles sont des conventions. Zend Framework 2 ne vous impose pas d'autre restriction que d'implémenter l'interface Zend\Stdlib\Dispatchable. Le framework fournit deux classes abstraites qui font cela à notre place : Zend\Mvc\Controller\ActionController et Zend\Mvc\Controller\RestfulController. Nous utiliserons le contrôleur standard ActionController, mais si vous souhaitez utiliser un service Web RESTful, RestfulController vous sera plus utile. Maintenant, créons notre contrôleur :

module/Album/src/Album/Controller/AlbumController.php

<?php

namespace Album\Controller;

use Zend\Mvc\Controller\ActionController,
Zend\View\Model\ViewModel;

class AlbumController extends ActionController
{

public function indexAction()
{
}

public function addAction()
{
}

public function editAction()
{
}

public function deleteAction()
{
}

}

Notez que nous avons déjà informé le module de l'existence du contrôleur dans la section controller du fichier config/module.config.php.

Nous avons désormais défini les actions que nous voulons utiliser. Elles ne fonctionneront pas tant que nous n'aurons pas défini les vues. Les URL de chaque action sont :

URL

Méthode appelée

http://zf2-tutorial.localhost/album

Album\Controller\AlbumController::indexAction()

http://zf2-tutorial.localhost/album/add

Album\Controller\AlbumController::addAction()

http://zf2-tutorial.localhost/album/edit

Album\Controller\AlbumController::editAction()

http://zf2-tutorial.localhost/album/delete

Album\Controller\AlbumController::deleteAction()

Nous avons désormais un routeur fonctionnel et les actions sont définies pour chaque page de notre application. Il est temps de construire les vues et la couche du modèle.

Initialiser les vues

Pour intégrer les vues dans l'application, nous devons créer les scripts de vue. Ces fichiers seront exécutés par l'objet ViewListener dans le module Application et transmettront les variables qui sont retournées par l'action du contrôleur. Ces vues sont placées dans le répertoire views du module, dans un répertoire portant le même nom que le contrôleur. Créez ces quatre fichiers vides :

- Module/Album/view/album/album/index.phtml ;

- Module/Album/view/album/album/add.phtml ;

- Module/Album/view/album/album/edit.phtml ;

- Module/Album/view/album/album/delete.phtml.

Nous pouvons désormais créer les données, en commençant par la base de données et les modèles.

La base de données

À ce stade nous avons le module Album avec un contrôleur, des actions, et des vues. Nous allons nous intéresser à la partie des modèles de notre application. Rappelez-vous que le modèle est la partie qui communique avec le cœur de l'application (ce qu'on l'appelle généralement les « règles métier ») et, dans notre cas, les relations avec la base de données. Nous allons utiliser la classe Zend\Db\TableGateway\TableGateway qui est utile pour trouver, insérer, mettre à jour et supprimer des lignes des tables d'une base de données.

Nous allons utiliser MySQL et son extension PDO pour créer une base de données nommée zf2tutorial et lancer ces requêtes SQL pour créer la table album et son contenu.

CREATE TABLE album (
id int(11) NOT NULL auto_increment,
artist varchar(100) NOT NULL,
title varchar(100) NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO album (artist, title)
VALUES ('The Military Wives', 'In My Dreams');
INSERT INTO album (artist, title)
VALUES ('Adele', '21');
INSERT INTO album (artist, title)
VALUES ('Bruce Springsteen', 'Wrecking Ball (Deluxe)');
INSERT INTO album (artist, title)
VALUES ('Lana Del Rey', 'Born To Die');
INSERT INTO album (artist, title)
VALUES ('Gotye', 'Making Mirrors');

(Ces données de test ont été choisies parce qu'elles faisaient partie des meilleures ventes sur Amazon UK lorsque ce tutoriel a été écrit !)

Nous avons maintenant des données dans la base et pouvons passer à la création d'un modèle basique.

Les fichiers du modèle

Zend Framework ne fournit pas un composant Zend\Model en tant que modèle logique, c'est à vous de décider comment vous voulez qu'il fonctionne. Il y a plusieurs composants qui peuvent être choisis, cela dépend de vos besoins. Une des approches possibles est d'avoir des classes faisant office de modèles, chacune représentant une entité de l'application et d'utiliser ces objets pour charger et enregistrer les entités en base de données. Une autre méthode possible : utiliser un ORM comme Doctrine ou Propel.

Pour ce tutoriel, nous allons créer un modèle simplifié, en créant une classe AlbumTable qui étend Zend\Db\TableGateway\TableGateway, où chaque objet de l'album est un objet Album (aussi appelé entité). Ceci est une implémentation du modèle de conception Table Data Gateway, ce qui permet de créer une interface (passerelle) avec les données de la base de données. Faites bien attention au pattern de Table Data Gateway, qui peut avoir des limites sur certains systèmes. Vous pouvez aussi être tentés d'établir l'accès à la base de données depuis l'action d'un contrôleur puisqu'elles sont liées au Zend\Db\TableGateway\AbstractTableGateway. Ne faites jamais cela !

Commençons par l'entité Album du répertoire Model :

module/Album/src/Album/Model/Album.php

<?php

namespace Album\Model;
use Zend\Db\ResultSet\Row;

class Album extends Row
{}

Nous définissons simplement une classe vide, qui étend l'objet Row. Nous ajouterons un filtre de saisie pour nos formulaires un peu plus loin.

Puis nous allons créer notre propre classe AlbumTable qui étendra la classe Zend\Db\TableGateway\AbstractTableGateway du répertoire Model, comme ceci :

module/Album/src/Album/Model/AlbumTable.php

<?php

namespace Album\Model;
use Zend\Db\TableGateway\AbstractTableGateway,
Zend\Db\Adapter\Adapter,
Zend\Db\ResultSet\ResultSet;

class AlbumTable extends AbstractTableGateway
{
protected $table ='album';
protected $tableName ='album';

public function __construct(Adapter $adapter)
{
$this->adapter = $adapter;
$this->resultSetPrototype = new ResultSet(new Album);
$this->initialize();
}

public function fetchAll()
{
$resultSet = $this->select();
return $resultSet;
}

public function getAlbum($id)
{
$id = (int) $id;
$rowset = $this->select(array('id' => $id));
$row = $rowset->current();
if (!$row) {
throw new \Exception("Could not find row $id");
}
return $row;
}

public function saveAlbum(Album $album)
{
$data = array(
'artist' => $album->artist,
'title' => $album->title,
);
$id = (int)$album->id;

if ($id == 0) {
$this->insert($data);
} else {
if ($this->getAlbum($id)) {
$this->update($data, array('id' => $id));
} else {
throw new \Exception('Form id does not exist');
}
}
}

public function addAlbum($artist, $title)
{
$data = array(
'artist' => $artist,
'title' => $title,
);
$this->insert($data);
}

public function updateAlbum($id, $artist, $title)
{
$data = array(
'artist' => $artist,
'title' => $title,
);
$this->update($data, array('id' => $id));
}

public function deleteAlbum($id)
{
$this->delete(array('id' => $id));
}

}

C'est un assez gros morceau... Tout d'abord nous avons défini les propriétés protected table et tableName avec le nom de la table de la base de données, album dans notre cas. Nous ajoutons ensuite un constructeur qui prend comme seul paramètre l'adapter de la base de données et nous l'assignons à la propriété adapter de notre classe.
Nous devons ensuite renseigner les données de chaque nouvel objet Album créé avec les données de la table. Les classes TableGateway utilisent un modèle de prototype pour créer les jeux de données et les entités. Cela signifie qu'à la place d'instancier un objet quand on en a besoin, le système clone le dernier objet instancié. Pour plus de détails, visitez ce site.

Nous créons ensuite cinq méthodes « helper » pour que notre application utilise l'interface pour accéder à la table de la base de données.

- fetchAll() remonte tous les albums de la base de données, c'est un objet de type ResultSet ;

- getAlbum() remonte un seul album, c'est un objet de type Row ;

- addAlbum() crée une nouvelle ligne dans la base de données ;

- updateAlbum() met à jour un album ;

- deleteAlbum() supprime complètement une ligne.

Le code de chaque méthode parle normalement de lui-même.

Utiliser le ServiceManager pour configurer les identifiants de la base de données et le passer au contrôleur

Pour toujours utiliser la même instance de notre AlbumTable, nous allons utiliser le ServiceManager pour en créer une. C'est relativement facile à faire dans la classe Module où nous avons créé une méthode nommée getServiceConfiguration() , qui est automatiquement appelée par le ModuleManager et appliquée au ServiceManager. Nous sommes désormais capables de la retrouver depuis le contrôleur dès que nous avons besoin.

Pour configurer le ServiceManager nous pouvons fournir le nom de la classe à instancier, ou une fabrique (closure ou callback) qui instancie l'objet quand le ServiceManager en a besoin. Nous commençons par implémenter getServiceConfiguration() pour fournir une fabrique qui va créer l'AlbumTable. Ajoutez cette méthode à la fin de la classe Module.

module/Album/Module.php

<?php

namespace Album;
use Album\Model\AlbumTable;

class Module
{

// getAutoloaderConfig() and getConfig() methods here
public function getServiceConfiguration()
{
return array(
'factories' => array(
'album-table' => function($sm) {
$dbAdapter = $sm->get('db-adapter');
$table = new AlbumTable($dbAdapter);
return $table;
},
),
);
}

}

Cette méthode retourne un tableau de factories qui sont fusionnées par le ModuleManager avant d'être passées au ServiceManager. Nous avons également besoin d'écrire une fabrique (« factory ») pour instancier l'adapter de notre base de données. En tant que service de l'application, nous faisons cela dans la classe Module du module Application :

module/Application/Module.php

<?php

namespace Application;
use Zend\Db\Adapter\Adapter as DbAdapter;

class Module
{
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}

public function getServiceConfiguration()
{
return array(
'factories' => array(
'db-adapter' => function($sm) {
$config = $sm->get('config');
$config = $config['db'];
$dbAdapter = new DbAdapter($config);
return $dbAdapter;
},
),
);
}
}

Dans la fonction getServiceConfiguration() du module Application, nous chargeons la configuration de la base de données depuis la fusion des configurations et l'utilisons pour instancier notre DbAdapter. Le ModuleManager de Zend Framework 2 fusionne toutes les configurations de chaque module présent dans les fichiers module.config.php des modules et fusionne ensuite les fichiers présents dans config/autoload (fichiers *.global.php puis *.local.php). Nous ajoutons la configuration de la base de données au fichier global.php, que vous pouvez commiter avec votre gestionnaire de versioning. Vous pouvez utiliser le fichier local.php (pas commité dans le gestionnaire de versioning) pour stocker les identifiants de votre base de données si vous le souhaitez.

config/autoload/global.php

return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=zf2tutorial;hostname=localhost',
'username' => 'rob',
'password' => '123456',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
),
);

Pensez à mettre vos propres identifiants dans ce tableau !

Maintenant que le ServiceManager peut créer une instance de AlbumTable à notre place, nous pouvons ajouter une méthode pour la retrouver. Ajoutez la méthode getAlbumTable() à la classe AlbumController :

module/Album/src/Album/Controller/AlbumController.php

public function getAlbumTable()
{
if (!$this->albumTable) {
$sm = $this->getServiceLocator();
$this->albumTable = $sm->get('album-table');
}
return $this->albumTable;
}

N'oubliez pas d'ajouter au début de votre classe :

protected $albumTable;

Nous pouvons désormais appeler la méthode getAlbumTable() depuis notre contrôleur dès que nous avons besoin d'interagir avec le modèle. Poursuivons avec la liste des albums, appelée par l'action index.

Lister les albums

Afin de lister les albums, nous avons besoin de les sélectionner depuis le modèle et de les passer à la vue. Pour cela, nous remplissons la méthode indexAction() de la classe AlbumController. Mettez à jour cette méthode avec ce code :

module/Album/src/Album/Controller/AlbumController.php

/**/
public function indexAction()
{
return new ViewModel(array(
'albums' => $this->getAlbumTable()->fetchAll(),
));
}
/**/

Avec Zend Framework 2, pour pouvoir envoyer des variables à la vue, il faut retourner une instance ViewModel avec un tableau contenant les données nécessaires à l'affichage en premier paramètre du constructeur. Ces données sont passées automatiquement à la vue. L'objet ViewModel permet aussi de changer le script de la vue qu'on utilise, mais le script de vue par défaut est nommé {controller name}/{action name}. Nous pouvons maintenant remplir le fichier index.phtml :

module/Album/view/album/album/index.phtml

<?php
$title = 'My albums';
$this->headTitle($title);
?>

<h1><?php echo $this->escape($title); ?></h1>
<p>
<a href="<?php echo $this->url('album', array('action'=>'add'));?>">Add new album</a>
</p>

<table class="table">
<tr>
<th>Title</th>
<th>Artist</th>
<th>&nbsp;</th>
</tr>
<?php foreach($albums as $album) : ?>
<tr>
<td><?php echo $this->escape($album->title);?></td>
<td><?php echo $this->escape($album->artist);?></td>
<td>
<a href="<?php echo $this->url('album', array('action'=>'edit', 'id' => $album->id));?>">Edit</a>
<a href="<?php echo $this->url('album', array('action'=>'delete', 'id' => $album->id));?>">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</table>

La première chose à faire est de définir le titre de la page (utilisé dans le layout), ainsi que le titre de la balise <head> affiché dans la barre du navigateur, en utilisant le helper de vue headTitle(). Nous créons ensuite un lien pour ajouter un nouvel album.

Le helper de vue url() est fourni par Zend Framework 2 et est utilisé pour créer le lien souhaité. Le premier paramètre de url() est le nom de la route que nous souhaitons utiliser pour construire l'URL. Le second paramètre est un tableau de toutes les variables à placer dans les zones de paramètres. Dans notre cas, nous avons notre route album qui accepte deux variables en zone de paramètre : action et id.

Nous faisons pareil avec la variable $albums que nous assignons depuis l'action du contrôleur. Le système des vues de Zend Framework 2 gère automatiquement le passage de ces variables vers le script de la vue, donc nous n'avons pas à gérer le préfixage avec $this-> comme nous le faisions avec Zend Framework 1, mais rien ne vous interdit de le faire.

Nous allons créer un tableau pour afficher chaque titre et artiste des albums, ainsi qu'un lien permettant d'éditer et de supprimer l'enregistrement. Une boucle classique foreach: est utilisée pour traiter chaque album de la liste. Nous utilisons la forme alternative, avec endforeach; c'est plus facile ainsi, plutôt que d'essayer de faire correspondre des accolades. Une nouvelle fois, le helper de vue url() est utilisé pour créer les liens d'édition et de suppression.

Notez que nous utilisons toujours le helper de vue escape() pour nous protéger des failles XSS.

Si vous ouvrez une page avec l'URL http://zf2-tutorial.localhost/album vous devriez voir ceci :

Les styles

Nous avons démarré avec SkeletonApplication, qui est une bonne base de travail, mais nous avons besoin de changer le titre et d'enlever le message du copyright. Nous pouvons faire cela dans la vue layout.phtml du module Application :

Dans module/Application/view/layout/layout.phtml, trouvez cette ligne :

<?php echo $this->headTitle('ZF2 Skeleton Application') ?>

Et changez-la en :

<?php echo $this->headTitle('ZF2 Tutorial') ?>

Cherchez également celle-ci :

<a class="brand" href="<?php echo $this->url('home') ?>">Skeleton Application</a>

Remplacez-la par :

<a class="brand" href="<?php echo $this->url('home') ?>">Tutorial</a>

Supprimez aussi ceci :

<p>&copy; 2006 - 2012 by Zend Technologies Ltd. All rights reserved.</p>

La page a un meilleur look désormais !

Ajouter de nouveaux albums

Nous pouvons maintenant coder l'ajout d'un nouvel album. Il est séparé en deux parties :

- afficher un formulaire pour que l'utilisateur saisisse les détails de l'album ;

- traiter les données soumises et les enregistrer en base de données.

Pour cela nous utilisons Zend\Form. Le composant Zend\Form gère le formulaire et la validation. Nous ajoutons un Zend\InputFilter à notre entité Album. Nous commençons par créer une nouvelle classe Album\Form\AlbumForm qui étend Zend\Form\Form pour définir notre formulaire. Vous devez mettre la classe dans le fichier AlbumForm.php du répertoire module/Album/src/Album/Form.

Créons ce fichier :

module/Album/src/Album/Form/AlbumForm.php

<?php
namespace Album\Form;
use Zend\Form\Form;

class AlbumForm extends Form
{

public function __construct()
{
parent::__construct();
$this->setName('album');
$this->setAttribute('method', 'post');

$this->add(array(
'name' => 'id',
'attributes' => array(
'type' => 'hidden',
),
));

$this->add(array(
'name' => 'artist',
'attributes' => array(
'type' => 'text',
'label' => 'Artist',
),
));

$this->add(array(
'name' => 'title',
'attributes' => array(
'type' => 'text',
'label' => 'Title',
),
));

$this->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'label' => 'Go',
'id' => 'submitbutton',
),
));
}

}

Dans le constructeur de l'AlbumForm, nous définissons le nom et l'attribut method du formulaire, puis nous créons quatre éléments pour l'id, l'artiste, le titre de l'album, et le bouton submit. Pour chaque élément, nous définissons ses différents attributs, sans oublier le label correspondant.

Nous passons ensuite à la validation du formulaire. Avec Zend Framework 2, elle se fait avec un filtre de saisie, qui peut soit être indépendant (« standalone »), soit implémenter InputFilterAwareInterface, en tant qu'entité modèle. Nous allons ajouter ce filtre de saisie à l'entité Album :

module/Album/src/Album/Model/Album.php

<?php

namespace Album\Model;
use Zend\Db\ResultSet\Row;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\Factory as InputFactory;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;

class Album extends Row implements InputFilterAwareInterface
{
protected $inputFilter;

public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception("Not used");
}

public function getInputFilter()
{
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();

$inputFilter->add($factory->createInput(array(
'name' => 'id',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
)));

$inputFilter->add($factory->createInput(array(
'name' => 'artist',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
),
)));

$inputFilter->add($factory->createInput(array(
'name' => 'title',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
),
)));

$this->inputFilter = $inputFilter;
}

return $this->inputFilter;
}

}

L'InputFilterAwareInterface définit deux méthodes : setInputFilter() et getInputFilter(). Nous allons seulement nous occuper de getInputFilter(), en levant une exception setInputFilter().

Dans getInputFilter(), nous instancions un objet InputFilter et lui ajoutons les champs requis. Nous ajoutons un champ de saisie pour chaque propriété que nous voulons filtrer ou valider. Pour le champ id, nous ajoutons un filtre Int car nous n'acceptons que des entiers. Pour les éléments texte, nous ajoutons deux filtres, StripTags et StringTrim pour supprimer le code HTML et les espaces inutiles. Nous les définissons en champs obligatoires, avec un validateur StringLength pour s'assurer que l'utilisateur ne saisit pas plus de caractères que ce qui est enregistré en base de données.

Nous allons maintenant afficher le formulaire et le soumettre, depuis l'action addAction() de AlbumController :

module/Album/src/Album/Controller/AlbumController.php

/**/

use Zend\Mvc\Controller\ActionController,
Zend\View\Model\ViewModel,
Album\Model\AlbumTable,
Album\Model\Album,
Album\Form\AlbumForm;

/**/

public function addAction()
{
$form = new AlbumForm();
$form->get('submit')->setAttribute('label', 'Add');
$request = $this->getRequest();
if ($request->isPost()) {
$album = new Album();
$form->setInputFilter($album->getInputFilter());
$form->setData($request->post());
if ($form->isValid()) {
$album->populate($form->getData());
$this->getAlbumTable()->saveAlbum($album);
// Redirect to list of albums
return $this->redirect()->toRoute('album');
}
}
return array('form' => $form);
}

/**/

Après avoir ajouté AlbumForm à la liste use, on implémente addAction(). Regardons son contenu en détail :

$form = new AlbumForm();
$form->submit->setAttribute('label', 'Add');

Nous instanciions AlbumForm et définissons le label du bouton submit à “Add. En faisant cela ici, nous pouvons réutiliser ce formulaire et utiliser un autre label.

$request = $this->getRequest();
if ($request->isPost()) {
$album = new Album();
$form->setInputFilter($album->getInputFilter());
$form->setData($request->post());
if ($form->isValid()) {

Si la méthode isPost() de l'objet Request renvoie true, cela signifie que le formulaire a été soumis, donc nous activons le filtre de saisie de l'instance de l'album. Nous évaluons ensuite les données postées et vérifions si elles sont valides, avec la méthode isValid().

// Redirect to list of albums
return $this->redirect()->toRoute('album');

Après avoir sauvegardé l'album, nous redirigeons l'utilisateur vers la liste des albums avec le plugin Redirect du contrôleur.

return array('form' => $form);

Nous retournons enfin les variables que nous voulons assigner à la vue. Dans notre cas, nous passons l'objet du formulaire. Notez que Zend Framework 2 nous permet de retourner un tableau avec les variables à assigner à la vue, ce qui créera un ViewModel. Cela est plus rapide à écrire.

Nous allons maintenant afficher le rendu du formulaire, dans la vue add.phtml :

module/Album/view/album/album/add.phtml

<?php
$title = 'Add new album';
$this->headTitle($title);
?>

<h1><?php echo $this->escape($title); ?></h1>

<?php
$form = $this->form;
$form->setAttribute('action', $this->url('album', array('action' => 'add')));
echo $this->form()->openTag($form);
?>

<dl class="zend_form">
<?php echo $this->formInput($form->get('id')); ?>
<dt><?php echo $this->formLabel($form->get('title')); ?></dt>
<dd><?php
echo $this->formInput($form->get('title'));
echo $this->formElementErrors($form->get('title'));
?></dd>
<dt><?php echo $this->formLabel($form->get('artist')); ?></dt>
<dd><?php
echo $this->formInput($form->get('artist'));
echo $this->formElementErrors($form->get('artist'));
?></dd>
<dd><?php
echo $this->formInput($form->get('submit'));
echo $this->formElementErrors($form->get('submit'));
?></dd>
</dl>

<?php echo $this->form()->closeTag($form); ?>

À nouveau, nous affichons le titre puis nous affichons le formulaire. Zend Framework fournit des helpers de vue qui nous facilitent la tâche. Le helper form() possède des méthodes openTag() et closeTag() que nous utilisons pour les balises d'ouverture et de fermeture du formulaire. Puis, pour chaque élément, nous utilisons formLabel(), formInput() et formElementErrors(). Le reste n'est que du HTML basique. Dans ce tutoriel, j'ai utilisé une liste de définition.

Nous pouvons maintenant utiliser le lien “Add new album de la page d'accueil pour ajouter un nouvel album à la liste.

Éditer un album

Éditer un album est pratiquement la même chose qu'en ajouter un, le code est très ressemblant. Cette fois nous utilisons la méthode editAction() de l'AlbumController :

module/Album/src/Album/AlbumController.php

/**/

public function editAction()
{
$id = (int)$this->getEvent()->getRouteMatch()->getParam('id');
if (!$id) {
return $this->redirect()->toRoute('album', array('action'=>'add'));
}
$album = $this->getAlbumTable()->getAlbum($id);

$form = new AlbumForm();
$form->bind($album);
$form->get('submit')->setAttribute('label', 'Edit');
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->post());
if ($form->isValid()) {
$this->getAlbumTable()->saveAlbum($album);
// Redirect to list of albums
return $this->redirect()->toRoute('album');
}
}

return array(
'id' => $id,
'form' => $form,
);
}

/**/

Ce code doit vous paraître familier. Observons les différences avec celui de l'ajout d'un album. Tout d'abord, penchons-nous sur l'id qui est dans la route, nous l'utilisons pour charger l'album qui doit être édité :

$id = (int)$this->getEvent()->getRouteMatch()->getParam('id');
if (!$id) {
return $this->redirect()->toRoute('album', array('action'=>'add'));
}
$album = $this->getAlbumTable()->getAlbum($id);

Notez que si l'id vaut zéro, nous redirigeons vers l'action de l'ajout d'un album.

$form = new AlbumForm();
$form->bind($album);
$form->get('submit')->setAttribute('label', 'Edit');

La méthode bind() lie le modèle au formulaire. On l'utilise de deux façons :

1. À l'affichage du formulaire, les valeurs par défaut de chaque élément sont extraites du modèle ;

2. Après le succès de la validation avec la méthode isValid(), les données du formulaire sont réinjectées dans le modèle.

Ces opérations sont effectuées par un objet « hydrator ». Il y a un grand nombre d'hydrators, mais celui qui est défini par défaut est Zend\Stdlib\Hydrator\ArraySerializable, qui attend deux paramètres dans le modèle : getArrayCopy() et populate(). Dans notre cas, Zend\Db\ResultSet\Row les implémente à notre place. Nous n'avons pas besoin de remplir le formulaire avec les données car cela est fait automatiquement, nous devons juste appeler le mapper saveAlbum() pour sauvegarder les changements dans la base de données.

Le template edit.phtml ressemble à celui de l'ajout d'un album :

module/Album/view/album/album/edit.phtml

<?php
$title = 'Edit album';
$this->headTitle($title);
?>

<h1><?php echo $this->escape($title); ?></h1>

<?php
$form = $this->form;
$form->setAttribute('action',
$this->url('album', array('action' => 'edit', 'id'=>$this->id)));
echo $this->form()->openTag($form);
?>

<dl class="zend_form">
<?php echo $this->formInput($form->get('id')); ?>
<dt><?php echo $this->formLabel($form->get('title')); ?></dt>
<dd><?php
echo $this->formInput($form->get('title'));
echo $this->formElementErrors($form->get('title'));
?></dd>
<dt><?php echo $this->formLabel($form->get('artist')); ?></dt>
<dd><?php
echo $this->formInput($form->get('artist'));
echo $this->formElementErrors($form->get('artist'));
?></dd>
<dd><?php
echo $this->formInput($form->get('submit'));
echo $this->formElementErrors($form->get('submit'));
?></dd>
</dl>

<?php echo $this->form()->closeTag($form); ?>

Les seuls changements sont le titre, qui passe à « Edit Album » et l'action du formulaire qui définit aussi l'action à « edit ».

Vous pouvez désormais éditer les albums.

Supprimer un album

Pour finaliser notre application, nous devons ajouter la fonctionnalité de suppression. Nous avons placé un lien de suppression pour chaque album dans notre liste. La première approche serait de faire une suppression lors d'un clic. Mais ce n'est pas une bonne solution. Rappelez-vous les spécifications HTTP, nous vous rappelons que vous ne devez pas effectuer d'action irréversible en passant par GET, à la place il est préférable d'utiliser POST.

Nous allons afficher un formulaire de demande de confirmation quand l'utilisateur clique sur le lien de suppression et lancer la suppression s'il la valide. Comme ce formulaire est assez trivial, nous le mettons directement dans notre vue (après tout, Zend\Form est facultatif !).

Commençons le code de l'action dans AlbumController::deleteAction() :

module/Album/src/Album/AlbumController.php

/**/

public function deleteAction()
{
$id = (int)$this->getEvent()->getRouteMatch()->getParam('id');
if (!$id) {
return $this->redirect()->toRoute('album');
}

$request = $this->getRequest();
if ($request->isPost()) {
$del = $request->post()->get('del', 'No');
if ($del == 'Yes') {
$id = (int)$request->post()->get('id');
$this->getAlbumTable()->deleteAlbum($id);
}
// Redirect to list of albums
return $this->redirect()->toRoute('default', array(
'controller' => 'album',
'action' => 'index',
));
}

return array(
'id' => $id,
'album' => $this->getAlbumTable()->getAlbum($id)
);
}

/**/

Comme auparavant, nous connaissons l'id indiqué dans la route. Nous vérifions la valeur de la méthode isPost() pour déterminer s'il faut afficher la page de confirmation ou supprimer l'album. Nous utilisons l'objet de la table (« AlbumTable ») pour supprimer la ligne, avec la méthode deleteAlbum(). Si la requête n'est pas en POST, nous chargeons cette ligne depuis la base de données pour l'assigner à la vue, et ce grâce à l'id.

Le script de la vue est un simple formulaire :

module/Album/view/album/album/delete.phtml

<?php
$title = 'Delete album';
$this->headTitle($title);
?>

<h1><?php echo $this->escape($title); ?></h1>

<p>Are you sure that you want to delete
'<?php echo $this->escape($album['title']); ?>' by
'<?php echo $this->escape($album['artist']); ?>'?
</p>

<?php
$url = $this->url('album', array('action' => 'delete', 'id'=>$this->id)); ?>
<form action="<?php echo $url; ?>" method="post">
<div>
<input type="hidden" name="id" value="<?php echo (int)$album['id']; ?>" />
<input type="submit" name="del" value="Yes" />
<input type="submit" name="del" value="No" />
</div>
</form>

Dans ce script, nous affichons le message de confirmation, puis un formulaire avec les boutons Oui ou Non. Dans l'action, nous lançons la suppression si le Oui a été choisi.

Faire en sorte que la page d'accueil affiche la liste des albums

Un dernier point : pour l'instant, la page d'accueil http://zf2-tutorial.localhost/ n'affiche pas la liste des albums. C'est à cause de la configuration de la route, définie dans le fichier module.config.php du module Application. Pour changer cela, ouvrez le fichier module/Application/config/module.config.php et trouvez la route home :

module/Application/config/module.config.php et trouvez la route home :

'home' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/',
'defaults' => array(
'controller' => 'index',
'action' => 'index',
),
),
),

Changez la valeur de la clé controller de index à album :

'home' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/',
'defaults' => array(
'controller' => 'album/album',
'action' => 'index',
),
),
),

C'est terminé, nous avons enfin une application fonctionnelle !

Conclusion et remerciements

Ceci conclut cette rapide présentation de la construction, basique mais fonctionnelle, d'une application MVC basée sur Zend Framework 2. J'espère que cela vous aura été utile. Si vous détectez une erreur, merci de me prévenir par e-mail à rob@akrabat.com !

Zend Framework 2 est toujours en version bêta, il n'existe pas encore beaucoup d'articles. Vous pouvez consulter le manuel à cette adresse.

Mon site web http://akrabat.com présente un grand nombre d'articles sur Zend Framework et je commence à en créer de nouveaux sur Zend Framework 2.

Cet article a été publié avec l'aimable autorisation de Rob Allen. L'article original peut être lu sur son site : http://akrabat.com/zend-framework-2-tutorial/.

Nous remercions particulièrement Philippe Peso pour avoir proposé de publier sa traduction de cet article.

Nous tenons aussi à remercier ClaudeLELOUP pour sa relecture attentive de cet article.