====== Tutoriel (deuxième partie) ====== **Ce tutoriel concerne la version de Stato en cours de développement (le trunk), qui peut être assez différente des versions précédentes.** Nous nous sommes quittés après avoir créé une vue affichant les dernières bières ajoutées. Cette vue utilisait le helper ''link_to'' pour créer des liens vers la fiche de chaque bière : nom, array('action' => 'view', 'id' => $beer->id)); ?> Si vous affichez le HTML généré, vous vous apercevrez que les liens ressemblent à ça : Eki En effet, nous avons vu précédemment que la route par défaut de Stato était '':controller/:action/:id''. Cette route signifie que chaque requête doit comporter un '':controller'' et une '':action'' à laquelle est fourni un '':id'' (en général la clé primaire d'un enregistrement dans la base). Si nous faisons une requête vers l'URL ci-dessus, Stato va nous fournir dans le controller une propriété ''$params'' qui contiendra : $params = array( 'controller' => 'beers', 'action' => 'view', 'id' => 1 ); Vous êtes maintenant prêt à implémenter l'action ''view''. Ouvrez le fichier ''app/controllers/beers_controller.php'' et ajoutez cette méthode : public function view() { $this->beer = Beer::$objects->get_or_404($this->params['id']); } Comme vous pouvez le constater, nous nous servons de la propriété ''$this->params'' du contrôleur pour récupérer l'ID demandée par l'utilisateur. La méthode ''get_or_404'' est une méthode spéciale de la classe ''SManager'' : elle prend en paramètre l'ID de l'enregistrement à récupérer, et si elle ne le trouve pas, génère une exception ''SHttp404''. Notez qu'il existe aussi une méthode ''get'' qui se contente de générer une exception ''SActiveRecordNotFound'' si elle ne trouve pas l'enregistrement, exception que vous devez "catcher" vous même. Nous allons voir dans quelques l'instants l'intérêt de générer une exception ''SHttp404''. Voici le code du template, à placer dans un nouveau fichier ''app/views/beers/view.php'' :

beer->nom; ?>

Brasserie beer->brasserie; ?>
% d'alcool beer->degre_alcool; ?>
Couleur beer->couleur; ?>
Goût beer->gout; ?>
Fermentation beer->fermentation; ?>
Description beer->description; ?>
Rien de bien sorcier, aussi ne le commenterais-je pas. Vous pouvez également rajouter ces quelques lignes à la feuille de style ''basic.css'' : th { text-align: left; } td { padding-left: 20px; } Il ne vous reste plus qu'à tester ! En cliquant sur l'un des liens, vous devriez obtenir ceci : {{fr:tuto_beer_frontend_2.png}} Revenons à notre exception ''SHttp404''. Essayer de modifier l'URL en utilisant un ID qui n'existe pas encore dans la base, comme par exemple http://ilovebeer/beers/view/1234. Vous devriez obtenir l'erreur suivante : SHttp404 in BeersController::view() Quel intérêt me direz vous ? Cela ne semble pas très propre, mais il y a une raison à cela : nous sommes actuellement en environnment de développement, et donc Stato nous affiche toutes les erreurs. Nous allons donc tester le mode "production" ; pour cela, ouvrez le fichier ''conf/boot.php'' et décommentez la ligne suivante : $_SERVER['STATO_ENV'] = 'production'; Si vous rafraîchissez la page, vous obtiendrez une page 404 plutôt sommaire, mais la bonne nouvelle est qu'elle est totalement modifiable : il vous suffit d'éditer le fichier ''public/404.html'', de même qu'il existe un fichier ''500.html'' pour couvrir tous les autres types d'erreurs en production. Pratique non ? ===== Création d'un premier formulaire ===== Il est temps maintenant d'offrir à nos futurs utilisateurs la possibilité d'ajouter de nouvelles bières. Pour cela, nous allons ajouter à notre contrôleur ''BeersController'' une action ''add''. Une requête GET vers cette action entraînera l'affichage d'un formulaire de création, tandis qu'une requête POST (lors de la soumission du formulaire) permettra d'ajouter la nouvelle fiche dans la base (en cas de problème, par exemple un champ obligatoire manquant, le formulaire sera réaffiché avec la liste des erreurs). Pour commencer, nous allons ajouter au template de l'action ''index'' (''app/views/beers/index.php'') un lien vers l'action ''add'' :

Dernières bières dégustées

'add')); ?> .....
Afin de nous faciliter la tâche de création du formulaire, nous allons utiliser la classe ''SFormBuilder''. Cette classe a besoin d'une instance de la classe ''Beer'' pour fonctionner. Voici donc une première version de l'action ''add'' : class BeersController extends ApplicationController { ... public function add() { $this->beer = new Beer(); $this->form = new SFormBuilder('new_beer', $this->beer); } } Créez un nouveau fichier ''app/views/beers/add.php'' avec le code suivant :

