Archive pour octobre 2007

Utiliser une façace pour accéder au modèle

Samedi 27 octobre 2007

Parmi les design patterns que l’on trouve en programmation il en est un qu’on oublie un peu trop à mon avis c’est la notion de façade. Pour bien comprendre que quoi il s’agit un petit exemple sera plus parlant. Imaginé que vous travaillez dans un centre de conception automobile. Votre boulot consiste à concevoir toute la partie commande de la voiture. Vous allez collaborer avec les designers qui conçoivent l’habitacle et le tableau de bord (la vue) et avec les ingénieurs qui conçoivent la partie mécanique (le modèle ou métier). À vous de relier le tout (faire le contrôleur). Si les mécanos commence à vous parler de boulon de six ou de durite d’injection vous allez droit à la crise de nerf. Ce dont vous avez besoin c’est de savoir comment on fait marcher le tout. Par exemple comment accélérer. Que la commande d’accélération, action un régulateur électrique, ouvre un carburateur, ou action un injecteur de gasoil vous importe peut. Vous avez besoin de définir une API pour votre partie mécanique. Cette définition peut se présenter simplement par une documentation. Mais aussi par un point d’entrée unique qui vous cache la complexité du métier. Cela s’appelle une façade.
Vous ne vous êtes jamais penché sur le problème de savoir comment votre banquier traitait vos chèques. Vous fournissez au guichet le bordereau dûment rempli comme le prévois la façade et ce qu’il se passe après vos importe peu du moment que votre argent va sur votre compte.
C’est donc une pratique courante qui étrangement se perd lorsqu’on développe des logiciels. Bien souvent on fait ce qu’il y a à faire sans trop se demander s’il ne serait pas opportun d’isoler un sous ensemble derrière une API. En programmation à objet on encapsule ainsi des éléments. Mais rarement on pense que tout un ensemble pourrait être caché par une seule classe. La littérature sur ce sujet est pourtant vaste. Et utiliser une façade à bon escient est un gage pour l’avenir.

Encore un exemple pour comprendre pourquoi.

Sur L’A300 les commandes de vols étaient mécaniques. Pour les commandes moteur un levier au centre du poste de pilotage actionnait une série de cames qui finalement jouait sur les commandes du moteur : Directement. Les compagnies demandant à Airbus des moteurs différents il était nécessaire de changer une bonne partie de cette quincaillerie pour adapter le moteur à l’avion. Sur l’A320 l’ensemble des commandes à été dé-corrélées de leur support. Pour adapter un nouveau moteur il suffit alors de redéfinir l’interface.
Il en va de même de vos développements. Si vous arrivés à bien cloisonner vos sous-ensembles alors en changer une partie n’impliquera que des changements mineurs sur le reste, tant que l’interface ne change pas.

Utiliser une façade avec MVC

Le modèle MVC propose une séparation nette et précise dans votre application. Dans Zend Framework, l’interface entre la vue et le contrôleur est bien définie. L’interaction entre la vue et le Modèle est à prohiber. Mais l’interface entre le contrôleur et le modèle est complètement à votre charge. Utiliser une façade comme modèle revient à n’avoir qu’un seul est unique objet à qui le contrôleur peut s’adresser. Comme pour votre banquier (le guichet). Cet objet ne va rien faire lui-même il va juste vous mettre à disposition vos objets métiers. Mais au passage il va vous cacher la complexité de leur organisation. Le contrôleur peut alors s’occuper de son travail la logique applicative, pendant que le modèle lui s’occupe de la logique métier. D’un côté vous parlez de clients de facture, d’automobile, d’avion, de téléphone, de livre et que sais-je d’autre. De l’autre vous parlez de table, de base de données, d’annuaire ldap, de ressources xml, de services web etc.
Agissant ainsi vous allez être contrait de définir clairement les fonctionnalités que le modèle mets à disposition du contrôleur. Un peu de réflexion sur son travail n’est jamais mauvais en soit. De plus vous allez pouvoir définir toute l’interface sans avoir à l’implémenter. Ainsi le développement du contrôleur pour commencer sans attendre que le modèle soit prêt. Dans un travail en équipe c’est un bon moyen de répartir le travail. Cela permet de mettre en place toute la cinématique de l’application sans qu’elle ne fasse quoi que ce soit. La maitrise ouvrage peut alors la valider. De son côté le métier n’a pas à se préoccuper de savoir comment ses objets seront utilisées il suffit qui réponde à la définition de l’interface. Et enfin en cours de vie de l’application un pant entier peu changer dans le métier sans impacter le reste.
Un autre point intéressant dans cette c’est que le contrôleur accédant à son modèle au travers de la façade, peu importe le contenu de cette façade, il devient possible tout comme pour la vue de prévoit à l’avance la relation entre le contrôleur et le modèle. Pouvoir accéder à sa vue juste au travers d’un membre et bien pratique. Pourquoi ne pas profiter de la façade et faire de même. Le contrôleur demande les services à son modèle et fournis les valeurs à sa vue.

<?php
$this->view->clientList = $this->model->getClientListOfCurrentUser() ; 

Il serait intéressant de rendre l’attachement du membre model transparent pour le développeur comme l’est l’attachement de la vue. A fin de garder un comportement du framework le plus standard possible, j’ai décidé de rendre cet attachement optionnel et commandé par un paramètre. Comme on l’a vu dans un article précédent.
J’ai choisi le paramètre : « useModel »

Une première approche simple

Définir une classe Model au sein de l’application. Dans /appclication/Model.php,
Puis dans chaque contrôleur dans la méthode ini on ajoute

      if ($parameters = Fast_Registry::getParameters()) {
         if (parameters->fast->get('useModel', false)) {
            Zend_Loader::loadClass('Model');
            $this->model = new Model();
         }
      }

Reste au développeur à ajouter les méthodes spécifiques à son métier dans la classe Model. Peu importe ce qu’il mettra dans la méthode getClientListOfCurrentUser si celle-ci réponds au prototype alors le contrôleur vu plus haut sera fonctionnel.
Mais devoir intervenir sur tous les contrôleurs pour bénéficier de l’attachement du modèle, n’est pas plus pratique que de devoir aller le chercher lorsqu’on en a besoin.
Un attachement automatique et transparent est plus pertinent. Gardons dans un premier temps la classe model dans l’application et ajoutons un classe d’action générique qui aura pour but de collecter ce qui est commun aux différents contrôleurs. C’est ce que fait déjà Zend avec la classe Zend_Controller_Action. En faisant dériver cette classe de Zend_Controller_Action puis nos contrôleurs de cette classe nous aurons un endroit pour mettre toutes les parties communes.
Un petit souci : où placer cette classe ? En suivant la nomenclature Zend je serais tenté de l’appeler Application_Controller_Action et elle se trouverait alors dans le dossier /application/controller/ or ce dossier n’existe pas. L’ajouter alourdirait la hiérarchie sans améliorer la lisibilité. Nous aurions cote à cote les dossiers controller et controllers. La différence sémantique entre les deux est subtile. Controller est le dossier contenant les éléments propre au mécanisme de contrôle et controllers est le dossier contenant les contrôleurs de l’application. Pour ne pas alourdir la hiérarchie, j’ai décidé de mettre ma classe dans controllers. Elle s’appellera donc Applications_Controllers_Action. Je déplace les quelques lignes de la méthode ini de mes contrôleurs dans celle de la classe action et je fais dériver mes contrôleurs de la classe Action.

class IndexController extends Application_Controllers_Action

Si le paramètre useModel est à true alors tous mes contrôleurs ont le membre model.

Helpers et autre plugins

J’avoue sincèrement ne pas avoir eut le temps de me pencher sur cette approche. Il me semble possible de reproduire un comportement similaire de cette façon. Se sera peut être une évolution future.

Les modules et composants.

