Zend Framework - Architecture MVC (Model-View-Controller)

Utilisateur de WordPress, j'ai souvent été limité sur deux fronts que j'estime fondamentaux : la gestion multi-lingues (tous mes contacts non-francophones ne peuvent pas lire aisément ce blog), et l'obligation de passer par des plugins plus ou moins complets pour interfacer des APIs telles que celle de Flick'R. Par conséquent, j'ai décidé de recoder ma propre plate-forme de blog, afin de découvrir ce Zend Framework, et de répondre à tous mes besoins. Ca va être long et fastidieux, mais le résultat sera, assurément, prometteur. :)

Cet article est le premier d'une longue série qui consistera en la fabrication d'un blog s'appuyant sur le Zend Framework. Dans ce premier billet, nous allons mettre en place l'architecture de base, s'appuyant sur le modèle MVC (Model-View-Controller), et afficher notre première page, statique. Nous progresserons étape par étape, étant donné la complexité de ce développement.

Présentation

Un framework est un ensemble de fonctions, de conventions et d'outils répondant à un seul but : augmenter la productivité du développeur. Zend a sorti dernièrement en version 1.7 le framework du même nom. Nous allons voir, petit à petit, l'utilisation de celui-ci, afin de mettre en place une nouvelle plate-forme de blog.

J'ai décidé de faire partager mon expérience en direct avec vous, fidèles lecteurs. La documentation présente sur Internet concernant le Zend Framework est en effet soit clairsemée (sur les différents forums ou autres blogs), soit extrêmement compliquée à lire (je parle notamment du guide de référence de Zend). D'où ma volonté de donner un condensé clair et concis des fonctionnalités de ce framework. Espérons que cela soit un pari réussi. :)

Commençons donc sans plus attendre la création du futur concurrent de WordPress ! ^^

Mise en place de l'architecture

Nous allons dans un premier temps mettre en place l'architecture de notre site. Suivons donc le modèle officiel de Zend, à savoir :

|_ application
| |_ config
| |_ controllers
| |_ forms
| |_ layouts
| | |_ scripts
| |_ models
| | |_ tables
| |_ views
| |_ scripts
|_ library
| |_ Zend
|_ public
|_ index.php

Cette arborescence peut paraître assez lourde, mais elle est très logique. Décrivons la petit à petit.

Le dossier application contiendra toute la partie logique de notre application. Elle se décompose en plusieurs sous parties :

  • config/ : il contiendra le(s) fichier(s) de configuration de notre application. Nous verrons en effet que le Zend Framework peut s'appuyer sur des fichiers *.ini afin de faciliter le déploiement entre développement et production.
  • controllers/ : on y mettra, ô surprise, tous les controllers de notre application.
  • forms/ : effectivement, nous y mettrons tous les formulaires.
  • layouts/scripts/ : dans ce dossier, nous mettrons tous les templates de page, contenant par exemple le header, le footer, le menu, etc.
  • models/ : nous placerons dans ce dossier la partie métier de notre blog. C'est ici que nous effectuerons les différents traitements : récupération des données, calculs éventuels, etc. Bref, c'est ce dossier qui nous occupera le plus longtemps.
  • models/tables/ : le Zend Framework a des fonctions d'ORM (Object Relational Mapping) : il s'agit d'une couche d'abstraction entre la table dans notre base de données et la partie métier. Par exemple, si nous rajoutons un champ dans notre table, celui-ci sera directement accessible dans notre classe, sans aucune intervention. Nous aurons besoin de ce dossier pour indiquer les liaisons entre nos différentes tables.
  • views/scripts/ : nous y placerons l'interface graphique de nos différentes parties, telle que la verront nos utilisateurs.

Le dossier library contiendra tous les fichiers propres au Zend Framework. Il serait possible de le mettre dans un dossier séparé du Document Root du serveur Apache, en modifiant la directive include_path de php.ini, mais par souci de simplicité, nous placerons le framework dans ce dossier.

Enfin, le dossier public sera le document root de notre blog. C'est ici (et ici seulement) que les internautes pourront consulter nos pages. On y placera nos feuilles de styles, nos images, etc. Bref, tout ce que l'internaute final aura le droit de visionner.

Afin de faire la liaison entre nos différents dossiers, et afin de faire fonctionner le framework, nous devrons aussi écrire un .htaccess dans le dossier public.

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]