Ajouter une bière

'add')); ?> render_partial('form'); ?>

ou 'index')); ?>

Une nouveauté introduite par ce template est l'utilisation de ''render_partial'' : cette méthode permet de faire le rendu d'un sous-template et ainsi de réutiliser certaines portions de templates. L'idée ici est de réutiliser le corps du formulaire dans un futur formulaire d'édition. Par convention, le nom du "partial" doit être préfixé par un underscore. Il vous faut donc créer un fichier ''app/views/beers/_form.php'' contenant : form->error_message(); ?> form->text_field('nom'); ?> form->text_field('brasserie'); ?> form->text_field('degree_alcool', array('size' => 5)); ?> form->text_field('couleur'); ?> form->text_field('gout'); ?> form->text_field('fermentation'); ?> form->text_area('description', array('cols' => 80, 'rows' => 10)); ?> Au sein de ce sous-template, nous disposons bien sûr de la propriété ''$this->form'' définie dans le contrôleur et contenant notre instance de ''SFormBuilder''. Cette classe contient de nombreuses méthodes pour générer des éléments de formulaire à partir des propriétés d'une instance de ''SActiveRecord''. Par exemple : form->text_field('nom'); ?> Va nous générer : Rappellez-vous en effet le premier paramètre que nous avons passé au constructeur de ''SFormBuilder'' : ''new_form''. Ce paramètre va donc préfixer les attributs ''name'' et ''id'' de nos champs. L'attribut ''size'' contient ici une valeur par défaut, valeur que nous avons modifié pour le champ ''degree_alcool''. Bien sûr, si l'instance de ''Beer'' que nous avons passé à ''SFormBuilder'' avait contenu des valeurs pour certains champs, ces valeurs auraient été affichées dans le formulaire. Avant de tester le formulaire, je vous recommande de modifier votre feuille de style en y incluant les styles suivants : label { display: block; } input, textarea, select { padding: 2px; border: 1px solid #ccc; margin-bottom: 10px; } p.submit { text-align: right; } p.submit input { margin-bottom: 0; } #form-errors { background-color: red; color: #fff; padding: 10px; margin: 20px 0; } #form-errors h3 { color: #fff; } #form-errors ul { padding-left: 15px; } Et si tout va bien, vous devriez obtenir ceci : {{fr:tuto_beer_frontend_3.png}} Notre formulaire est maintenant opérationnel, mais rien ne se passera si vous cliquez sur "Ajouter", nous devons encore implémenter la sauvegarde de la fiche. Editez la méthode ''add'' de votre ''BeersController'' de la façon suivante : class BeersController extends ApplicationController { ... public function add() { if ($this->request->is_post()) { $this->beer = new Beer($this->params['new_beer']); if ($this->beer->save()) { $this->redirect_to(array('action' => 'view', 'id' => $this->beer->id)); return; } } else { $this->beer = new Beer(); } $this->form = new SFormBuilder('new_beer', $this->beer); } } Nous allons détailler ce code pas à pas. Tout d'abord, nous utilisons la méthode ''is_post'' de la classe ''SRequest'' (dont une instance est toujours disponible dans vos actions) pour déterminer si la requête est de type POST ou non. Si c'est le cas, et étant donné que tous les attributs ''name'' de nos champs de formulaire sont préfixés par ''new_beer'', nous disposons dans ''$this->params'' d'un paramètre ''new_beer'' contenant un tableau des valeurs saisies dans le formulaire. Nous instancions donc un nouvel objet ''Beer'' avec ces valeurs, et nous essayons de le sauver. Si la méthode ''save()'' retourne ''true'', nous pouvons alors rediriger l'utilisateur vers la page de visualisation de cette bière nouvellement créée. **Il est très important de faire un ''return'' après un ''redirect_to'' afin de s'assurer que le reste du code de l'action n'est pas exécuté, ce qui pourrait poser problème.** Si l'objet n'a pas pû être sauvé, l'instance de ''SFormBuilder'' que nous initialisons en dernière ligne contiendra cet objet avec les valeurs saisies par l'utilisateur et non un objet vierge, ce qui nous permet de réafficher le formulaire avec les valeurs déjà saisies. Mais pourquoi l'objet ne pourrait-il pas être sauvé me direz-vous ? Tout simplement parce que la classe ''SActiveRecord'' vous permet d'effectuer certaines validations avant la sauvegarde de l'enregistrement. Ouvrez donc le fichier ''app/models/beer.php'' et ajoutez la méthode ''validate()'' ci-dessous : class Beer extends SActiveRecord { public static $objects; public $record_timestamps = true; public function validate() { $this->validate_presence_of('nom', 'Le champ %s est obligatoire'); } } ''SActiveRecord'' définit en effet 3 methodes de validation surchargeables : ''validate()'', qui est appellée lors de la sauvegarde d'un enregistrement, qu'il existe déjà dans la base ou non, ainsi que ''validate_on_create()'' et ''validate_on_update()''. Nous nous contentons ici de rendre le champ ''nom'' obligatoire. Afin d'afficher les erreurs générées lors de la validation, ''SFormBuilder'' dispose de la méthode ''error_message()'' que nous avons justement utilisé dans notre template. Essayez donc de soumettre le formulaire sans rien saisir et vous obtiendrez ceci : {{fr:tuto_beer_form_errors_1.png}} C'est bien, mais pas top. Heureusement, ''error_message'' comme la plupart des helpers de Stato, accepte de nombreuses options ; modifiez la première ligne du partiel de formulaire comme ceci : form->error_message(array( 'header_tag' => 'h3', 'header_text' => 'Veuillez corriger les erreurs ci-dessous' )); ?> .... {{fr:tuto_beer_form_errors_2.png}} C'est beaucoup mieux. Nous pouvons encore améliorer notre formulaire en remplaçant certains champs texte par des select. Bien que n'étant pas expert en bière (plutôt un amateur éclairé), un petit tour sur Wikipédia m'a confirmé que le nombre de types de fermentation ainsi que le nombre de couleurs étaient limités. Modifiez donc votre formulaire de cette façon : ... form->select('couleur', array('blonde', 'ambrée', 'brune', 'noire', 'blanche')); ?> ... form->select('fermentation', array('basse', 'haute', 'spontanée', 'mixte')); ?> Notre formulaire est maintenant complet et fonctionnel. Je vous propose donc de polir un peu notre application avant d'aller plus loin dans les fonctionnalités. Personnellement, je ne suis pas très fan des URLs du type http://ilovebeer/beers/view/214. Cet identifiant numérique n'est pas signifiant, pour ne pas dire très laid. Nous allons les remplacer par des URLs du type http://ilovebeer/beers/leffe_triple. Pour ce faire, nous devons stocker dans la base une version "simplifiée" du nom de la bière qui puisse être utilisé dans l'URL, ce qui signifie que nous allons devoir ajouter un champ dans la table ''beers''. Si vous pouvez le faire à la main dans phpMyAdmin (ou tout autre outil), Stato vous propose une solution élégante : les migrations. En mode console (et en vous plaçant comme d'habitude dans le dossier de votre appli), exécutez la commande suivante : php.exe scripts/do.php generate migration add_slug_column create db/migrate/1_add_slug_column.php Ouvrez le fichier créé, et modifiez-le comme suit : class AddSlugColumn extends SMigration { public function up() { $this->add_column('beers', 'slug', 'string', array('after' => 'nom')); SMapper::reset_meta_information('Beer'); foreach (Beer::$objects->all() as $beer) { $beer->update_attribute('slug', SInflection::urlize($beer->nom)); } } public function down() { $this->remove_column('beers', 'slug'); } } La méthode ''up()'' définit les modifications à opérer sur les tables, tandis que la méthode ''down()'' est là pour permettre d'annuler ces modifications. En effet, il est préférable de rendre les migrations réversibles. Si vous vous rendez compte une fois en production qu'il y a un problème avec votre code, vous pouvez ainsi revenir à une version antérieure de la base. Détaillons maintenant le code de la méthode ''up()'' : * ''add_column'' nous permet comme son nom l'indique d'ajouter notre nouvelle colonne que nous choisirons d'appeller ''slug''. * la deuxième ligne réinitialise les informations de mapping de notre classe ''Beer''. Ainsi le nouveau champ ''slug'' pourra être pris en compte dans la suite du code. * nous parcourons ensuite l'ensemble des enregistrements de la table en leur attribuant une valeur pour le champ ''slug'', valeur obtenue grâce au helper ''SInflection::urlize()'' qui transforme tout mot ou phrase en une chaîne utilisable dans une URL. Il ne vous reste plus qu'à exécuter la migration : php.exe scripts/do.php migrate Migrating to AddSlugColumn (1) => AddSlugColumn: migrating => AddSlugColumn: migrated (0.5758s) Pour annuler la migration, il vous suffit d'exécuter la commande suivante : php.exe scripts/do.php migrate -v 0 // ou bien : php.exe scripts/do.php migrate --version=0