Si j’utilise des modules je vais pouvoir reproduire dans le module le fonctionnement décrit ci-dessus. Mes modules auront alors leur propre façade sur la partie métier qui les concerne. C’est plutôt pas mal. Mais comment faire si un module à besoin de tout ou partie du modèle d’un autre ? S’il utilise que le modèle d’un autre module je peu m’arranger pour qu’il prenne comme façade celle de ce module. Il suffit pour cela de bien définir le chargement de la classe. Mais s’il doit utiliser son propre modèle plus des morceaux de celui d’un autre je suis obligé de recopier une partie de cette façade. Dupliquer du code n’est jamais une bonne chose. Surtout lorsqu’il n’y a aucun changement entre les deux copies. De plus on perd alors l’intérêt d’une façade dont le but est justement de n’avoir qu’un point d’accès.
Pour répondre à cette problématique tout comme Zend l’a fait pour les vues, j’ai définis un classe modèle générale qui accepte des plugins, qui dans mon cas seront des Composants de modèle, des partie de modèle. Permettant aux contrôleurs ainsi de ne charger que les composants dont ils ont besoin. Chaque composant étant une façade sur un sous ensemble du modèle. Le remplacement d’un composant par un autre est ainsi facilités et l’ajout de composant dans l’application aussi. De plus les modules peuvent toujours définir leurs propres modèles, tout en laissant la possibilité à d’autres de les utiliser.
Reprenant le fonctionnement décrit plus haut j’ai cette fois définis une classe Action générale. Fast_Controller_Action qui dérive de Zend_Controller_Action. Mes contrôleurs ou Application_Contollers_Action peuvent maintenant dériver de celle-ci. C’est elle qui embarque le chargement de la classe Fast_Model dont une instance est attachée aux contrôleurs. Elle dispose d’une méthode addComponent qui prend comme paramètres le nom d’une classe composant et d’un chemin optionnel. Elle charge alors cette classe et ajoute ses méthodes au modèle. La classe modèle à fournir doit dériver de Fast_Model_Component. Un composant pouvant avoir besoin d’interroger le modèle un membre protégé est disponible.

Exemple d’utilisation

Parammeters.ini

useModel = true

Classe client

<?php
Zend_Loader::loadClass('Fast_Model_Component');
class Model_Client extends Fast_Model_Component
{
   public function getClientList($nameStart) {
       …
   }
}

Contrôleur

Zend_Loader::loadClass('Fast_Controller_Action');
class ClientController extends Fast_Controller_Action
{
   public function showListAction(){
      //charger le composant model_Client du dossier model de ce module
      $this->model->addComponent('Model_Client' , dirname(dirname(__FILE__)));
      $this->view->clientList = $this->model->getClientList() ;
   }
}

Il y a fort à parier que le ClientController devra utiliser ce composant dans la majorité des ses méthodes. Le plus simple est alors de le charger dans la méthode ini. Mais il est parfaitement possible de charger un composant pour tout le module et en même temps d’en avoir un autre qui n’est chargé que pour une action.

Zend_Loader::loadClass('Fast_Controller_Action');
class ClientController extends Fast_Controller_Action
{
   public function ini(){
      //charger le composant model_Client du dossier model de ce module
      $this->model->addComponent('Model_Client' , dirname(dirname(__FILE__)));
   }
   public function showListAction(){
      //charger le composant model_Article du dossier model de ce module
      $this->model->addComponent('Model_Article' , dirname(dirname(__FILE__)));
      $this->view->clientList = $this->model->getClientList() ;
      $this->view->articleList = $this->model->getArticleList() ;
   }
}

Notez au passage qu’il est très simple ici de faire un bouchon. Il suffit dans la méthode getClientList de retourner une valeur du type attendue sans brancher réellement celle-ci sur le vrai modèle. Lorsque le modèle est prêt les branchements sont relativement simple puis qu’il n’y a plus à chercher où sont utilisé les objets métier. Ils ne le sont qu’au travers de la façade.
A+JYT

Utiliser Le moteur de template de son choix

Vendredi 26 octobre 2007

Je fais partit de ces gens qui utilisent un moteur de template. Les raison en sont multiples. La première est qu’il est particulièrement compliqué dans un moteur de template de faire du traitement. Ainsi on n’est pas tenté de faire faire à la vue des choses qui ne lui sont pas dévolue.
La vue présente un point c’est tout. Au contrôleur de lui fournir la matière. À chacun son boulot. Il est parfaitement possible de faire de même avec le système que propose Zend_Framework. Mais il est tout aussi facile de mettre directement dans sa présentation quelque chose comme

<?php echo Zend_Db:: getInstance()->getMyUserName() ?>

Voir des choses encore plus complexes. Si la vue à besoin de myUserName c’est au contrôleur de lui donner. L’évolutivité et la maintenabilité de l’application en dépendent. Je n’ai pas été le premier à me pencher sur ce problème. Et force est de constater que les moteurs de templates on leur aficionados.
J’utilise le plus fréquemment, le moteur ETS (Easy Template System) c’est le moteur utilisé par Marcopoly entre autre. J’ai aussi usé de Smarty. Les deux sont relativement proches dans leur syntaxe. ETS est un moteur ultra léger (89 521 octets) il n’utilise pas de système de cache. Je développe des applications qui sont très dynamique et dans mon cas le cache est plus un handicap qu’un avantage. Ce n’est pas le cas de toutes les applications et utiliser un cache peut s’avérer efficace.

Comme je l’ai dis je travaille en équipe et suivant les projets un moteur ou un autre voire pas du tout est plus opportun. Je me suis donc demandé comment intégrer efficacement des moteurs à ZF. La littérature de ZF sur le sujet est plutôt légère et la solution proposée est totalement inefficace. Elle revient à demander au programmeur d’écrire autant de fichier phml comme on le ferait avec ZF sans template. Plus tout autant de templates plus du code pour relier le tout.
On trouve sur internet quelques implémentations plus probantes. Je dois à un collègue (Patrick Dubois) une première intégration d’ETS dans ZF qui avait l’avantage de ne pas avoir à changer une seule ligne dans son contrôleur. Mais pour y parvenir nous avions fait un méchant hack qui ne me paraissait pas viable. À la suite de cela nous avons trouvé sur internet une intégration de Smarty particulièrement bien faite. Mais qui elle nécessitait de changer le code du contrôleur.
Je trouvais la solution de Patrick séduisante et celle de Philippe Le Van (KitPage) élégante. Concilier les deux serait une bonne chose. Je dois vous avouer que j’ai déroulé ZF en pas à pas pour arriver à comprendre comment il fonctionnait en interne et particulièrement Zend_View. Après avoir échauffé les neurones. J’espère ne pas en avoir perdu de trop au passage. Je suis enfin arrivé à une solution. Qui a demandé une petite demi-journée de travail de mise au point. Dans la foulée, avec l’aide de Patrick (on pense mieux à deux têtes) nous avons intégré à ZF cinq moteurs dans une demi-journée.

Notre approche consiste à dériver Zend_View et à remplacer l’instance de cette dernière dans le contrôleur, par la notre. Pour y parvenir il va falloir respecter le travail de Zend. Lors de la phase préparatoire au lancement de l’action de votre contrôleur, ZF va instancier un objet Zend_View. Puis dans votre action vous allez lui indiquer les valeurs à afficher. Éventuellement vous donnez l’ordre à la vue d’effectuer un rendu, puis vous rendez la main au Front contrôleur qui lui va passer au rendu final. Donc si comme moi vous mettez toujours que le minimum dans votre action, votre code se contente de faire des affectations de valeurs dans la vue. N’indiquant pas de rendu dans l’action ZF va demander à la vue de rendre le modèle de vue correspondant au nom du contrôleur et de l’action. La proposition de Philippe Le Van nécessite d’appeler explicitement un rendu dans l’action. Pour ma part je serais content de conserver le fonctionnement par défaut de ZF.

Include ou main page ?

Une autre chose que je trouve pratique avec les templates c’est de pouvoir les imbriquer. Avec phtml tel que proposé par ZF pour avoir une application qui a toujours la même apparence et don seul le contenu change, il est nécessaire d’en passer par un include d’un entête et d’un pied de page. Solution simple qui existait bien avant php lui-même. Mais cette approche implique de gérer la cohérence entre ce qui est ouvert dans l’entête et doit être refermé dans le pied. Avec un modèle principal qui inclut le contenu on a un système de boites, qui est homogène. Ainsi tout ce qui est ouvert dans un fichier est fermé dans ce fichier. On peu résumer ces deux approches ainsi