RewriteRule ^.*$ /index.php [NC,L]

Je vous vois déjà vous affoler. On se calme, il n'y a rien de compliqué.

Première étape (et première ligne), on active l'URL Rewriting (réecriture d'URL).

Dans un second temps, on redirige toutes les requêtes pointant vers des fichiers ayant une taille réelle (-s, pour size), vers un lien symbolique (-l, pour link) ou vers un répertoire (-d, pour directory) vers le fichier original. Les options NC et L signifient respectivement de ne pas se soucier de la casse, et de considérer cet ordre comme le dernier (au cas où l'une des conditions sus-citées soit remplies). Je vous invite, pour plus d'informations, à consulter ce site.

Si jamais il s'agit d'une requête virtuelle (comprendre, qui ne correspond à aucun fichier), alors on le redirige vers index.php, qui transmettra au framework notre requête.

Fichier principal : index.php

A présent, occupons-nous du code à proprement parler. Le code de ce framework vous paraîtra obscur dans un premier temps. Ne vous inquiétez pas, tout cela est tout à fait normal. Ce framework est relativement conséquent, et il vous faudra un certain temps avant de vous habituer à toutes les fonctions. N'hésitez donc pas à faire des aller-retours réguliers entre
votre IDE et votre navigateur branché sur ce blog ou sur toute autre documentation. ;)

Commençons tout d'abord par une première portion de code, à inclure dans index.php :

define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application/'));
set_include_path(
    APPLICATION_PATH . '/../library'
    . PATH_SEPARATOR . get_include_path()
);

Nous définissions tout d'abord une constante APPLICATION_PATH correspondant au dossier application. Puis, nous l'incluons dans le include_path de PHP, afin de pouvoir inclure aisément tout fichier de la librairie Zend.

Deuxième étape :

require_once "Zend/Loader.php";
Zend_Loader::registerAutoload();

Cette étape est un peu l'équivalent de la fonction __autoload, qui permet de charger automatiquement un fichier lorsque l'on rencontre une classe qui n'existe pas. Ces deux lignes permettent d'indiquer au Zend Framework de charger automatiquement toutes les classes dont il a besoin. Cela nous évitera d'avoir à alourdir notre code avec des require pénibles à écrire.

try
{
	require '../application/bootstrap.php';
}
catch (Exception $exception)
{
	$buffer  = '<p>An exception has occured while bootstrapping the application.</p>';

    	if (defined('APPLICATION_ENVIRONMENT')	&& APPLICATION_ENVIRONMENT != 'production' )
    	{
    		$buffer .= '<h3>Message:<h3>';
    		$buffer .= '<p>'.$exception->getMessage().'</p>';
		$buffer .= '<h3>Stack trace:</h3>';
		$buffer .= '<pre>'.$exception->getTraceAsString().'</pre>';
	}

	echo $buffer;

    	exit(1);
}

Cette partie va tout d'abord inclure un fichier que nous rédigerons dans un court instant. Il nous permettra de définir différentes constantes et d'initialiser
différentes entités qui nous seront utiles dans toutes les étapes de la création de nos pages. Ce fichier pourra lever des exceptions. Si tel est le cas, nous vérifions que nous ne sommes pas en production (grâce à la constante
APPLICATION_ENVIRONMENT) avant d'afficher le message complet de l'exception. En effet, il est interdit, pour des raisons de sécurité, d'afficher des messages d'erreurs dans un environnement de production. Moins l'utilisateur en sait, mieux c'est.

Finissons notre fichier index.php avec cette ligne :

Zend_Controller_Front::getInstance()->dispatch();

Celle-ci va s'occuper de rediriger toutes nos requêtes vers les bons fichiers. Nous reviendrons dessus un peu plus tard.

Design pattern MVC

Le design pattern MVC (ou Model-View-Controller), va nous permettre de séparer notre application en trois couches indépendantes, afin d'améliorer la pérennité et la maintenabilité de notre application. Ces trois couches sont décomposées comme suit :

  1. Model : couche d'accès et de manipulation des données
  2. View : interface homme-machine, typiquement le design de nos pages Web
  3. Controller : sécurisation des saisies utilisateurs, communication entre la vue et le modèle, gestion de la trajectoire de l'internaute, etc.

La partie la plus importante est le contrôleur. C'est lui qui se chargera de rediriger la requête de l'internaute vers la partie concernée du modèle. Le modèle et la vue ne communiqueront jamais directement. Nous devons garder cette remarque en vue durant tout le processus de développement.

