Vous êtes sur la page 1sur 68

Présentation du Zend Framework - Premiers pas

par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Date de publication : 03/04/2007

Dernière mise à jour : 15/03/2010

Depuis Mars 2006 est apparu le Framework maison de chez Zend, pour PHP5. Pour
rappel, Zend est une société qui commercialise tout un tas d'outils pour les entreprises,
autour de PHP. Zend Framework est un d'entre eux si ce n'est qu'il n'est pas commercialisé :
distribué sous "New BSD Licence", il est gratuit, libre, conçu par une grande communauté
de développeurs interessés dont je fais parti, piloté par Zend, et il propose génération de
documents PDF, connecteurs vers de multiples services webs, connecteurs vers des
bases de données diverses et support de MVC...
On peut utiliser le Framework en tant que cadre de developpement directif, ou l'utiliser dans
le cadre d'un simple support bibliothéquaire, à la manière de PEAR.
Le projet Zend Framework est en perpetuel développement, et certains composants sont
développés par les entreprises intéréssées : les Webservices Gdata sont développés par
Google eux-mêmes, mais dans le respect de la licence du projet.
Zend Framework reprend en tout point le concept de PHP lui-même : libre, ouvert, simple,
efficace et puissant : un vrai régal.
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

I - Présentation............................................................................................................................................................ 3
I-A - Préambule...................................................................................................................................................... 3
I-B - Introduction.....................................................................................................................................................4
I-C - Pré-requis.......................................................................................................................................................4
I-D - Le coeur du Framework................................................................................................................................ 5
I-E - Les conventions de nommage - Vocabulaire................................................................................................ 5
II - #Zend_Loader........................................................................................................................................................ 7
III - #Zend_Debug #Zend_Version............................................................................................................................ 10
IV - #Zend_Registry...................................................................................................................................................11
V - #Zend_Config.......................................................................................................................................................13
VI - Zend_Cache........................................................................................................................................................16
VII - #Zend_Validate.................................................................................................................................................. 19
VIII - #Zend_Filter...................................................................................................................................................... 23
VIII-A - Zend_Filter............................................................................................................................................... 23
VIII-B - Zend_Filter_Input..................................................................................................................................... 23
IX - #Zend_Db........................................................................................................................................................... 28
IX-A - Introduction................................................................................................................................................ 28
IX-B - Requêtes simples...................................................................................................................................... 30
IX-C - Récupération de résultats......................................................................................................................... 31
IX-D - Insert, Update et Delete............................................................................................................................ 34
IX-E - Transactions...............................................................................................................................................35
IX-F - Sélections...................................................................................................................................................36
IX-G - Passerelle vers les tables......................................................................................................................... 38
IX-G-1 - Généralites........................................................................................................................................38
IX-G-2 - L'exemple.......................................................................................................................................... 38
IX-G-3 - Définition des passerelles.................................................................................................................39
IX-G-4 - Les résultats (Row)...........................................................................................................................42
IX-G-5 - Les jeux de résultats (Rowset).........................................................................................................43
IX-G-6 - Gestion des dépendances................................................................................................................44
IX-H - Rappels......................................................................................................................................................45
X - #Zend_Log........................................................................................................................................................... 46
XI - #Zend_View........................................................................................................................................................ 49
XI-A - Principe...................................................................................................................................................... 49
XI-B - Helpers (aides au rendu)...........................................................................................................................50
XII - Zend_Form.........................................................................................................................................................53
XII-A - Manipuler des éléments de formulaire..................................................................................................... 54
XII-B - Ajouter des validateurs et valider le formulaire........................................................................................ 56
XII-C - Décorateurs : gérer le rendu HTML......................................................................................................... 59
XII-D - Autres fonctionnalités............................................................................................................................... 62
XII-E - Etendre Zend_Form..................................................................................................................................62
XIII - Zend_Application.............................................................................................................................................. 63
XIII-A - Configurer ses objets (ressources)......................................................................................................... 64
XIII-B - Utiliser les plugins pour configurer ses objets (ressources)....................................................................66
XIV - Conclusions...................................................................................................................................................... 68

-2-
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

I - Présentation

I-A - Préambule

Le Zend Framework (ZF) fait beaucoup de bruit sur la toile du développement PHP. Il sait se classer dans la vague
des Framework "reconnus" sous PHP. Google le présente d'ailleurs parmi ses outils pour developpeurs. J'ai décidé
de vous y faire goûter du bout des lèvres ;-).

Ce Framework a bien grandi. En l'espace d'un an, il a changé de licence pour s'ouvrir au développement
communautaire, et il accuse déjà une quarantaine de composants opérationnels. Il est vrai qu'à ses débuts, début
2006 (le projet date d'Octobre 2005), tout était différent. Beaucoup de refactorisation et de correction de bugs ont
été faits. Une si belle progression n'aurait pu être possible sans un modèle de développement communautaire, mais
bien architecturé.
Tout le monde peut reporter un bug sur le tracker, proposer ses idées, faire partager ses expériences, tout ceci
orchestré par l'équipe de gestion du projet de Zend.

Personnellement, j'ai bien aimé mes débuts sous ZF, c'est en découvrant peu à peu sa structure et son articulation
que j'ai décidé de contribuer un peu, tout d'abord via ce tutoriel de présentation. Mon blog regorge d'informations
diverses concernant ZF, et j'écris aussi d'autres articles ou ateliers techniques. ZendFramework représente aussi
une partie de mon travail à titre professionnel, en effet je suis formateur sur ce Framework et j'aide à son déploiement
ou son utilisation dans le milieu professionnel en consulting architectural.
Je bosse sous ce Framework depuis environ Octobre 2006 (0.2), et je suis de près tout ce qui le touche, notamment
son développement actuel qui tend à se rapprocher d'un style Ruby On Rails, de par certaines caractéristiques. Nul
doute que les programmeurs habitués à du "full object", ne seront pas dépaysés (les adeptes de Java par exemple).
Je suis contributeur, je participe donc au développement de certains composants ainsi qu'à la correction de bugs et
à l'amélioration de certaines fonctions. Il fallait bien un membre du club developpez.com qui mette les mains dedans,
et Zend a bien apprécié le geste en plus ;-).

A la tête du Zend Framework, on trouve Andi Gutmans, qui n'est autre qu'un des 2 architectes responsables du Zend
Engine, le moteur de PHP ayant succédé au projet initialement conçu par Rasmus Lerdof.
Le but de Zend est simple : faire de ZF un framework calqué sur PHP : aussi simple, intuitif, et puissant, que le
langage PHP lui-même, et je dois dire que pour le moment, c'est nettement le cas !
Certes il existe des tonnes de frameworks dans l'univers de PHP, et des bons en plus, mais on peut dire du Zend
Framework qu'il est amené à devenir la brique que tout développeur PHP sera à même de savoir utiliser, surtout
dans un environnement professionnel.
Car c'est bien là que Zend intervient : l'entreprise vend du support et des services autour de produits, eux, libres :
PHP d'abord, et aujourd'hui : ZendFramework. Une entreprise est bien plus rassurée lorsqu'un support professionnel
est présent derrière un produit, à fortiori libre. Passer le framework sous une licence "ouverte" est donc bienvenu
de la part de Zend.

ZF n'est pas fermé et peut se relier à d'autres frameworks; de même , il y a 2 moyens d'utiliser ZF : en "glue" : vous
utilisez uniquement les composants dont vous avez besoin, ponctuellement, à la manière de PEAR. Autre méthode :
"full-stack" : vous décidez dès le départ de bâtir votre architecture entière sur ZF.
Cette perméabilité de Zend Framework en fait sa force ultime : vous pouvez doucement apprendre à le maitriser,
avant de totalement l'utiliser, à ne plus pouvoir vous en passer; car ZF a bel et bien pour objectif de répondre aux
besoins les plus redondants du développement de sites Internet en PHP
ZF est la surcouche fonctionnelle de PHP5, testé, sécurisé, développé et pensé par des ingénieurs, architectes et
développeurs d'importance, le tout dans un esprit collaboratif, libre et open source.

L'AFUP, Association Francaise des Utilisateurs de PHP, dont je suis membre, a organisé début décembre 2006 une
réunion ayant pour thème le Framework de Zend. Zeev Suraski était présent. Je vous revoie sur cette page pour
plus d'infos, et pour télécharger les slides. Il ne s'agit pas ici de dresser un comparatif avec d'autres Frameworks PHP,
mais tout simplement un avant goût sous forme de mini tutoriel, qui va s'étendre profondément sur certains points,
ceci dans le but de faciliter votre apprentissage, et ce quel que soit votre niveau d'expérience dans la programmation
sous Framework.

-3-
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Vous allez au fur et à mesure ressentir un fait avéré : le ZendFramework offre une souplesse de programmation
vraiment inégalée actuellement.

Si vous êtes intéressés pour approfondir le vaste sujet que représente le ZendFramework, j'ai co-écrit un ouvrage à
son sujet, vous en apprendrez plus sur sa page dédiée.

I-B - Introduction

Un Framework apporte un cadre standard pour le développement d'applications à interface web.


Il agrège différentes classes, ce qui augmente la couverture fonctionnelle d'un langage tout en en simplifiant sa
manipulation.
Il permet à plusieurs personnes (notamment en Entreprise) de s'organiser autour d'un projet, quelle que soit sa taille.
Il introduit des règles de codage et d'architecture qui sont là pour faire en sorte que toutes les personnes relatives
au projet parlent la même langue. Tous les développeurs vont ainsi écrire du code à partir du même cadre de travail,
et l'échange des données entre eux est très facilité.
Les architectes et les chefs de projet s'y retrouvent aussi, car la puissance de la programmation orientée objet leur
permet d'utiliser des méthodes connues et ayant fait leurs preuves, comme la modélisation UML par exemple.
La conception objet permet aussi un fort découplage applicatif, la réutilisabilité maximale du code, et favorise
grandement les étapes de test et de gestion de la qualité d'un produit.

Avec à ce jour 48 packages fonctionnels; le Zend Framework propose "une collection modulaire de classes PHP 5
qui simplifient les tâches courantes du développeur".
Il s'agit pour l'essentiel de fonctionnalités très utilisées comme une couche d'abstraction de bases de données (fondée
principalement sur PDO), le support de MVC, la génération de documents PDF, l'envoi d'e-mails, la gestion de
flux de syndication Atom et RSS, la gestion de l'internationalisation...
Le Zend Framework intègre aussi des "connecteurs" pour les services en ligne de Yahoo!, Google, Amazon, Flickr.
On peut noter aussi une classe de gestion des dates, un package complet pour l'indexation de contenu basé sur le
célèbre moteur Lucène de l'Apache Group, tout un tas d'outils facilitant l'internationalisation, les sessions, etc.

Pour s'assurer du succès de son Framework, Zend s'est appuyé sur un modèle participatif et communautaire
impliquant plus de 180 participants au nombre desquels figurent de grands acteurs comme Yahoo!, Google, IBM
et General Electric. La concision du code qu'apporte un Framework de haut niveau, plus fonctionnel que technique,
augmente la productivité des développeurs. Par exemple, il faut environ 15 lignes de code PHP pour afficher un flux
RSS, tandis que 5 suffisent avec le Zend Framework.

I-C - Pré-requis

Ce tutoriel suppose que vous ayez de solides connaissances du travail sous PHP dans sa version 5, et notamment du
modèle objet de celui-ci, ainsi que de la SPL (Standard Php Library), qui fournit un ensemble de classes interne à PHP.
Dans le cadre de la compréhension de cet article, il est conseillé de posséder de bonnes compétences en
programmation orientée objet (POO), de bonnes notions de conception UML et des Design Patterns, ainsi que
quelques connaissances du langage SQL; étant donné que nous ne ferons pas que "survoler" certaines de ses
fonctionnalités
Vous retrouverez les diagrammes de classe des principaux packages étudiés en annexe.
L'environnement de travail étudié dans cet article est un serveur Apache en version 2.2.x, un SGBD MySQL dans sa
version 5.x et PHP version 5.3.x. Zend Framework requiert PHP 5.2.4 minimum pour fonctionner de manière optimale.

Sans nul doute, votre point de repère reste l'API en ligne, le mécanicien ne bossant pas sans sa caisse à outils...
Histoire de ne pas se perdre dans les méthodes et les objets, on garde un oeil sur le manuel.
Attention tout de même, la version originale du manuel est la version anglaise. Les autres versions sont des
traductions, effectuées par des groupes de traduction de l'équipe de développement, et peuvent donc être
désynchronisées, le temps de la traduction. Vérifiez donc les statuts des traductions, ou préférez la doc originale,
en anglais.

-4-
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Commençons par télécharger la dernière version stable de ZF. À ce jour, ce tutoriel est écrit avec ZF 1.10

Il est important de vérifier la version du framework utilisée. Les ChangeLog peuvent vous
aider.

I-D - Le coeur du Framework

Je vous renvoie au sommaire afin de prendre connaissance des quelques composants que nous allons étudier dans
cet article.

Rien qu'avec ces quelques composants, nous serons déjà en mesure de faire une petite
appli web sympathique, vous remarquerez que je n'utiliserai pas ici #Zend_Controller,
donc pas de MVC dans cet article.
Cependant, notre section Zend Framework devrait satisfaire vos autres curiosités.
Il n'est pas obligatoire de réaliser un projet à 100% sous ZF. Ainsi, même sans MVC, ZF
va tout de même nous simplifier la vie.

Cet article n'a pas pour vocation d'être exhaustif, certaines méthodes ne seront pas
présentées, elles sont cependant bien présentes dans l'API.

Le coeur du Framework est représenté par le dossier library de l'archive téléchargée. La structure interne de ZF est
régie par des conventions de nommage, que voici :

I-E - Les conventions de nommage - Vocabulaire

#Zend_Db représente un composant que l'on peut aussi appeler package (en référence à Java), il est
matérialisé par le script /ZF-path/Zend/Db.php, la classe représentée étant Zend_Db.
Cette convention de nommage sera utilisée pour charger des classes plus tard. Elle est identique à certains projets,
tels que PEAR. Remplacez les underscores ( _ ) dans le nom de la classe par des slashs ( / ), pour voir apparaître
l'arborescence du fichier.
Si votre application respecte la même norme, alors vous verrez que le développement devient encore plus souple.
Comme pour PEAR, chaque underscore ( _ ) dans le nom de la classe représente donc une descente dans
l'arborescence, et un lien de dépendance fonctionnelle.
Chaque composant donne naissance à une classe, portant le même nom, et chaque composant est une entité du
Framework, qui peut contenir d'autres classes qui l'enrichissent et qui dépendent d'elle.
Ainsi, Zend_Db_Adapter_Abstract est une classe dont le fichier est /ZF-path/Zend/Db/Adapter/Abstract.php, contenu
dans le package #Zend_Db

On peut donc dire que tout ce qui descend, dépend. Une dépendance est un lien UML soit direct : héritage, soit
indirect : utilisation, implémentation, etc. Mais un des avantages de ZF est son couplage relativement faible. Tous
les packages ne dépendant pas les uns des autres, seules quelques liaisons internes sont effectuées, lorsque c'est
nécessaire. Ainsi, vous pouvez sans problème utiliser #Zend_Db, par exemple, sans avoir à dépendre d'un autre
package.
Seules quelques dépendances entre packages sont à noter (elles sont même de temps en temps facultatives) où
c'est réellement nécessaire : #Zend_Mail va utiliser #Zend_Mime (ça se comprend), #Zend_Http_Client va utiliser
#Zend_Uri. Mais ce n'est pas le gros foutoir, dans lequel l'inclusion du moindre composant, entraîne une inclusion
de dizaines d'autres (et donc un temps de traitement revu à la hausse).
Il est conseillé de placer le répertoire du Framework en dehors de la racine web, sinon, d'interdire son accès, par
exemple avec un fichier .htaccess.

Comme pour tout Framework, dans un souci de pratique, il faut d'abord ajouter son répertoire à l'include path de
PHP. Ceci se fait soit par l'intermédiaire du fichier php.ini, soit via la fonction set_include_path(). Nous utiliserons
en développement une gestion d'erreur large (E ALL ou alors E_ALL | E_STRICT).

-5-
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

L'utilisation d'un framework apporte une baisse notable des performances d'une
application, ceci est dû à l'introduction d'un moteur 100% objets, mais surtout à l'inclusion
de dizaines de fichiers.
Lors de l'utilisation de MVC notamment, il n'est pas rare qu'une simple petite requête HTTP
provoque l'ouverture de centaines de fichiers PHP sur le serveur, pour la traiter.
Pour absorber et faire disparaitre cet inconvénient de taille, un cache d'opcodes (type
APC) secondé par un cache applicatif (type Zend_Cache) sont des solutions tout à fait
reconnues et utilisées.

-6-
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

II - #Zend_Loader

La classe Zend_Loader comporte déjà toute une série d'outils très pratiques. Ce ne sont que des méthodes statiques,
sans dépendances externes (Elles ne necessitent pas d'autres composants, cependant d'autres composants ont
besoin d'elle). Le composant à inclure est #Zend_Loader, il est donc représenté par le fichier /Zend/Loader.php.
Quasiment tous les composants du Framework dépendent de #Zend_Loader.

configuration pour utilisation de ZF


<?php
// ZFHome est le chemin où le dossier 'library' est placé.
error_reporting(E_ALL | E_STRICT);
set_include_path('path/to/ZFHome' . PATH_SEPARATOR . get_include_path());

ZF utilise sa propre gestion d'exceptions qui n'est autre qu'un alias de la classe Exception native de PHP.
class Zend_Exception extends Exception{}
Toute exception qui sera levée dans vos applications enverra, au plus bas, une Zend_Exception. Notez que
Zend_Exception n'étant pas déclarée en 'final', il est possible de l'étendre, ce qu'il est hautement conseillé de faire.
Je ne vais pas faire un tutoriel sur les Exceptions, mais juste rappeler que chaque package, ou chaque entité
élémentaire, doit renvoyer sa propre exception, nommée. Cela facilite grandement le débuguage, notamment dans le
cadre de projets travaillés en équipe. Si tout le monde envoie des 'Exception', toutes simples, on ne peut absolument
pas savoir d'où viennent les erreurs, de même lors des processus de tests, tout se complique.

Remarquez qu'il faut que Zend Framework soit dans l'include_path de PHP. Il est conseillé
d'effectuer cette modification dans PHP.ini directement. Nous supposerons celà par la
suite.

Nous allons poursuivre avec les méthodes 'helpers' du composant #Zend_Loader. Utiliser des helpers, alors que
PHP seul peut représenter les mêmes fonctions, est utile si vous chargez des fichiers dont le nom ou le chemin
d'accès sont variables (des vérifications sont faites sur le nom). Sinon, un require() fait l'affaire.

loadFile() charge un fichier PHP.

utilisation de Zend_Loader supposant ZF présent dans l'include_path


<?php
// manière classique
require('example1.php');

// manière ZF
require('Zend/Loader.php');
try {
Zend_Loader::loadFile('example2.php');
} catch (Zend_Exception $e) {
echo $e->getMessage();
}

<?php
// manière classique avec once
require_once('example1.php');

// manière ZF avec once


require('Zend/Loader.php');
try {
Zend_Loader::loadFile('example2.php', null, true);
} catch (Zend_Exception $e) {
echo $e->getMessage();
}

La grande différence avec PHP, comme pour tous les outils du Framework Zend, est qu'ils s'appuient sur une gestion
d'Exception interne, que vous pouvez contrôler comme vous le souhaitez. Si un fichier comporte des caractères
interdits, une Zend_Exception sera levée.

-7-
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Plus répandu déjà, loadClass() charge une classe en utilisant loadFile(). Cette méthode s'utilise surtout avec les
mécanismes d'autoload.

Configuration de ZF avec autoload


<?php
require('Zend/Loader/Autoloader.php');
Zend_Loader_Autoloader::getInstance();

spl_autoload_register() est utilisé en interne par cette méthode, et enregistre la fonction d'autoload de Zend
Framework sur la pile des autoloaders.
Avec l'autoload, dès que PHP rencontre une classe qu'il ne connait pas (c'est à dire dont le fichier la définissant n'a
pas encore été inclu), il va utiliser la fonction d'autoload pour chercher le fichier correspondant à cette classe. La
fonction d'autoload de ZF lui fait utiliser loadClass()
Après que le fichier ait été trouvé, loadClass() s'assure que la classe a été chargée suivant les conventions de
nommage. loadClass() effectue donc 2 actions : charge le fichier de la classe, et vérifie le nom de la classe dans
ce fichier.

Pour de plus amples informations sur l'autoload, parcourez cet atelier Zend
Framework

Une fois l'autoload activé grâce à l'instruction Zend_Loader_Autoloader::getInstance(), nous pouvons charger
toute classe Zend_ sans avoir à l'inclure avant.
Pour charger des autres classes, il faut utiliser un espace de nommage, c'est à dire un préfixe de classe (Comme
Zend_ pour le Zend Framework). Il faut ensuite ajouter le dossier contenant ce dossier espace de noms dans l'include
path de PHP, et déclarer l'espace de nom à l'autoloader.
Ca parait compliqué ? Regardez comme c'est simple :

Utiliser l'autoload pour charger ses classes


<?php
define ('APP_PATH', __DIR__);
set_include_path(get_include_path() . PATH_SEPARATOR .
APP_PATH . '/mesclasses' . PATH_SEPARATOR);

require('Zend/Loader/Autoloader.php');
$loader = Zend_Loader_Autoloader::getInstance();
$loader->registerNamespace(array('Dvp_', 'JP_'));