<?php
include 'header.phtml';
<h2>contenu de ma page</h2>
include 'footer.phtml';

et

<html>
<body><h1>ma belle application</h1>
< ?php include 'content.phtml'; ?>
</body>
</html>

Incontestablement je préfère l’approche main page, qui comme on le voit peut très bien être mise en œuvre en phtml. Mais on a vu que ZF allait chercher à rendre le fichier controller/action.phtml. Si on adopte l’approche main page, on va être en contradiction avec ZF. Il va falloir adapter la vue pour qu’elle accepte de fonctionner ainsi tout laissant croire au contrôleur qu’elle fonctionne exactement comme une Zend_View.
En supposant qu’on y parvienne, on a introduit alors un nouveau problème. Comment faire si on a un rendu à faire qui n’utilise pas la même main page, ou qui n’en utilise pas du tout ? De ce côté-là inutile de fouiller la doc de ZF, il n’y a rien car Zend_View ne connais pas cette notion. Il sera alors nécessaire d’introduire une nouvelle fonctionnalité à la vue : setMainTemplate.

Un exemple Simple

En tout premier lieu, je vais essayer de faire une vue qui accepte la notion de main template, et qui respecte le fonctionnement de ZF. A savoir pas d’intervention dans le contrôleur et pas d’appel sauvage dans un fichier de script comme le propose ZF. On va garder exactement la même architecture les mêmes fichiers de script que ce que propose ZF mais au lieu d’avoir à inclure l’entête et le pied dans toutes les pages, on fournira un fichier main.phtml
Première étape : dériver de Zend_View

<?php
/**
 *
 * @author Jean-Yves Terrien
 *
 */

Zend_Loader::loadClass('Zend_View');
class Fast_View_Phtml extends Zend_View

Prévoyant plusieurs moteurs, il me fallait trouver une façon de les nommer. Se sera Fast_View_Engine. Ces vues ne sont pas les moteur de template mais l’interface entre ZF et le moteur qui lui doit rester inchangé. Il n’est pas question ni de modifier le code d’un moteur ni de modifier le code de ZF
J’ai dit que je voulais pouvoir écrire : include ‘content.phtml’; or mon content va dépendre du script à rendre. J’ai donc besoin d’une variable dans ma vue pour identifier ce script. J’ai aussi dit que je devais pouvoir changer de main template. Et au passage suivant le moteur mes fichiers ne doivent pas toujours avoir le même type phtml pour php tpl pour Smarty et html pour ETS. La façon d’écrire un include dépend du moteur de template. Je ne me suis pas penché sur tous les moteurs pour mettre en œuvre l’approche main template. Je l’ai préparé dans mes moteurs mais il faut pour smarty et phptal trouver comment on fait un include d’un fichier dont le nom est fournit par une variable. Dans le fichier joint vous trouverez donc des exemple avec ou sans main page en fonction du moteur de template.

   public $_content;
   public $_mainTemplate;
   protected $_suffix = 'phtml';

Arrivé là il me faut surcharger une seule méthode.

   /**
     * Includes the view script in a scope with only public $this variables.
     *
     * @param string The view script to execute.
     */
   protected function _run()
   {
      // récupère le chemin complet du template demandé
      $name = func_get_arg(0);
      // le template principal est considéré à la racine des templates
      // de l'application (ou du module).
      if (!isset($this->_mainTemplate)) $this->setMainTemplate(dirname(dirname($name)) . '/main.phtml');
      // initialiser ets
      // on indique à la quel est le template à inclure
      $this->_content = $name;
      // rendu de la page
     include $this->_mainTemplate;
   }

Un copier coller de la méthode _run de Zend_View, une petite adaptation et le tour est joué. On va chercher la main page à la racine des vues. La méthode _run reçoit en argument le chemin complet vers le script à rendre. Il suffit donc de remonter d’un répertoire pour se trouver à la racine des vues soit de l’application soit du module. Pour fixer la main page j’ai dit que j’allais ajouter une méthode setMainTemplate je l’ai donc utilisé. Comme pour la page principale il peut être utile de permettre à qui en aurait besoin de connaitre le suffixe utilisé. J’ajoute donc une méthode getSuffix et pour faciliter l’écriture des scripts avoir un membre qui donne le chemin d’inclusion n’est pas mal non plus. Ne nous privons pas. Voici la classe en entier.

<?php
/**
 *
 * @author Jean-Yves Terrien
 *
 */

Zend_Loader::loadClass('Zend_View');
class Fast_View_Phtml extends Zend_View
{
   public $_content;
   public $_templatesDir;
   public $_mainTemplate;
   protected $_suffix = 'phtml';

   public function setMainTemplate($main)
   {
      $this->_mainTemplate = str_replace(chr(92), '/', $main);
   }

   public function getSuffix() {
      return $this->_suffix;
   }

   /**
     * Includes the view script in a scope with only public $this variables.
     *
     * @param string The view script to execute.
     */
   protected function _run()
   {
      // récupère le chemin complet du template demandé
      $name = func_get_arg(0);
      // le template principal est considéré à la racine des templates
      // de l'application (ou du module).
      if (!isset($this->_mainTemplate)) $this->setMainTemplate(dirname(dirname($name)) . '/main.phtml');
      // initialiser ets
      // on indique à la vue quel est le template à inclure
      $this->_content = $name;
      $this->_templatesDir = dirname(dirname($name)) . '/';

      // rendu de la page
     include $this->_mainTemplate;
   }
}

Un exemple de main page

<html>
<body><h1>ma belle application</h1>
<?php include $this->_content; ?>
</body>
</html>

Ajouter la vue au Framework

Reste maintenant à indiquer au front contrôleur d’utiliser notre vue. ZF n’a rien prévu pour ce remplacement. Impossible de lui indiquer quelle classe utiliser comme vue. Impossible aussi d’ajouter des plugins comme pour les actions. La seule solution instancier la vue soit même et la donner au contrôleur. Cela se passe au démarrage.

Zend_Loader::loadClass('Fast_View_Phtml');
self::$_instance->_view = new $'Fast_View_Phtml' ();
$suffix = self::$_instance->_view->getSuffix();
$viewManager = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
$viewManager->setView(self::$_instance->_view)->setViewSuffix($suffix);

J’ai justement prévu dans mon front contrôleur la possibilité d’ajouter des éléments de ce type en fonction de la configuration. J’ai donc ajouté une entrée dans mon fichier de paramètre et l’ai ajouté le nécessaire dans mon front contrôleur.

   public static function setViewEngine ($name = null) {
      $className = ucfirst($name);
      if (null == $className) {
         $className = 'Zend_View';
         $suffix = 'phtml';
      } else {

         if (substr($className, 0, 10) != 'Fast_View_') {
            $className = 'Fast_View_' . $className;
         }
      }
      try {
         Zend_Loader::loadClass($className);
         self::$_instance->_view = new $className();
         self::$_instance->_templateEngine = strtolower($name);
         if (!isset($suffix)) $suffix = self::$_instance->_view->getSuffix();
         $viewManager = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
         $viewManager->setView(self::$_instance->_view)->setViewSuffix($suffix);
      } catch (Exception $e) {
         Zend_Loader::loadClass('Fast_Exception_View');
         throw new Fast_Exception_View('Invalid View Engine: '. $className);
      }
   }