Bootstrap

La première ligne va nous permettre de définir une constante indiquant dans quel environnement nous nous situons.

define('APPLICATION_ENVIRONMENT', 'development');

Nous indiquons donc, vu que nous sommes en local, que nous avons face à nous une version de développement. Lors du basculement de notre site vers notre environnement de production, il suffirait de changer cette constante en production pour éviter l'affichage des erreurs (notamment celle de l'exception de index.php).

$frontController = Zend_Controller_Front::getInstance();
$frontController->setControllerDirectory(APPLICATION_PATH . '/controllers');
$frontController->setParam('env', APPLICATION_ENVIRONMENT);

Nous définissons à présent l'emplacement du dossier de nos classes Controllers, et rappellons au frontController l'environnement dans lequel nous sommes actuellement. Le contrôleur frontal va récupérer l'URL de la page courante afin de redistribuer les responsabilités entre les trois parties de notre modèle MVC (modèle, vue, contrôleur).

Zend_Layout::startMvc(APPLICATION_PATH . '/layouts/scripts');
$view = Zend_Layout::getMvcInstance()->getView();
$view->doctype('XHTML1_STRICT');

Puis, nous activons le modèle MVC. En effet, il est tout à fait possible d'utiliser le Zend Framework sans cette composante essentielle de tout développement moderne. Nous lui indiquons en paramètres le chemin d'accès de tous nos patrons visuels (ou layouts).

Enfin, nous récupérons la vue, et configurons nos pages pour s'afficher selon les normes XHTML Strict 1.0. Cette ligne s'occupera elle-même de gérer le doctype ainsi que tous les autres éléments auto-générés par le framework afin de coller aux dernières normes du W3C.

$configuration = new Zend_Config_Ini(
	APPLICATION_PATH . '/config/app.ini',
	APPLICATION_ENVIRONMENT
);

Nous récupérons ensuite la configuration de notre application, située dans le fichier du premier argument, et lui indiquons quelle section lire (ici, la section development). On obtiendra dans $configuration un objet contenant toutes les valeurs enregistrées dans notre fichier *.ini, nous évitant ainsi de le parser manuellement.

$registry = Zend_Registry::getInstance();
$registry->configuration = $configuration;

L'utilisation de variables globales est à éviter en PHP. En effet, on ne sait jamais trop quoi est défini où (je sais que cette phrase est à la limite de la francophonie, et sachez que j'assume entièrement (: ). Pour contrer cela, nous allons stocker toutes les variables dont nous avons besoin dans un espace mémoire : le registre Zend. C'est ainsi que nous pourrons réutiliser notre objet $configuration dans la suite de notre programme.

Enfin, on libère un peu la mémoire occupée par tous ces objets :

unset($frontController, $view, $configuration, $registry);

Fichier de configuration

Il ne nous reste plus qu'à créer notre fichier de configuration /application/config/app.ini.

[production]

[development : production]

[testing : production]

Pour l'instant, celui-ci ne contient aucun paramètre de configuration, mais uniquement les différentes sections propres à différents environnement : production, développement et de test. Nous le remplirons dans un prochain tutorial.

Contrôleur par défaut