// Dvp_Class est définie dans APP_PATH/mesclasses/Dvp/Class.php


$class = new Dvp_Class;

// JP_Exemple est définie dans APP_PATH/mesclasses/JP/Exemple.php


$class2 = new JP_Exemple;
?>

L'utilisation de l'autoload est aujourd'hui très largement répandue et recommandée dans les projets webs.
Sinon, loadClass() et loadFile() acceptent un second paramètre $dirs, chaîne ou tableau représentant un (des)
dossier(s) dans lequel (lesquels) rechercher le fichier.

isReadable() est une copie de son homologue PHP, à la seule différence qu'isReadable() parcourt l'include path.

Pour gérer correctement l'extensibilité de ses applications, la classe Zend_Loader_PluginLoader vous propose de
charger manuellement une classe en précisant uniquement son suffixe. Cela permet notamment de charger un
ensemble de préfixes que la classe essayera en ordre LIFO :

Utilisation de PluginLoader
<?php
require 'Zend/Loader/PluginLoader.php';
$loader = new Zend_Loader_PluginLoader();
$loader->addPrefixPath('Dvp', 'chemin/developpez');

-8-
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Utilisation de PluginLoader
$loader->addPrefixPath('Other', 'autrechemin/ailleurs');

$obj = $loader->load('Utils');
/* $obj est instance de la classe Other_Utils si autrechemin/ailleurs/Utils.php existe
et possède la classe, sinon il s'agira d'un objet Dvp_Utils se trouvant dans chemin/libs/Developpez/
Utils.php
si ce fichier existe, dans le dernier cas : une exception sera levée */

Se servir de ce composant manuellement est plutôt rare, en revanche le ZendFramework l'utilise lui en interne très
souvent. Dès lors qu'on vous demandera de préciser le nom d'une classe par une chaine représentant son préfixe,
alors Zend_Loader_PluginLoader est utilisé en interne. C'est notamment le cas pour les formulaires, les vues, etc...

-9-
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

III - #Zend_Debug #Zend_Version

#Zend_Debug ne contient actuellement qu'une méthode (utile) statique de débbuguage.


