PageRenderTime 53ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/jobeet/fr/04.markdown

https://github.com/rafaelgou/symfony1-docs
Markdown | 878 lines | 718 code | 160 blank | 0 comment | 0 complexity | 2a588922c6b94de822f6827351f630bc MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. Jour 4 : Le contrôleur et la vue
  2. ================================
  3. Hier, nous avons appris comment symfony nous simplifie la gestion des bases de données
  4. en faisant abstraction des différences entre les moteurs de base de données et en convertissant les
  5. éléments relationnels sous forme de classes orientées objets. Nous avons également vu le principe
  6. de fonctionnement de ##ORM## permettant de définir le schéma de la base de données, créer les tables et
  7. remplir la base de données avec quelques valeurs initiales.
  8. Aujourd'hui, nous allons personnaliser le module `job` que nous avons créé
  9. hier. Actuellement, ce module a déjà tout le code utile pour Jobeet :
  10. * Une page listant tous les jobs
  11. * Une page pour créer un nouveau job
  12. * Une page pour mettre à jour une job déjà existant
  13. * Une page pour supprimer un job
  14. Bien que le code est prêt à être utilisé tel qu'il est, nous devons modifier les
  15. Templates afin que nos pages correspondent à notre maquette.
  16. L'architecture ~MVC~
  17. --------------------
  18. Si vous avez l'habitude de développer des sites web en PHP sans framework, vous avez
  19. probablement utilisé le principe d'un fichier PHP par page HTML. Ces fichiers PHP ayant très
  20. certainement une structure proche de : l'initialisation et la configuration globale, le traitement
  21. associé à la page demandée, la récupération des données depuis la base de données et enfin
  22. la mise en place du code HTML formant la page.
  23. Vous pouvez également utiliser un moteur de Template permettant de séparer la logique du HTML.
  24. Vous utilisez peut-être une couche d'abstraction permettant de séparer l'interaction du modèle
  25. avec celui du traitement des données. Mais la plupart du temps, vous vous retrouvez avec beaucoup de code
  26. absolument cauchemardesque à maintenir. Le code a rapidement été mis en place, mais la plupart du temps, ce
  27. dernier est de plus en plus difficile à modifier, notamment parce que personne, excepté vous,
  28. ne sait comment votre site a été conçu et comment il fonctionne.
  29. Comme toujours : à chaque problème, ses solutions. Pour le développement Web, les
  30. solutions les plus populaires pour organiser votre code de nos jours est la mise en place
  31. d'une [**architecture MVC**](http://fr.wikipedia.org/wiki/Mod%C3%A8le-Vue-Contr%C3%B4leur).
  32. En résumé, l'architecture MVC définit un cadre d'organisation de votre code en accord
  33. avec sa nature. Ce modèle permet une séparation de votre code en
  34. **trois couches** :
  35. * La couche **~Modèle~** contenant le traitement logique de vos données (les accès
  36. à la base de données se trouvent dans cette couche). Vous savez déjà que symfony stocke toutes
  37. les classes et tous les fichiers relatifs au Modèle dans le répertoire `lib/model`.
  38. * La **~Vue~** est la couche interagit l'utilisateur (un moteur de template fait parti de
  39. cette couche). Dans symfony, la couche vue est principalement faite de Templates PHP.
  40. Ces fichiers sont stockés dans les différents dossiers `templates/` comme nous le verrons
  41. plus loin.
  42. * La **~Contrôleur~** est un morceau de code qui appelle le modèle pour obtenir certaines données
  43. qu'il passe à la Vue pour le rendu au client. Quand nous avons installé
  44. symfony le premier jour, nous avons vu que toutes les requêtes étaient gérées par des
  45. contrôleurs frontaux (`index.php` et `frontend_dev.php`). Ces contrôleurs frontaux
  46. délèguent le réel travail à des **actions**. Comme nous l'avons vu hier, ces
  47. actions sont logiquement regroupées dans des **modules**.
  48. ![MVC](http://www.symfony-project.org/images/jobeet/1_4/04/mvc.png)
  49. Aujourd'hui, nous allons utiliser la maquette définie le 2ième jour afin de personnaliser
  50. la page d'accueil et la page d'un emploi. Nous allons les rendre dynamique. En chemin, nous allons ajuster
  51. un tas de choses dans beaucoup de fichiers différents afin de montrer la structure de répertoire
  52. de symfony et la manière de séparer le code entre les couches.
  53. La mise en page
  54. ---------------
  55. D'abord, si vous regardez de plus près la maquette, vous remarquerez que la quantité de
  56. chaque page vous semble le même. Vous savez déjà que la duplication de code est mauvais,
  57. si nous parlons de code HTML ou PHP, donc nous devons trouver un moyen
  58. d'empêcher ces éléments communs de la vue d'aboutir à la duplication du code.
  59. Une manière de résoudre le problème est de définir une entête et un pied de page et
  60. de les inclure dans chaque Template :
  61. ![Entête et pied de page](http://www.symfony-project.org/images/jobeet/1_4/04/header_footer.png)
  62. Mais dans ce cas, les fichiers header et footer ne contiennent pas de code HTML valide. Il doit
  63. y avoir un meilleur moyen. Plutôt que de réinventer la roue, nous allons utiliser un autre modèle
  64. pour résoudre ce problème : le
  65. [modèle décorateur](http://fr.wikipedia.org/wiki/D%C3%A9corateur_(patron_de_conception)).
  66. Le modèle décorateur résout le problème d'une manière différente : le
  67. Template est décorée après que le contenu soit mise en page grâce à un template global,
  68. appelé **~layout|Layout~** dans symfony :
  69. ![Layout](http://www.symfony-project.org/images/jobeet/1_4/04/layout.png)
  70. La mise en page par défaut d'une application est appelée `layout.php` et se
  71. trouve dans le dossier `apps/frontend/templates/`. Ce répertoire contient
  72. tous les Templates globaux pour une application.
  73. Remplacez le contenu par défaut du layout par le code suivant :
  74. [php]
  75. <!-- apps/frontend/templates/layout.php -->
  76. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  77. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  78. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  79. <head>
  80. <title>Jobeet - Your best job board</title>
  81. <link rel="shortcut icon" href="/favicon.ico" />
  82. <?php include_javascripts() ?>
  83. <?php include_stylesheets() ?>
  84. </head>
  85. <body>
  86. <div id="container">
  87. <div id="header">
  88. <div class="content">
  89. <h1><a href="<?php echo url_for('job/index') ?>">
  90. <img src="/images/logo.jpg" alt="Jobeet Job Board" />
  91. </a></h1>
  92. <div id="sub_header">
  93. <div class="post">
  94. <h2>Ask for people</h2>
  95. <div>
  96. <a href="<?php echo url_for('job/index') ?>">Post a Job</a>
  97. </div>
  98. </div>
  99. <div class="search">
  100. <h2>Ask for a job</h2>
  101. <form action="" method="get">
  102. <input type="text" name="keywords"
  103. id="search_keywords" />
  104. <input type="submit" value="search" />
  105. <div class="help">
  106. Enter some keywords (city, country, position, ...)
  107. </div>
  108. </form>
  109. </div>
  110. </div>
  111. </div>
  112. </div>
  113. <div id="content">
  114. <?php if ($sf_user->hasFlash('notice')): ?>
  115. <div class="flash_notice">
  116. <?php echo $sf_user->getFlash('notice') ?>
  117. </div>
  118. <?php endif; ?>
  119. <?php if ($sf_user->hasFlash('error')): ?>
  120. <div class="flash_error">
  121. <?php echo $sf_user->getFlash('error') ?>
  122. </div>
  123. <?php endif; ?>
  124. <div class="content">
  125. <?php echo $sf_content ?>
  126. </div>
  127. </div>
  128. <div id="footer">
  129. <div class="content">
  130. <span class="symfony">
  131. <img src="/images/jobeet-mini.png" />
  132. powered by <a href="http://www.symfony-project.org/">
  133. <img src="/images/symfony.gif" alt="symfony framework" />
  134. </a>
  135. </span>
  136. <ul>
  137. <li><a href="">About Jobeet</a></li>
  138. <li class="feed"><a href="">Full feed</a></li>
  139. <li><a href="">Jobeet API</a></li>
  140. <li class="last"><a href="">Affiliates</a></li>
  141. </ul>
  142. </div>
  143. </div>
  144. </div>
  145. </body>
  146. </html>
  147. Un ~template|Templates~ symfony est juste un fichier PHP. Dans le template layout, vous
  148. trouverez des appels à des fonctions PHP et des références à des variables PHP. ~`$sf_content`~ est
  149. la variable la plus intéressante : elle est définie par le framework lui-même et
  150. contient le code HTML généré par l'action.
  151. Si vous parcourez le module `job`
  152. (`http://jobeet.localhost/frontend_dev.php/job`), vous verrez que toutes
  153. les actions sont décorés par le layout.
  154. Les feuilles de style, les images et les Javascripts
  155. ----------------------------------------------------
  156. Comme ce tutoriel n'est pas sur le design web, nous avons déjà préparé toutes les
  157. ressources que nous utiliserons pour Jobeet :
  158. [téléchargez les fichiers image](http://www.symfony-project.org/get/jobeet/images.zip)
  159. et mettez les dans le dossier `web/images/`;
  160. [téléchargez les fichiers feuilles de style](http://www.symfony-project.org/get/jobeet/css.zip)
  161. et mettez les dans le dossier `web/css/`.
  162. >**NOTE**
  163. >Dans le layout, nous avons inclus un *favicon*. Vous pouvez
  164. >[télécharger celle de Jobeet](http://www.symfony-project.org/images/jobeet/favicon.ico)
  165. >et la déposer dans le dossier `web/`.
  166. ![Le module job avec le layout et les ressources](http://www.symfony-project.org/images/jobeet/1_4/04/job_layout_assets.png)
  167. >**TIP**
  168. >Par défaut, la tâche `generate:project` a créé trois dossiers pour les
  169. >ressources du projet : `web/images/` pour les images, `web/~css|CSS~/` pour
  170. >les ~feuilles de style|Feuilles de style~ et `web/js/` pour les ~JavaScript~s. Ceci fait partie des
  171. >nombreuses ~conventions|Conventions~ définies par symfony, mais vous pouvez évidemment les
  172. >placer dans un autre dossier sous le répertoire `web/`.
  173. Un lecteur avisé aura remarqué que, bien que le fichier `main.css` n'est
  174. défini nul part dans le layout par défaut, il est nécessairement présent dans le
  175. code HTML généré. Mais pas pour les autres. Comment est-ce possible ?
  176. La feuille de style a été incluse grâce à la fonction `include_stylesheets()`
  177. située entre les balises `<head>` du fichier layout. La fonction `include_stylesheets()`
  178. est appelée un **helper**. Un helper est une fonction, définie par symfony,
  179. pouvant prendre des paramètres et renvoyant du code HTML. La plupart du temps, les helpers
  180. permettent de gagner du temps, ils contiennent du code fréquemment utilisé dans les templates. Le
  181. helper `include_stylesheets()` génère une balise `<link>` spécifique aux feuilles de style.
  182. Mais comment le helper sait quelle feuille de style inclure ?
  183. La couche de la ~Vue~ peut être paramétrée en éditant le fichier de configuration `view.yml`
  184. de l'application. Voici le fichier par défaut généré lors de l'appel par la tâche
  185. `generate:app` :
  186. [yml]
  187. # apps/frontend/config/view.yml
  188. default:
  189. http_metas:
  190. content-type: text/html
  191. metas:
  192. #title: symfony project
  193. #description: symfony project
  194. #keywords: symfony, project
  195. #language: en
  196. #robots: index, follow
  197. stylesheets: [main.css]
  198. javascripts: []
  199. has_layout: true
  200. layout: layout
  201. Le fichier `view.yml` configure les paramètres par défaut pour tous les Templates de
  202. l'application. Par exemple, l'entrée `stylesheets` définit un tableau de
  203. fichiers de feuille de style à inclure pour chaque page de l'application (ceci
  204. grâce au helper `include_stylesheets()`).
  205. >**NOTE**
  206. >Dans le fichier de configuration par défaut `view.yml`, le fichier référencé est
  207. >`main.css`, et non pas `css/main.css`. En fait, les deux définitions sont équivalentes.
  208. >Symfony ~préfixe|Préfixe~ les chemins relatifs avec `/css/`.
  209. Si plusieurs fichiers sont définis, symfony les inclura dans le même ordre que celui
  210. dans lequel ils ont été définis :
  211. [yml]
  212. stylesheets: [main.css, jobs.css, job.css]
  213. Vous pouvez également définir l'attribut `media` et omettre le suffixe `.css` :
  214. [yml]
  215. stylesheets: [main.css, jobs.css, job.css, print: { media: print }]
  216. Cette configuration génèrera le code suivant :
  217. [php]
  218. <link rel="stylesheet" type="text/css" media="screen"
  219. href="/css/main.css" />
  220. <link rel="stylesheet" type="text/css" media="screen"
  221. href="/css/jobs.css" />
  222. <link rel="stylesheet" type="text/css" media="screen"
  223. href="/css/job.css" />
  224. <link rel="stylesheet" type="text/css" media="print"
  225. href="/css/print.css" />
  226. >**TIP**
  227. >Le fichier de configuration `view.yml` définit également le layout utilisé par
  228. >défaut pour l'application. Par défaut, son nom est `layout`. Par conséquent, symfony
  229. >met en page chacune de vos pages à partir du fichier `layout.php`. Vous pouvez également
  230. >désactiver cette mise en page en définissant l'entrée ~`has_layout`~ à `false`.
  231. Il fonctionne comme `jobs.css`, mais le fichier n'est nécessaire que pour la page d'accueil et le
  232. fichier `job.css` n'est nécessaire que pour la page emploi. Le fichier de configuration `view.yml`
  233. peut être personnalisés sur la base de chaque module. Changez la clé `stylesheets` du fichier
  234. `view.yml` de l'application pour contenir uniquement le fichier `main.css` :
  235. [yml]
  236. # apps/frontend/config/view.yml
  237. stylesheets: [main.css]
  238. Pour personnaliser la vue du module `job`, créez un fichier `view.yml` dans le
  239. répertoire `apps/frontend/modules/job/config/` :
  240. [yml]
  241. # apps/frontend/modules/job/config/view.yml
  242. indexSuccess:
  243. stylesheets: [jobs.css]
  244. showSuccess:
  245. stylesheets: [job.css]
  246. Sous les sections `indexSuccess` et `showSuccess` (qui sont les noms des Templates
  247. associés aux actions `index` et `show`, comme nous le verrons plus tard),
  248. vous pouvez personnaliser les entrées se trouvant sous la section `default` du
  249. fichier `view.yml` de l'application. Toutes les entrées spécifiques sont fusionnées avec la configuration
  250. de l'application. Vous pouvez également définir une configuration de toutes les actions d'un
  251. module avec la section spéciale `all`.
  252. >**SIDEBAR**
  253. >Principes de configuration dans symfony
  254. >
  255. >Pour beaucoup de fichiers de ~configuration|Configuration~ de symfony, un même paramètre peut
  256. >être défini à différents niveaux :
  257. >
  258. > * La configuration par défaut se trouve dans le framework
  259. > * La configuration globale pour le projet (dans le répertoire `config/`)
  260. > * La configuration locale pour l'application (dans le répertoire `apps/APP/config/`)
  261. > * La configuration locale est restreinte à un module (dans le répertoire
  262. > `apps/APP/modules/MODULE/config/`)
  263. >
  264. >Lors de l'exécution, le système de configuration fusionne tous les valeurs depuis les
  265. >différents fichiers si ils existent et met le résultat en cache pour de meilleures performances.
  266. En règle générale, quand quelque chose est configurable via un fichier de configuration,
  267. la même chose peut être faite avec du code PHP. Au lieu de créer un fichier `view.yml`
  268. pour le module `job` par exemple, vous pouvez aussi utiliser le helper `use_stylesheet()`
  269. pour inclure une feuille de style depuis un template :
  270. [php]
  271. <?php use_stylesheet('main.css') ?>
  272. Vous pouvez également utiliser ce helper dans le layout pour inclure une feuille de style globale.
  273. Le choix entre une méthode ou une autre est réellement une question de goût. Le
  274. fichier `view.yml` permet de définir quelque chose pour toutes les actions d'un module,
  275. ce qui n'est pas possible depuis un template. Cela dit, la configuration est plus statique.
  276. A l'inverse, le ~helper|Helpers~ `use_stylesheet()` est plus flexible et
  277. plus encore, tout se trouve au même endroit : la définition des feuilles de style et le
  278. code HTML. Pour Jobeet, nous allons utiliser le helper `use_stylesheet()`, nous pouvons donc
  279. supprimer le fichier `view.yml` que nous venons de créer, et mettre à jour le template `job` avec
  280. les appels à `use_stylesheet()` :
  281. [php]
  282. <!-- apps/frontend/modules/job/templates/indexSuccess.php -->
  283. <?php use_stylesheet('jobs.css') ?>
  284. <!-- apps/frontend/modules/job/templates/showSuccess.php -->
  285. <?php use_stylesheet('job.css') ?>
  286. >**NOTE**
  287. >De la même manière, la configuration JavaScript est faite via l'entrée `javascripts`
  288. >du fichier de configuration `view.yml` et via le ~helper `use_javascript()`~ permettant
  289. >d'inclure des fichiers JavaScript dans un Template.
  290. La page d'accueil Job
  291. ---------------------
  292. Comme vu le troisième jour, la page d'accueil est générée par l'action `index`
  293. du module `job`. L'action `index` fait partie de la couche Contrôleur de la page
  294. et le template associé, `indexSuccess.php`, fait parti de la couche Vue :
  295. apps/
  296. frontend/
  297. modules/
  298. job/
  299. actions/
  300. actions.class.php
  301. templates/
  302. indexSuccess.php
  303. ### L'action
  304. Chaque action est représentée par une méthode de la classe. Pour la page d'accueil job,
  305. la classe est `jobActions` (le nom du module avec le suffixe `Actions`) et la méthode
  306. est `executeIndex()` (le nom de l'action avec le préfixe `execute`). Dans notre cas,
  307. cela renvoie tous les jobs de la base de données :
  308. [php]
  309. // apps/frontend/modules/job/actions/actions.class.php
  310. class jobActions extends sfActions
  311. {
  312. public function executeIndex(sfWebRequest $request)
  313. {
  314. <propel>
  315. $this->jobeet_jobs = JobeetJobPeer::doSelect(new Criteria());
  316. </propel>
  317. <doctrine>
  318. $this->jobeet_jobs = Doctrine::getTable('JobeetJob')
  319. ->createQuery('a')
  320. ->execute();
  321. </doctrine>
  322. }
  323. // ...
  324. }
  325. <propel>
  326. Analysons de plus près le code : la méthode `executeIndex()` (le Contrôleur)
  327. appelle le Modèle `JobeetJobPeer` pour renvoyer tous les jobs (`new Criteria()`).
  328. Le modèle renvoie un tableau d'objet de type `JobeetJob` que l'on affecte à la propriété
  329. `jobeet_jobs` de l'objet courant.
  330. </propel>
  331. <doctrine>
  332. Analysons de plus près le code : la méthode `executeIndex()` (le Contrôleur)
  333. appelle la Table `JobeetJob` pour créer une requête renvoyant tous les jobs.
  334. La Table renvoie une 'Doctrine_Collection` d'objets de type `JobettJob` que l'on affecte
  335. à la propriété `jobeet_jobs` de l'objet courant.
  336. </doctrine>
  337. Toutes les propriétés des objets sont automatiquement passées au template (la Vue).
  338. Pour transmettre des données du Contrôleur à la Vue, il vous suffit simplement de créer une
  339. nouvelle propriété :
  340. [php]
  341. public function executeFooBar(sfWebRequest $request)
  342. {
  343. $this->foo = 'bar';
  344. $this->bar = array('bar', 'baz');
  345. }
  346. Cette méthode rendra les variables `$foo` et `$bar` accessibles depuis le template.
  347. ### Le Template
  348. Par défaut, le nom du ~template|Templates~ associé à l'action est déduit par symfony
  349. grâce a une convention (le nom de l'action avec le suffixe `Success`).
  350. Le template `indexSuccess.php` génère une table HTML pour tous les jobs. Voici
  351. le code du Template actuel :
  352. [php]
  353. <!-- apps/frontend/modules/job/templates/indexSuccess.php -->
  354. <?php use_stylesheet('jobs.css') ?>
  355. <h1>Job List</h1>
  356. <table>
  357. <thead>
  358. <tr>
  359. <th>Id</th>
  360. <th>Category</th>
  361. <th>Type</th>
  362. <!-- more columns here -->
  363. <th>Created at</th>
  364. <th>Updated at</th>
  365. </tr>
  366. </thead>
  367. <tbody>
  368. <?php foreach ($jobeet_jobs as $jobeet_job): ?>
  369. <tr>
  370. <td>
  371. <a href="<?php echo url_for('job/show?id='.$jobeet_job->getId()) ?>">
  372. <?php echo $jobeet_job->getId() ?>
  373. </a>
  374. </td>
  375. <td><?php echo $jobeet_job->getCategoryId() ?></td>
  376. <td><?php echo $jobeet_job->getType() ?></td>
  377. <!-- more columns here -->
  378. <td><?php echo $jobeet_job->getCreatedAt() ?></td>
  379. <td><?php echo $jobeet_job->getUpdatedAt() ?></td>
  380. </tr>
  381. <?php endforeach; ?>
  382. </tbody>
  383. </table>
  384. <a href="<?php echo url_for('job/new') ?>">New</a>
  385. Dans ce code, la boucle `foreach` parcourt la liste d'objets `job` (`$jobeet_jobs`)
  386. et pour chaque job, chaque valeur de colonne est affichée.
  387. Souvenez-vous, pour accéder à la valeur d'une colonne, il suffit simplement d'appeller une méthode accesseur.
  388. dont le nom commence par `get` et suivit du nom de la colonne en ~camelCased|Formattage du code~
  389. (par exemple, la méthode `getCreatedAt()` permet d'accéder à la colonne `created_at`).
  390. Faisons un peu de tri dans tout ça afin de n'afficher qu'une partie des colonnes :
  391. [php]
  392. <!-- apps/frontend/modules/job/templates/indexSuccess.php -->
  393. <?php use_stylesheet('jobs.css') ?>
  394. <div id="jobs">
  395. <table class="jobs">
  396. <?php foreach ($jobeet_jobs as $i => $job): ?>
  397. <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>">
  398. <td class="location"><?php echo $job->getLocation() ?></td>
  399. <td class="position">
  400. <a href="<?php echo url_for('job/show?id='.$job->getId()) ?>">
  401. <?php echo $job->getPosition() ?>
  402. </a>
  403. </td>
  404. <td class="company"><?php echo $job->getCompany() ?></td>
  405. </tr>
  406. <?php endforeach; ?>
  407. </table>
  408. </div>
  409. ![Homepage](http://www.symfony-project.org/images/jobeet/1_4/04/homepage.png)
  410. La fonction `url_for()` utilisée dans ce template est un helper symfony que nous détaillerons
  411. dans le chapitre de demain.
  412. Le template de la page job
  413. --------------------------
  414. Personnalisons maintenant le template de la page job. Ouvrez le fichier `showSuccess.php`
  415. et remplacez son contenu par le code suivant :
  416. [php]
  417. <!-- apps/frontend/modules/job/templates/showSuccess.php -->
  418. <?php use_stylesheet('job.css') ?>
  419. <?php use_helper('Text') ?>
  420. <div id="job">
  421. <h1><?php echo $job->getCompany() ?></h1>
  422. <h2><?php echo $job->getLocation() ?></h2>
  423. <h3>
  424. <?php echo $job->getPosition() ?>
  425. <small> - <?php echo $job->getType() ?></small>
  426. </h3>
  427. <?php if ($job->getLogo()): ?>
  428. <div class="logo">
  429. <a href="<?php echo $job->getUrl() ?>">
  430. <img src="/uploads/jobs/<?php echo $job->getLogo() ?>"
  431. alt="<?php echo $job->getCompany() ?> logo" />
  432. </a>
  433. </div>
  434. <?php endif; ?>
  435. <div class="description">
  436. <?php echo simple_format_text($job->getDescription()) ?>
  437. </div>
  438. <h4>How to apply?</h4>
  439. <p class="how_to_apply"><?php echo $job->getHowToApply() ?></p>
  440. <div class="meta">
  441. <propel>
  442. <small>posted on <?php echo $job->getCreatedAt('m/d/Y') ?></small>
  443. </propel>
  444. <doctrine>
  445. <small>posted on <?php echo $job->getDateTimeObject('created_at')->format('m/d/Y') ?></small>
  446. </doctrine>
  447. </div>
  448. <div style="padding: 20px 0">
  449. <a href="<?php echo url_for('job/edit?id='.$job->getId()) ?>">
  450. Edit
  451. </a>
  452. </div>
  453. </div>
  454. Ce template utilise la variable `$job` passée en paramètre par l'action pour afficher
  455. les informations sur un job. Comme nous avons renommé la variable utilisée dans le template
  456. de `$jobeet_job` en `$job`), vous devez également faire modification dans l'action `show`
  457. (attention, la variable s'y trouve deux fois) :
  458. [php]
  459. // apps/frontend/modules/job/actions/actions.class.php
  460. public function executeShow(sfWebRequest $request)
  461. {
  462. <propel>
  463. $this->job =
  464. JobeetJobPeer::retrieveByPk($request->getParameter('id'));
  465. </propel>
  466. <doctrine>
  467. $this->job = Doctrine::getTable('JobeetJob')->
  468. find($request->getParameter('id'));
  469. </doctrine>
  470. $this->forward404Unless($this->job);
  471. }
  472. <propel>
  473. Remarquez que certains ~accesseurs|Acesseurs~ Propel prennent des argumentss. Comme nous avons
  474. défini la colonne `created_at` de type timestamp, l'accesseur `getCreatedAt()` prend
  475. en paramètre le format de la date à renvoyer comme premier argument :
  476. [php]
  477. $job->getCreatedAt('m/d/Y');
  478. </propel>
  479. <doctrine>
  480. Notez que les colonnes de date peuvent être converties en instances d'objets PHP DateTime. Comme
  481. nous avons défini la colonne `created_at` comme un timestamp, vous pouvez convertir l
  482. valeur de la colonne en un objet DateTime en utilisant la méthode `getDateTimeObject()`
  483. et ensuite appeler la méthode `format()` dont son premier argument prend un format de
  484. mise en forme d'une date :
  485. [php]
  486. $job->getDateTimeObject('created_at')->format('m/d/Y');
  487. </doctrine>
  488. >**NOTE**
  489. >La description d'un job utilise le helper `simple_format_text()` afin de formater
  490. >le texte en HTML, en remplaçant notamment les retours chariots par des balises `<br />`.
  491. >Comme ce helper fait parti du groupe `Text` et que celui-ci n'est pas chargé par défaut,
  492. >nous le chargeons manuellement en utilisant le ~helper `use_helper()`~.
  493. ![La page job](http://www.symfony-project.org/images/jobeet/1_4/04/job.png)
  494. Les ~Slot~s
  495. -----------
  496. Actuellement, le titre de toutes les pages est défini dans la balise `<title>`
  497. du layout :
  498. [php]
  499. <title>Jobeet - Your best job board</title>
  500. Mais pour un job, nous aimerions avoir des informations plus utiles telles que le
  501. nom de la société et le type d'emploi.
  502. Avec symfony, quand une zone du layout dépend du template à afficher, vous devez utiliser
  503. un slot :
  504. ![Slots](http://www.symfony-project.org/images/jobeet/1_4/04/layout_slots.png)
  505. Ajoutez un slot au layout afin de rendre le titre dynamique :
  506. [php]
  507. // apps/frontend/templates/layout.php
  508. <title><?php include_slot('title') ?></title>
  509. Chaque slot est identifié par un nom (ici `title`) et peut être affiché en
  510. utilisant le helper `~include_slot()~`. Maintenant, au début du template
  511. `showSuccess.php`, utilisez le helper `slot()` afin de définir le contenu
  512. du slot pour la page job :
  513. [php]
  514. // apps/frontend/modules/job/templates/showSuccess.php
  515. <?php slot(
  516. 'title',
  517. sprintf('%s is looking for a %s', $job->getCompany(), $job->getPosition()))
  518. ?>
  519. Si le titre est complexe à définir, le helper `slot()` peut aussi être utilisé
  520. dans un block de code :
  521. [php]
  522. // apps/frontend/modules/job/templates/showSuccess.php
  523. <?php slot('title') ?>
  524. <?php echo sprintf('%s is looking for a %s', $job->getCompany(), $job->getPosition()) ?>
  525. <?php end_slot(); ?>
  526. Pour certaines pages, comme la page d'accueil, nous avons juste besoin d'un titre
  527. générique. Plutôt que de répéter le même titre encore et encore dans chaque template,
  528. nous pouvons définir un titre par défaut dans le layout :
  529. [php]
  530. // apps/frontend/templates/layout.php
  531. <title>
  532. <?php include_slot('title', 'Jobeet - Your best job board') ?>
  533. </title>
  534. Le deuxième argument de la méthode `include_slot()` est la valeur par défaut pour
  535. le slot si elle n'a pas été défini. Si la valeur par défaut est plus longue ou a
  536. certaines balises HTML, vous pouvez aussi le définir comme dans le code suivant :
  537. [php]
  538. // apps/frontend/templates/layout.php
  539. <title>
  540. <?php if (!include_slot('title')): ?>
  541. Jobeet - Your best job board
  542. <?php endif; ?>
  543. </title>
  544. Le helper `include_slot()` renvoie `true` si le slot a été défini. Ainsi,
  545. lorsque vous spécifiez une valeur pour le slot `title` dans un template, il est utilisé, sinon,
  546. ce sera le titre par défaut qui sera utilisé.
  547. >**TIP**
  548. >Nous avons déjà vu quelques helpers commençant par `include_`. Ces helpers
  549. >renvoient du code HTML et dans la plupart des cas, ils ont un helper `get_`
  550. >permettant de renvoyer uniquement le contenu :
  551. >
  552. > [php]
  553. > <?php include_slot('title') ?>
  554. > <?php echo get_slot('title') ?>
  555. >
  556. > <?php include_stylesheets() ?>
  557. > <?php echo get_stylesheets() ?>
  558. L'action de la page job
  559. -----------------------
  560. La page job est générée grâce à l'action `show`, définie par la méthode
  561. `executeShow()` du module `job` :
  562. [php]
  563. class jobActions extends sfActions
  564. {
  565. public function executeShow(sfWebRequest $request)
  566. {
  567. <propel>
  568. $this->job =
  569. JobeetJobPeer::retrieveByPk($request->getParameter('id'));
  570. </propel>
  571. <doctrine>
  572. $this->job = Doctrine::getTable('JobeetJob')->
  573. find($request->getParameter('id'));
  574. </doctrine>
  575. $this->forward404Unless($this->job);
  576. }
  577. // ...
  578. }
  579. <propel>
  580. Comme dans l'action `index`, la classe `JobeetJobPeer` est utilisée pour récupérer
  581. un job, cette fois en utilisant la méthode `retrieveByPk()`. Le paramètre de cette
  582. méthode est un identifiant unique d'un job, sa ~clé primaire|Clé primaire~. La section
  583. suivante explique pourquoi l'instruction `$request->getParameter('id')` renvoie la clé
  584. primaire d'un job.
  585. </propel>
  586. <doctrine>
  587. Comme dans l'action `index`, la classe de la table `JobeetJob` est utilisée pour récupérer un job,
  588. cette fois en utilisant la méthode `find()`. Le paramètre de cette méthode est
  589. l'identifiant unique d'un job, sa ~clé primaire|Clé primaire~. La prochaine
  590. section explique pourquoi l'instruction `$request->getParameter('id')` renvoie la clé
  591. primaire d'un job.
  592. </doctrine>
  593. <propel>
  594. >**TIP**
  595. >Le modèle de classe généré contient un grand nombre de méthodes utiles pour interagir avec
  596. >les objets du projet. Prenez un peu de temps pour parcourir le code situé dans le dossier
  597. >`lib/om/` et découvrez toute la puissance embarqué dans ces classes.
  598. </propel>
  599. Si le job n'existe pas dans la base de données, nous voudrions renvoyer l'utilisateur vers
  600. une page ~404|erreur 404~, c'est ce que fait exactementla méthode `forward404Unless()`.
  601. Elle prend en premier paramètre un Booléen et, à moins que ce paramètre ne soit à true,
  602. elle arrête l'exécution normale. Cette méthode génère une exception `sfError404Exception`
  603. et vous n'avez donc pas besoin de rajouter de `return` après cette
  604. méthode.
  605. Comme pour toutes ~exceptions|Gestion de l'exception~, la page affichée est différente en fonction de
  606. l'~environnement|Environnement~ de `prod` ou de `dev` :
  607. ![Erreur 404 dans l'environnement dev](http://www.symfony-project.org/images/jobeet/1_4/05/404_dev.png)
  608. ![Erreur 404 dans l'environnement prod](http://www.symfony-project.org/images/jobeet/1_4/05/404_prod.png)
  609. >**NOTE**
  610. >Avant que nous déployons notre site Jobeet sur le serveur de production, vous
  611. >apprendrez à personnaliser la page 404 par défaut.
  612. -
  613. >**SIDEBAR**
  614. >La famille des méthodes "~forward|Action de redirection~"
  615. >
  616. >L'appel à la méthode `forward404Unless` est équivalent à :
  617. >
  618. > [php]
  619. > $this->forward404If(!$this->job);
  620. >
  621. >qui est équivalent à :
  622. >
  623. > [php]
  624. > if (!$this->job)
  625. > {
  626. > $this->forward404();
  627. > }
  628. >
  629. >La méthode `forward404()` elle-même étant juste un raccourci pour :
  630. >
  631. > [php]
  632. > $this->forward('default', '404');
  633. >
  634. >La méthode `forward()` renvoie vers une autre action de la même application;
  635. >dans l'exemple précédent, vers l'action `404` du module `default`.
  636. >Le module `default` fait parti intégrante de symfony et fournit des actions
  637. >par défaut pour afficher les pages 404, de sécurité et de connexion.
  638. Les requêtes et les réponses
  639. ----------------------------
  640. Quand vous accédez à la page `/job` ou `/job/show/id/1` depuis votre navigateur, vous
  641. provoquez un ensemble de traitements entre le serveur web et votre ordinateur. Votre navigateur envoie
  642. une **~requête|Requête HTTP~** et le serveur vous renvoie une **~réponse|Réponse HTTP~**.
  643. Nous avons déjà vu que symfony encapsule les requêtes dans un objet `sfWebRequest`
  644. (regardez la signature de la méthode `executeShow()`). Et comme symfony est un
  645. framework Orienté Objet, la réponse est également un objet de classe
  646. `sfWebResponse`. Vous pouvez récupérer l'objet de la réponse dans une action en appelant
  647. `$this->getResponse()`.
  648. Ces objets permettent un accès pratique et simple pour obtenir des informations
  649. sur des fonctions PHP et des variables globales PHP.
  650. >**NOTE**
  651. >Pourquoi symfony redéfinit-il des fonctions PHP déjà existantes ? Premièrement,
  652. >parce que celles de symfony sont plus puissantes que leur homologue PHP. Ensuite,
  653. >parce que quand vous testez une application, il est plus facile de simuler des requêtes
  654. >ou des réponses grâce à des objets plutôt que d'essayer d'utiliser des variables globales
  655. >ou travailler avec des fonctions PHP comme `header()` si mystiques.
  656. ### La requête
  657. La classe `sfWebRequest` redéfinit les tableaux globaux PHP ~`$_SERVER`~,
  658. ~`$_COOKIE`~, ~`$_GET`~, ~`$_POST`~, et ~`$_FILES`~ :
  659. Nom de la méthode | Équivalent PHP
  660. -------------------- | --------------------------------------------------
  661. `getMethod()` | `$_SERVER['REQUEST_METHOD']`
  662. `getUri()` | `$_SERVER['REQUEST_URI']`
  663. `getReferer()` | `$_SERVER['HTTP_REFERER']`
  664. `getHost()` | `$_SERVER['HTTP_HOST']`
  665. `getLanguages()` | `$_SERVER['HTTP_ACCEPT_LANGUAGE']`
  666. `getCharsets()` | `$_SERVER['HTTP_ACCEPT_CHARSET']`
  667. `isXmlHttpRequest()` | `$_SERVER['X_REQUESTED_WITH'] == 'XMLHttpRequest'`
  668. `getHttpHeader()` | `$_SERVER`
  669. `getCookie()` | `$_COOKIE`
  670. `isSecure()` | `$_SERVER['HTTPS']`
  671. `getFiles()` | `$_FILES`
  672. `getGetParameter()` | `$_GET`
  673. `getPostParameter()` | `$_POST`
  674. `getUrlParameter()` | `$_SERVER['PATH_INFO']`
  675. `getRemoteAddress()` | `$_SERVER['REMOTE_ADDR']`
  676. Nous avons déjà accédé aux paramètres d'une requête en utilisant la méthode
  677. `getParameter()`. Elle renvoie une valeur depuis la variable globale `$_GET` ou `$_POST`,
  678. ou depuis la variable `~PATH_INFO~`.
  679. Si vous voulez être sûr qu'un paramètre demandé provienne de l'une de ces variables en
  680. particulier, vous devez utiliser respectivement la méthode `getGetParameter()`,
  681. `getPostParameter()` et `getUrlParameter()`.
  682. >**NOTE**
  683. >Quand vous voulez restreindre une action pour une ~méthode HTTP|Méthode HTTP~ spécifique,
  684. >par exemple quand vous voulez être sûr qu'un formulaire ait été envoyé via la méthode `POST`,
  685. >vous pouvez utiliser la méthode `isMethod()` :
  686. >`$this->forwardUnless($request->isMethod('POST'));`.
  687. ### La réponse
  688. La classe `sfWebResponse` redéfinit les méthodes PHP `~header|HTTP Headers~()` et
  689. `setraw~cookie|Cookies~()` :
  690. Nom de la méthode | Équivalent PHP
  691. ----------------------------- | ----------------
  692. `setCookie()` | `setrawcookie()`
  693. `setStatusCode()` | `header()`
  694. `setHttpHeader()` | `header()`
  695. `setContentType()` | `header()`
  696. `addVaryHttpHeader()` | `header()`
  697. `addCacheControlHttpHeader()` | `header()`
  698. Évidemment, la classe `sfWebResponse` permet aussi de définir la réponse du serveur web
  699. (`setContent()`) et de l'envoyer au navigateur (`send()`).
  700. Plus tôt aujourd'hui, nous avons vu comment gérer les feuilles de style et les JavaScripts dans le
  701. fichier `view.yml` et dans les templates. Finalement, ces deux techniques utilisent les méthodes
  702. `addStylesheet()` et `addJavascript()` de l'objet réponse.
  703. >**TIP**
  704. >Les classes [`sfAction`](http://www.symfony-project.org/api/1_4/sfAction),
  705. >[`sfRequest`](http://www.symfony-project.org/api/1_4/sfRequest), et
  706. >[`sfResponse`](http://www.symfony-project.org/api/1_4/sfResponse)
  707. >fournissent un grand nombre de méthodes très utiles. N'hésitez pas à
  708. >parcourir [la documentation de l'API](http://www.symfony-project.org/api/1_4/)
  709. >pour en apprendre plus sur les classes internes de symfony.
  710. Conclusion
  711. ----------
  712. Tout au long de ce chapitre, nous avons décrit quelques uns des modèles de conception utilisés par symfony. Espérons que
  713. la structure des répertoires du projet ait maintenant plus de sens. Nous avons joué avec les
  714. Templates en manipulant la mise en page et les fichiers des Templates. Nous avons également rendu
  715. les pages un peu plus dynamiques grâce aux slots et aux actions.
  716. Au cours du prochain chapitre, nous en apprendrons davantage sur le helper `url_for()` que nous avons aperçu aujourd'hui,
  717. et le "sous-framework" de routage qui lui est associé.
  718. __ORM__