Il est temps à présent de mettre en place le contrôleur par défaut. En effet, un contrat ne peut-être réalisé que si toutes les parties sont présentes. Invitons donc ce contrôleur à se joindre à notre projet. Pour ce faire, créeons le fichier IndexController.php dans le dossier controllers. Attention, il est très important de respecter les conventions de nommage : le fichier et le nom de la classe doivent être les mêmes (pour l'auto-load), et être suffixés de Controller. Sinon, c'est à vos risques et périls.

<?php

	class IndexController extends Zend_Controller_Action
	{
		public function indexAction()
		{
		}
	}

?>

Toutes les classes contrôleurs devront dériver de la classe mère Zend_Controller_Action, afin d'être considérées comme telles par le Zend Framework.

Nous avons défini ici une action par défaut : indexAction. Elle ne fait rien pour l'instant. Qu'est-ce qu'une action ? Pour répondre à cette question, petite explication sur le
fonctionnement de routage des URL par le Zend Framework.

Par défaut, toutes les URL sont du style : http://www.monsite.com/posts/read. En rouge, le contrôleur concerné. En vert, l'action à exécuter. Ainsi, le Zend Framework exécutera la méthode readAction de la classe postsController avec une URL de la sorte.

Si on ne spécifie rien, le contrôleur par défaut appelé sera IndexController et l'action par défaut indexAction. Toutes les méthodes d'actions sont suffixés par Action.

Essayons de lancer notre application. Nous tombons alors nez à nez avec un message d'erreur :

Uncaught exception 'Zend_Controller_Dispatcher_Exception' with message 'Invalid controller specified (error)'

Il nous indique simplement que le framework n'est pas parvenu à trouver le contrôleur error. Définissons le.

Contrôleur d'erreur

Afin de pouvoir gérer au mieux toutes les erreurs et afficher (ou non) le pourquoi du comment, le Zend Framework a besoin d'un contrôleur d'erreur. Comme vous vous en doutez, nous allons donc définir ErrorController dans le fichier /controllers/ErrorController.php.

<?php

	class ErrorController extends Zend_Controller_Action
	{
		public function errorAction()
		{
			$errors = $this->_getParam('error_handler'); 

			switch ($errors->type)
			{
				case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
				case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION: 

					// 404 error -- controller or action not found
					$this->getResponse()->setHttpResponseCode(404);
					$this->view->message = 'Page not found';
	                			break; 

           		default: 

           			// application error
      				$this->getResponse()->setHttpResponseCode(500);
       				$this->view->message = 'Application error';
       				break;
  			} 

			$this->view->env			= $this->getInvokeArg('env');
  			$this->view->exception 	= $errors->exception;
   			$this->view->request    	= $errors->request;
		}
	}

?>

Le framework a besoin de l'action error associé au contrôleur éponyme. Nous la définissons donc avant de s'en apercevoir dans un message d'erreur.

A l'intérieur, nous récupérons l'erreur depuis le gestionnaire d'erreur. Puis, selon le type d'erreur, nous indiquons soit que la page n'a pas été trouvée (si le contrôleur ou l'action est invalide), soit que c'est une erreur applicative. Puis, nous passons à la couche vue différentes variables.

Fichiers de vues

Relançons notre application afin de s'apercevoir d'un grand changement... Le message d'erreur n'est plus le même. (soupir). Courage, nous sommes presques arrivés à la fin de l'installation particulièrement ardue, il est vrai. :)

Uncaught exception 'Zend_View_Exception' with message 'script 'error/error.phtml' not found

Il nous indique ici qu'il n'a pas trouvé le fichier de la couche View correspondant à cette action. Et pour cause : chaque action doit être liée à un fichier dynamique (phtml) du nom de l'action, situé dans le dossier du nom du contrôleur. Créeons donc le fichier /application/views/scripts/error/error.phtml.

<h1>Erreur fatale</h1>
<p>?= $this->message ?></p>

<?php if ( $this->env == 'development' ): ?>

    	<h2>Exception :</h2>

    	<h3>Message :</h3>
	<p><?= $this->exception->getMessage() ?></p>

    	<h3>Stack trace:</h3>
    	<pre><?= $this->exception->getTraceAsString() ?></pre>

	<h3>Request Parameters:</h3>
	<pre><? var_dump($this->request->getParams()) ?></pre>

<? endif ?>

Nous affichons ici le message de l'erreur générée (le $this faisant référence ici à la couche vue), et, si nous sommes en développement, nous donnons des détails supplémentaires sur l'erreur.

On peut remarquer l'écriture <?=, qui est équivalente à <?php echo .

On retente de lancer notre application.

Uncaught exception 'Zend_View_Exception' with message 'script 'layout.phtml' not found

On ne désespère pas. Plus que le fichier /application/layouts/script/layout.phtml à créer, comme convenu dans notre bootstrap.

<?= $this->doctype() ?>
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<title>Bienvenue sur mon blog</title>
		<?= $this->headLink()->appendStylesheet('/css/global.css') ?>
	</head>
	<body>
		<?= $this->layout()->content ?>
	</body>
</html>

On récupère le doctype de notre layout, et mettons en place toute la structure de base de notre page, afin de la rendre valide. On en profite aussi pour inclure une feuille de style, grâce à la méthode headLink de notre objet view. Enfin, on affiche le contenu variable de notre page situé dans la balise body.

On relance une n-ième fois notre page. Et cela ne marche toujours pas. On remarque cependant que nous avons une page d'erreur bien plus sympathique, grâce au contrôleur d'erreur. Le message d'erreur est désormais :