dump(): est une amélioration très appréciée de var_dump() qui rajoute des <pre> de formatage pour un confort
de lisibilité amélioré. C'est le remplaçant des echo() et autres print_r() var_dump(), extrêmement pratiques en
débuguage. setSapi():, permet de spécifier manuellement le SAPI sur lequel PHP tourne, le formatage de la sortie
ne sera plus encadré de <pre> mais de PHP_EOL (End Of Line, dépendant de l'OS sous lequel PHP tourne).
En théorie vous n'aurez pas besoin d'utiliser cette méthode sauf cas très spécifiques.

#Zend_Version ne contient qu'une seule méthode statique de comparaison de version.


compare(): compare la version passée en paramètre, avec la version actuelle de Zend_Framework. Elle utilise
version_compare() de PHP, elle renvoie cependant uniquement un String. Intéressant à noter, la constante
Zend_Version::VERSION contient la version actuelle du Zend Framework (par exemple "1.8.3"). Ceci pourra être
utilisé pour de la detéction d'environnement par exemple.

- 10 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

IV - #Zend_Registry

Ce composant est issu du design pattern Registre. Le Registre est un pattern qui fournit un mécanisme de
stockage de données, destiné à favoriser le mélange global des objets dans une application.
C'est une alternative à l'utilisation de variables globales. Le registre de ZF s'utilise de manière statique ou dynamique,
nous utiliserons plutôt les méthodes statiques. Le Framework l'utilise lui-même en interne pour son mélange de
données. set() et get() en sont les méthodes principales :

Enregistrement d'un objet dans le registre


<?php
$config = new Zend_Config_Ini('./
config.ini', 'database'); // $config représente un objet de configuration
Zend_Registry::set('conf', $config);

Dans cet exemple, un objet de configuration (que nous verrons juste après) est crée et enregistré dans le registre
sous le nom de 'conf', qui va nous servir à récupérer l'instance :

Voiture.php
<?php
class Voiture
{
public $marque;
public $couleur;

public function __construct()


{
$conf = Zend_Registry::get('conf');
$this->marque = $conf->voiture->marque;
$this->couleur = $conf->voiture->couleur;
}
}

Dans cette classe, nous extrayons l'objet config du registre pour définir la couleur et la marque des voitures instances
de Voiture. Ceci est très avantageux, et nous permet d'écrire un code portable, écrit une fois, stocké dans le registre,
et utilisé partout où il le faut.
L'avantage par rapport à une variable globale est qu'on n'est pas dépendant du nom d'une variable précisément.
Dans l'exemple ci-dessus, je peux à tout moment changer le nom de la variable $config, la classe Voiture utilise le
registre et non le nom de la variable directement, déclarée ailleurs.
Précisons à nouveau : le registre de ZF renvoie toujours la même instance d'un objet (mais pas d'un tableau même
si ce cas d'utilisation est plus que rare). Ainsi, n'importe où dans un script :

Récupération d'un objet depuis le registre


<?php
$conf = Zend_Registry::get('conf');
$conf->voiture->marque = 'une marque'

Ceci applique la modification sur $conf, l'unique instance dans le registre, et modifie donc l'objet partagé 'conf'.
Souvenez-vous, le registre est là dans le seul but de partager une info unique, entre tous les scripts.

Il faudra veiller toutefois à ne pas tout stocker dans le registre. En génie logiciel, il faut
garder absolument sous contrôle la notion de dépendance (ou aussi de couplage) : le
registre mélange tout, il faut l'utiliser à bon escient et aussi rarement que possible. Préférez
toujours l'injection de dépendances : un programme se reposant trop sur un registre est
intestable et inmaintenable.

isRegistered() : Afin d'éviter l'écrasement accidentel d'objets, vous pouvez tester l'appartenance d'un objet au
registre via cette méthode en lui passant directement l'objet en paramètre ; elle retourne TRUE s'il, FALSE sinon.

- 11 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

setInstance() est une méthode qui permet carrément d'implémenter son propre pattern Registre étendant
Zend_Registry. Si votre application doit se brancher sur une structure comportant déjà un registre, cette méthode
permettra de l'utiliser.

Zend_Registry est un composite : il auto gère sa(ses) propre(s) instance(s). Zend_Registry


étend ArrayObject, de la SPL.

- 12 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

V - #Zend_Config

Tout le monde a un jour écrit quelque chose comme cela :

Exemple de configuration classique d'un site en PHP


<?php
$db_host = 'localhost';
$db_name = 'name_of_database';
$db_user = 'myname';
$db_pass = 'secret';
?>

Sur de gros projets, cela ne tient plus la route et il devient difficile d'accéder aux variables de configuration.

Autre exemple de configuration classique d'un site en PHP


<?php
$GLOBALS['config']['db_name'] = 'localhost';
?>

Pourquoi pas. Mais dans un souci de portabilité, et afin de pouvoir établir plusieurs configs pour plusieurs utilisateurs,
par exemple, pourquoi ne pas extraire tout simplement la config dans un fichier de config à part : config.ini ou
config.xml ?
Nul besoin alors d'écrire du code pour parser ces fichiers, voyons le cas ini :
Zend_Config_Ini permet de charger un fichier de configuration ini en utilisant la fonction PHP parse_ini_file().
Reportez-vous à ce manuel pour écrire vos fichiers ini, il existe quelques petites subtilités.

Config.ini :
[database]
username = foo
password = bar
hostname = localhost

[voiture]
marque = ma-marque-de-voiture
couleur = vert

Avec ce fichier de configuration, le script suivant va encapsuler nos données dans un objet et nous les montrer :

Utilisation de Zend_Config avec un fichier .ini


<?php
$config = new Zend_Config_Ini('./config.ini');

echo $config->database->username . '-' . $config->database->password . '-' . $config->database-


>hostname;
?>

Le fichier de config ini est donc transformé en class->propriétés et, mieux encore, le parseur gère aussi l'héritage.
Par exemple, un fichier de config unique, mais dont les paramètres sont écrasés, avec une gestion d'utilisateurs :

Config.ini :
[database]
name = database
hostname = localhost

[developers : database]
name = dev-database
username = developers
password = dev-password

[teamleaders : database]

- 13 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Config.ini :
username = team-leaders
password = tl-password

Chargement de sections différentes à partir d'un fichier ini


<?php
$config = new Zend_Config_Ini('./config.ini', 'developers');

echo $config->username . '-' . $config->name ; // developers - dev-database


?>

<?php
$config = new Zend_Config_Ini('./config.ini', 'teamleaders');

echo $config->username . '-' . $config->name ; // team-leaders - database


?>

ème
Ici, nous passons au constructeur le nom de la section à charger comme 2 paramètre. "Developers : database"
signifie que developers hérite des propriétés de database. Il peut en redéfinir d'autres ou écraser les propriétés
parentes (héritage OO avec visibilité protected). Notez que le fait de charger une section fait entrer notre objet dedans.
Nous pointons donc $config->leparamètre directement, et non plus $config->lasection->leparamètre.
En revanche, l'objet $config reste en lecture seule, toute tentative d'affectation renvoie une Zend_Config_Exception.
Il n'est donc pas possible de modifier un paramètre ini, depuis le framework par défaut. Ce comportement peut être
ème
changé via une 3 option constructeur :

Autoriser les modifications sur l'objet Zend_Config


<?php
$config = new Zend_Config_Ini('./config.ini', null, true);

$config->database->username = 'myUserName'; // autorisé


?>

Notez qu'il n'est toutefois pas possible de sauvegarder l'objet vers le fichier Ini. Le fichier Ini est en lecture seule
forcée, lui.
De plus, getSectionName() retourne la section qui a été précédemment chargée, et areAllSectionsLoaded()
retourne true si vous n'avez pas choisi de section dans votre constructeur (en passant à null le paramètre de section).

Zend_Config est un design pattern composite. Chaque section représente une instance
de l'objet Zend_Config nichée dans son père. Les interfaces Countable et Iterator, de la
SPL, sont utilisées.
Il s'agit de la représentation des données sous forme d'un arbre, chaque noeud est une
branche à l'extrémité de laquelle se trouve une feuille : la donnée.

Nous pouvons en plus des sections, créer un héritage avec le point '.'. Ce séparateur est modifiable.

Config.ini :
[site]
debug = 1
db.hostname = localhost
log.writter = database

[dev : site]

[prod : site]
debug = 0
db.hostname = mydbprod

Chargement de la section 'dev'


$config = new Zend_Config_Ini('./config.ini', 'dev');

- 14 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Chargement de la section 'dev'


echo $config->debug // 1
echo $config->db->hostname // localhost

Chargement de toutes les sections


$config = new Zend_Config_Ini('./config.ini');

echo $config->site->debug // 1
echo $config->prod->debug // 0
echo $config->site->db->hostname // localhost

Chargement de la section 'prod'


$config = new Zend_Config_Ini('./config.ini', 'prod');

echo $config->debug // 0

- 15 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

VI - Zend_Cache

#Zend_Cache est un composant fort sympathique pour mettre tout un tas de données diverses et variées dans un
cache. Il convient dès lors de définir un peu de vocabulaire.
Une façade de cache, aussi appelée en anglais (et dans la doc officielle) "frontend", représente le type de données
que l'on veut pouvoir mettre en cache. Un support de cache, en anglais "backend", représente l'endroit dans lequel
les données vont être stockées.
Le composant #Zend_Cache permet donc de lier une façade à un support, et c'est ce couple là qui sera utilisé.

Concernant les façades, il est possible de mettre en cache :

- 16 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Différentes façades Zend_Cache


1 Zend_Cache_Frontend_Output : sortie standard
2 Zend_Cache_Frontend_Function : résultat de l'appel d'une fonction
3 Zend_Cache_Frontend_Class : résultat de l'appel des méthodes statiques d'une classe
4 Zend_Cache_Frontend_File : fichier
5 Zend_Cache_Frontend_Page : page HTTP complète
6 Zend_Cache_Frontend_Capture : page HTTP complète avec intégration au modèle MVC de ZendFramework

Toutes ces façades spécialisées héritent d'une façade hautement générique : Zend_Cache_Core. Celle-ci fournit des
méthodes bas niveau de gestion du cache, comme save(), load(), clean(), remove() ...

Les supports eux, sont aussi nombreux :

- 17 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Différents supports Zend_Cache


1 Zend_Cache_Backend_File : stocke les données de cache dans un fichier
2 Zend_Cache_Backend_Sqlite : stocke les données de cache dans SQLite
3 Zend_Cache_Backend_Memcached : stocke les données de cache dans un serveur ou une ferme
Memcached
4 Zend_Cache_Backend_Apc : stocke les données de cache dans le cache OPCode APC
5 Zend_Cache_Backend_Xcache : stocke les données de cache dans le cache OPCode Xcache
6 Zend_Cache_Backend_ZendServer : stocke les données de cache dans la distribution PHP intégrée
ZendServer
7 Zend_Cache_Backend_ZendPlatform : stocke les données de cache dans le serveur d'application
ZendPlatform
8 Zend_Cache_Backend_TwoLevels : stocke les données de cache dans deux supports différents, en
permettant la gestion de la bascule de l'un à l'autre
9 Zend_Cache_Backend_BlackHole : stocke les données de cache dans rien du tout : simulacre servant pour
les tests
10 Zend_Cache_Backend_Static : stocke les données de cache provenant de Capture dans des fichiers

Ce qu'il faut savoir, c'est que les supports basés sur les fichiers sont lents, mais par contre ils permettent souvent
de stocker des quantités de données énormes.
A l'inverse, un support basé sur la mémoire vive (Memcached, APC, XCache, Sqlite dans certains cas) sont
extrêmement rapides, mais en général limité en capacité.

L'utilisation de #Zend_Cache est donc plutôt simple : Zend_Cache possède une méthode factory() qui va créer un
cache, c'est à dire une association entre une façade et un support.
Il convient de les paramétrer, et la doc officielle détaille très bien tout cela.

<?php
$bO = array();
$fO = array('lifetime' => 60,
'automatic_serialization' => true);

$cache = Zend_Cache::factory('Core', 'APC', $fO, $bO);

Ce code crée un cache générique (qui va servir à tout faire : Zend_Cache_Core), avec comme support de stockage :
APC. APC ne nécessite aucune donnée de configuration, nous passons un tableau vide en quatrième paramètre.
Core ne nécessite aucune option, mais les paramètres par défaut ne vont pas nous arranger. Nous passons
donc 2 options : lifetime est très importante : il s'agit de la durée de vie des infos dans le cache (en secondes).
automatic_serialization indique à Zend_Cache_Core de sérialiser la donnée de manière automatique si celle-ci le
nécessite : par exemple un tableau ou un objet.

Utilisation du cache
<?php
// ...
if (($donnees = $cache->load('mesdonnées')) === false) {
$donnees = $db->findBigData();
$cache->save($donnees, 'mesdonnées');
}

Simplicité extrême : load() demande de charger des données stockées à l'index "mesdonnées". Si cette méthode
retourne false, alors c'est que les données n'étaient pas présentes dans le cache. Nous les récupérons donc depuis
un objet factice $db, puis nous n'oublions pas de les stocker dans les cache au bon index.
Il existe des options de nettoyage et de taguage du cache qui sont très pratiques et font de #Zend_Cache une
solution souple et efficace dans son domaine.

Enfin si vous devez gérer plusieurs objets de cache (ce qui est souvent le cas), Zend_Cache_Manager saura alors
mémoriser leurs informations respectives et vous fournir une passerelle simple vers les objets de cache stockés.

- 18 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

VII - #Zend_Validate

La sécurité est un point que tout bon développeur doit avoir en permanence à l'esprit. Je ne vais pas faire un cours
de sécurité ici, mais juste vous parler des composants #Zend_Validate et #Zend_Filter qui agissent selon le même
modèle, ils seront traités ensemble car possèdent beaucoup de ressemblances.
Zend_Validate est un conteneur de validateurs qui va permettre de créer des chaînes de validateurs sur mesure, qui
vont nous simplifier la vie face à des situations courantes en développement web.

Un validateur est une fonction qui vérifie si une donnée respecte un schéma. Un filtre au
contraire, modifie la donnée selon un schéma semblable.

Je ne vais montrer que quelques exemples, mais l'API en ligne regorge d'autres méthodes sympathiques. On
notera Zend_Validate_* { Alnum, Alpha, Between, Hostname, Int, Ip, Regex (...) }
Nous pouvons créer des instances de chacun de ces validateurs et les utiliser individuellement, mais nous pouvons
aussi les chaîner dans Zend_Validate : On aura bien compris qu'un validateur va servir à valider les données reçues
par l'utilisateur, c'est un composant de la catégorie sécurité des applications web, dont on va pouvoir user et abuser,
sa méthode principale est isValid().
Un validateur commence par retourner true, puis teste la valeur soumise à travers sa classe et ses conditions, dès
que l'une d'elle n'est pas remplie, son retour vaut false.
Plutôt que d'instancier un validateur à chaque fois, et de lui soumettre isValid(), la méthode statique raccourcie is()
peut s'avérer pratique pour un validateur précis :

Exemple d'utilisation basique de Zend_Validate


<?php
$float = new Zend_Validate_Float();
$float->isValid(25.3); // true

//équivalent à :
Zend_Validate::is(25.3,'float'); //true

En premier paramètre, passez votre valeur, en deuxième paramètre, un string représentant le validateur (qui doit être
atteignable et lisible), le troisième paramètre sert pour les options du validateur.
Lorsqu'un validateur retourne false : la donnée n'est donc pas valide, des méthodes permettent de savoir pourquoi.
getMessages() renvoie les messages d'avertissement que le validateur retourne, destinés à etre affichés.
getErrors() lui, va renvoyer des codes d'erreurs, qui sont présents sous forme de constantes dans la classe du
validateur, ceux-ci ne sont pas destinés à être affichés, mais à permettre un traitement adéquat de l'erreur, voyons ca :

Exemple d'utilisation plus avancée de Zend_Validate


<?php
$validator = new Zend_Validate_Between(2,8);
$value = 25;

if ($validator->isValid($value)) {
echo "tout est bon";
} else {
foreach ($validator->getMessages() as $message) {
echo "$message\n";
}
}
// ce code affiche '25' is not between '2' and '8', inclusively

Nous demandons ici de valider la donnée : la valeur doit être comprise entre 2 et 8. Nous lui fournissons '25' et nous
affichons les messages d'échec du validateur.
Tous les validateurs ont des messages par défaut, qui sont personnalisables par setMessages(). Etant donné qu'un
validateur peut générer plusieurs messages, getMessages() retourne un tableau, qu'il faut donc passer via un foreach,
par exemple.

- 19 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Changement des messages d'erreur dans Zend_Validate


<?php
$validator = new Zend_Validate_Between(2,8);
$validator-
>setMessages(array(Zend_Validate_Between::NOT_BETWEEN => "Attention '%value%' n'est pas compris inclusivement entr
$value = 25;

if ($validator->isValid($value)) {
echo "tout est bon";
} else {
foreach ($validator->getMessages() as $message) {
echo "$message\n";
}
}
// ce code affiche Attention '25' n'est pas compris inclusivement entre '2' et '8'

Pour illustrer l'utilité de getErrors(), voici un petit exemple. Rappelons que getErrors() est similaire à getMessages(),
mais va servir à effectuer un traitement spécial sur une condition d'échec du validateur en s'appuyant sur des
constantes de classe. Par exemple, le validateur Digit vérifie que la chaine passée de contient bien que des chiffres,
mais vérifie aussi si celle-ci n'est pas vide. Nous pouvons effectuer un traitement spécial dans ce cas :

Exemple d'utilisation avancée Zend_Validate


<?php
$validator = new Zend_Validate_Digits();
$value = '';

if ($validator->isValid($value)) {
echo "tout va bien";
} else {
if ( in_array(Zend_Validate_Digits::STRING_EMPTY, $validator->getErrors()) ) {
// traitement pour le cas où la chaine est vide

}else{ // la chaine n'est pas vide, mais provoque un échec de validation : il ne s'agit donc pas que de chiffres
foreach ($validator->getMessages() as $message) {
echo "$message\n";
}
}

Utilisons le principe de chaînage des validateurs maintenant : nous avons toute une série de validateurs individuels
à disposition, que nous ajouterons à un validateur personnalisé grâce à addValidator(). Lors de l'appel à isValid(),
tous les validateurs vont passer en revue la donnée passée en paramètre, puis chacun écrira dans un journal si oui
ou non, son test est passé. On pourra récupérer le journal après via getMessages(), pour affichage, ou getErrors()
pour un traitement.

Création d'une chaine de validateurs


<?php
$chaine = new Zend_Validate();
$chaine->addValidator(new Zend_Validate_Int())
->addValidator(new Zend_Validate_GreaterThan(8));

if ($chain->isValid($data)) {
// la donnée est validée
} else {
// itération sur les messages d'erreur
foreach ($chaine->getMessages() as $message) {
echo "$message\n";
}
}
?>

Dans notre exemple ci dessus, nous créons un validateur composé d'une chaine de validateurs individuels associés,
qui vérifie si $data est un entier - supérieur à 8.
Dans le cas où notre donnée n'est pas un entier, le validateur va tout de même continuer sa chaine, et passer dans
GreaterThan. Les validateurs sont utilisés dans leur ordre d'ajout à la chaine de validation et getMessages() va alors
retourner tous les messages d'echec : ceux de Int, et ceux de GreaterThan.

- 20 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Il est possible de lui spécifier de s'arrêter de valider, dès qu'un des validateurs de la chaine échoue, ceci grâce à un
deuxième paramètre pour addValidator() :

Création d'une chaine de validateurs avec arrêt en cas d'erreur sur un validateur
<?php
$chaine = new Zend_Validate();
$chaine->addValidator(new Zend_Validate_Int(), true) // true en second paramètre
->addValidator(new Zend_Validate_GreaterThan(8));

if ($chain->isValid($data)) {
// la donnée est validée
} else {
// itération sur les messages d'erreur
foreach ($chaine->getMessages() as $message) {
echo "$message\n";
}
}
?>

Dans l'exemple si dessus, nous avons mis à true le deuxième paramètre pour addValidator() concernant le validateur
Int. Ainsi, si notre donnée ($data) n'est pas un entier, la chaine va écrire le message d'echec de Int dans sont journal,
puis va stopper, sans passer par GreaterThan, et les autres validateurs, si on en avait chainé plus.
Il est de même possible d'écrire ses propres validateurs, en implémentant Zend_Validate_Interface, et en
rédéfinissant les méthodes isValid(), getMessages() et getErrors(), par exemple nous souhaitons un mot de passe
de 8 caractères minimum, avec au moins une lettre majuscule, une minuscule, et un chiffre :

Création d'un validateur personnalisé


<?php
class MyValid_PasswordStrength extends Zend_Validate_Abstract
{
const LENGTH = 'length';
const UPPER = 'upper';
const LOWER = 'lower';
const DIGIT = 'digit';

protected $_messageTemplates = array(


self::LENGTH => "'%value%' doit avoir une longueur d'au moins 8 caractères",
self::UPPER => "'%value%' doit contenir au moins une lettre majuscule",
self::LOWER => "'%value%' doit contenir au moins une lettre minuscule",
self::DIGIT => "'%value%' doit contenir au moins un chiffre"
);

public function isValid($value)


{
$this->_setValue($value);

$isValid = true;

if (strlen($value) < 8) {
$this->_error(self::LENGTH);
$isValid = false;
}

if (!preg_match('/[A-Z]/', $value)) {
$this->_error(self::UPPER);
$isValid = false;
}

if (!preg_match('/[a-z]/', $value)) {
$this->_error(self::LOWER);
$isValid = false;
}

if (!preg_match('/\d/', $value)) {
$this->_error(self::DIGIT);
$isValid = false;
}

- 21 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Création d'un validateur personnalisé


return $isValid;
}
}

Nous allons tout de même dire un petit mot du gros validateur que représente Zend_Validate_EmailAddress

Zend_Validate_EmailAddress utilise Zend_Validate_Hostname pour au final permettre de vérifier si l'adresse email


est bien formée et si la partie pseudonyme respecte la RFC2822. Concernant la partie domaine, il est vérifié si elle
respecte la syntaxe d'un nom DNS du type nomdhote.org.
Il est possible de régler le filtre pour prendre en compte les adresses IP (et donc vérifier leur syntaxe), ou alors les
noms de domaines locaux.
Une fonction pour vérifier si le domaine accepte les Emails est prévue. Elle vérifie dans l'enregistrement du DNS la
présence d'un champ MX signifiant que le domaine est apte à recevoir des emails.

- 22 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

VIII - #Zend_Filter

#Zend_Filter va se décomposer en 2 grands paragraphes

VIII-A - Zend_Filter

#Zend_Filter est en tout point semblable à #Zend_Validate à l'exception qu'un filtre va transformer la donnée selon
son modèle, et non pas juste vérifier qu'elle respecte ce modèle. Zend_Filter est un conteneur de filtres qui va
permettre de créer des chaînes de filtres sur mesure.
Nous pouvons créer des instances de chacun de ces filtres et les utiliser individuellement, mais nous pouvons aussi
les chaîner dans Zend_Filter :

Utilisations banales de Zend_Filter


<?php
$alpha = new Zend_Filter_Alpha();
echo $alpha->filter('Foo45Bar'); // 'FooBar'

$balise = '<h1>balise</h1>';
$pasDeTag = new Zend_Filter_noTags();
echo $pasDeTag->filter($balise); // 'balise'

Chainage de filtres
<?php
$chaine = new Zend_Filter();
$chaine->addFilter(new Zend_Filter_Alpha())
->addFilter(new Zend_Filter_noTags());

$message = $chaine->filter($_POST['message']);

En plus des filtres déjà incorporés dans le Framework, vous pouvez écrire vos propres filtres en implémentant
l'interface Zend_Filter_Interface. La seule méthode à définir est filter()
Si vous n'avez pas envie de charger une instance de la classe de votre filtre, et d'appliquer la méthode filter, vous
pouvez passer par la méthode raccourcie get() de Zend_Filter, qui fonctionne sur le même principe que is(), de
Zend_Validate.

VIII-B - Zend_Filter_Input

Zend_Filter_Input en revanche, se voit reservé un paragraphe complet. Il mèle #Zend_Validate et #Zend_Filter, qui
sont très semblables en fonctionnement, afin de fournir un moyen de valider/filtrer les données d'entrée d'un script,
typiquement on pensera à $_GET et $_POST.
On crée une instance d'Input, une 'boite' ou 'box', composée à la fois de validateurs, et de filtres, puis nous passons
nos variables d'entrée à l'interieur, pour récupérer des variables saines en sortie. Le plan est donc :
Déclarer des règles de validation et de filtrage - Créer une instance Zend_Filter_Input avec les règles précédemment
définies - Passer les données dans cette moulinette - Récupérer les données saines, et éventuellement les messages
d'erreur.

chainage de filtres ET de validateurs sur un tableau d'entrées


<?php
$filters = array(
'nom' => 'StringTrim'
);

$validators = array(
'nom' => 'Alpha',
'annee' => array( 'Int',
array('Between', 1900, 2000)
)
);

- 23 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

chainage de filtres ET de validateurs sur un tableau d'entrées


$box = new Zend_Filter_Input($filters,$validators);
$box->setData($_GET); // filtrage et validation de toute l'entrée GET
if ($box->isValid()){
echo "tout est OK";
}else{
Zend_Debug::dump($box->getInvalid());
}
?>

setData() s'applique sur sur notre "boite", instance de Zend_Filter_Input, et lui spécifie quelle est la donnée d'entrée
à analyser. Notez bien que chaque règle écrite dans les validateurs et les filtres, est un tableau dont la clé doit
représenter le nom de la variable à analyser, la valeur représente une chaine épelant le nom du validateur/filtre à
appliquer, on peut aussi fournir une instance personnalisée.
Nous aurions pu utiliser la syntaxe équivalente $box = new Zend_Filter_Input($filters,$validators,$_GET); aussi
getInvalid() retourne les problèmes rencontrés par le validateur. Les filtres eux ne rencontrent pas de problème, ils
appliquent leur(s) filtre(s), bêtement, aux variables.

http://monserveur/monscript.php?nom=john&annee=1998
Tout est OK

http://monserveur/monscript.php?nom=j1o2h3n&annee=2008
array(2) {
["nom"] => array(1) {
[0] => string(46) "'j1o2h3n' has not only alphabetic characters"
}
["annee"] => array(1) {
[0] => string(52) "'2008' is not between '1900' and '2000', inclusively"
}
}

Notez que les filtres sont d'abord appliqués sur les valeurs, puis les validateurs
passent derrière. Il n'est donc pas necessaire, et déconseillé, d'appliquer le filtre
Zend_Filter_HtmlEntities sur les entrées, car celui-ci pourrait fausser le jugement des
validateurs qui passent après
Le filtre Zend_Filter_HtmlEntities est de toute façon appliqué en fin de traitement
automatiquement, la récupération des valeurs se fait par un accesseur, sinon par
getUnescaped().

http://monserveur/monscript.php?nom=j%9Ej%9E
<?php
$filters = array(
'nom' => 'StringTrim'
);

$validators = array(
'nom' => 'NotEmpty',
);

$box = new Zend_Filter_Input($filters,$validators);


$box->setData($_GET);

echo $box->nom // j&eacute;j&eacute;


echo $box->getEscaped('nom') // idem
echo $box->getUnescaped('nom') // jéjé
?>

Ici j'ai spécifié que je veux juste que ma variable 'nom', contenu dans mon tableau $_GET, soit non vide. Je
lui passe volontairement des caractères spéciaux, comme 'é' (%9E), qui seront convertis automatiquement par
Zend_Filter_Input, après y avoir fait passé les filtres d'abord, et les validateurs ensuite. On comprend ainsi que si

- 24 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

j'avais voulu passer du Zend_Filter_HtmlEntities en filtre, mon validateur passant après les filtres aurait pu être troublé
dans son jugement, qu'il aurait porté sur une variable déja modifiée.
Vous pouvez changer le filtre d'echappement si celui-ci (htmlentities) ne vous convient pas, ce qui sera cependant
rarement le cas. Procédez avec setDefaultEscapeFilter() :

Modification du filtre final d'échappement


<?php
$filters = array(
'nom' => 'StringTrim'
);

$validators = array(
'nom' => 'NotEmpty',
);

$box = new Zend_Filter_Input($filters,$validators);


$box->setData($_GET);
$box->setDefaultEscapeFilter(new Zend_Filter_StringTrim());
?>

Dernier rappel : Zend Framework echappe automatiquement (par défaut avec Zend_Filter_HtmlEntities) les données,
une fois celles-ci d'abord filtrées, puis validées.

Ce n'est pas terminé, car d'autres options toutes aussi agréables, viennent compléter la classe Zend_Filter_Input.
Par exemple la clause spéciale notée *, qui permet d'appliquer un filtre à toutes les valeurs contenues dans une
entrée (typiquement, nous traitons $_GET ici):

Toutes les variables passeront par le filtre StringTrim


<?php
$filters = array(
'*' => 'StringTrim'
);

En plus de getInvalid(), qui nous renvoie un tableau avec pour clé la variable qui a causé un echec d'un des
validateurs, et pour valeur, le message de cet echec, nous avons d'autres méthodes :
getUnknown() nous renvoie les champs qui sont présents dans la requête, mais qui n'ont pas été prévus dans une
des règles. Logiquement donc : des champs injectés :

Traitement des champs superflus ou additonnels


<?php
$filters = array(
'adresse' => 'StringToLower'
);

$validators = array(
'adresse' => 'NotEmpty',
);

$box = new Zend_Filter_Input($filters,$validators);


$box->setData($_GET);
echo $box->adresse;

if ($box->hasUnknown()){
Zend_Debug::Dump($box->getUnknown());
}
?>

Si j'interroge ce script via http://monserveur/cescript.php?adresse=Abc&uneinjection=injection , alors j'obtiens cette


sortie :

http://monserveur/cescript.php?adresse=Abc&uneinjection=injection
<?php
abc // ok, mon filtre StringToLower a été appliqué

- 25 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

http://monserveur/cescript.php?adresse=Abc&uneinjection=injection
array(1) {
["uneinjection"] => string(9) "injection"
}

hasUnknown() renvoie true si un champ a été injecté, et getUnknown() renvoie un tableau contenant les variables
injectées dans le script (par $_GET ici)
De la même manière hasMissing() et getMissing() existent aussi, pour spécifier cette fois le contraire : si des champs
on été prévus dans les règles, mais n'apparaissent pas dans la variable d'entrée filtrée.
Cependant, par défaut, Zend Framework ne les traite que si on le lui spécifie, via une constante de classe, et la
valeur 'required' :

Traitement des champs requis, mais absents dans le tableau d'entrée


<?php
$filters = array(
'adresse' => 'StringToLower'
);

$validators = array(
'adresse' => array('NotEmpty',
Zend_Filter_Input::PRESENCE => 'required'
)
);

$options = array(Zend_Filter_Input::MISSING_MESSAGE=>'Le champ %field% est requis par la règle %rule


%, mais n\'est pas fournit');
$box = new Zend_Filter_Input($filters,$validators,$_GET,$options);

if ($box->hasMissing()){
Zend_Debug::Dump($box->getMissing());
}
?>

Intérrogé sans fournir de paramètre 'adresse', ce script va nous renvoyer :

array(1) {
["adresse"] => array(1) {
[0] => string(71) "Le champ adresse est requis par la règle adresse, mais n'est pas fournit"
}
}

Nous avons utilisé une variable $options pour passer un message d'erreur que notre classe Zend_Filter_Input devra
nous retourner, comme déja vu.
Notez que ce paramètre est le 4ème paramètre de l'instanciation de la classe, le 3ème représentant les données
à passer dans la boite.
Attention, ceci est utilisé pour la métacommande 'required', si vous voulez personnaliser chaque message d'erreur
de chaque validateur, procédez comme suit :

La métacommande 'messages', permet de personnaliser les messages d'erreurs, elle se fournit via un tableau indexé
dont la clé représente le numéro du validateur, relativement à sa position d'insertion :

Utilisation de la métacommande 'messages'


<?php
$filters = array(
'adresse' => 'StringToLower'
);

$validators = array(
'adresse' => array('NotEmpty',
new Zend_Validate_StringLength(10),

'messages' => array(0=>'Le champ adresse est vide', // validateur d'index 0 : NotEmpty

- 26 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Utilisation de la métacommande 'messages'


1=>'Le champ adresse doit faire 10 caractères' // validateur d'index 1 : StringLength
)
)
);
$box = new Zend_Filter_Input($filters,$validators,$_GET);
?>

D'autres métacommandes sont encore disponibles.


Même si cette classe semble un peu complexe (nombreux tableaux imbriqués), elle suit évidemment une logique
qu'il suffit de capter (un peu d'entrainement et c'est bon), et elle s'avère non pas pratique, mais indispensable.

- 27 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

IX - #Zend_Db

IX-A - Introduction

Ce composant est bien entendu d'une importance extrême. Il régit toute la couche d'abstraction aux bases de
données. C'est la partie du tutoriel la plus longue car elle détaille, dans un cas précis mais quelque peu commun
(MySQL, via PDO), les différents cheminements des objets dans la jungle que représente #Zend_Db.
Ce composant traite en effet plusieurs classes et plusieurs manières d'aborder la base de données. Il y a les couches
d'accès, les aides aux requêtes, les conteneurs de résultats (Rows), les jeux de résultats (Rowsets) pour le TDG
( Table Data Gateway) et la gestion des relations entre les tables.

Il est donc possible d'exécuter des requêtes simples, manuelles, de bien des façons différentes, ou alors de traiter
des requêtes préparées, des transactions ; si bien qu'on a tendance à être un peu perdu au début et que des coups
d'oeil fréquents à l'API s'imposent.
Je vais donc m'efforcer de détailler le coeur du composant, le parcours des classes et les méthodes résultantes sur
les objets instanciés.

#Zend_Db est composé d'un Adaptateur qui permet de se connecter à un type de base. Il est possible de passer par
PDO, ou non. Je conseille, lorsque c'est possible (PDO est activé dans les dernières distrib de PHP, mais les drivers
ne sont pas forcément présents ou activés sur les serveurs) d'utiliser PDO, car il s'agit de l'interface de branchement
de PHP (Php Data Object), et l'extension PDO demeure très puissante pour piloter un SGBD.
Seuls MySQLi, Oracle et DB2 sont disponibles hors PDO pour Zend_Framework, mais il existe des bibliothèques
(non officielles) pour d'autres SGBD.
De notre coté, nous parlerons de MySQL via PDO, vous devez donc posséder l'extension PDO activée sur votre
serveur, mais c'est la configuration courante de PHP depuis PHP5.1.
Vous devez aussi posséder le driver PDO de votre SGBD, je vous renvoie à la doc sur PDO pour plus d'info : certains
drivers sont disponibles dans les binaires PHP depuis PHP 5.1, d'autres sont téléchargeables sur PECL. Nous ne
traiterons donc ici que de MySQL sous PDO ; de ce fait, il faudra activer le driver PDO_MYSQL dans php.ini si vous
êtes sous Windows. Sous Linux, il faudra prendre soin de compiler PHP avec PDO et le driver Mysql. Il est conseillé
aussi à ceux qui ne sont pas à l'aise avec PDO, de s'y mettre :-) Un petit tour sur la doc officielle, par exemple...
Chaque SGBD possède sa propre couche d'abstraction, mais la manipulation de #Zend_Db est quasiment la même,
quelque soit le SGBD utilisé.

Le système complexe de #Zend_Db est basé sur de multiples héritages de méthodes. Ainsi, lorsqu'on veut se
connecter via un driver, cela crée un objet qui hérite de méthodes générales, puis les méthodes propres au driver
sont rajoutées ou bien redéfinissent les méthodes générales. Il en résulte donc tout un arsenal de méthodes
disponibles. Tous les résultats (Statements) sont gérés par Zend_Db_Statement, les résultats PDO le sont par
Zend_Db_Statement_PDO, là encore, via la POO, tout est redéfini, factorisé et hérité. Les Exceptions envoyées sont
des instances de Zend_Db_Adapter_Exception
Notez qu'un profiler, Zend_Db_Profiler, est aussi de la partie et permet de faire du profiling sur sa base. Le profiling
est l'action d'écouter l'état de sa base lors de requêtes et notamment de déterminer le temps d'exécution de chaque
requête, le nombre de requêtes effectuées, et ainsi d'optimiser au mieux l'interfaçage de votre application avec votre
base de données, de manière à faire en sorte que celle-ci ne devienne pas un goulot d'étranglement pour votre
application. Nous ne détaillerons pas le profiling dans ce tutoriel.

Les adaptateurs qui composent #Zend_DB utilisent le profiler Zend_Db_Profiler en


interne. Celui-ci n'est pas activé par défaut car il consomme un peu de ressources pour
ses calculs. L'activer est très simple.
Un autre objet Zend_Db_Profiler_Firebug existe aussi : il envoie ses résultats dans
FireBug.

#Zend_Db est le composant principal, il est composé de :


• Zend_Db
• Zend_Db_Adapter
• (Zend_Db_Profiler) non traité

- 28 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

• Zend_Db_Select
• Zend_Db_Expr
• (Zend_Db_Statement) peu utilisé de manière directe
• Zend_Db_Table(_Abstract)
• Zend_Db_Table_Row(_Abstract)
• Zend_Db_Table_Rowset(_Abstract)
• Zend_Db_Table_Select

Zend_Db ne comporte qu'une méthode : une fabrique d'objets de connexion Factory(). Le design Pattern
AbstractFactory est utilisé, il est très classique et très simple.

Utilisation de la fabrique de Zend_Db pour créer un adaptateur


<?php
$params = array ('host' => '127.0.0.1',
'username' => 'developper',
'password' => 'mypassword',
'dbname' => 'myapp');
$db = Zend_Db::factory('PDO_MYSQL', $params);
?>

$db est donc votre objet de plus bas niveau d'interfaçage avec votre base. Vous verrez par la suite que nous
n'utiliserons quasiment plus que des objets de plus haut niveau, mais il faut aborder les étapes dans l'ordre ;-)

Utilisation de la fabrique de Zend_Db conjointement avec un objet Zend_Config


<?php
$config = new Zend_Config_Ini('config.ini');
$db = Zend_Db::factory($config->database);

config.ini
[app]
database.adapter = pdo_mysql
database.params.host = 127.0.0.1
database.params.username = developper
database.params.password = mypassword
database.params.dbname = myapp

Voyez comme Zend_Db joue avec Zend_Config. Très souvent dans ZendFramework, un objet Zend_Config pourra
être passé à un autre objet.
$db est donc un objet qui, au passage, s'est vu doté des méthodes de Zend_Db_Adapter_Abstract, puis
Zend_Db_Adapter_Pdo_Abstract, pour finir en une instance de la classe Zend_Db_Adapter_Pdo_Mysql. Voyez
comme l'architecture interne de ZF a été développée de manière souple et adaptable : nous passons sous 3 couches
pour obtenir un objet connexion de bas niveau. Il faut dire qu'il existe un tas de SGBD différents, et ceux-ci ont la
fâcheuse tendance à différer quelque peu au niveau des méthodes des drivers.
ZF fait donc son boulot comme il faut : le développeur n'a que faire de ce qu'il se passe dessous.

Attention toutefois, lors de la création de l'objet $db, la fabrique ne teste pas si la connexion a réussi. La connexion
ne s'effectue que lors de l'appel d'une requête, ce qui peut être gênant. En général, on aime bien lorsqu'on crée une
connexion, la tester. Et ne pas attendre de balancer une requête pour s'apercevoir que la base n'est pas disponible,
que l'on n'a mal orthographié ses identifiants, que l'on n'a pas les bons droits... Autant tout de suite le savoir.
Nous avons pour cela sous la main une méthode héritée de Zend_Db_Adapter_Abstract, getConnection(). Elle
effectue une connexion, puis retourne l'identifiant de connexion PDO (dont nous n'avons pas besoin en réalité)

Rappel de sécurité : il faut stocker ses identifiants d'accès aux bases de données dans
des fichiers HORS de la racine web.

Essai explicite de la connection


<?php
// $params = array(...);

- 29 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Essai explicite de la connection


try {
$db = Zend_Db::factory('PDO_MYSQL', $params);
$db->getConnection();
} catch (Zend_Db_Adapter_Exception $e){
echo $e->getMessage();
}

Ici, nous capturons et affichons une éventuelle Exception provenant de l'adaptateur générique. Nous pourrions par
exemple voir :
SQLSTATE[28000] [1045] Access denied for user 'developer'@'localhost' (using password: YES)
ou tout autre message nous signalant un problème (driver PDO introuvable, extension PDO non compilée, etc.)

Je ne détaille pas le schéma de la base de données, ca sera fait plus tard. Les requêtes
sont élémentaires, et ne concernent ici qu'une seule table.
Ce qu'il faut noter, ce sont les méthodes utilisées, et la présentation des résultats. Notre
connecteur étant configuré, exécutons une requête simple :

IX-B - Requêtes simples

query() prépare une requête et l'exécute pour retourner un résultat issu de Zend_Db_Statement, dans notre cas
c'est un Zend_Db_Statement_PDO :

Utilisation de la méthode query() pour requêter un SGBD


<?php
// ... configuration de $db comme vu avant ...
$result = $db->query('SELECT num, nom FROM membres');

$result est un objet de la classe Zend_Db_Statement_PDO. Si vous utilisiez PDO auparavant, vous ne serez pas
déboussolé ; si ce n'est pas le cas, je vous conseille quand même un petit tour sur le manuel PHP, rubrique PDO.
Ceci au regard de la suite de ce tutoriel, concernant notamment les différents 'fetch_modes' (modes de captures de
résultat) qu'offre PDO.
Dans le cadre de l'utilisation de ZF, il n'est en théorie pas nécessaire de toucher aux fetch_modes de PDO, car c'est
ZF qui va s'en charger quand ca sera nécessaire. Vous pouvez toutefois spécifier un mode de capture via la méthode
setFetchMode() sur l'objet $db. Les modes PDO suivants sont acceptés : PDO::FETCH_{LAZY - ASSOC - NAMED
- BOTH - NUM - OBJ}, le reste ne l'est pas : ceci est un choix de l'équipe de développement car le Framework
risque de ne pas être compatible avec. Leur utilisation lèvera donc systématiquement une exception, il est conseillé
de ne pas se soucier du tout des fetch_mode bas niveau de PDO, et d'utiliser plutôt les méthodes de capture que
Zend Framework propose : c'est lui qui va piloter PDO pour vous, si vous utilisez un cas très particulier, vous pouvez
toujours redéfinir des méthodes et créer votre propre interfaçage avec le SGBD.