Toujours une exception typée pour mieux gérer les problèmes et au passage un petit membre privé qui conserve le nom du moteur ça mange pas de pain.
Et dans la méthode run

      if ($parameters&&$config) {
         $engine = $parameters->fast->get('templateEngine', null);
         $controller = self::getInstance();
         Fast_Controller_Front::setViewEngine($engine);

C’est fini. Ma Fast_View_Phtml remplace Zend_View et utilise main.phtml. Les fichiers touchées sont donc parameters.ini pour le paramètre ‘templateEngine’, Fast_Controller_Front pour charger la vue, Fast_Exception_View, et Fast_View_Phtml.

D’autres moteurs

Maintenant tout est en place.
Pour ajouter Ets par exemple je crée le fichier Fast_View_Ets que je place dans le dossier library/Fast/View/. Il me faudra aussi garder quelque part le moteur ets lui-même. Je les places tous dans library/TemplatesEngines/
En gros la vue Ets va préparer le moteur et faire un appel à printt comme Zend_View fait un include pour lancer le rendu. Cette classe aura la charge de gérer tous ce qui relève du paramétrage du moteur. S’il y a besoin de paramètre de configuration elle peut obtenir les valeurs avec Fast_Registry.
Ets à besoin d’un arbre de valeur. On ajoute donc à la vue un membre pour les stocker : _ets on va utiliser les setters et getters de php pour affecter les valeurs tout comme le fait Zend_View on va aussi fournir la méthode assign qui a ses adeptes et qui existe aussi dans Zend_View. Et c’est dans la méthode run qu’on va faire le gros du travail.

   /**
     *
     * @param string The view script to execute.
     */
   protected function _run()
   {
      // récupère le chemin complet du template demandé
      $name = func_get_arg(0);
      // le template principal est considéré à la racine des templates
      // de l'application (ou du module).
      if (!isset($this->_ets->_mainTemplate))
         $this->setMainTemplate(str_replace(chr(92), '/', dirname(dirname($name)) . '/main.html'));
      // initialiser ets
      require_once("TemplatesEngines/Ets/Ets.php");
      // on indique à l'arbre de données d'Ets quel est le template à inclure
      $this->_ets->_content = str_replace(chr(92), '/', $name);
      $this->_ets->_templatesDir = dirname(dirname($name)) . '/';
      // rendu de la page
      printt($this->_ets, $this->_ets->_mainTemplate);
   }

Elle ressemble à celle de Phtml qui n’est qu’une copie de celle de Zend légèrement amélioré. La différence on charge le moteur (require) et on l’invoque (printt)
Vous trouverez dans le fichier joint Phtml (phtml script avec main page) Ets, Smarty et Phptal. Ajouter un moteur est devenu quelque chose d’abordable et je vous laisse le loisir d’ajouter le votre.
Une petite note à propos de Phptal : TAL est le système de template de Zope. Son moteur a été porté dans plusieurs langages. Il n’est pas très gros (en ko) mais il est très structuré au niveau de son code. Mais surtout il utilise des fichiers conforme xhtml la structure XML du document n’est pas altérée. Tous les éléments de tal étant dans un namespace. Du coup tous les templates TAL peuvent être édité par un designer web avec l’outil de son choix. Il est même possible d’embarque dans le modèle des données d’exemples. Imaginé que vous demandez à votre designer en chez de vous faire vos écran et qu’il doive présenter un tableau de donnée s’il l’édite sans aucune valeur dedans il ne pourra se faire une idée du rendu final. Il va donc naturellement mettre des lignes dans sa table. Avec TAL ces ligne ne sont pas gênantes Tal sait en tenir compte pour afficher les valeurs réelles de l’application à lors qu’il y a des lignes d’exemples dans la table. Bref il est très souple, propre et bien structuré. Je vous conseil d’y jeter un œil, même si vous n’envisagez pas l’usage d’un tel système.

Zend Layout

Pendant que je faisais cette intégration, Zend Layout est apparu. Je n’ai pas encore eu le temps de me pencher dessus. Mais à première vu cela me parait intéressant. Mais je ne peux en dire plus pour le moment.

Helper

Une autre approche est d’utiliser les helpers. Je n’ai pas nom plus eu le temps de tester la chose. Une chose est sure si je le fait je garderais en vue que l’utilisation d’un moteur ne doit en aucun cas nécessiter un changement quelconque dans mes contrôler. Ce n’est pas parce qu’on change de présentation qu’on doit changer la logique de l’application. C’est le principe de fondement de MVC. Et je ne veux pas le remettre en question.

Pour tester

A fin de vous permettre de tester je vous ai fait une copie de la chose. il faut pour la rendre fonctionnelle placer la librarie Zend dans le library.
vous pouvez alors jouer avec les paramettre debug et template dans parameters.ini

A+JYT

Effacer ses traces

Mardi 23 octobre 2007

Le sujet de ce propos sera beaucoup plus large de portée que mes articles sur les Zend_Framework. Que celui qui n’a jamais mis un petit echo dans sont code pour voir ce qu’il se passait me jette la première pierre.
C’est une pratique courante car simple à mettre en œuvre, et facile à comprendre. Mais voilà si on met une trace, il faut ensuite l’enlever. Dans un monde parfait, on ne devrait pas avoir besoin de mettre ainsi des traces dans son code. L’usage d’un débuggeur pas à pas, remplaçant avantageusement cette pratique. Reste qu’il n’est pas du tout évident de s’en servir, ni même de l’installer.
La pratique de la trace existant et étant bien ancrée, autant ne pas fermer les yeux dessus. Mettre une trace permet de répondre à deux problématiques : « mon code passe-t-il par là ? » et « Quelle est la valeur de cette variable à cet endroit ? » On le voit un simple écho ou print_r ou var_dump permet de faire cela. Lorsque les traces se multiplient il est intéressant de les repérer. On ne va plus alors faire un simple écho, mais on l’accompagnera d’un message permettant de savoir quelle trace à provoqué l’affichage. « État de ma variable avant la boucle : $mavar »
Comment lorsque j’ai quelques dizaines de milliers de lignes de code, retrouver toutes les traces pour les supprimer ? Un refactoring général ne permet pas d’obtenir le résultat car que ce soit echo print_r var_dump il est impossible à priori de savoir si c’est une trace ou un affichage.

Une fonction debug

Une première approche serait d’utiliser une fonction debug, ainsi toute les traces sont repérables, pour les supprimer il suffit de remplacer dans son code tous les debug par #debug et plus rien ne s’affiche. Au passage on peut définir cette fonction avec deux paramètres un message pour repérer la trace et un autre pour afficher la valeur. Je peux dès lors poser une trace ainsi

debug(' État de ma variable avant la boucle', $mavar);

ou

debug(' je suis passé ici');

Continuons car ça ne mange pas de pain. La lisibilité d’une trace est essentielle. Sinon elle ne sert à rien. Avoir un gros tas de valeur en vrac à l’écran n’aide pas beaucoup à comprendre. Si à la place d’un simple echo ou print_r, je procède ainsi :

echo '<pre>'.$message . ' => ' ;
print_r($variable) :
echo '</pre>';

Mon affichage va devenir beaucoup plus lisible. Le tout associé à une feuille de style est ça devient du luxe. Mais je suis gourmand. Il est parfois intéressant d’avoir des informations complémentaires comme le nom du fichier la ligne où à été posé la trace ou encore quelles sont les fonctions qui ont été appelées.
On le voit vite le fait d’avoir une fonction pour gérer ces traces permet rapidement d’en avoir plus pour peu.

Et les sessions alors ?

Que viennent faire les sessions dans les traces me direz-vous. C’est très simple. Le mécanisme de session de php ne peut fonctionner que si aucun élément n’a été transmit au client. Et une trace est justement une réponse au client. De même il est impossible de faire une redirection si une trace à été posée. C’est bigrement embêtant. Car on va immanquablement provoquer une erreur. Il n’y a rien à faire, la trace posée et transmise, ni l’ouverture de la session, ni les headers, ne fonctionneront. Le mieux que nous pouvons faire est de prévenir le développeur qu’il ne pourra exécuter ses appels. Un moyen simple est de définir une constante. Dès la première trace on définit une constante DEBUG_STARTED et ainsi on pourra en tenir compte. Quitte à avoir un mécanisme général pourquoi ne pas le mettre en place aussi pour supprimer tous les affichages de traces. Une constante pour dire à la fonction d’afficher ou non les trace. Ainsi un paramètre de configuration pour supprimer toutes les traces d’un coup.
Une classe statique
Une classe statique permet de répondre à tous ces besoins. L’indicateur d’affichage étant un membre statique et la fonction débug une méthode statique. Ainsi tout est regroupé dans une même unité fonctionnelle.
Je vous donne ma classe
La méthode Fast_Debug::displayDebug(true) ; active ou désactive l’affichage. Qui et inactif par défaut.
La méthode Fast_Debug::show(’mon message’, $var); affiche une trace.
Elle indique le nom du fichier et la ligne sur la quelle la trace à été posée.
Puis le message et si elle est fourni la valeur de la variable. Quelque soit le type de donnée elle l’affiche comme le fait un print_r avec un objet.
Puis elle affiche la trace de tous les appels de fonction avec le nom du fichier et la ligne d’appel.
Je l’ai intégré à mon chargeur de configuration et peut donc l’activer par un simple debug = true dans le fichier de configuration.

un exemple d’affichage

Dans GroupController.php:55
user => stdClass Object
(
    [usr_id] => 1
    [usr_ident] => juat6340
    [usr_name] => Terrien
    [usr_firstname] => Jean-Yves
    [usr_mail] => jyterrien@orange.com
)

    * Appel de : Adm_GroupController::showListAction()
      dans E:\Htdocs\Fast_Framework\library\Fast\Controller\Action.php:357
    * Appel de : Fast_Controller_Action::dispatch()
      dans E:\Htdocs\Fast_Framework\library\Fast\Controller\Dispatcher.php:78
    * Appel de : Fast_Controller_Dispatcher::dispatch()
      dans E:\Htdocs\Fast_Framework\library\Zend\Controller\Front.php:911
    * Appel de : Zend_Controller_Front::dispatch()
      dans E:\Htdocs\Fast_Framework\library\Fast\Controller\Front.php:117
    * Appel de : Fast_Controller_Front::run()
      dans E:\Htdocs\Fast_Framework\index.php:62

A+JYT

Le front Contrôleur

Lundi 22 octobre 2007

J’ai abordé précédemment la problématique du BootStrap et de la configuration. Pour la configuration l’essentiel est fait. Il sera peut être nécessaire d’adapter un peu la chose. Mais la structure est suffisamment solide pour supporter de telles évolutions. Quant au BootStrap il est resté aussi simple que je le souhaitais. Facile me direz-vous, j’ai botté en touche. J’ai donc un démarrage simplifié, un chargeur de configuration, mais dans tout ça rien qui dise à mon application comment l’utiliser.

Le chef d’orchestre

Le front contrôleur est un élément central dans le modèle MVC. C’est lui le grand chef d’orchestre. Son rôle est de distribuer le travail. Il va préparer le travail de l’application, distribuer les activités, vérifier la cohérence du tout et s’assurer que la demande aboutie bien à une réponse correcte. En tant que chef d’orchestre il est le mieux placer pour prendre en compte les paramètres de configuration générale et préparer les éléments nécessaires pour les rendre accessible à l’application.
Le front contrôleur de Zend_Framework est particulièrement adaptable. Mais pour cela il est nécessaire d’écrire beaucoup de code. Chose que je cherche justement à éviter. Je pourrais écrire mon propre front contrôleur comme je l’ai fait par le passé en php4. Mais celui de ZF est plutôt bien fait et je n’ai pas envie de m’en priver. Comme toujours la programmation par objet permet d’adapter une classe à son besoin. Un petit retour sur le BootStrap vous montrera que finalement vu de ce dernier mon front contrôleur n’est rien d’autre qu’un Zend_Controller_Front. Il ne fera rien d’autre que tenir compte de la configuration avant de lancer l’application.
Tout ce que vous trouvez comme paramétrage du contrôleur dans le BootStrap parce que dépendant de l’application l’application peut être chargé automatiquement dans la méthode run. Il suffit pour cela de définir un attribut de paramétrage

Un exemple

Par exemple l’authentification. Vos applications utilisent une authentification des utilisateurs, d’autre pas. Vous pouvez placer un paramètre auth à true ou false dans le fichier de paramètre. Et dans la méthode run en fonction de sa valeur utiliser un Zend_Auth il est nécessaire pour cela d’avoir les paramètres du système d’authentification. Ajoutons alors une section auth dans le fichier de configuration.
La méthode run ressemblera alors à ceci

   public static function run($controllerDirectory)
   {
      // choix du moteur de
      $config = Fast_Registry::getConfiguration();
      $parameters = Fast_Registry::getParameters();
      if ($parameters&&$config) {
         $controller = self::getInstance();
         $controller->throwExceptions(true);
         $controller->setControllerDirectory($controllerDirectory);
         $controller->setRequest('Fast_Controller_Request_Http');
         $controller->_setDispatcher('Fast_Controller_Dispatcher');

         Zend_Loader::loadClass('Zend_Session');
         Zend_Session::start();

         $auth = $parameters->fast->get('auth', false);
         if($auth) {
            Zend_Loader::loadClass('Zend_Db_Table');
            Zend_Loader::loadClass('Zend_Auth');
            if (!$config->get($parameters->fast->db)) {
               Zend_Loader::loadClass('Fast_Exception_Db');
               throw new Fast_Exception_Db('No configuration found for database: '.$parameters->fast->db);
            }
            $params = $config->get($parameters->fast->db)->toArray();
            // connexion
            $dbAdapter = Zend_Db::factory($parameters->fast->db, $params);
            Zend_Db_Table::setDefaultAdapter($dbAdapter);
            Zend_Registry::set('dbAdapter', $dbAdapter);
         }

Etc. encore une fois dès qu’une nouvelle option du front contrôleur est utilisée par une application. La reporter ici permet de la mettre à disposition de toutes les applications futures.
Vous trouverez dans mon code des classes dont je n’ai pas encore évoqué la fonctionnalité. Supprimez les lignes ou remplacez les part les classes Zend correspondantes. Je reviendrais dessus au fur et à mesure. Elle sont simplement la preuve que l’ont peut capitaliser beaucoup de développements tout en gardant une facilité de mise en œuvre.

Fast_Controller_Front
A+JYT

Gérer la configuration d’une application

Lundi 22 octobre 2007

Mon but est de mettre en place une structure générique qui me permette de gérer les configurations des diverses applications que je suis amené à écrire. Le contexte dans lequel je me trouve fait que mes applications ont beaucoup de points communs (elles sont toutes destinées à la même entreprise). Je sais très bien que je ne pourrais mettre en place un système de configuration universel. Mais il doit être suffisamment souple pour accepter la flexibilité nécessaire à la réutilisation.

Configuration et paramétrage

Dans l’article précédent je débâtais de la différence entre le paramétrage et la configuration. C’est le modèle alors proposé, que je vais ici tenter de mettre en œuvre.
J’ai donc trois fichiers : le fichier environment.ini, le fichier parameters.ini et le fichier de configuration désigné par le fichier environnement.
Si on se reporte au code de l’article précédent dans le BootStrap le chargement de la config se fait par

<?phpFast_Config::load();

Ou

<?phpFast_Config::load("mon fichier d'environnement");

Le chargeur de configuration

Au vue de ce code, le chargeur de configuration est donc une classe possédant une méthode statique.
En regardant de plus près l’application ne doit avoir qu’une seule configuration, au sens opérationnel. C’est-à-dire qu’une instance de l’application tournant à un moment donné ne peut avoir deux configurations différentes. La configuration doit donc au sein de l’application être unique. Une solution est de faire un singleton. Mais ici nous n’avons pas vraiment d’intérêt à créer un objet même unique pour charger la configuration.
Charger une configuration est purement fonctionnel. Notre chargeur doit prendre en compte plusieurs fichiers mais il ne fait qu’un seul chargement. Je dirais même que c’est typiquement une simple fonction.
Que faire alors de la configuration chargée ? Zend Framework nous propose une solution : Zend_Registry. La base de registre du framework sert à garder toujours accessible un ensemble de valeur structurée.
Il n’y a pas de raisons de se priver.
Chargeur de configuration et lecteur de fichier de configuration.
ZF nous fournit la classe Zend_Config et quelques dérivées. La classe Zend_Config ne ferait-elle pas l’affaire pour notre chargeur ?
Pour répondre à cette question il faut se pencher sur sont fonctionnement. Zend_Config est une classe qui permet de lire des fichiers de configurations. Mais elle ne détient pas le mécanisme à trois fichiers que j’ai décrit. Il est parfaitement envisageable de mettre ce mécanisme dans le BootStrap et d’utiliser Zend_Config pour cela.
Cette approche simple à l’inconvénient d’alourdir le BootStrap et si on y prend garde celui-ci va se retrouver follement encombré par tout un tas de choses qu’il est facile de faire ainsi, jusqu’à le rendre illisible.
Fast_Config doit donc porter le mécanisme de chargement de la configuration. Ainsi s’il évolue seule cette classe devra changer. Une autre question se pose alors. Fast_Config doit-elle dériver de Zend_Config. Nous venons de voir que leur rôle est fondamentalement différent. Une dérivation n’aurait pas de sens. J’aurais peut-être dû choisir un autre nom. Comme Fast_Config_Loader par exemple. Fast_Config m’est apparu naturel.

Organiser la base de registre.

On peut se poser la question de l’organisation de la base de registre. Cette base étant structurée quelle structure adopter. Je pense que pour le commun des mortel retrouver dans une structure la même organisation que celle qu’on à choisit dans les fichiers de configuration est une facilité appréciable, même si d’autre regroupement logiques sont envisageable.
Ma base de registre contiendra donc trois parties environnement, parameters, et configuration.
Mon algorithme de chargement sera donc
Lecture du fichier environnement
Écriture de l’environnement dans la base.
Lecture du fichier parameters
Écriture des paramètres dans la base.
Lecture du fichier de configuration
Écriture de la configuration dans la base.
Je serais bien tenté par une base de registre qui m’offre les services suivants.

<?php
Zend_Loader::loadClass('Zend_Registry');
class Fast_Registry extends Zend_Registry
{
   public static function getEnvironment();
   public static function setEnvironment($env);
   public static function getConfiguration();
   public static function setConfiguration($config);
   public static function getParameters();
   public static function setParameters($config);
}

Ainsi mon chargeur pourrait utiliser les méthodes set et l’application les méthodes get. De plus Fast_Registry dérivant de Zend_Registry, j’ai à disposition toutes les fonctionnalités d’une base de registre.

Une première approche de Fast_Config

<?php
Zend_Loader::loadClass('Fast_Registry');
class Fast_Config
{
   /**
      @function load() charge toute la config en fonction du fichier environment.
      @param $environement String|null Nom du fichier d'environnement
      @return Fast_Config le contenu du fichier désigné par la clef environnement.
   */
   public static function load($environement = null)
   {
      // initialise une base de registre
      Zend_Registry::setClassName('Fast_Registry');
      if ($environment == null) {
         $environment = './application/config/environment.ini';
      }
      Fast_Registry::setConfigPath(str_replace(chr(92), '/', dirname($environment).'/'));
      // recherche un fichier de config déterminant l'environnement de travail.
      $config = Fast_Registry::setEnvironment(lireLeFichier($environment));

La ligne 13 montre comment utiliser sa propre classe comme classe de registre dans ZF.
Une petite remarque au passage str_replace(chr(92), ‘/’, dirname($environment).’/') Cet appel permet de remplacer tous les \ par des / cela n’est pas bien grave sauf dans quelques cas. Ainsi « c:\real\path » ne sera pas toujours bien compris simplement parce que le \r est un caractère qui sera interprété par php. Il existe bien des solutions pour se sortir de ce problème. J’utilise pour ma part cette expression et je travaille ainsi toujours avec des chemins à la UNIX.
Revenons à notre code. On le voit une petite méthode pour lire le fichier serait bien venue.
J’ai dit que j’allais utiliser Zend_Config pour lire le fichier. Un des avantages ce celui-ci est de pouvoir lire différents types de fichiers de configuration .ini .xml etc.
Ma petite procédure de lecture serait bien pratique si en plus elle prenait divers types de fichier.
Comme pour la base de registre faisons comme si et écrivons notre méthode.
La suite à donner est de regarder dans le fichier environnement si nous trouvons bien les informations sur la configuration. Si ce n’est le cas inutile de continuer nous avons là un crash de l’application.

<?php
Zend_Loader::loadClass('Fast_Registry');
class Fast_Config
{
   /**
      @function load() charge toute la config en fonction du fichier environment.
      @param $environment String|null Nom du fichier d'environnement
      @return Fast_Config le contenu du fichier désigné par la clef environnement.
   */
   public static function load($environment = null)
   {
      // initialise une base de registre
      Zend_Registry::setClassName('Fast_Registry');
      if ($environment == null) {
         $environment = './application/config/environment.ini';
      }
      Fast_Registry::setConfigPath(str_replace(chr(92), '/', dirname($environment).'/'));
      // recherche un fichier de config déterminant l'environnement de travail.
      $config = Fast_Config::getFile('setEnvironment', $environment);
      if(!isset($config->main->environment)) {
         Zend_Loader::loadClass('Fast_Exception_Config');
         throw new Fast_Exception_Config('Environment non défini.');
      } else {
         $environmentFile = Fast_Registry::getConfigPath().$config->main->environment;
      }

Inutile de chercher une échappatoire s’il n’y a pas de config on plante tout. Une exception est parfaite pour cela.
À l’appelant du charger de trapper l’exception et de traiter le problème. Le chargeur lui ne peut rien.
Là encore vous pouvez constater que j’utilise une Fast_Exception_Config, je pourrais très bien utiliser une Zend_Exception. Mais utiliser des exceptions typées permet de les traiter plus facilement. Je vous conseille de toujours typer les exceptions. Pour cela il suffit de fournir une classe qui dérive de Zend_Exception.
Voici non pas la mais les miennes.
class Fast_Exception extends Exception{}
class Fast_Exception_Config extends Exception{}
Un gestionnaire d’exception peut ainsi trapper les exceptions en général, trapper les exceptions fast, trapper les exceptions de configuration fast et apporter un traitement approprié à chaque cas.

Fast_Config

À ce stade j’ai le nom du fichier de configuration qui m’a été fourni par la clef environnement dans la section main du fichier environnement.ini
Je serais bien tenté de faire pareil avec les paramètres laissant ainsi un peu de liberté à l’application.

<?php
Zend_Loader::loadClass('Fast_Registry');
class Fast_Config
{
   /**
      @function load() charge toute la config en fonction du fichier environment.
      @param $environment String|null Nom du fichier d'environnement
      @return Fast_Config le contenu du fichier désigné par la clef environnement.
   */
   public static function load($environment = null)
   {
      // initialise une base de registre
      Zend_Registry::setClassName('Fast_Registry');

      if ($environment == null) {
         $environment = './application/config/environment.ini';
      }

      Fast_Registry::setConfigPath(str_replace(chr(92), '/', dirname($environment).'/'));

      // recherche un fichier de config déterminant l'environnement de travail.
      $config = Fast_Config::getFile('setEnvironment', $environment);

      if(!isset($config->main->environment)) {
         Zend_Loader::loadClass('Fast_Exception_Config');
         throw new Fast_Exception_Config('Environment non défini.');
      } else {
         $environmentFile = Fast_Registry::getConfigPath().$config->main->environment;
      }

      if(!isset($config->main->parameters)) {
         Zend_Loader::loadClass('Fast_Exception_Config');
         throw new Fast_Exception_Config('Parameters non défini.');
      } else {
         $parametersFile = Fast_Registry::getConfigPath().$config->main->parameters;
      }
      // recherche un fichier de configuration générale de l'application.
      $config = Fast_Config::getFile('setConfiguration', $environmentFile);
      // recherche un fichier de parametres généraux de l'application.
      $params = Fast_Config::getFile('setParameters', $parametersFile);
      return $config;
   } // end function load
}

Me reste à définir la méthode getFile qui va déterminé le type de fichier lire le contenu et le mettre dans la base de registre avec la méthode spécifiée.
Vous trouverez le tout dans le fichier join.
Au passage il serait intéressant d’activer certains éléments de la configuration immédiatement. Pour en bénéficier pendant le BootStrap

<?php      // affichage des erreurs
      $display_errors = $config->debug->get('display_errors', false);
      if (ini_get('display_errors') != $display_errors) {
         // pas d'ini_set inutile, c'est gourmand !
         ini_set('display_errors', $display_errors);
      }
      // niveau d'erreur (ne pas les afficher n'empęche pas de les loguer)
      // avec un niveau raisonnable d'alarme par défaut en prod
      // le niveau peut ętre indiqué avec des opérateurs (ex. E_ALL ^ E_STRICT)
      $error_reporting = $config->debug->get('error_reporting', E_WARNING);
      if (!is_numeric($error_reporting)) {
         eval("error_reporting($error_reporting);");
      } else {
         error_reporting($error_reporting);
      }

Placé jute avant le return $config; ce permet d’avoir les messages d’erreur dès le démarrage lors du développement. Et de les retirer en production juste en modifiant le fichier de configuration.

Les fichiers de config

Environnement.ini

; Définit le fichier de configuration générale à charger au bootstrap.
; Ce fichier varie généralement selon que l'on est en développement, en recette ou en production.
; Exemple :
; [main]
; environment = dev.ini
; parameters = parameters.ini

[main]
environment = dev.ini
parameters = parameters.ini

Parameters.ini

[fast]
; debug true ou false charge la classe Fast_Debug absent pas de classe
; auth active le la protection
debug = true
db = Pdo_Mysql
auth = true
audiance = false
menu = true
version = G0-RC1

[login_messages]
login_need = l'identifiant est obligatoire
pass_need = le mot de passe est obligatoire
unknow = Identifiant ou mot de passe inconnu.
unknow_user = Veuillez vous identifier avant de poursuivre.

Dev.ini

[debug]
error_reporting = E_ALL ^ E_STRICT
display_errors = true

[app]
baseUrl = /myApp/

[Pdo_Mysql]
host = 127.0.0.1
port = 3307
username = sekaijin
password =
dbname = test

Note :
Vous trouverez dans les fichiers joint des appels à d’autres classes. Je les ai ajoutées au fil du temps. Et je reviendrais dessus plus tard.
A+JYT

Zend_Framework BootStrap

Dimanche 21 octobre 2007

Zend_Framework à besoin pour démarrer de quelques petites choses, comme, par exemple le chemin où trouver les contrôleurs ou encore les vues. Et bien d’autres encore.

Démarrage à froid

On trouve sur le net nombre d’exemples de BootStrap pour ZF qui explique comment ajouter telle ou telle option au démarrage.

Par exemple : celui de Simon Mundy et Alain Sahli dans leur tutoriel sur la mise en œuvre des ACL developpez.com

<?php
// Initialisation de la configuration / environnement
$config = new Zend_Config(new Zend_Config_Ini('../application/config/config.ini', 'live'));

// Création du sitemap à partir du .ini en utilisant la structure de l'exemple
$sitemap = new Zend_Config(new Zend_Config_Ini('../application/config/sitemap.ini', 'live'));

// Création de l'objet de base de données et activation / désactivation du débogage
$db = Zend_Db::factory($config->db->connection, $config->db->asArray());
...etc...

// Création de l'objet Auth
$auth = Zend_Auth::getInstance();

// Création de l'objet Acl
$acl = new MyAcl($auth); // see

// Création du routeur et configuration (Ordre LIFO pour les routes)
$router = new Zend_Controller_RewriteRouter;
...add rules...

// Création des vues et enregistrement des objets
$view = new My_View;
...init view...

$front = Zend_Controller_Front::getInstance();
$front->throwExceptions(true);
$front->setRouter($router)
      ->setDispatcher(new Zend_Controller_ModuleDispatcher())
      ->registerPlugin(new My_Plugin_Auth($auth, $acl))
      ->registerPlugin(new My_Plugin_Agreement($auth))
      ->registerPlugin(new My_Plugin_View($view))
      ->setControllerDirectory(array('default' => realpath('../application/controllers/default'),
                                     'admin' => realpath('../application/controllers/admin')))
      ->setParam('auth', $auth)
      ->setParam('view', $view)
      ->setParam('config', $config)
      ->setParam('sitemap', $sitemap)
      ->dispatch();

Si je m’en réfère à la documentation de ZF le BootStrap est on ne peut plus simple

<?php
require_once 'Zend/Controller/Front.php';
Zend_Controller_Front::run('/chemin/vers/application/controllers');

Cette simplicité est séduisante, mais elle ne permet pas de personnaliser le démarrage.

Et devoir reproduire quelque chose d’aussi complexe que l’exemple ci-dessus est source d’erreurs.

En passer par une config

C’est pourquoi j’ai envisagé d’en passer par un fichier de configuration. Le BootStrap se résumant alors à charger la config, charger le front controller et le démarrer.

<?php
/**
 * Bootstrap.
 *
 * Point d'entrée unique de l'application, ce script fixe l'environnement
 * de travail par rapport aux fichiers de configuration disponibles et
 * lance un Front Controller qui est le moteur principal de l'application
 * qui tourne sous le design pattern MVC tel qu'implémenté par
 * Zend Framework.
 */

// chemin des librairies
set_include_path(
realpath('library')
. PATH_SEPARATOR . realpath('application')
. PATH_SEPARATOR . get_include_path()
);
// charge le loader général.
require 'Zend/Loader.php';
Zend_Loader::loadClass('Fast_Config');
Fast_Config::load(); //ou Fast_Config::load("mon fichier d'environement");

// instancie un front contrôleur
// et lui indique le chemin des contrôleurs d'action
Zend_Loader::loadClass('Zend_Controller_Front');
Zend_Controller_Front::run('application/controllers');

Notre BootStrap reste relativement simple et portable d’une application sur l’autre les variations des options étant définie par la configuration.

Reste que Zend_Controller_Front ne sait pas utiliser mon Fast_Config. De plus, un fichier de configuration peut facilement devenir aussi compliqué que d’écrire un BootStrap.

Organiser la config.

Dans mon travail, mes développements se font de la façon suivante le projet est géré avec CVS ou SVN, chaque développeur a une copie de l’application, et l’exécute sur un environnement qui lui est propre. L’application passe ensuite, sur une plateforme de développement commune qui est identique à la plateforme cible. Cette dernière permet de tester les relations avec les autres applications lorsque cela est nécessaire. Ensuite l’application est déployée sur une plateforme de recette. Celle-ci est destinée à la maitrise d’ouvrage qui va valider l’application. Très souvent, l’étape suivante est une mise en près production. Don le but est de tester l’application en situation. Et, au final, elle est enfin placée sur son environnement de production. Il va sans dire qu’immanquablement tous ses environnements ne sont pas parfaitement identiques. La configuration de l’application va donc en dépendre pour partie. À l’opposé, il existe toujours dans une application des paramètres qui ne sont pas liés à l’environnement. Pour ne pas avoir à systématiquement rééditer un gros fichier de configuration, j’ai pris l’habitude d’en faire plusieurs. En premier lieu, séparer ce qui est du paramétrage de l’application de ce qui est de la configuration due à l’environnement.

J’ai donc un fichier parameters qui définit les paramètres de l’application, et un fichier de configuration. Ce dernier existe en autant d’exemplaires que de plateforme ou presque. Une solution pour sélectionner le bon est de le nommer config et de renommer les autres qui pourraient éventuellement traîner par là. L’inconvénient de cette approche est que lorsqu’on voit dans le dossier config le fichier config on ne sait pas à quel environnement il se rapporte. J’ai donc choisis de les nommer en fonction de l’environnement. dev, recette,preprod, prod, etc. Reste donc à indique au BootStrap lequel choisir. J’ai pour cela défini un fichier qui ne fait que ça. Environnement.ini

Si vous regardez la hiérarchie que j’ai choisie dans l’article précédent, il faut déterminer où placer ces fichiers. Pour moi il s’agit de la configuration de l’application. J’ai donc ajouté un dossier config dans le dossier application. Dans lequel je place les fichiers environnement.ini, parameters.ini et dev.ini etc. Le dossier application étant déjà protégé la configuration n’est normalement pas accessible pour le client.

Comment déterminer ce que l’on place dans parameters et ce qui va dans dev ou autre. Il suffit pour cela de se poser la question l’attribut de configuration doit-il changer si je change de plateforme. Par exemple l’option « utiliser les acl » ne dépend pas de l’endroit où l’on place l’application. C’est un paramètre. « database host » quant à lui dépend de la plateforme il va donc dans la config.

Ainsi le passage d’une plateforme à l’autre se résume à créer un fichier config propre à la plateforme et à changer la valeur de environnement.ini.

Cette façon de faire n’est peut-être pas la meilleure, mais elle a fait ses preuves.

Apprendre au Front_Controller à utiliser la config.

Améliorer le FrontController

La seule approche que permet par défaut ZF pour configurer le Front Controller est d’en passer par le BootStrap. Mais ZF est une belle architecture à objet qui autorise l’héritage et le polymorphisme. Je vais me servir de cette caractéristique pour construire un Front_Controller paramétrable par la configuration.

Fast_Controler_Front va donc dériver de Zend_Controller_Front est fournir les mêmes fonctionnalités, mais, au passage, il va utiliser la configuration pour ajouter seul, les options demandées.

Pour cela, il suffit de surcharger la méthode statique run

<?php
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Controller_Front');

/**
 * Contrôleur principal permettant l’utilisation de la configuration.
 *
 * @author Patrick Dubois
 * @author Jean-Yves Terrien
 *
 * @uses Zend_Controller_Front
 * @package Fast_Controller
 */
class Fast_Controller_Front extends Zend_Controller_Front
{
/**
     * Convenience feature, calls setControllerDirectory()->setRouter()->dispatch()
     *
     * In PHP 5.1.x, a call to a static method never populates $this -- so run()
     * may actually be called after setting up your front controller.
     *
     * @param string|array $controllerDirectory Path to Zend_Controller_Action
     *   controller classes or array of such paths
     * @return void
     * @throws Zend_Controller_Exception if called from an object instance
     */
   public static function run($controllerDirectory)
   {
      $config = Fast_Registry::getConfiguration();
      $parameters = Fast_Registry::getParameters();
      if ($parameters&&$config) {
        …
      } else {
         Zend_Controller_Front::run('application/controllers');
      }

   }
}

Ainsi dans notre BootStrap nous pouvons garder un code simplifié, il suffit d’utiliser Fast_Controller_Front à la place de Zend_Controller_Front.

<?php
/**
 * Bootstrap.
 *
 * Point d'entrée unique de l'application, ce script fixe l'environnement
 * de travail par rapport aux fichiers de configuration disponibles et
 * lance un Front Controller qui est le moteur principal de l'application
 * qui tourne sous le design pattern MVC tel qu'implémenté par
 * Zend Framework.
 */

// chemin des librairies
set_include_path(
realpath('library')
. PATH_SEPARATOR . realpath('application')
. PATH_SEPARATOR . get_include_path()
);
// charge le loader général.
require 'Zend/Loader.php';
Zend_Loader::loadClass('Fast_Config');
Fast_Config::load(); //ou Fast_Config::load("mon fichier d'environement");

// instancie un front contrôleur
// et lui indique le chemin des contrôleurs d'action
Zend_Loader::loadClass('Fast_Controller_Front');
Fast_Controller_Front::run('application/controllers');

Notez que j’ai pris la précaution de lancer un Zend_Controller_Front, si je n’ai pas de config. Permettant ainsi de garder le fonctionnement par défaut de ZF.

Je n’ai abordé ici que la structure générale. La mise en œuvre fera l’objet d’une publication future.

Organisation des dossiers

Samedi 20 octobre 2007

Une des premières choses à faire lorsqu’on commence à coder une application est de définir la hiérarchie des dossiers.
Y réfléchir avant de commencer permet d’éviter la pagaille qui attend les fichiers au fil du temps si on n’y prend garde.
Une chose est certaine, il n’existe pas d’organisation qui réponde à tous les besoins. Mais on peut être tout aussi assuré, que si on ne définit rien, au fil du temps, il va être difficile de retrouver ses petits.

Structure d’une Application web.

Mon expérience m’a conduit à définir trois grandes parties dans une application web. Une première séparation apparaît entre ce qui est purement du ressort du web et ce qui est applicatif. Immanquablement on trouve dans une web application des éléments directement accessibles par le client. Les images, feuilles de styles, etc. tous ces éléments ont juste à se trouver dans un dossier visible du serveur http.
De l’autre côté de la frontière, on trouve tout ce qui a besoin du moteur applicatif pour être exploitable. Parmi ces derniers éléments, on peut encore trouver une séparation entre tous les éléments réutilisables et ceux qui sont propres à l’application qu’on développe. Zend_Framework propose une organisation tout en laissant libre le développeur de définir la sienne.

Et la sécurité ?

Une question importante est de définir ce que nous devons ou ne devons pas mettre dans l’arborescence http. À priori, une bibliothèque de fonctions ne devrait pas se trouver dans cette hiérarchie. Seuls les éléments visibles du client le devraient. Malheureusement il m’est arrivé à plusieurs reprises de ne pouvoir écarter des fichiers de l’espace public, l’hébergement ne l’autorisant pas. Il convient donc de protéger ce qui doit l’être de toute tentative. Au final, j’ai fini par tout avoir dans l’espace web. ainsi quel que soit l’hébergement, je suis en mesure de déployer mon application. Le choix d’une organisation à priori impose de s’être une bonne fois pour toutes posé cette question de la sécurité des ressources.

Une première approche

J’ai pour ma part organisé mes développements ainsi

/
   application/
   library/
   public/

Travaillant en équipe, la normalisation de l’organisation est une source de gain. Ainsi lorsqu’on reprend l’application d’un autre on y voit clair. Mais cette première hiérarchie est un peu trop basique. Partant des ajustements que nous avons faits au fil du temps.
J’ai organisé le dossier public en sous-dossiers en fonction du type de contenus : images, styles, scripts, medias.
Pour le dossier application, j’ai suivi les recommandations de ZF. Ce n’était pas bien compliqué vu qu’ils correspondaient aux noms près à l’organisation que j’avais déjà mise en place dans le passé.
Quant au dossier library, il contient la bibliothèque Zend bien sur et d’autres bibliothèques organisées en fonction de leur origine.
Pour tout ce qui est des éléments que j’ai été amené à ajouter je les ai regroupés dans la bibliothèque Fast. Non pas que ma bibliothèque soit rapide mais elle à pour but d’accélérer les développements.

Et les modules ?

Restait un petit problème d’organisation. ZF nous propose de mettre nos modules applicatifs dans le dossier Application. C’est une bonne chose que je pratique volontiers. Mais un des avantages de faire des modules c’est de pouvoir les réutiliser d’une application sur l’autre. Il existe des modules qui n’ont d’autres intérêt que pour l’application qui lui a donné naissance. Mais il est possible d’en avoir qui soit suffisamment génériques pour être réutilisables. Je me suis donc demandé s’il était opportun de ceux-ci conserver dans le dossier Application. Après mûres réflexions, on peut voir ces modules comme de mini applications dans l’application. J’ai donc décidé de les sortir du dossier applications. Là encore, pour bien identifier ceux que je développes j’ai décidé de les regrouper dans un dossier.

Une proposition d’organisation

Au final je suis arrivé à l’organisation suivante.

/
   application/
      Controllers/
      Models/
      Views/
   fast_modules/
      module1/
          Controllers/
          Models/
          Views/
   index.php
   library/
      Fast/
      Zend/
   public/
      Images/
      Scripts/
      Styles/

Chaque dossier ayant un rôle bien identifié s’insérer dans l’organisation devient relativement aisé. Mais il ne faut pas perdre de vu qu’on ne pourra éviter qu’un jour, on tombera immanquablement sur un éléments qui nous posera des problèmes.
Dans toute cette organisation seuls : « index.php » et le dossier public doivent être visible du client. Tout le reste doit être protégé. Une bonne configuration du serveur fait le nécessaire.
Ainsi paré je n’ai plus à me préoccuper de l’organisation de mes applications. Elles sont toutes bâties sur ce modèle.
Pour ceux qui commencent avec ZF ne vous inquiétez pas trop de la quantité de dossiers ici présents. Je les passerais en revue au fils de mes posts. Pour la plupart vous les trouverez dans la documentation de ZF.
A+JYT