script 'index/index.phtml' not found in path (/home/imagiweb/sites/blog/application/views/scripts/:./views/scripts/)

Que se passe-t-il ? Il recherche simplement la vue associée à l'action par défaut (index) du contrôleur par défaut (index). L'une des parties est manquantes, le contrat ne peut pas se réaliser. On crée donc ce fichier /applications/views/scripts/index/index.phtml.

<h1>Hello world!</h1>

Et, sous vos yeux ébahis et soulagés, votre page s'affiche enfin ! Ouf !

Conclusion

Comme nous venons de le voir, la mise en place du Zend Framework est particulièrement fastidieuse. Elle est donc réservée aux projets ambitieux, et on ne doit pas envisager l'utilisation d'un framework pour la mise en ligne de son CV par exemple. Cependant, les plus malins d'entre vous auront déjà fait une archive de toute cette architecture afin de la rendre prête à l'emploi pour d'autres projets. Et ils ont raison. Rien ne sert de réinventer la roue à chaque fois. :)

N'hésitez pas à m'apporter vos retours sur ce tutorial. Il est long, et peut amener pas mal d'erreurs, malgré tout le soin que je lui ai apporté. A noter que je me suis inspiré librement du
Quick start disponible sur le site officiel. Cependant, je l'ai assorti à ma sauce personelle, trouvant que certains passages sont un peu alambiqués pour au final pas grand chose.

Je vous laisse donc vous amuser un peu avec tout cela, et vous dis à dans quelques jours, le temps pour moi de rédiger le second pas à pas de cette série, dans laquelle nous relierons nos pages à notre base de données, pour afficher nos premiers billets.

Mots-clefs : , ,

7 réactions sur cet article.

  1. K'ao says:

    Et encore, là t'as encore rien vu =)

    Cela va faire 8 mois que je travaille avec, et j'en découvre tous les jours.

    J'ai envoyé un mail à Mauricio pour proposer comme projet la création d'un bon tutoriel sur ce framework (celui sur developpez est trop vieux, et peu performant...)

    En tout cas, bienvenue dans la communauté ZF. ;D

  2. Merci K'ao ! :)

    Pour ton projet, je suis de tout coeur avec toi. Puisse tu travailler pour la gloire de notre laboratoire adoré qu'est le labo Twitter... euh... Web and Dev. plutôt. :p

  3. Syndrael says:

    Perso je t'aurais plus orienté sur Symfony, tutos assez nombreux et le démarrage reste assez simple. Le souci avec Zend, c'est que souvent tu te rends compte que la 'surcouche' Zend est bien présente.. J'ai toujours un peu peur des trucs trop 'propriétaires'...

  4. Simon G says:

    Super job Jonathan ! Je suis toujours surpris de la qualité de ton travail (au niveau du PHP).

    Tous mes encouragements pour ta plate-forme de blogging. Je suis amoureux de WordPress, et je ne vois pas encore ses défauts. Mais cela viendra.

    Bon courage !

  5. Trouffman says:

    Salut Jonathan !

    Moi aussi ça fait un moment que je me tape des docs sur le ZF...

    Quoiqu'il en soit c'est fort intéréssant, je dirais simplement : je n'aime pas trop utiliser "<?= ". Je préfére utiliser "<? echo " même si c'est plus lourd à écrire : sur certaines config ça chie (si t'y as pas accès).

    Anyway, une gestion par modules (par exemple ton module 'blog', un module forum etc) pourrait être une bonne idée. ;)

    Avis perso : ça manque un site où tu pourrais "recupérer" des modules Zend ;) En gros avec une base comme un CMS et y ajouter des modules... ça serais pas mal mais vlà le taff :)

    Bye ;)

  6. Vi says:

    Le Zend Framework attire de plus en plus de monde; c'est une bonne chose de voir de plus en plus de tutos sur le sujet, merci à l'auteur de ce billet.

    Trouffman > certains ont déjà eu l'idée mais ont abandonné en cours de route : http://www.z-f.fr/forum/viewtopic.php?id=1641 . C'est une idée très intéressante mais qui demande un peu de travail effectivement :)

Ils ont fait un lien vers cet article :

  1. Zend Framework - Interagir avec une base de données MySQL | Jonathan Petitcolas

Réagissez sur cet article !