Nous pouvons appliquer la méthode fetchAll() sur le résultat :

Utilisation de méthodes de Zend_Db_Statement


<?php
$rows = $result->fetchAll();
?>

$rows est de la forme :


array(2) {
[0] => array(2) {
["num"] => string(1) "1"
["nom"] => string(3) "Foo"
}
[1] => array(2) {
["num"] => string(1) "2"
["nom"] => string(3) "Bar"
}
}

- 30 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

A peu près toutes les méthodes de la classe PDOStatement sont applicables sur les résultats, fetchObject(), fetch(),
fetchColumn()...
Ce genre de requête étant rare, voyons une requête avec un WHERE, échappé comme il se doit :

<?php
$name = "foo's name"; // notez la simple quote
$sql = $db->quoteInto('SELECT * FROM members WHERE name = ?', $name);
//$sql est la requête échappée, c'est un string, on peut l'echo
$result = $db->query($sql);
$data = $result->fetchAll();

La méthode quoteInto() s'applique sur l'objet de bas niveau $db et retourne la requête (non le résultat) en échappant
les caractères avec la méthode propre au SGBD utilisé, nul besoin de s'en soucier.
$sql = "SELECT * FROM members WHERE name = 'foo\'s name'";
Pour echapper un simple terme, utilisez quote():

<?php
$name = $db->quote("l'echappement"); // l\'echappement

quoteIdentifier() va servir à echapper un nom de colonne, ou de table, ayant une valeur ambigue pour le SGBD,
comme "order", ou "select"
quoteColumnAs() fait la même chose, mais en spécifiant un alias SQL :

<?php
$order = $db->quoteIdentifiers("order"); // `order`
$column = $db->quoteColumnAs("order", "o"); // `order` AS `o`

Voyez la backquote ajoutée. C'est le caractère d'échappement spécial de Mysql pour sa syntaxe. Pour obtenir sa
valeur, getQuoteIdentifierSymbol() est là.
Il est de même possible, et c'est plus courant, d'envoyer une requête directement via query(), en envoyant plusieurs
paramètres :

<?php
$name = "foo's name";

$result = $db-
>query('SELECT * FROM membres WHERE nom = :nom AND country = :pays', array('nom'=>'Julien', 'pays'=>'France'));
$data = $result->fetchAll();
?>

Les données sont automatiquement échappées ici.


query() prépare la requête puis l'exécute pour retourner un jeu de résultats. Dans le cas où l'on souhaite conserver
sa requête et profiter pleinement du mécanisme des requêtes préparées, il est possible de préparer ses requêtes
manuellement avec prepare() et ses amies, adeptes de PDO ou de mysqli, c'est le même mécanisme :
Ici la requête est préparée, mais utilisée tout de suite, ceci est donc exactement équivalent à query(), qui prépare
ses requêtes avant des les envoyer :

<?php
$sql = $db->prepare('SELECT * FROM membres WHERE nom = :name');
$sql->bindValue('name', 'Estelle');
$sql->execute();

$result = $sql->fetchAll();

IX-C - Récupération de résultats

Bon, ceci est un peu long, c'est décomposé en 2/3 étapes : on prépare optionnellement la requête, on l'exécute et
on capture le jeu de résultats sur lequel nous appliquons une méthode de récupération (fetch***).

- 31 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Il est possible de relier les 4 étapes en 1 seule, et ce via 6 méthodes qui vont toutes préparer, échapper les paramètres,
exécuter la requête et récupérer le jeu de résultats. La seule différence entre elles est la manière de récupérer ce
jeu de résultats.
Elles exécutent directement une requête et retournent un jeu de résultats formaté de manière concrète. Selon la
situation, on choisira une méthode plutôt que l'autre et elles sont toutes adaptées à des situations courantes du
développement web.
Ces méthodes s'utilisent donc directement avec l'objet $db.
Imaginons une table qui n'aurait que 2 enregistrements. Il est important ici de noter la forme du résultat, le nombre
de dimensions des tableaux de retour, les clés et les valeurs :

fetchAll() renvoie tous les résultats sous forme d'un tableau à 2 dimensions :

<?php
$result = $db-
>fetchAll('SELECT id, name FROM membres WHERE country = :country', array('country'=>'France');
?>
// $result contient :
array(2) {
[0] => array(2) {
["id"] => string(1) "1"
["name"] => string(3) "Foo"
}
[1] => array(2) {
["id"] => string(1) "2"
["name"] => string(3) "Bar"
}
}

fetchRow() renvoie le premier résultat trouvé sous forme d'un tableau à 1 dimension :

<?php
$result = $db-
>fetchRow('SELECT id, name FROM membres WHERE country = :country', array('country'=>'France');
?>

array(2) {
["id"] => string(1) "1"
["name"] => string(3) "Foo"
}

Notez qu'on ne peut plus accéder aux résultats suivants après, utilisez cette méthode lorsque vous savez pertinament
qu'un seul résultat vous sera retourné (recherche par clé primaire).
fetchOne() renvoie la valeur du premier résultat trouvé :

<?php
$result = $db-
>fetchOne('SELECT id, name FROM membres WHERE country = :country', array('country'=>'France');
?>

string(1) : 1

1 est le numéro id du premier membre dont le pays est 'France'. Il est donc inutile de sélectionner plusieurs champs
avec fetchOne(), seul le premier champ du premier résultat est retourné, il en résulterait un gaspillage de ressources.

fetchCol() renvoie tous les résultats sous forme de tableau à 1 dimension ne comportant que les valeurs de la
première colonne :

<?php
$result = $db-
>fetchCol('SELECT id, name FROM membres WHERE country = :country', array('country'=>'France');
?>

array(2) {

- 32 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

[0] => string(1) "1"


[1] => string(1) "2"
}

"1" et "2" sont les id des membres dont le pays est 'France'. Ici aussi, inutile de vouloir sélectionner plusieurs colonnes,
seule la première est renvoyée.

fetchAssoc() renvoie tous les résultats sous forme d'un tableau à 2 dimensions dont la clé de dimension 1 est le
résultat du premier champ :

<?php
$result = $db-
>fetchAssoc('SELECT id, name FROM membres WHERE country = :country', array('country'=>'France'));
?>
// $result contient :
array(2) {
[1] => array(2) {
["id"] => string(1) "1"
["name"] => string(3) "Foo"
}
[2] => array(2) {
["id"] => string(1) "3"
["name"] => string(3) "Bar"
}
}

Presque identique à fetchAll(), fetchAssoc() crée un tableau associatif. Changeons un peu la requête :

<?php
$result = $db-
>fetchAssoc('SELECT name, id, country FROM membres WHERE country = :country', array('country'=>'France');
?>
// $result contient :
array(2) {
["Foo"] => array(3) {
["name"] => string(3) "Foo"
["id"] => string(1) "1"
["country"] => string(6) "france"
}
["Bar"] => array(3) {
["name"] => string(3) "Bar"
["id"] => string(1) "2"
["country"] => string(6) "france"
}
}

Enfin, fetchPairs() renvoie tous les résultats sous forme d'un tableau à 1 dimension dont la clé est la donnée de la
première colonne et la valeur est la donnée de la 2ème colonne :

<?php
$result = $db-
>fetchPairs('SELECT id, name FROM membres WHERE country = :country', array('country'=>'France');
?>
// $result contient :
array(2) {
[1] => string(3) "Foo"
[2] => string(3) "Bar"
}

Il est inutile de vouloir sélectionner plus de 2 colonnes. La première colonne donnant la clé du tableau dans le résultat,
il faut que ca soit une clé primaire de table, sinon le tableau va être écrasé par les données suivantes satisfaisant
la requête :

<?php

- 33 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

$result = $db-
>fetchPairs('SELECT country, name FROM membres WHERE country = :country', array('country'=>'France');
?>
// $result contient :
array(2) {
["France"] => string(3) "Bar" // écrasement du résultat pour name='Foo'
}

Ces 6 méthodes s'adaptent donc à des situations du web et vous permettent de choisir la manière dont le résultat
doit vous être présenté.
Si aucun résultat ne correspond à la requête, le retour est une identité à FALSE, c'est-à-dire qu'en castant le retour
en bool, on obtient FALSE. Ainsi, si une chaîne doit être retournée, le booléen FALSE est renvoyé à sa place, mais si
un tableau multidimensionnel doit être retourné, un tableau vide à une dimension est retourné (ce qui est équivalent
en identité à FALSE).

Mémorisez bien : Ces 6 méthodes executent ET récupèrent le résultat (chacune à sa


manière), en une seule opération. Si vous voulez décomposer, pour pouvoir "fetcher" vous
même, dans un while, par exemple, utilisez alors la syntaxe vue plus haut, à base de
query().

IX-D - Insert, Update et Delete

Nous allons désormais tenter l'insertion, la mise à jour ou l'effacement de données. Nous allons commencer à écrire
de moins en moins de syntaxe SQL et, pour rester clair, les noms des variables sont explicites. De cette manière,
je n'écris pas la requête SQL réelle mais elle va se deviner très facilement, même si elle peut paraitre idiote dans
sa sémantique SQL, c'est pour le principe :

insert() insère des données dans une table, elle prend 2 paramètres :

insertion de données
<?php
$rows = array (
'name' => 'David',
'country' => 'England',
);

$table = 'membres';

$affectedRows = $db->insert($table, $rows);


$idInsere = $db->lastInsertId();
?>

Les données sont échappées automatiquement. La valeur de retour est le nombre de lignes affectées.
De même, lastInsertId() retourne le numéro identifiant de la dernière insertion, si celle-ci est une colonne auto
incrémentée, comme c'est le cas très souvent des clé primaires, souvent aussi nommées "id".

update() met à jour des données dans une table, elle prend 3 paramètres :

Mise à jour de données


<?php
$set = array (
'country' => 'france',
);

$table = 'membres';

$where[] = 'name = David';


$where[] = 'country = England';

$affectedRows = $db->update($table, $set, $where);


?>

- 34 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Les données du SET sont échappées automatiquement, mais c'est à vous d'échapper le WHERE si c'est nécessaire.
La valeur de retour est le nombre de lignes affectées. la clause WHERE est mise dans un tableau, si il y a plusieurs
arguments comme c'est le cas ici, un AND est automatiquement effectué.

Enfin, delete() supprime des enregistrements dans une table, elle prend 2 paramètres :

Suppression de données
<?php
$table = 'membres';

$where = $db->quoteInto('name = ?', 'Foo');

$deletedRows = $db->delete($table, $where);


?>

Vous devez échapper le WHERE si nécessaire. La valeur de retour est le nombre de lignes affectées (supprimées
ici). N'oubliez pas la clause WHERE, sinon toutes les données seront supprimées ;-) Ca reste du SQL n'est-ce pas.

describetable() effectue une syntaxe SQL similaire et retourne un tableau.


listTables() retourne un tableau avec la liste des tables.

Autres fonctionnalités
<?php
$db->describeTable('test');
/*
array(2) {
["id"] => array(14) {
["SCHEMA_NAME"] => NULL
["TABLE_NAME"] => string(4) "test"
["COLUMN_NAME"] => string(2) "id"
["COLUMN_POSITION"] => int(1)
["DATA_TYPE"] => string(9) "mediumint"
["DEFAULT"] => NULL
["NULLABLE"] => bool(false)
["LENGTH"] => NULL
["SCALE"] => NULL
["PRECISION"] => NULL
["UNSIGNED"] => NULL
["PRIMARY"] => bool(true)
["PRIMARY_POSITION"] => int(1)
["IDENTITY"] => bool(true)
}
... ...
*/
$db->listTables();
/*
array(5) {
[0] => string(8) "messages"
[1] => string(7) "membres"
[2] => string(4) "test"
[3] => string(5) "test2"
}
*/?>

IX-E - Transactions

Un petit mot sur la gestion des transactions, tout de même. Ici on est en surcouche de PDO, donc toutes ses
caractéristiques s'y appliquent.
Les transactions existent quel que soit le SGBD piloté, car même s'il ne les supporte pas, lui, ou le moteur de stockage
de la table intérrogée; PDO les émulera. C'est une des caractéristiques sympathiques de PDO.
D'origine, le mode 'autocommit' est activé dans l'adaptateur, qui transmet donc cet état à PDO ; cela signifie que toute
requête envoyée au SGBD est systématiquement validée. Bien entendu, pour utiliser les transactions, votre SGBD
et votre moteur de stockage doivent pouvoir les gérer. C'est le cas d'INNODB pour MySQL mais pas de MyISAM.

- 35 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Si vous utilisez MyIsam, tout se passera comme si les transactions n'existaient pas, mais pas de message d'erreur :
PDO émule les méthodes relatives aux transactions, il les fait exister mais elles ne peuvent fonctionner que si coté
SGBD, tout est OK
beginTransaction() Désactive l'autocommit et démarre une transaction
commit() Valide la transaction courante et réactive l'autocommit
rollback() Annule la transaction courante et réactive l'autocommit
Très simple, un exemple :

Exemple d'utilisation des transactions


<?php
$rows = array (
'name' => 'John',
'country' => 'USA',
);

$table = 'membres';
$db->beginTransaction();
try {
$db->insert($table, $rows);
$db->commit();
}catch(Zend_DB_Adapter_Exception $e)
{
$db->rollback();
echo $e->getMessage();
}
?>

Notez que les noms des 3 méthodes proposées par Zend Framework concernant les
transactions sont les mêmes que celles proposées par PDO dans PHP.

IX-F - Sélections

Et maintenant, nous allons voir grâce à Zend_Db_Select comment effectuer des requêtes complexes (jointures,
prédicats, etc.) sans écrire la moindre syntaxe SQL. Cependant, la rigueur reste de mise. Zend_Db_Select va
permettre d'écrire la syntaxe d'une requête SQL, manière objets, en s'affranchissant du SGBD utilisé en dessous :
l'adapter va convertir la syntaxe objet en une syntaxe compréhensible pour le SGBD (car je rappelle que tous les
SGBD ne respectent pas forcément la norme SQL, et une syntaxe SQL [manuelle] peut donner lieu a des erreurs
sur un SGBD, mais pas sur un autre).
En plus d'apporter cet avantage, lorsqu'il s'agit d'écrire des requêtes à la volée, vous allez voir que c'est bien plus
simple de manipuler un objet, avec toutes ses méthodes pratiques.
En effet, Zend_Db_Select est dite à interface 'fluent' : chaque méthode exprime de par son orthographe, ce qu'elle
fait. Et chaque méthode retourne $this, donc on peut flécher et chainer les méthodes les unes à la suite des autres.
Essayons un "SELECT id, name FROM membres WHERE country = 'usa' ORDER BY name ASC LIMIT 2" (syntaxe
SQL pour MySQL - notre cas donc -).

select() permet d'écrire une requête de sélection complète, en utilisant un pourcentage infirme de langage SQL. Elle
permet aussi l'échappement automatique des caractères, elle gère tous les SGBD que gère l'Adapter, elle va traduire
notre requête façon objet en requête SQL compatible avec le SGBD interrogé via l'Adapter.

Exemple de requêtage de manière objet avec Zend_Db_Select


<?php
$select = $db->select(); // récupération de notre objet de Zend_Db_Select
// équivalent à $select = new Zend_Db_Select($db);
$select->from('membres',array('id','name'));
$select->where('country = ?','usa');
$select->order('name ASC');
$select->limit(2);

$result = $db->fetchAll($select);
?>

- 36 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Bien entendu, les jointures sont possibles. En voici un exemple très simple (2 tables) :

Zend_Db_Select : jointures
<?php
$select = $db->select();
$select->from('messages','message_txt')
->join('membres','membres.id=messages.membreid','name')
->where('messages.id = :idmess');

$result = $db->fetchAll($select, array('idmess'=>2) );


?>

"SELECT message_txt, name FROM messages JOIN membres ON membres.id=messages.membreid WHERE


messages.id = '2'" C'est la traduction MySQL.
C'est tout simple, j'ai changé aussi la manière d'interroger la base avec un binding, comme nous l'avons vu aupravant
(le ':idmess'). C'est le cas le plus pratique, car il permet de passer la valeur lors de l'exécution de la requête et non
dans son corps.
On peut effectuer tout type de jointure, joinLeft(), joinRight(), joinFull(), joinCross(), joinNatural(). Les jointures
par défaut récupèrent *, mais on peut bien entendu les monter comme on le souhaite, de manière très propre. La
doc officielle vous y aidera.
Les valeurs externes passées dans un select() sont toutes échappées automatiquement, pour éviter l'échappement
automatique, la classe Zend_Db_Expr permet de former une variable de clause non echappée.
Pour effectuer un SELECT FOR UPDATE (spécifique Mysql), la méthode forUpdate() est là.

La méthode select() crée un objet Zend_Db_Select qui retourne lui-même, à chaque modification, ce qui nous permet
d'enchaîner les actions par de multiples -> se succédant.
Zend_Db_Select possède de même une méthode magique __toString(), qui permet d'afficher la requête textuelle
traduite par l'Adapter, par un simple <?php echo $select;?>. Utilisez-la pour bien voir comment la requête a été traduite
(tout est échappé, les colonnes sont traduites par 'table.colonne'...). Vous pouvez aussi demander à visualiser un
élément précis de la requête.

getPart() retourne une partie précise de la requête, demandée via une constante de classe.

<?php
$select = $db->select();
$select->from('messages')
->order('id');

$orderClause = $select->getPart(Zend_Db_Select::ORDER); // 'id'


?>

reset() efface une partie précise de la requête, demandée via une constante de classe, exactement comme la syntaxe
de getPart()

Un petit mot sur limitPage() : permet d'effectuer une pagination, avec sélection automatique de la tranche de résultat :

<?php
$select = $db->select();
$select->from('membres', '*');
$select->order('id DESC');

$select->limitPage(3, 10);
$result = $db->fetchCol($select);
?>

J'ai utilisé fetchCol() pour vous rappeler que vous pouvez utiliser n'importe quelle méthode de retour de résultat déjà
vu. Ici, limitPage() demande sur la liste de tous les membres, organisée par id inverse, de sélectionner pile la bonne
partie pour afficher la page 3, en ayant chaque contenant 10 résultats.
Zend_Db_Select possède encore having(), group(), distinct(), forUpdate()...

- 37 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

IX-G - Passerelle vers les tables

IX-G-1 - Généralites

Pour terminer, nous allons passer à la gestion de la passerelle vers les tables dont l'utilisation se rapproche de celle de
RubyOnRails ou de CakePHP. L'accès aux données et aux tables est une partie très importante dans la modélisation
d'applications n-tiers.
Dans Zend Framework, une couche d'accès aux tables et aux enregistrements de celles-ci est prévue par défaut,
mais toutes les classes mères sont abstraites. On pourra donc personnaliser entièrement les processus d'accès, si
celui par défaut de ZF ne nous convient pas.
L'accès aux tables et à leurs enregistrement dans ZendFramework est matérialisé par
Zend_Db_Table / Zend_Db_Table_Row / Zend_Db_Table_Rowset, ils héritent de Zend_Db_Table_Abstract /
Zend_Db_Table_Row_Abstract / Zend_Db_Table_Rowset_Abstract.

Zend_Db_Table est la classe qui permet de faire correspondre une table ou vue du SGBD, à une classe : elle
implémente le design pattern Table Data Gateway, elle effectue les requêtes personnalisées : elle représente une
table de la base de données (il y aura donc [au moins] autant de classes que de tables à mapper).
Ce schéma est modifiable. Toute table doit obligatoirement posséder une clé primaire sur au moins une colonne de
table, Zend Framework ne sait pas traiter les tables qui n'ont pas de clé primaire lorsque vous utilisez la passerelle
Zend_Db_Table.
En général, une table d'entités comporte une clé primaire, sur une colonne (il est de coutume de l'appeler 'id') qui
est auto incrémentée. En revanche, les tables d'association peuvent avoir une clé primaire sur une colonne, ou sur
plusieurs, selon la cardinalité.
Un membre ne peut emprunter un même livre qu'une seule fois : dans la table emprunts, la clé primaire sera composée
de la clé primaire de la table membres et de la primaire clé de la table livres. On s'assure ainsi qu'on ne peut enregistrer
2 fois le même membre, empruntant un même livre.
Mais s'il existe plusieurs exemplaires de ce livre, alors on aura une clé primaire dans la table emprunts unique, en
plus des 2 clés étrangères membres et livres. Un membre pourra donc emprunter plusieurs fois le même (exemplaire)
du livre, chaque emprunt étant distingué par une clé unique sur une colonne.
Il s'agit des concepts connus de OneToMany (1 à plusieurs) - OneToOne (un vers un seul) - et ManyToMany (plusieurs
à plusieurs).
Zend_Db_Table représente une table (ou une vue) du SGBD, elle sert des objets Zend_Db_Table_Row. Ce sont les
résultats mappés (propriétés = colonne de table). On les appellera les résultats. Ils implémentent le design pattern
Row Data Gateway
Zend_Db_Table_Rowset est un conteneur de résultats pourvu d'un itérateur, on appellera cela un jeu de résultats.
C'est un design pattern connu aussi : le dataset.

Un résultat ne comporte pas d'accesseurs : on modifie une propriété directement par affectation. Par contre des
méthodes existent pour récupérer des références de tables, lorsque les tables sont liées entre elles par une intégrité
référentielle (clés étrangères).
La couche d'accès aux données agit donc en mode LazyLoading, elle ne charge pas automatiquement les collections
dépendantes, nous allons voir ça :
Voici l'exemple utilisé ici, il est très simple,

IX-G-2 - L'exemple

CREATE TABLE `livres` (


`isbn` varchar(14) NOT NULL,
`titre` varchar(70) NOT NULL,
`auteur` varchar(70) NOT NULL,
PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `livres` VALUES ('978-2212116762', 'Best Practices PHP5', 'Guillaume Ponçon');
INSERT INTO `livres` VALUES ('978-2212120042', 'PHP5 Avancé', 'Cyril Pierre De Geyer');
INSERT INTO `livres` VALUES
('978-2841773381', 'Pratique de MySQL et PHP (Broché) ', 'Philippe Rigaux');

- 38 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

CREATE TABLE `membres` (


`num` smallint(5) NOT NULL auto_increment,
`nom` varchar(50) NOT NULL,
`date_naissance` date NOT NULL,
PRIMARY KEY (`num`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;

INSERT INTO `membres` VALUES (1, 'julien', '1982-11-01');


INSERT INTO `membres` VALUES (2, 'Benoit', '1984-02-08');
INSERT INTO `membres` VALUES (3, 'Estelle', '1977-08-07');

CREATE TABLE `emprunts` (


`membre` smallint(5) NOT NULL,
`livre` varchar(14) NOT NULL,
`date` date NOT NULL,
PRIMARY KEY (`livre`,`membre`),
KEY `membre` (`membre`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;

INSERT INTO `emprunts` VALUES (1, 1, '2007-03-24');


INSERT INTO `emprunts` VALUES (2, 2, '2007-03-08');
INSERT INTO `emprunts` VALUES (3, 1, '2007-04-02');

ALTER TABLE `emprunts`


ADD CONSTRAINT `emprunts_ibfk_1` FOREIGN KEY (`membre`) REFERENCES `membres` (`num`) ON DELETE
CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `emprunts_ibfk_2` FOREIGN KEY (`livre`) REFERENCES `livres` (`isbn`) ON DELETE
CASCADE ON UPDATE CASCADE;

Un exemple simple, comme celui décrit juste avant. Membres - Livres - Emprunts.
Emprunts possède une clé primaire sur 2 colonnes, un livre peut être emprunté plusieurs fois (il a des exemplaires),
mais un même membre ne peut pas posséder plusieurs exemplaires d'un même livre).
La table emprunts répond donc à la question "qui a emprunté quoi, et quand ?"
Voici les fichiers de passerelle des tables Membres et Livres, ils étendent Zend_Db_Table_Abstract. Si nous avions
voulu retoucher le mécanisme interne de la passerelle, nous aurions étendu Zend_Db_Table, dans laquelle nous
aurions modifié la logique (ce qui est très pratique pour personnaliser totalement sa couche d'accès aux données).
On placera les fichiers des passerelles (qui comportent donc des classes), qui ne sont autre que notre logique
métier, dans un répertoire /modele qui se trouvera hors racine web, car il n'a pas besoin de s'y trouver. On rajoutera
simplement son chemin dans l'include_path, au début des scripts (en attendant un système automatisé, prévu; vous
pouvez toujours créer le votre).
Comme je vous l'ai dit, je fonctionne dans cet article avec l'autoload activé, pour économiser des lignes. Toutes les
classes sont supposées incluses et disponibles.

IX-G-3 - Définition des passerelles

modele/Membres.php
<?php
class Membres extends Zend_Db_Table_Abstract
{
protected $_name = 'Membres';
protected $_primary = 'num';

public function findByNom($nom)


{
$where = $this->getAdapter()->quoteInto('nom = ?',(string)$nom);
return $this->fetchRow($where);

- 39 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

modele/Membres.php
}
}

modele/Livres.php
<?php
class Livres extends Zend_Db_Table_Abstract
{
protected $_name = 'Livres';
protected $_primary = 'isbn';
}

La variable $_name représente le nom de la table SQL, et la variable $_primary représente la clé primaire (c'est
un array si la clé est multi-colonnes).
Ici, je les ai précisés, mais le mécanisme de Zend Framework permet de s'en affranchir. Si on ne spécifie pas la clé
primaire, ZF va utiliser DESCRIBE TABLE pour se la chercher lui-même. Si le nom de la table n'est pas précisé, alors
le nom de la classe sera utilisé (Cette information est mise en cache pour soulager le SGBD).
Je conseille, ne serait-ce que pour y voir plus clair, de toujours spécifier la config de sa table dans la définition de sa
classe, de plus, on peut vouloir nommer sa classe, et donc son fichier de classe, différement du nom de sa table ;-).
$_sequence est un booléen à true par défaut dans l'abstraction. Il signifie que nos clés primaires sont en auto-
incrémentation, on ne le spécifie donc pas.Il peut contenir aussi un string pour agir sur les séquences, une notion
d'unicité de clé qu'utilise PostgreSQL ou encore DB2
Le système de passerelle vers les tables de Zend Framework ne fonctionne qu'avec des tables ayant une clé
primaire(ou une séquence)

modele/Emprunts.php
<?php
class Emprunts extends Zend_Db_Table_Abstract
{
protected $_name = 'emprunts';
protected $_primary = array('membre', 'livre');

protected $_referenceMap = array(


'emprunteur' => array( // le rôle UML de Membres vers Emprunts
'columns' => 'membre',// la colonne de emprunts est membre
'refTableClass' => 'Membres', // la classe des membres est 'Membres'
'refColumns' => 'num' // la clé primaire de membres est num (clause facultative)
),
'locations' => array(
'columns' => 'livre',
'refTableClass' => 'Livres',
'refColumns' => 'isbn' //facultatif, il s'agit de la PK par défaut
));
}

$_referenceMap sert à lier les tables. Le principe est simple : toute table recevant une clé étrangère, doit remplir cet
attribut de classe pour préciser quelles tables, et quelles clés sont liées avec elle.

La clé primaire d'emprunts est sur 2 colonnes. C'est spécifié par un array.
La definition des références est très importante et indique la logique de notre base de données au Framework.
Pour les emprunts, dans la table emprunts, un membre est un emprunteur. C'est en fait le rôle UML qui relie les
2 classes dans un Diagrammes de Classes de Conception. On fait référence à la classe Membres, et la colonne
est num.
Il est possible qu'il y ait plusieurs colonnes (même si c'est rare). Lorsqu'on a plusieurs rôles, par exemple un membre
ème
est l'emprunteur d'un livre mais il pourrait aussi être son auteur. On aurait alors une 2 clé de membres dans
emprunts, et on aurait par exemple matérialisé le rôle par "Auteur" ; en plus du rôle "emprunteur". Il y a autant de
rôles UML que de dépendances entre les tables.
Si la clé de référence est une clé sur plusieurs colonnes, on les spécifiera dans un array(), dans le même ordre
qu'elles ont été définies dans la classe parent.
Si on ne spécifie pas la clé de la table parente (refColumns), alors la clé primaire déclarée est prise par défaut.

- 40 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Je vous conseille quand même de spécifier la refColumns, je sais que les programmeurs sont paresseux (Lazy), mais
rien qu'en lisant la définition des références, on a tout de suite le schéma de la base et des classes en visuel. C'est
très agréable lorsqu'on reprend 1 an après le projet (même si on a commenté son code).

En plus de ceci, les classes passerelles des tables ont nécessairement besoin de notre adaptateur, rappelez vous
notre objet $db qui sert à manipuler la base au plus bas niveau. Forcément, les classes passerelles en ont besoin,
et plutôt que de le passer en paramètre aux objets à chaque fois, vu que nous n'utilisons qu'une seule base et une
seule connexion, nous allons dire à tout le système de passerelle que l'adaptateur par défaut est $db.
Dans le script principal, juste après avoir crée notre objet $db, nous le déclarons en tant qu'adaptateur par défaut
aux passerelles :

Déclaration de l'adaptateur par défaut aux passerelles


<?php
// $params = ...
try {
$db = Zend_Db::factory('PDO_MYSQL', $params);
$db->getConnection();
Zend_Db_Table::setDefaultAdapter($db);
} catch (Zend_Db_Adapter_Exception $e){
echo $e->getMessage();
}

Zend_Db_Table::setDefaultAdapter($db); ca parle bien. Dans le cas de plusieurs bases, il faut trimbaler les 2 objets
de connexion, mais des méthodes simplifient la tâche, ou alors redéfinissez une classe, tout reste possible.
findByNom() est une méthode que j'ai écrite moi-même, elle va permettre de chercher des membres par leur nom.
Regardez comment elle est écrite.
On récupère l'adaptateur, car c'est lui qui possède la méthode d'échappement quoteInto(). On échappe le paramètre,
casté au préalable en string, et on envoie ca à fetchRow(). L'échappement des paramètres n'est pas automatique,
nous devons donc utiliser les méthodes quote(), quoteInto() ou quoteIdentifier(), de l'adaptateur. Nous les avons
déjà vues.
Dans les classes métier, on définira donc toute la logique métier d'accès aux données (findByNom(), mais aussi des
méthodes du style findLivreLePlusRecent(), ou encore findPaniersVides()...), et c'est le contrôleur qui accèdera à
cette logique, dans un modèle MVC.

Outre les méthodes métier que nous pouvons écrire, il y en a déjà pas mal qui sont implémentées dans ZF. Ainsi,
sur des instances des classes Membres, Livres ou Emprunts :
find() recherche des enregistrements par clé primaire. Vous récupérez un jeu de résultats (Rowset) contenant les
enregistrements correspondants. Si on passe à find() un tableau, alors on cherche plusieurs enregistrements :

Recherche par clé primaire


<?php
// ... Définition de l'adaptateur et des classes passerelles ...
$membre = new Membres();
$membresTrouves = $membre->find(array(1,2));
?>

createRow() retourne un résultat vide, prêt à etre rempli ou utilisé.


fetchAll() exécute une requête personnalisée, retournant un jeu de résultats (on choisira la méthode fetchAll()
lorsqu'on sait que notre requête est susceptible de retourner plusieurs résultats).
fetchRow() exécute une requete personnalisée, retournant un résultat (on choisira la méthode fetchRow() lorsqu'on
sait que notre requête ne retourne qu'un seul résultat ; s'il y en a plusieurs, elle ne retournera alors que le premier
d'entre eux, attention).
getAdapter() retourne l'objet bas niveau de connexion attribué à la table.
info() retourne toute la configuration du mapping de la table, sur un array (clé primaire, colonnes, types de colonnes,
tables dépendantes...)
getDependantTables() nous rappelle les tables dépendantes de l'objet, que nous avons configurées au préalable,
il s'agit d'un tableau PHP.

- 41 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

insert() et update, sont identiques à celles déjà vues mais mappées sur la table en cours (on ne spécifie pas le
nom de la table, juste les paires colonne->valeur). select(), génère un objet Zend_Db_Table_Select, comparable à
Zend_Db_Select (déja vu plus haut), mais verrouillé sur la table dont il est issu.

Les méthodes fetchAll() et fetchRow() acceptent en paramètre un objet


Zend_Db_Table_Select, tout comme le font les méthodes de l'adaptateur. Cet objet sert
à diriger la requête, qui par défaut est de type "select *".

IX-G-4 - Les résultats (Row)

Nous venons de décrire les schémas de passerelle de nos tables ainsi que la plupart des méthodes applicables sur
les classes de définition étendant Zend_Db_Table_Abstract. Lorsque nous cherchons un résultat (fetch***), selon la
méthode utilisée, nous nous retrouverons avec soit une instance de Zend_Db_Table_Row (un résultat unique), soit
une instance de Zend_Db_Table_Rowset (un jeu de résultats uniques Zend_Db_Table_Row).
(Ces 2 conteneurs de données spéciaux sont redéfinissables de manière très simple, si le coeur vous en dit.)
Pour l'exemple, createRow() nous retourne un unique résultat, ce qui est logique ; en revanche, find() peut trouver
plusieurs résultats. La méthode find() retourne donc un jeu de résultats, fetchAll() retourne un jeu, tandis que
fetchRow() retourne un résultat. C'est très important car selon le type d'objet retourné, nous ne pourrons utiliser les
mêmes méthodes dessus. Si on nous retourne un jeu de résultat, on ne pourra pas appliquer les méthodes d'un
résultat dessus. Faites y attention, cela a tendance à embrouiller au début. Même si la logique est tout à fait de mise,
il faut la comprendre, lorsqu'on y est pas habitué.

Récupération, modification et sauvegarde d'un enregistrement de la table membres


<?php
// ...
$tableMembre = new Membres();
$julien = $tableMembre->findByNom('julien');
$julien->date_naissance = '1982-12-08';
$julien->save();
?>

Sur un résultat, toutes les colonnes SQL sont mappées en tant que propriétés de l'objet. Attention, en utilisant PDO,
le paramètre PDO::CASE_NATURAL lui est passé par Zend Framework. Cela signifie que les échanges entre le
SGBD et l'OS se font en casse naturelle. Ainsi sous Windows, et concernant MySQL (je n'ai pas d'infos pour d'autres
SGBDs) la casse n'est pas existante, et une colonne appelé 'MACOLONNE', pourra être appelée via 'macolonne',
mais pas sous Linux, qui respecte la casse. Il est conseillé de toujours appeler ses noms de tables/colonnes, en
minuscules, pour éviter les ennuis de plateforme. Dans tous les cas, ZF utilise lui-même en interne la casse naturelle,
il est possible de changer le mode, les méthodes sont là, mais ça va troubler ZF, de manière générale, il vaut mieux
laisser ZF gérer les paramètres PDO pour nous. Nous n'utilisons pas PDO nous programmeurs mais bel et bien le
Framework qui, lui, utilise PDO comme il le souhaite (dans nos exemples, basés sur PDO)
Regardez, $julien->date_naissance signifie que j'accède à la colonne date_naissance du membre julien. Etant donné
qu'il n'y a pas d'accesseur (get(), set()), je la modifie directement et je sauvegarde le tout en base via la méthode
save().

Ainsi, sur un résultat, je peux aussi utiliser :


save() sauvegarde le résultat en base. Notez que si celui ci n'existe pas (issu d'un createRow(), par exemple) alors
une requête SQL "insert" sera utilisée, sinon "update" sera utilisé.
delete() efface (dans la base bien sûr) le résultat.
toArray() est très pratique et transforme le résultat en un tableau du type 'nom_de_la_colonne' => 'valeur-
correspondante'.
setFromArray() est du même style, elle attribue des valeurs à notre objet suivant un tableau passé en paramètre du
type 'nom_de_la_colonne' => 'valeur-correspondante'. Ceci est très utilisé lors de récupération de formulaires, pour
remplir rapidement un résultat et le sauvegarder.
D'autres méthodes find* existent, décrites plus bas dans la section gestion des relations.

- 42 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

IX-G-5 - Les jeux de résultats (Rowset)

Certaines méthodes, donc, renvoient un jeu de résultats. Un jeu de résultats (Zend_Db_Table_Rowset) est un simple
conteneur de résultats implémentant les interfaces SeekableIterator et Countable de la SPL. J'aurai aimé seekable
aussi, mais ca n'est pas le cas.
Bref il s'agit d'un conteneur de résultats, itérable via un foreach et comportant toutes les méthodes héritées de la SPL
(current(), count(), next()). Un genre de ResultSet, pour les habitués de Java ;-)

<?php
// ...
$tableLivres = new Livres();
$deuxLivres = $tableLivres->find(array('978-2212116762','978-2841773381'));
foreach ($deuxLivres AS $ceLivre){
echo $ceLivre->titre.'<br />';
}
?>

Ici je récupère les 2 livres par leur clé primaires (Isbn), et j'affiche leur titre. Si je ne veux qu'un enregistrement, je
procède comme suit :

<?php
// ...
$tableLivres = new Livres();
$leLivre = $tableLivres->find('978-2212116762')->current();
$leLivre->titre = 'Best Practices PHP 5';
$leLivre->save();
?>

Je suis obligé de spécifier que je veux dans le jeu de résultats le premier (et unique) résultat, en l'atteignant par
current(). Les autres méthodes d'accès proviennent de la SPL et d'Iterator.
En implémentant aussi Countable, j'ai accès à une méthode count() pour compter le nombre de résultats retournés
(attention, mon code n'a ici aucun sens sémantique, selectionner tous les résultats pour les compter est une aberration
pure coté SGBD, un simple count(*) étant suffisant) :

<?php
// ...
$tableLivres = new Livres();
$lesLivres = $tableLivres->fetchAll();
echo 'il y a actuellement '. $lesLivres->count() .' livres dans ma base de données';
?>

Une méthode toArray(), comparable à celle d'un résultat, est disponible sur un jeu de résultats. Elle renvoie un
tableau multidimensionnel de résultats :

<?php
// ...
$tableLivres = new Livres();
$tabLesLivres = $tableLivres->fetchAll()->toArray();
/* $tabLesLivres contient :
array(3) {
[0] => array(3) {
["isbn"] => string(14) "978-2212116762"
["titre"] => string(20) "Best Practices PHP 5"
["auteur"] => string(16) "Guillaume Ponçon"
}
[1] => array(3) {
["isbn"] => string(14) "978-2212120042"
["titre"] => string(11) "PHP5 Avanc"
["auteur"] => string(21) "Cyril Pierre De Geyer"
}
[2] => array(3) {
["isbn"] => string(14) "978-2841773381"
["titre"] => string(34) "Pratique de MySQL et PHP (Broché) "

- 43 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

["auteur"] => string(15) "Philippe Rigaux"


}
}
*/
?>

IX-G-6 - Gestion des dépendances

Nous allons nous pencher sur la gestion des dépendances sous ZF. Si les termes 'Lazy mode', et 'full mode' vous
disent quelque chose, sachez que ZF est lazy. Lors de la récupération d'un enregistrement, il est possible de faire
référence à ses parents ou à ses enfants. Ce n'est que lors de l'appel de la référence que le résultat est chargé.
Si par exemple je récupère un membre, je peux facilement afficher les titres des livres qu'il emprunte. Ou encore,
si j'ai un enregistrement représentant un 'emprunt', je peux retrouver, grâce à des méthodes spéciales, le membre
y faisant référence ou bien le livre.
Zend Framework tire toute sa puissance de PHP5 et, grâce aux méthodes magiques __get(), __set() et surtout
__call(), il va intercepter les appels de méthodes pour nous, de manière très intuitive. Je vais vous donner un exemple.
Avant toute chose, sachez que ces méthodes ne s'appliquent que sur un résultat. On ne peut pas les appliquer sur
des instances de jeu de résultats, ce qui est logique.
Je récupère un membre, et je veux lister tous les livres qu'il a emprunté :

<?php
// ...
$tblMembres = new Membres();
$monMembre = $tblMembres->find(1)->current();
$sesLivres = $monMembre->findLivresViaEmprunts();
foreach ($sesLivres AS $unDeSesLivres) {
echo $unDeSesLivres->titre.'<br />';
}

J'instancie ma table membres. Je récupère le membre n°=1, n'oubliez pas que le retour de find()
est systématiquement un jeu de résultats (Rowset), je dois donc passer par current() (méthode de
Zend_Db_Table_Rowset_Abstract) pour récupérer mon résultat.
Sur un résultat, je peux trouver ses dépendances par rôle. En effet, Zend_Db_Table_Row est doté d'une méthode
magique, qui va intercepter les appels du style find<tableClass>Via<IntersectionTableClass>().
$monMembre->findLivresViaEmprunts() retourne donc un jeu de résultats représentant tous les livres - empruntés
par monMembre. Le foreach est un itérateur, n'oubliez pas $sesLivres est un jeu de résultats : en itérant dessus,
on récupère des résultats représentatifs de livres. Sur ces résultats, nous avons accès aux propriétés de l'objet, ici
c'est titre qui nous intéresse.

find<tableClass>Via<IntersectionTableClass>() récupère un jeu de résultats dans un schéma à


cardinalités de type 'plusieurs à plusieurs'. C'est un raccourci de commodité de la méthode
findManyToManyRowset('tableClass','tableClass', 'role'). Pensez bien cardinalités dans un modèle conceptuel.
Un membre peut emprunter zéro ou plusieurs livres. Un livre peut être emprunté par zéro ou plusieurs membres
(on a dit qu'on raisonnait de cette manière, par 'exemplaires'). C'est une association plusieurs vers plusieurs, qui se
traduit par une table de liaison, 'emprunts'.
Si maintenant j'ai un livre, et que je veux lister toutes les dates auxquelles il a été emprunté, je procède comme suit :

<?php
// ...
$tblLivres = new Livres();
$monLivre = $tblLivres->find('978-2841773381')->current();
$sesEmprunts = $monLivre->findEmprunts();
foreach ($sesEmprunts AS $unDeSesEmprunts) {
echo $unDeSesEmprunts->date.'<br />';
}

find<tableClass>() récupère un jeu de résultats d'une table dépendante, dans un schéma '1 à plusieurs'. C'est
un raccourci de commodité de la méthode findDependentRowset('tableClass','role'). 1 livre peut avoir plusieurs
références dans la table emprunts, nous récupérons donc un jeu de résultats sur lequel nous itérons.

- 44 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Agissons enfin dans le dernier sens : nous avons un résultat de type emprunts, et nous voulons connaître le nom
du membre à qui il appartient :

<?php
// ...
$tblEmprunts = new Emprunts();
$monEmprunt = $tblEmprunts->find('2','978-2841773381')->current();
echo $monEmprunt->findParentMembres()->nom.'<br />';

findParent<tableClass>() récupère un jeu de résultats d'une table parente. C'est un raccourci de commodité de la
méthode findParentRow('tableClass','role'). De type '1 à 1' (1 emprunt donné ne fait référence qu'à un membre),
nous récupérons un unique résultat, sur un Row donc.

Je n'ai pas parlé des rôles mais il est possible d'y faire référence. Si par exemple un membre emprunte un livre
mais peut aussi être son auteur, alors lister les livres écrits par un membre se fera de la manière $leMembre-
>findLivresViaEmpruntsByAuteur().
Tout est automatique, Zend Framework se charge pour nous de faire les jointures nécessaires. Je n'ai pas non plus
testé les résultats de mes requêtes : un find(), peut par exemple ne retourner aucun résultat, à vous de le gérer.
Idem pour l'effet de cascade des SGBDs. ZF inclue un système de cascade, qui permet d'agir à la manière d'un
SGBD, en actionnant en cascade les résultats (DELETE ou UPDATE) : je n'en parle pas ici, car la gestion de l'intégrité
de la base doit être laissée à la charge du SGBDR et non de l'application qui tourne dessus. Le staff de ZF nous le
précise clairement : se reposer sur la gestion de l'intégrité référentielle via ZF n'est pas recommandé et peut mener à
des comportements hasardeux. Mes tables sont au format Innodb, un format de table gérant les relations et l'intégrité,
je ne m'occupe en rien de cela dans mon code applicatif.

Abuser des méthodes de récupération des dépendances va générer une charge


insoutenable pour le SGBD. Ces méthodes sont là, mais en pratique on les utilise très très
peu. Utiliser une méthode de récupération de dépendance dans une boucle foreach, par
exemple, amène à une dégradation certaine des performances de l'application.
Créez vous même vos jointures, limitez les champs retournés (*, ne sert que très très
rarement), et si possibles, utilisez des vues et des procédures stockées. N'agissez pas à
l'aveugle en vous reposant sur le framework, ayez toujours en tête les requêtes envoyées
à votre SGBD. ZF ne fait que proposer une interface avec les SGBDs, à vous de l'utiliser
de manière correcte, réfléchie et sensée.

Les 3 méthodes de récupération des dépendances que nous vennons de voir, prennent
optionnellement en paramètre un objet Zend_Db_Table_Select, très pratique pour limiter
les colonnes des résultats, et ne pas choisir '*', comme c'est le cas par défaut.

IX-H - Rappels

#Zend_Db et tous ses amis n'est pas une solution ORM. Un ORM est un système qui masque totalement les données
manipulées dans l'application, du support qui va servir à les faire persister (une base de données très souvent).
Le système Zend_Db_Table_* peut être utilisé comme couche basse dans un ORM, mais une fois de plus : ça n'est
pas un ORM : voyez Doctrine pour cela.

- 45 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

X - #Zend_Log

Ce composant plutôt petit est très facile d'utilisation. Il va servir à journaliser des évènements divers : une exception
levée, la base de données qui ne répond plus, une tentative d'intrusion...
Plutôt que d'écrire à la main le code, #Zend_Log est relativement complet. Sa classe, Zend_Log va utiliser des
Zend_Log_Writer_*** afin de définir le(s) support(s) qui va contenir les logs. On a le choix entre un flux (fichier, sortie
standard...) ou une table de base de données.
Pour les tests, on a un objet déguisé (Mock), ou un NULL, qui va neutraliser la journalisation.
Enfin, les messages loggués pourront être mis en forme, via des Zend_Log_Formatter_**, et même filtrés.

Utilisation basique de Zend_Log


<?php
$log = new Zend_Log();
$fileWriter = new Zend_Log_Writer_Stream('my/file.log');
$log->addWriter($fileWriter);

$log->info('Message d\'information');
$log->alert('Oula, gros problème');
?>

Sur le journaliseur, j'ai 7 méthodes à disposition, qui vont dépendre du degré de gravité de l'erreur survenue. Ces 8
degrés sont compatibles RFC-3164 et viennent du protocole BSD syslog, compatibles PEAR log.

EMERG 0 : Urgence : le système est inutilisable


ALERT 1 : Alerte: une mesure corrective doit être
prise immédiatement
CRIT 2 : Critique : états critiques
ERR 3 : Erreur: états d'erreur
WARN 4 : Avertissement: états d'avertissement
NOTICE 5 : Notice: normal mais état significatif
INFO 6 : Information: messages d'informations
DEBUG 7 : Debug: messages de déboguages

Issu de la doc officielle. Ainsi si j'applique une méthode err() à mon objet de log, je journalise une erreur de niveau 3.
Mais qu'est ce qui est enregistré, réellement ? Et bien il s'agit de 4 infos, timestamp - message - priority -
priorityName. Pour rajouter un paramètre à prendre en compte lors d'un évènement de journalisation, indiquez-le
par setEventItem()

Modification du format d'enregistrement dans le journal


$log = new Zend_Log();
$fileWriter = new Zend_Log_Writer_Stream('my/file.log');
$log->addWriter($fileWriter);

$log->setEventItem('Memory used', memory_get_usage());


$log->debug('Juste pour déconner');

Et voilà, mon journal retiendra en plus la mémoire utilisée par PHP à cet instant-là : à chaque fois que je journaliserai
un évènement.
Ici, ils sont retenus dans un fichier. Pour les enregistrer en base de données, et bien j'instancie un nouveau support,
je peux en utiliser autant que je veux, j'écrirai donc mon log à plusieurs endroits différents, mais avec toujours une
seule méthode :

Enregistrement du journal à plusieurs endroits


<?php
$log = new Zend_Log();
$fileWriter = new Zend_Log_Writer_Stream('my/file.log');

// $db est une instance Zend_Db_Adapter_Abstract

- 46 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Enregistrement du journal à plusieurs endroits


$dbWriter = new
Zend_Log_Writer_Db($db, 'logTable', array('date_col'=>'timestamp', 'message_col'=>'message'));
$log->addWriter($fileWriter);
$log->addWriter($dbWriter);

$log->notice('This is a notice');

Ici, mon évènement de type 'notice' va être enregistré dans le fichier ET dans la base de données. J'ai pris soin de
lui fournir une instance de Zend_Db_Adapter (voir chapitre sur #Zend_Db).
Je lui ai dit aussi que je veux utiliser la table nommée 'logTable', et que sur cette table, à chaque évènement du
journal, la colonne 'date_col' enregistre le paramètre 'timestamp' du journal, et 'message_col', enregistre le paramètre
'message'. Facile quand même...

Si un jour j'ai envie de débogguer et de désactiver mon journal, alors je le fais à l'endroit où je l'ai configuré (et non
à chaque endroit ou je journalise... vla la galère!), et je lui passe un support spécial de leurre, en désactivant les
autres réels, évidemment.
Il s'agit du Zend_Log_Writer_Null, je peux même chainer en faisant un $log = new Zend_Log(new
Zend_Log_Writer_Null()). Pour leurrer le journal, entendez par là pouvoir l'utiliser dans l'application, le support Mock
peut nous aider :

Ecrire le journal dans un tableau PHP


<?php
$log = new Zend_Log();
$mock = new Zend_Log_Writer_Mock();
$log->addWriter($mock);

$log->debug('Un petit message comme ca');

Zend_Debug::Dump($mock);
/* affiche :
object(Zend_Log_Writer_Mock)#2 (4) {
["events"] => array(1) {
[0] => array(4) {
["timestamp"] => string(25) "2007-09-04T22:57:16+02:00"
["message"] => string(21) "Message d'information"
["priority"] => int(6)
["priorityName"] => string(4) "INFO"
}
}
["shutdown"] => bool(false)
["_filters:protected"] => array(0) {
}
["_formatter:protected"] => NULL
}
*/
?>

Notre support de stockage 'Mock' contient une propriété publique qui contient un tableau où sont stockés les
évènements du journal, on y accède donc par $mock->events[**]['message'], par exemple.

Je vais vite fait parler des formateurs, qui servent à formater les évènements du journal. Par exemple pour le sortir
en XML, on fait comme ça :

Journalisation au format XML


<?php
$log = new Zend_Log();
$writer = new Zend_Log_Writer_Stream('php://output');

$formateur = new Zend_Log_Formatter_Xml();


$log->setFormatter($formateur);
$log->addWriter($writer);

$log->debug('Un petit message comme ca');

- 47 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Journalisation au format XML


/* affiche
<logEntry>
<timestamp>2007-09-04T23:04:39+01:00</timestamp>
<message>Un petit message comme ca</message>
<priority>7</priority>
<priorityName>DEBUG</priorityName>
</logEntry>
*/
?>

Si vous avez besoin de plusieurs objets de log, chacun configuré avec ses supports (Writers) et ses éventuels filtres/
formateurs, vous pourrez alors utiliser la méthode Zend_Log::factory(), elle permet de décrire des objets log avec
une seule méthode prenant en paramètre un tableau, ou encore un objet de type Zend_Config

#Zend_Log comporte donc tout ce dont on a besoin. Comme d'habitude avec Zend Framework, des interfaces et
classes abstraites existent pour écrire et brancher son propre système ou pour étendre celui-ci.
Zend_Log, grâce aux mock, permet aussi un test facile de l'application.

- 48 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

XI - #Zend_View

XI-A - Principe

#Zend_View constitue la partie vue du MVC. Il n'est absolument pas obligatoire de fonctionner sous MVC pour
l'utiliser ;)
En fait, #Zend_View participe à la séparation du code de traitement et de la mise en forme des résultats. Il permet
de créer une sortie vers un ou plusieurs fichiers template, un fichier de vue. Cela fait partie intégrante de MVC mais
aussi de toute appli bien fondée ^^.
#Zend_View n'est donc pas un moteur de template à proprement parler, car il ne fusionne pas 2 syntaxes
distinctes. Il prépare des données qu'il va envoyer à un ou plusieurs fichiers de vue. Cette vue va s'occuper des
données reçues et les présenter.
#Zend_Layout, relié à #Zend_View constitue une approche de template simple et efficace. En pratique, voici un
script simple de traitement qui rend une vue :

<?php
// configuration �
$view = new Zend_View();
$view->setScriptPath('./');

$country = "france";
$sqlResult = $db-
>fetchPairs('SELECT id, name FROM members WHERE country = :country',array('country'=>$country));
$view->result = $sqlResult;
echo $view->render('myview.phtml');
?>

Nous créons d'abord une instance de Zend_View, puis nous lui spécifions où se trouvent les vues que nous voulons
utiliser. Ici, elles se trouvent dans le même dossier que le script courant mais, généralement, elles sont à part.

setScriptPath() indique à notre instance de vue, où chercher les fichiers vue qu'on va lui faire charger. Cela se fait
une fois pour toutes et il est possible de spécifier le chemin via une autre méthode :

addScriptPath() ajoute un dossier dans la liste des dossiers de vue. Notre instance va chercher dans le dossier
spécifié par addScriptPath() d'abord puis, si elle ne trouve pas les vues, elle cherchera alors dans le dossier spécifié
par setScriptPath(). C'est intéressant si l'on veut créer plusieurs looks sur notre site et spécifier un des looks via
cette méthode.

getScriptPaths() renvoie la liste des dossiers de vues sous la forme d'un tableau.

Pour assigner une variable 'myVar' par exemple, on utilise simplement $view->myVar = 'my data'. Une méthode
assign($myVar,'my data') existe aussi. On ne peut passer à assign() que 2 types : des tableaux de résultats (comme
c'est le cas dans notre exemple) ou des chaînes de caractères ; du moment que, dans la vue, on les utilise comme
il faut.
clearVars() détruit toutes les variables préalablement passées à la vue.

render() enfin, retourne le résultat de la vue, que l'on doit afficher (echo) ou bien capturer dans l'OB.
Du côté du fichier vue myview.phtml, maintenant, nous pouvons le présenter comme ceci :

<?php if ($this->result): ?>


<!-- Tableau de rendu des membres -->
<table>
<?php foreach($this->result as $memberId=>$memberName): ?>
<tr>
<td><?php echo $memberId?></td>
<td><?php echo $this->escape(memberName)?></td>
</tr>
<?php endforeach; ?>

- 49 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

</table>

<!-- Pas de membres à lister -->


<?php else : ?>

<p>Pas de résultats</p>

<?php endif; ?>

Ici, j'ai simplement utilisé PHP pour décrire ma vue, mais rien n'empêche d'utiliser un moteur de template quelconque.
La doc officielle offre un bon exemple. Nous remarquons que toutes les propriétés envoyées à la vue via assign(), ou
en affectation directe, sont disponibles dans le contexte de la vue et sont donc utilisables via la pseudo variable $this :

<?php
// script de contrôle
$view->someVar = 'someVal'; // ou $view->assign($someVar,'someVal');
echo $view->render('myViewFile.phtml');
?>
<?php
// script de vue myViewFile.phtml
echo $this->someVar // affiche someVal;
?>

Bien entendu, étant dans la vue, nous avons à disposition une méthode d'échappement, la fonction bas niveau
implémentée par défaut est htmlspecialchars(), avec le paramètre ENT_COMPAT (pas d'échappement des
guillemets simples), sur le jeu de caractères ISO-8859-1.

escape() échappe la variable passée en paramètre pour un affichage plus serein.

setEscape() spécifie une fonction d'échappement personnalisée, qui peut être n'importe quoi : une fonction PHP ou
même une méthode statique de classe, voire méthode d'un objet :

$view->setEscape('myClass','myEscapeFunction');
$view->setEscape($myObj,'myObjEscapeFunction');

En général, on pourra spécifier htmlentities() et, dans le cas d'une fonction telle que celle-ci, qui accepte un
paramètre spécifiant l'encodage de caractères, on peut le changer :

setEncoding() spécifie une autre fonction d'encodage pour une escape htmlspecialchars(), ou htmlentities():

$view->setEscape('htmlentities');
$view->setEncoding('UTF8');

Ici, l'échappement sera équivalent à htmlentities($var, ENT_COMPAT, UTF-8);


Je rappelle qu'il n'est pas possible de changer le comportement du quote_style.
strictVars() est utilisée pour intercepter les appels à des variables n'existant pas. Par défaut, elle est réglée sur
false, si vous lui passez true, et que dans votre vue vous appelez $mavue->uneVariableInexistante, alors une erreur
NOTICE sera levée.

Zend_View est utilisé par le modèle MVC, et donc par Zend_Controller_*. Ces composants
configurent de manière automatique la vue, rendant son utilisation au sein du système
MVC très intuitive et simple.

XI-B - Helpers (aides au rendu)

Zend_View est accompagné de tout un tas d'helpers, notamment concernant la construction de formulaires HTML.
Je ne vais pas tous les présenter mais juste un exemple.

- 50 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Script de vue d'édition d'une fiche quelconque :


<form action="action.php" method="post">
Nom :
<?php echo $this->formText('name', $this->escape($this->name, array('size'=>30, 'maxlength'=>30)) ?>
<br />Pays :
<?php echo $this->formSelect('pays', 'fr', null, $this->countries) ?>
<br />Recevoir la Newsletter?
<?php echo $this->formCheckbox('newsletter', 'oui', null, array('oui', 'non')) ?>
</form>
?>

Je ne vais pas tout détailler ici, je vous renvoie à l'API. Dans le cas le plus large, la syntaxe est helperFunction($name,
$value, $attribs, $options).
Ces helpers facilitent la mise en forme d'un formulaire, en particulier avec le fameux problème du <select><option></
option></select>, avec une boucle dans le <option> pour sélectionner la valeur déjà présente en base. Ça se fait
très simplement ici, avec :

script de contrôle
<?php
$view = new Zend_View();
$view->setScriptPath('./');
$view->countries = array('fr'=>'France','us' => 'United States','de' => 'Germany');
defaultCountry = $db->fetchOne('SELECT country FROM test WHERE id = 1');
echo $view->render('theView.phtml');

<?php // script de vue theView?>


<form action="action.php" method="post">
Pays :
<?php echo $this->formSelect('pays', $this->defaultCountry, null, $this->countries);
echo $this->formSubmit('Send','Envoyer');
?>

Ici, le select est rempli d'options présentes dans le tableau countries, et defaultCountry est en statut selected.
formSubmit a donné naissance à <input type="submit" name="send" id="send" value="envoyer" />

script de vue
echo $this->doctype('XHTML1_STRICT'),
$this->headLink()->appendStylesheet('/styles/basic.css')
->headLink(array('rel' => 'favicon', 'href' => '/img/favicon.ico'), 'PREPEND')
->prependStylesheet('/styles/moz.css', 'screen', true),

$this->headMeta()->appendName('keywords', 'framework php productivity')


->appendHttpEquiv('expires', 'Wed, 26 Feb 1997 08:21:57 GMT')
->appendHttpEquiv('pragma', 'no-cache')
->appendHttpEquiv('Cache-Control', 'no-cache'),

$this->headScript()->appendFile('/js/prototype.js')
->appendScript($onloadScript),

$this->headStyle()->appendStyle($styles),

$this->headTitle('Titre de la page');

Ces helpers sont orientés vers le code HTML contenu dans <head>. En plus d'éventuellement utiliser un moteur de
templates, nous pouvons écrire des helpers personnalisés. Voici tout de suite un exemple, imaginons :

script de vue
<?php
echo "Bienvenue, $this->showCart()";

La méthode showCart() est une méthode personnalisée qui a pour but d'afficher clairement le nombre d'articles dans
un panier. La méthode est issue d'un helper personnalisé qui pourrait ressembler à ceci :

- 51 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

<?php
class Zend_View_Helper_ShowCart
{
public function showCart($elements)
{
$output = "Votre panier contient $elements articles";
return htmlspecialchars($output);
}
}

Une fois encore, on garde les conventions de codage en tête : la classe doit s'appeler
Zend_View_Helper_MonHelperPerso pour une méthode nommée monHelperPerso(), ceci se trouvant dans un
fichier MonHelperPerso.php.
Notez la casse, elle fait partie des conventions de nommage que nous avons rappelées en début de tutoriel.

Avec les helpers personnalisés, on peut "casser" son code et une logique de séparation comme celle-ci peut être
utilisée en complément avec la méthode addHelperPath().

addHelperPath() ajoute un dossier dans la liste des dossiers des helpers. Notre instance de vue va chercher dans
le dossier spécifié ici, sinon dans le dossier par défaut Zend/View/Helper/.
On peut donc ranger ses vues et ses helpers, et ainsi correctement séparer son code si on le souhaite.

Autre fonctionnalité : la répétition de motifs dans plusieurs vues peut être factorisée (extraite, écrite à part). Ceci
grâce aux helpers partial() et partialLoop() :

controle
$view = new Zend_View();
$view->setScriptPath('./');
$view->data = $db->fetchAll("SELECT * FROM livres");
echo $view->render('view.phtml');

tableau.phtml
<tr>
<td><?php echo $nom?></td>
<td><?php echo $prenom?></td>
</tr>

view.phtml
<h1>Liste de nos membres :</h1>
<table>
<?php echo $this->partialLoop("tableau.html",$this->data)?>
</table>

partialLoop() boucle sur le script qu'on lui passe en paramètre. Le tableau de données ($this->data) passé en
paramètre doit être de la forme array( array('nom'=>'valeur','prenom'=>'valeur') ).
C'est exactement ce que nous donne comme résultat un fetchAll() (Consultez le chapitre #Zend_Db).

- 52 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

XII - Zend_Form

#Zend_Form est un composant qui comme son nom l'indique permet de créer et de gérer des formulaires Web
(x)HTML. Ce composant est extrêmement pratique en ce qui concerne la gestion et la présentation des données de
manière générale. Aussi, si ce n'est déja fait, il est conseillé de lire le chapitre sur #Zend_Validate et #Zend_Filter
afin de bien comprendre les principes de #Zend_Form
Pour rester simple, #Zend_Form se décompose en plusieurs classes :

1 Zend_Form : le formulaire en question, comme il est validable, il implémente Zend_Validate_Interface


2 Zend_Form_Element_* : des éléments de formulaires, (des champs, des boutons ...), ils héritent tous de
Zend_Form_Element, qui implémente aussi Zend_validate_Interface car chaque élément est validable
indépendamment
3 Zend_Form_Decorator_* : des décorateurs. Ces objets ont pour seule responsabilité de présenter
visuellement les éléments, et le formulaire contenant les éléments

Un formulaire se compose donc d'éléments qu'il faut lui ajouter, ainsi que d'autres petites options. Voyons en tout
de suite quelques unes :

construction d'un formulaire


<?php
$form = new Zend_Form();
$form->setAction('/go/here')
->setAttrib('id', 'dvp-form')
->setDescription('formulaire exemple developpez.com')
->setEnctype(Zend_Form::ENCTYPE_URLENCODED)
->setLegend('légende')
->setMethod('POST')
->setName('dvp-form')
->setView(new Zend_View);

echo $form;

Les méthodes parlent plutot d'elles-mêmes je pense, inutile de les détailler. Afficher un formulaire est aussi simple
que de faire un "echo" devant l'objet mais attention, la plupart du temps le formulaire Zend_Form aura besoin d'un
objet Zend_View car il va en utiliser les aides (aides de vues, ou "View Helpers") pour pouvoir afficher les éléments
qui le composeront plus tard. Zend_Form est capable d'aller chercher l'objet vue utilisé dans le système MVC de lui-
même, comme nous n'utilisons pas le modèle MVC de Zend Framework ici, nous devons passer manuellement un
objet Zend_View à Zend_Form, de manière à être tranquilles par la suite.

Le code source HTML de ce formulaire est le suivant :

<form id="dvp-form" enctype="application/x-www-form-urlencoded" action="/go/


here" method="post"><dl class="zend_form">
</dl></form>

Il est très important de noter qu'entre la composition du formulaire et/ou de ses éléments et leur rendu HTML
respectifs, se situent des objets appelés "décorateurs". Si le rendu HTML ne vous plait pas, pas de panique! Nous
le modifierons plus tard grâce aux décorateurs.
Notez aussi que la légende, ou encore la description du formulaire n'apparaissent pas dans la source HTML, là
encore, c'est une question de décorateurs.

Certains des paramètres de configuration que nous avons définis auraient pu être utilisés avec setOptions(), ou
encore avec setConfig() qui prend comme argument un objet Zend_Config.
Notez bien que le composant #Zend_Form plus que tout autre, propose toujours différentes méthodes permettant
d'arriver au même résultat, selon le cas dans lequel vous vous trouvez vous utiliserez l'une ou l'autre des manières
de faire.

- 53 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

XII-A - Manipuler des éléments de formulaire

Un formulaire ne sert à rien s'il n'est pas composé d'éléments. Les éléments (objets de la classe Zend_Form_Element)
possèdent au moins autant d'options que l'objet Zend_Form lui-même.
Ainsi, les éléments sont configurables de manière totalement indépendante du formulaire auquel ils seront rattachés
plus tard. Là encore, plusieurs manières de procéder :

1 Vous créez et configurez chaque élément à part, puis vous les rajoutez dans le formulaire
2 Vous demandez au formulaire de créer des éléments et de les configurer, puis vous les ajoutez au formulaire
3 Vous créez et ajoutez dans le formulaire tous les éléments d'un seul coup avec une seule méthode

Quoi que vous choisissiez, et quel que soit l'élément considéré, tout élément doit au minimum posséder un nom
unique qui permettra de l'identifier dans le flot d'éléments que votre formulaire comportera.
Ainsi, 2 éléments peuvent avoir le même nom, mais si vous ajoutez les 2 au formulaire, alors celui-ci ne considèrera
que le dernier ajouté. Voyons celà :

ajout d'éléments au formulaire, méthode n°=1


<?php
// $form est notre formulaire précedemment crée
$text = new Zend_Form_Element_Text('un champ texte');
$envoyer = new Zend_Form_Element_Submit('envoyer');
$form->addElement($text);
$form->addElement($envoyer);
// peut aussi s'écrire comme ceci :
// $form->addElements(array($text, $envoyer));
echo $form;

ajout d'éléments au formulaire, méthode n°=2


<?php
// $form est notre formulaire précedemment crée
$text = $form->createElement('text','un champ texte');
$envoyer = $form->createElement('submit','envoyer');
$form->addElements(array($text,$envoyer));
echo $form;

ajout d'éléments au formulaire, méthode n°=3


<?php
// $form est notre formulaire précedemment crée
$form->addElements(array(
array('text','un champ texte'),
array('submit','envoyer')
));
echo $form;

Oui répétons : il existe toujours plusieurs (2, 3 voire 4) manières d'effectuer la même action avec #Zend_Form
Des éléments, comme on peut le deviner, il en existe plein, et la documentation officielle vous renseignera à leur
sujet. Ils se configurent tous des la même manière (car ils étendent tous Zend_Form_Element), et possèdent en plus
chacun des options bien à eux (par exemple un élément de type 'select' possède une méthode permettant de lui
ajouter les options qu'il contiendra).

Le code source HTML ressemble maintenant à ça :

<form id="dvp-form" enctype="application/x-www-form-urlencoded" action="/go/


here" method="post"><dl class="zend_form">
<dt id="unchamptexte-label">&nbsp;</dt>
<dd id="unchamptexte-element">
<input type="text" name="unchamptexte" id="unchamptexte" value=""></dd>
<dt id="envoyer-label">&nbsp;</dt><dd id="envoyer-element">
<input type="submit" name="envoyer" id="envoyer" value="envoyer"></dd></dl></form>

- 54 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Je répète que la manière dont sont présentés visuellement les éléments et le formulaire (les balises HTML), dépend
d'objets décorateurs que nous verrons plus tard.
Nous pouvons tout de même remarquer que malgré leurs noms respectifs, certains éléments comme le "text" ne
l'affichent pas visuellement. C'est parce que tous les éléments possèdent un label.
Le label est le nom qu'aura visuellement l'élément (généralement situé à coté de lui pour le décrire), ce qui n'a rien
à voir avec son nom propre permettant de l'identifier dans l'objet Zend_Form ou dans la source HTML (grâce aux
attributs 'id' et 'name').
Nos éléments doivent donc tous posséder un label, à quelques exceptions près comme l'élément bouton qui prend
son nom comme label d'affichage.

Ajouter un label à un élément revient à lui ajouter une option, et des options, il en possède tous un tas. Voyons celà
(liste non exhaustive) :
setAttrib() permet d'ajouter un attribut HTML à l'élément, par exemple $text->setAttrib('class','element'), ou encore
$text->setAttrib('disabled','disabled') ce qui rend la saisie impossible dans le champ
setDescription() ajoute une description à l'élément. Celle-ci pourra être présentée visuellement lors de l'affichage si
l'élément possède un décorateur permettant cela (c'est le cas par défaut pour certains d'entre eux)
setErrorMessages() affecte les messages d'erreur de validation à l'élément, dans un cadre plus général, il demeure
conseillé d'éviter les appels à cette méthode et d'utiliser un objet Zend_Translate pour l'affectation et la traduction
des différents messages d'erreur des validateurs d'un élément.
setIgnore() prend en paramètre un booléen et informe alors que la valeur de cet élément sera ignorée lors de la
récupération de l'ensemble des valeurs depuis le formulaire général, une fois celui-ci envoyé
setOrder() prend en paramètre un entier indiquant l'ordre dans lequel l'élément apparaitra dans le visuel HTML.
Pratique pour configurer en dernier des éléments devant être affichés en premiers
setName() permet de modifier le nom de l'élément
setLabel() permet d'affecter un label à l'élément, c'est à dire un message descriptif visuel généralement apposé
contre l'élément dans la source HTML
setRequired() permet d'obliger cet élément à être non vide à la saisie (sinon le formulaire sera en erreur lorsqu'on
demandera de le valider)
setValue() permet d'affecter la valeur par défaut qu'aura l'élément lors de son affichage HTML ( en général il s'agit
du tag "value=", cela peut aussi être un tag "selected")
Toutes ces méthodes ont des "get" associés (getAttrib(), getDescription() ...)

En plus de cela, certains éléments comme le select acceptent des méthodes spécifiques :

<?php
$select = new Zend_Form_Element_Select('selection de données');
$select-
>setMultiOptions(array('option 1'=>'valeur 1', 'option 2'=>'valeur 2')); // les options à afficher dans le select
$select->setValue('option 2'); // selectionne la valeur 2 à l'affichage

Il est temps maintenant de faire un formulaire plus complet. Je vais mixer différentes manières de procéder dans
l'exemple suivant :

ajout d'éléments divers, de diverses manières


<?php
// $form est déja configuré et vide de tout élément

$text = new Zend_Form_Element_Text('un champ texte');


$text->setRequired(true)
->setErrorMessages(array('required'=>'Element requis'))
->setLabel('Voici un champ texte :')
->setDescription('Ce champ est là à titre informatif pour le tutoriel')
->setAttrib('size','70');

$form->addElements(array(
$text,
// type | nom | options |
array('select','selection de données', array('label'=>'séléctionnez une donnée :',

'multioptions'=>array('option 1'=>'valeur 1','option 2'=>'valeur 2'),


'value'=>'option 2')),

- 55 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

ajout d'éléments divers, de diverses manières


array('submit','envoyer'),
));
echo $form

Les "tableaux dans les tableaux de tableaux plein de tableaux" peuvent devenir déroutants, mais sont extrêmement
logiques. Soit je passe par un tableau, soit je sors l'élément et j'utilise sa méthode de configuration appropriée.
On s'y habitue vite, et cette grande fléxibilité dans les manières d'arriver à un même résultat est un des points forts
de #Zend_Form
Le code HTML est le suivant :

<form id="dvp-form" enctype="application/x-www-form-urlencoded" action=""


method="post"><dl class="zend_form">
<dt id="unchamptexte-label"><label for="unchamptexte" class="required">Voici un champ
texte :</label></dt>
<dd id="unchamptexte-element">
<input type="text" name="unchamptexte" id="unchamptexte" value="" size="70">
<p class="description">Ce champ est là à titre informatif pour le tutoriel</p></dd>
<dt id=""><label for="selectiondedonnées" class="optional">séléctionnez une donnée :</label></dt>
<dd id="">
<select name="selectiondedonnées" id="selectiondedonnées">
<option value="option 1" label="valeur 1">valeur 1</option>
<option value="option 2" label="valeur 2" selected="selected">valeur 2</option>

</select></dd>
<dt id="envoyer-label">&nbsp;</dt><dd id="envoyer-element">
<input type="submit" name="envoyer" id="envoyer" value="envoyer"></dd></dl></form>

Voyons quelques options avancées utiles.


Vous pouvez récupérer un élément du formulaire en fléchant son nom comme propriété de l'objet Zend_Form, et
donc piloter l'élément hors du formulaire :

récupérer un élément depuis le formulaire


<?php
// soit le formulaire ci-dessus, construit et rempli de nos 3 éléments (text, select et submit) :
$elementText = $form->unchamptexte;
echo $elementText // affiche juste l'élément text

On comprend pourquoi chaque élément doit avoir un nom propre unique. D'ailleurs, mettre des espaces dans son
nom n'aura aucun effet, ils seront en toute logique supprimés ("un champ texte"=>"unchamptexte").
$form->removeElement() prend en paramètre le nom d'un élément et le supprime du formulaire.
$form->getValue() prend en paramètre le nom d'un élément et récupère sa valeur, s'il en a une par défaut ou si le
formulaire a été envoyé et que le champ a été rempli $form->getValues() existe aussi, il retourne, comme on peut
se douter, un tableau avec les éléments et leurs valeurs associées.

XII-B - Ajouter des validateurs et valider le formulaire

Le coeur de la puissance de #Zend_Form : la validation du formulaire. Ici, nous ne parlons pas javascript. Il est
possible de rendre Zend_Form "ajaxé", notamment grâce à Zend_Dojo_Form, mais ceci dépasse le cadre de cet
article.
La validation du formulaire se produit après l'envoi de celui-ci (ou aussi après affectation de valeurs à ses éléments).
La page recevant les données recrée l'objet formulaire Zend_Form dans l'état, et demande si celui-ci est valide avec
les données reçues. Ceci s'effectue de cette manière en général:

valider le formulaire
<?php
// nous supposons le même formulaire que dans le sous-
chapitre précédent, simplement nous modifions sa destination
// afin qu'il renvoie vers la même page que celle l'ayant crée, ceci simplifie les traitements.
$form->setAction($_SERVER['SCRIPT_NAME']);

- 56 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

valider le formulaire
if ($_SERVER['REQUEST_METHOD'] == "POST") { // venons-nous ici depuis une méthode POST ?
if ($form->isValid($_POST)) { // si le formulaire est valide avec les données POST reçues
$form = "formulaire validé"; // on redéfinit la variable $form plus tard affichée
} else {
$form->populate($_POST); // sinon on remplit tous les champs avec les valeurs reçues
}
echo $form;

isValid() prend en paramètre un tableau, généralement venu d'HTTP, et envoie chacune des données aux éléments,
puis demande à chaque élément de valider cette donnée. populate() prend en paramètre un tableau, généralement
venu d'HTTP, et envoie chacune des données aux éléments en leur spécifiant de se l'affecter comme valeur par
défaut.

Une fois que isValid() est appelée, si on demande à réafficher le formulaire, alors les éventuelles erreurs des
validateurs des éléments vont être décorées, et donc affichées.
Ce simple petit script et la combinaison des deux méthodes isValid() et populate() permet de valider et de réafficher
entièrement le formulaire, quelle que soit sa compléxité et sa taille, en un seul geste. Génial !

Mais qu'est ce que la validation des données ?


C'est très simple. isValid() demande à l'objet Zend_Form de fournir les valeurs à chacun de ses éléments, et de
demander à chaque élément de valider cette valeur après l'avoir filtrée.
La validation se passe donc au niveau des éléments de formulaire. Il faut, pour chaque élément qui le nécessite,
ajouter les validateurs de type Zend_Validate_Abstract.
Concernant les validateurs, renseignez vous sur leur fonctionnement dans cet article ou dans la documentation
officielle. Pour l'ajout de validateurs, là encore, il existe plusieurs manières de faire, et aussi plusieurs options. Voici
un exemple de formulaire email/password les utilisant :

ajout de validateurs à des éléments


<?php
// $form est toujours le même, configuré mais vide de tout élément
$text = new Zend_Form_Element_Text('email');
$text->setRequired(true)
->setLabel('email :')
->setAttrib('size','30')
->addValidator(new Zend_Validate_StringLength(7));

$form->addElements(array(
$text,
array('password','motdepasse', array('label'=>'Password :',
'required'=>true,
'validators'=>array('Alnum',
array('validator'=>'stringlength',
'options'=>array('min'=>8)))
)
),
array('submit','envoyer'),
));

$form->addElement('checkbox','caseacocher',array('label'=>'cochez ici svp :'));


$form->caseacocher->addValidator(new Zend_Validate_Identical('1'));

$email = new Zend_Validate_EmailAddress();


$form->email->addValidator($email);

if ($_SERVER['REQUEST_METHOD'] == "POST") {
if ($form->isValid($_POST)) {
$form = 'validé';
} else {
$form->populate($_POST);
}
}

- 57 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

ajout de validateurs à des éléments


echo $form;

Voyez plutot, les éléments possèdent une méthode addValidator() qui prend en paramètre soit un objet
Zend_Validate_Abstract, soit une chaine décrivant le nom du validateur, soit encore un tableau avec à la clé 'validator'
une chaine décrivant le validateur, et à la clé 'options' un paramètre, souvent de type tableau, décrivant les options
à passer au constructeur du validateur en question.
addValidators() avec un 's' à la fin, est indentique mais rajoute une dimension de tableau au tout.

Aussi, on peut intégrer les validateurs directement lors du addElement() ou addElements() sur le formulaire. La
syntaxe se complique un peu, mais elle respecte la même règle interne, et avec un peu de présentation dans le
code, ça reste largement lisible.

Précisons tout de même (la documentation officielle aide) que l'élément de type Checkbox renvoie 0 si non coché,
et 1 si coché, ceci par défaut. Nous voulons qu'elle soit coché, donc nous ajoutons un validateur d'équivalence à
la valeur 1.
Essayez d'envoyer ce formulaire, vous remarquerez alors qu'il est bel et bien validé, et les erreurs s'affichent
par défaut en anglais (c'est personnalisable et très simple si effectué au moyen de Zend_Translate, voyez la
documentation officielle). Aussi, les erreurs d'affichent en dessous, dans des structures "ul-li", pas de panique je
répète encore : la présentation se fait grâce aux décorateurs que nous verrons bientot.

Les validateurs sont enchainés sur la valeur reçue par l'élément, dans l'ordre dans lequel
ils ont été ajoutés.
Aussi, les validateurs agissent sur la valeur filtrée de l'élément. Chaque élément accepte
en plus des validateurs, des filtres de types Zend_Filter_Interface avec des méthodes très
semblables à celles des validateurs. Nous n'ajouterons pas de filtres sur les données dans
cet article.

Les validateurs sont enchainés sur la valeur reçue par l'élément, dans l'ordre dans lequel ils ont été ajoutés. Ainsi,
chaque validateur valide la donnée, qui est ensuite passée au validateur suivant. Tous les validateurs en echecs
renseignent alors l'objet Element en lui injectant leur message d'erreur (traduit entre temps si Zend_Translate utilisé).
Dire à un élément qu'il est requis ("required") a pour effet d'ajouter un validateur de type "NotEmpty", en haut de la
pile des validateurs déja présents éventuellement dans l'élément.

Il est possible de casser la chaine. Exemple : mon champ password contient 3 validateurs : required (not empty),
puis Alnum (seuls les caractères alphanumériques sont accéptés), et enfin stringlength (la taille du champ doit faire
au minimum 8 caractères).
Si la chaine insérée est composée de caractères exotiques faisant échouer "Alnum", "Stringlength" sera quand même
appelé, même si au final, c'est inutile.
Pour demander à casser la chaine de validation dès qu'un des validateurs d'un élément échoue, il faut le préciser
avec l'option "breakChainOnFailure" lors de l'ajout du validateur concerné.

casser la chaine de validation de email et password


<?php
$text = new Zend_Form_Element_Text('email');
$text->setRequired(true)
->setLabel('email :')
->setAttrib('size','30')
->addValidator(new
Zend_Validate_StringLength(7), true); // true en deuxième option active le bris de chaine si echec

$form->addElements(array(
$text,
array('password','motdepasse', array('label'=>'Password :',
'required'=>true,
'validators'=>array(

array('validator'=>'Alnum', // nous sommes obligés de passer par un tableau ici


'breakChainOnFailure'=>true),
array('validator'=>'stringlength',

- 58 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

casser la chaine de validation de email et password


'options'=>array('min'=>8))
)
)
),
array('submit','envoyer'),
));

$email = new Zend_Validate_EmailAddress();


$form->email-
>addValidator($email); // on ajoute bien un deuxième validateur à l'élément email, après son StringLength déja pré
// suite

"required" ajoute automatiquement un validateur "NotEmpty" et lui affecte l'option


breakChainOnFailure
Si vous ne voulez pas l'ajout automatique de cette option, utilisez
setAutoInsertNotEmptyValidator(true) sur votre élément

Comme vous pouvez vous-mêmes créer vos propres validateurs, et vos propres filtres, on sent bien que #Zend_Form
rend de grands services.
Sachez aussi que chaque validateur ne fait pas que recevoir la donnée de l'élément auquel il est affecté en entrée.
Il reçoit aussi optionnellement les données de tous les éléments. Un validateur d'égalité de 2 champs mot de passe
est tout à fait réalisable (ou toute autre idée).

XII-C - Décorateurs : gérer le rendu HTML

Passons maintenant au rendu HTML. Il faut bien comprendre que le formulaire est un objet contenant des objets
éléments, qui eux mêmes contiennent des validateurs et/ou des filtres.
Accessoirement un objet formulaire peut aussi contenir d'autres objets formulaires dans le cas de formulaires
multiples (plusieurs pages, ou décomposés).
La présentation visuelle du formulaire et de ses éléments est déléguée entièrement à des objets décorateurs (si
toutefois vous décidez de présenter le formulaire, cela reste factulatif car un formulaire peut servir juste pour de la
validation de données, même si ce cas est plutôt rare). On en déduit donc qu'à la fois Zend_Form, mais aussi tous
les Zend_Form_Element, vont contenir des objets décorateurs.

Les décorateurs de l'objet Zend_Form vont gérer le rendu HTML du formulaire, hors éléments. Les éléments eux,
possèdent chacun leurs décorateurs (généralement ils ont tous les mêmes, car on apprécie qu'un formulaire ait un
look "uni", mais ce n'est pas obligatoire).
Le principe de la décoration est basé sur l'embelissement successifs d'objets. Chaque décorateur va rajouter du
contenu au contenu précédent.
Voici, par défaut, les décorateurs que possèdent les Zend_Form_Element :

la décoration par défaut des éléments


$elements->setDecorators(array('ViewHelper'),
array('Errors'),
array('Description', array('tag' => 'p', 'class' => 'description')),
array('HtmlTag', array('tag' => 'dd')),
array('Label', array('tag' => 'dt')),
);

Notez que les méthodes et les tableaux permettant la gestion des décorateurs, respectent la même logique que ceux
permettant de créer et d'ajouter des éléments dans un formulaire.
Pour bien comprendre la technique, lors de la demande du rendu visuel de l'élément, il se passera ceci :

$label->render(
$htmlTag->render(
$description->render(
$errors->render(

- 59 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

$viewHelper->render('')))));

Les décorateurs reçoivent en paramètre l'élément sur lequel ils s'appliquent, et décorent (présentent visuellement)
chacun un petit morceau de cet élément.
Le décorateur ViewHelper utilise l'aide de vue Zend_View_Helper_* qui se charge de rendre l'élément de formulaire
avec ses attributs éventuels ("input", "select" etc...).
Puis ensuite, le décorateur Errors demande à l'élément si il possède des messages d'erreurs (renseignés par ses
éventuels validateurs), si c'est le cas, alors il les encapsule dans des structures "ul li". Ensuite si l'élément possède
une description, celle-ci est décorée par le décorateur du même nom, et ainsi de suite ...

Chaque décorateur se charge donc du rendu d'un "petit bout" de ce qui compose un élément (l'élément, ses erreurs,
sa description ...), et ce rendu est effectué de différentes manières.
Soit avant le rendu des décorateurs précédents, soit après, soit à la place de celui-ci, le remplaçant (c'est le cas pour
le premier décorateur : ViewHelper effectue son rendu à la place de rien du tout. On peut aussi citer HtmlTag qui
effectue son rendu à la place du rendu précédent, mais en l'encapsulant d'un tag HTML).

le rendu HTML par défaut d'un élément text


<?php
array('ViewHelper'),
array('Errors'), // APRES
array('Description', array('tag' => 'p', 'class' => 'description')), // APRES
array('HtmlTag', array('tag' => 'dd')), // REMPLACE
array('Label', array('tag' => 'dt')), // AVANT
// -------------------------- ?>
<dt id="email-label"><label for="email" class="required">email :</label></dt>
<dd id="email-element">
<input type="text" name="email" id="email" value="" size="30">
<ul class="errors"><li>Value is required and can't be empty</li></ul>
<p class="description">Une description exemple ici</p></dd>

Rappelez vous bien, chaque décorateur agit avant, après ou à la place du contenu décoré précédent. Cela demande
certes une petite gymnastique au début, mais on prend très vite l'habitude.
On peut en déduire que si on ajoute par exemple une description à un élément, mais que l'on affecte aucun décorateur
permettant de gérer cette description dans le rendu HTML final, alors celle-ci n'apparaitra tout simplement jamais.
C'est le cas du formulaire Zend_Form qui peut posséder une description, mais qui par défaut n'enregistre aucun
décorateur permettant de l'afficher.

Aussi, chaque décorateur fournit avec #Zend_Form est unique. L'un va remplacer du contenu, l'autre va chercher
tel composant de l'élément (la description par exemple) et le faire suivre dans le flux HTML, d'autres encore sont
fantômes comme 'Errors' qui surveille les messages d'erreurs, mais s'il n'y en a pas, ne font rien, donnant l'impression
qu'ils sont absents.
Les décorateurs par défaut suffisent en théorie, car ils sont facilement manipulables par CSS, et une petite feuille
de style bien menée permet une mise en page complète. Cependant, si on souhaite changer les décorteurs, il faut
noter quelques astuces :

1 Les décorateurs agissent dans l'ordre dans lequel ils sont enregistrés, et vous aurez compris j'espère que cet
ordre est extrêmement important.
2 Il n'est pas possible d'ajouter un décorateur dans la pile, à une place spécifique. L'ajout d'un décorateur le
met forcément à la suite des autres.
3 Un élément peut ne posséder aucun décorateur, il sera alors totalement invisible, mais comptera dans le
processus de validation et sera bien présent dans la mémoire
4 Il est possible de créer ses propres décorateurs, une interface et une classe abstraite existent. Cela permet
de faire réellement tout ce que l'on souhaite

Exerçons nous : nous souhaitons que nos éléments (nous verrons le formulaire lui-même plus tard) soient présentés
dans des cellules d'un tableau HTML.
Même si chaque élément peut être décoré de manière individuelle, on a très souvent recours aux mêmes décorateurs
pour tous les éléments. Ainsi, nous pouvons les factoriser et les passer à chaque élément.

- 60 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Suivez bien
1 Rendre l'élément de formulaire (qu'on ne peut entouré tout de suite)
2 Entouré le tout d'un tag "td"
3 Rendre le label de l'élément entouré d'un tag 'td' (on peut l'entourer celui-ci)
4 Entourer le tout d'un tag 'tr'

Afin d'arriver au résultat HTML suivant, pour tous les éléments :

<tr><td id="email-label"><label for="email" class="required">email :</label></td>


<td>
<input type="text" name="email" id="email" value="" size="30"></td></tr>

$elementDecorators = array(array('ViewHelper'),
array('Errors'),
array('decorator'=>array('1er'=>'HtmlTag'),'options'=>array('tag'=>'td')),
array('label',array('tag' => 'td')),
array('decorator'=>array('2eme'=>'HtmlTag'),'options'=>array('tag'=>'tr')));
$email->setDecorators($elementDecorators);
// et ainsi de suita pour tous les éléments

Là encore, tout en une seule fois nous oblige à utiliser plein de tableaux, mais structurés visuellement cela reste
compréhensible.
Il aurait exister plein de manières de faires différentes encore, en utilisant les objets Zend_Form_Decorator_*
configurés un à un par exemple. Tout ceci ressemble fort à la gestion de l'ajout d'éléments dans le formulaire que
nous avons déja vu ensemble dans les chapitres précédents.
Une technique agréable consiste à demander au formulaire d'utiliser tels décorateurs pour tous les éléments y étant
déja enregistrés :

Demander au formulaire d'affecter des décorateurs à tous les éléments le composant


$form->setElementDecorators($elementDecorators);

On peut aussi demander à la création d'un élément, de ne pas lui faire charger ses décorateurs par défaut, il est donc
"nu" dès la création, il faut pour celà lui passer l'option 'disableLoadDefaultDecorators' à true.
Concernant Zend_Form lui-même, il possède aussi des décorateurs permettant donc de rendre le formulaire en
question. Voici les décorateurs par défaut :

$this->addDecorator('FormElements')
->addDecorator('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form'))
->addDecorator('Form');

'FormElements' est spécial : il retourne le rendu des éléments décorés au auparavent.


Nous souhaitons donc ce rendu, remplacé par le contenu d'un tag HTML entouré autour de lui, remplacé par le
contenu d'une balise "form" entouré autour de lui.
Pour continuer notre mise en page en tableau d'exemple, nous devons utiliser :

mise en page en tableau du formulaire lui-même


<?php
$formDecorators = array('FormElements',
'Form',
array('decorator'=>array('1er'=>'HtmlTag'),'options'=>array('tag'=>'table')),
);
$form->setDecorators($formDecorators);

- 61 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

XII-D - Autres fonctionnalités

#Zend_Form regorge d'autres fonctionnalités. Par exemple pour traduire les messages d'erreurs reçus par les
validateurs des éléments, c'est aussi simple que d'enregitrer dans le registre Zend_Registry un objet de type
Zend_Translate (configuré).
Aussi, Zend_Form supporte les sous-formulaires Zend_Form_SubForm. Il devient extrêmement simple de gérer des
formulaires en plusieurs parties, chacun réutilisable n'importe où.
Chaque élémént peut aussi faire partie d'un "groupe d'affichage" ("DisplayGroup"). Le groupe d'affichage possédant
des décorateurs, cela permet de regrouper visuellement des champs ensemble.
Zend_Form_Element_File permet de gérer l'upload de fichiers de manière très simple.
Zend_Form_Element_Captcha simplifie aussi fortement la manipulation des CAPTCHAs.
L'intégralité d'un formulaire peut être configurée grâce à un seul (ou plusieurs) fichier INI, ou XML, grâce à l'utilisation
astucieuse de Zend_Config
Zend_Form offre aussi des options intéréssantes concernant les champs sous forme de tableaux (avec des [] ).
Si vous souhaitez utiliser vos propres classes comme décorateurs ou éléments, Zend_Form possède en lui
Zend_Loader_Pluginloader pour gérer vos objets.

XII-E - Etendre Zend_Form

Plus que tout autre composant, Zend_Form doit être étendu car il a été fait pour.
Pour vous donner une idée, rendez-vous sur l'atelier Zend Framework prévu à cet effet

- 62 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

XIII - Zend_Application

Même si les liaisons avec le système MVC de #Zend_Application sont nettes, comme toujours ce composant peut
tout à fait être utilisé hors modèle MVC de Zend Framework.
A quoi sert-il ? Voici les objectifs de #Zend_Application :

1 Uniformiser la création des objets du framework et leur configuration


2 Enregistrer Zend_Loader_Autoloader automatiquement
3 Configurer tous les paramètres PHP.INI simplement (ini_set)
4 Gérer le changement d'environnement (prod/dev) facilement
5 Faciliter la configuration de MVC
6 Gérer les dépendances entre les objets

#Zend_Application sert surtout à créer un moyen unique de configurer (presque) tous les objets du ZendFramework,
puis de les stocker à un endroit qui permet plus tard de les retrouver simplement.
Pour celà nous allons voir plusieurs classes :

1 Zend_Application, qui sert à configurer l'environnement global


2 Zend_Application_Bootstrap_*, qui sert à configurer les objets du framework que nous voulons utiliser
3 Zend_Application_Resource_*, qui sert à uniformiser la configuration des objets du framework que nous
voulons utiliser

Voici un cas simple pour une première approche. Nous allons utiliser la classe Zend_Application, seule.

Le fichier lu par Zend_Application (application.ini ici)


[app]
phpsettings.soap.wsdl_cache_enabled = 1

includepaths[] = APP_PATH "/model"


includepaths[] = APP_PATH "/foo/bar"

autoloadernamespaces[] = "Form"
autoloadernamespaces[] = "Table"

[dev:app]
phpsettings.display_errors = 1

[prod:app]
phpsettings.display_errors = 0

Utilisation de Zend_Application
define ('APP_ENV', 'dev');
define ('APP_PATH', __DIR__ . '/application');

require_once 'Zend/Application.php';
$app = new Zend_Application(APP_ENV, APP_PATH .'/config/application.ini');

Rappel : les fichiers ini, lorsque lus par PHP, savent importer les constantes PHP définies
en leur sein.
Nous avons placé le fichier ini sous APP_PATH/config/application.ini, pour l'exemple.

Voyez la simplicité : nous créons simplement un objet Zend_Application, en lui passant 2 paramètres : une section
à charger puis un fichier de configuration qui comporte ces sections.
Le fichier de configuration utilisé ici est au format .ini mais un fichier .xml ou même .php peut aussi fonctionner. En
interne, Zend_Application utilisera le bon objet Zend_Config_* pour pouvoir lire ce fichier là : nous n'avons donc pas
besoin de créer un objet Zend_Config_* à la main.

- 63 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Dès sa création, l'objet Zend_Application va utiliser Zend_Config pour lire le fichier de configuration et va
immédiatement prendre les mesures suivantes :

1 Utiliser set_include_path() avec tous les chemins déclarés dans les includepaths[]
2 Enregistrer Zend_Loader_Autoloader
3 Utiliser Zend_Loader_Autoloader::registerNamespace() avec tous les préfixes déclarés dans les
autoloadernamespaces[]
4 Utiliser ini_set() avec tous les paramètres utilisés derrière phpsettings

Toutes les clés sont insensibles à la casse. Nous aurions pu écrire


AuToLoaDerNameSpaces[] = "Foo" , en revanche, n'oubliez pas les "s" des pluriels sur
les clés, sinon ça ne fonctionnera pas.

Notez que nous avons déja une uniformisation : la simple création de l'objet Zend_Application exécute toutes ces
actions pour nous.

XIII-A - Configurer ses objets (ressources)

Nous allons maintenant utiliser la partie "Bootstrap" de #Zend_Application. Celle-ci va nous permettre de
configurer les objets nécessaires à une application : un Zend_Log, un Zend_Db, un Zend_Session ... Le tout de
manière uniformisée, ce qui est fort appréciable. Pour cela, nous allons devoir déclarer une classe, qui étend
Zend_Application_Bootstrap_BootstrapAbstract et la préciser à Zend_Application grâce au fichier de configuration.

suite de application.ini
[dev:app]
bootstrap.class = DvpBootstrap
bootstrap.path = APP_PATH "/Boot.php"

La classe DvPBootstrap, se trouvant dans APP_PATH/Boot.php


<?php
class DvPBootstrap extends Zend_Application_Bootstrap_BootstrapAbstract
{
public function run()
{
// code pour lancer l'application, laissez vide sinon
}

protected function _initLog()


{
// cette méthode va configurer un objet Zend_Log
}
}

Cette classe doit déclarer une méthode run(). Celle-ci est sensée contenir le code qui servira à lancer
votre application. Dans le cas où vous utilisez le modèle MVC de Zend Framework, basé sur le
composant #Zend_Controller, il est possible d'étendre Zend_Application_Bootstrap_Bootstrap qui étend elle-même
Zend_Application_Bootstrap_BootstrapAbstract, mais définit sa méthode run() afin qu'elle lance le contrôleur frontal.
Ensuite, votre classe de bootstrap devra comporter autant de méthodes protégées nommées _init****() que d'objets
différents à configurer.
Dans la classe de bootstrap, vous avez accès aux paramètres de configuration (fichier ini dans notre cas) au moyen
des méthodes getOption() ou getOptions().

suite de application.ini : configuration de nos objets


[dev:app]
db.params.charset = utf8
db.adapter = pdo_mysql
db.params.unix_socket = /var/run/mysqld/mysqld.sock
db.params.username = julien

- 64 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

suite de application.ini : configuration de nos objets


db.params.password = secret
db.params.dbname = dvp

view.doctype = XHTML1_STRICT
view.encoding = "utf-8"
view.basePath = APP_PATH "/mesvues"

log = APP_PATH "/logs/log.log"

Suite de la classe DvPBootstrap


<?php
class DvPBootstrap extends Zend_Application_Bootstrap_BootstrapAbstract
{
public function run()
{
// code pour lancer l'application, laissez vide sinon
}

protected function _initLog()


{
$log = new Zend_Log(new Zend_Log_Writer_Stream($this->getOption('log'));
return $log;
}

protected function _initDatabase()


{
$options = $this->getOption('db');
$db = Zend_Db::factory($options['adapter'], $options['params']);
return $db;
}

protected function _initVue()


{
return new Zend_View($this->getOption('view'));
}
}

getOption($param) prend en paramètre une clé du tableau géré par Zend_Config, et la retourne. S'il s'agit d'une
feuille: c'est une donnée sous forme de chaine, s'il s'agit d'une branche: c'est un tableau. Ceci est le fonctionnement
de Zend_Config (Rappel: Zend_Application et le bootstrap utilisent Zend_Config).
Dans chaque méthode _init***(), nous configurons donc un objet en tirant sa configuration via getOption(), puis nous
le retournons (c'est important).

Voyez comme la configuration de chaque objet est bien rangée dans une méthode, alors que le fichier contenant
Zend_Application est lui toujours aussi simple. Faisons en sorte qu'il charge maintenant nos objets en passant dans
les méthodes déclarées dans le bootstrap :

Le bootstrap sait maintenant récupérer nos objets


define ('APP_ENV', 'dev');
define ('APP_PATH', __DIR__ . '/application');

require_once 'Zend/Application.php';
$app = new Zend_Application(APP_ENV, APP_PATH .'/config/application.ini');

$boot = $app->getBootstrap(); // récupération de l'objet bootstrap


$boot->bootstrap('vue'); // déclenchement de la méthode _initVue()
$view = $boot->getResource('vue'); // récupération de la vue fraichement configurée

Sur un objet de bootstrap, bootstrap('foo') permet de passer dans la méthode _initFoo() de sa classe. Notez bien
que le nom de la méthode et de la ressource demandée sont liés.
Zend Framework partira de 'foo', mettra la première lettre en majuscule 'Foo', puis rajoutera '_init' devant, avant de
lancer la méthode : à ce stade, il faut qu'elle existe.

- 65 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

getResource('foo') est une méthode du bootstrap qui permet de récupérer l'objet (appelé 'resource') 'foo', configuré
dans la méthode _initFoo(), à condition que celle-ci le retourne bien (n'oubliez pas le return)
Vous devez avoir lancé la méthode bootstrap('foo') avant l'appel à getResource('foo') sinon le retour de la méthode
getResource() vaudra null.

Dès lors, à chacun de s'organiser comme il le sent, vous avez la procédure à suivre pour configurer vos objets et
les récupérer. Notez qu'appeler la méthode bootstrap() sans aucun paramètre fera que le bootstrap chargera toutes
les ressources (toutes les méthodes _init**()) dans l'ordre dans lequel elles apparaissent. Vous pourrez donc par la
suite les récupérer avec getResource() sans problème.

XIII-B - Utiliser les plugins pour configurer ses objets (ressources)

Les méthodes _initMonobjet() ont un inconvénient tout de même : il est difficile de les faire évoluer, et un bootstrap
va vite se remplir avec beaucoup de méthodes de ce type là.
Il existe une solution, intégrée dans le composant #Zend_Application, c'est une autre manière de configurer et
charger ses objets (ressources), plus fléxible.

Plutôt que d'utiliser des méthodes pour configurer nos ressources, il est possible d'utiliser des classes qui étendent
Zend_Application_Resource_Abstract, et il en existe déja pas mal pour les objets les plus utilisés de Zend Framework.
Nous appellerons ces classes des "plugins".
Afin d'indiquer au bootstrap qu'il doit utiliser un plugin pour charger une ressource, plutôt qu'une méthode
_initMaRessource(), il convient de suivre ces règles :

1 Ne pas indiquer de méthode _initRessource() dans la classe de bootstrap


2 Préfixer toute configuration de la ressource par "resource." dans le fichier .ini

Le premier point n'est pas obligatoire, mais il est recommandé de le suivre; des manipulations supplémentaires sont
nécessaires dans le cas contraire.
Dès que le bootstrap va rencontrer le mot "resource." dans le fichier .ini, il va chercher un objet
Zend_Application_Resource_ correspondant et l'utiliser. Voyons celà :

application.ini
[dev:app]
resources.db.params.charset = utf8
resources.db.adapter = pdo_mysql
resources.db.params.unix_socket = /var/run/mysqld/mysqld.sock
resources.db.params.username = julien
resources.db.params.password = secret
resources.db.params.dbname = dvp

classe DvPBootstrap
<?php
class DvPBootstrap extends Zend_Application_Bootstrap_BootstrapAbstract
{
public function run()
{
// code pour lancer l'application, laissez vide sinon
}
// PAS de méthode <i>initDb()
}

Zend_Application en action
<?php
// rien ne change par rapport à avant ...
$boot = $app->getBootstrap();
$boot->bootstrap('db');
$db = $boot->getResource('db');

- 66 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

Voyez plutôt. bootstrap() et getResource() sont utilisées avec une resource nommée "db", mais il n'existe pas de
méthode _initDb() dans la classe du bootstrap.
Dans le fichier .ini, la ressource s'appelle "db", et puisqu'elle est préfixé sur mot-clé "resource.", l'objet de bootstrap
sait qu'il faut qu'il utilise la classe Zend_Application_Resource_Db pour configurer un objet du composant #Zend_Db.

Il existe actuellement (cela évolue) dans Zend Framework, les classes Zend_Application_Resource_XXX avec XXX
= Db, Session, Log, Cache, Locale, Translate, FrontController, Module, CacheManager, Dojo, Layout, Mail, MultiDb ...
Renseignez-vous sur le manuel pour savoir comment elles fonctionnent, mais la plupart du temps les paramètres
utilisés sont ceux de l'objet (ressource) à configurer. Voici un exemple :

resources.db.params.charset = utf8
resources.db.adapter = pdo_mysql
resources.db.params.unix_socket = /var/run/mysqld/mysqld.sock
resources.db.params.username = julien
resources.db.params.password = secret
resources.db.params.dbname = dvp

resources.view.doctype = XHTML1_STRICT
resources.view.encoding = "utf-8"
resources.view.basePath = APP_PATH "/modules/site/views"

resources.log.writer.writerName = Stream
resources.log.writer.writerParams.stream = APP_PATH "/logs/log.log"

resources.session.use_cookies = on
resources.session.name = my_app
resources.session.save_path = tcp://127.0.0.1:11211
resources.session.save_handler = memcache

- 67 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/
Présentation du Zend Framework - Premiers pas par Julien Pauli (Tutoriels, conférences PHP) (Blog)

XIV - Conclusions

Voilà, cet article de présentation est terminé, mais il sera complété et mis à jour de temps à autre. mon blog pourra
vous éclairer, ou alors mon repertoire developpez, sans compter sur notre section Zend Framework.
Notez que j'ai facilité l'accès aux codes d'exemple dans cet article. Je n'ai pas pris en compte les aspects sécurité,
j'ai fait des sous-entendus, je n'ai presque pas utilisé les exceptions, etc. Dans des cas concrets, il y a bien entendu
plus de travail à fournir que ce qui est écrit ici.
Ici, c'est un avant-goût, assez complet tout de même, de Zend Framework, qui comporte beaucoup d'outils et son
développement est en perpétuelle évolution.
J'espère vous avoir un peu éclairé sur ce Framework qui représente un atout considérable à maitriser, dans tout
développement web PHP, en particulier dans un monde professionnel, ou le travail en équipe règne. Zend a signé
là un outil vraiment très robuste, dans la lignée de PHP lui-même.

- 68 -
Copyright © 2007 - Julien Pauli. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
http://julien-pauli.developpez.com/tutoriels/zend-framework/presentation/