PageRenderTime 58ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/jobeet/it/04.markdown

https://github.com/rafaelgou/symfony1-docs
Markdown | 860 lines | 700 code | 160 blank | 0 comment | 0 complexity | 0e874c0c6d88949dbcc508c8a6d93ac9 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. Giorno 4: Il controllore e la vista
  2. ===================================
  3. Ieri abbiamo analizzato come symfony possa semplificare la gestione
  4. del database astraendo le differenze tra i vari motori di database e convertendo
  5. gli elementi dello schema relazionale in classi orientate agli oggetti. Abbiamo inoltre
  6. giocato con ##ORM## per descrivere lo schema del database, creare le tabelle e
  7. popolare il database con alcuni dati iniziali.
  8. Oggi andremo a personalizzare il modulo `job` creato ieri. Il modulo `job`
  9. contiene già tutto il codice di cui abbiamo bisogno per Jobeet:
  10. * Una pagina per elencare tutte le offerte di lavoro
  11. * Una pagina per creare una nuova offerta
  12. * Una pagina per aggiornare un'offerta esistente
  13. * Una pagina per cancellare un'offerta
  14. Nonostante il codice sia pronto per essere usato com'è, rifattorizzeremo
  15. i template per attenerci il più possibile ai mockup di Jobeet.
  16. L'architettura ~MVC~
  17. --------------------
  18. Se siete abituati a sviluppare siti web con PHP senza utilizzare un framework,
  19. probabilmente utilizzate il paradigma del singolo file PHP per singola pagina
  20. HTML. Questi file PHP probabilmente contengono lo stesso tipo di struttura:
  21. inizializzazione e configurazioni globali, business logic relativa alla pagina
  22. richiesta, recupero dei record dal database e infine il codice HTML che
  23. costruisce la pagina.
  24. Potreste usare un motore per i template per separare la logica dall'HTML.
  25. Forse usate un layer per l'astrazione del database per separare l'interazione
  26. tra il modello e la business logic. Purtroppo il più delle volte finite per
  27. avere una grande quantità di codice che è un vero e proprio incubo da mantenere.
  28. È stato veloce da realizzare, ma con il passare del tempo è sempre più difficile
  29. apportare cambiamenti, specialmente perché nessuno eccetto voi capisce
  30. come è fatto e come funziona.
  31. Come per tutti i problemi esistono piacevoli soluzioni. Per lo sviluppo web la
  32. soluzione più diffusa di questi tempi per organizzare il codice è
  33. rappresentata dal [**pattern architetturale MVC**](http://it.wikipedia.org/wiki/Model-View-Controller).
  34. Brevemente il pattern MVC definisce un modo per organizzare il proprio
  35. codice secondo la sua natura. Questo pattern separa il codice in **tre strati**:
  36. * Il **~Modello~** è lo strato che definisce la business logic (il database
  37. appartiene a questo strato). Probabilmente siete al corrente del fatto che symfony
  38. memorizza tutte le classi e i file relativi al Modello nella cartella `lib/model/`.
  39. * La **~Vista~** rappresenta ciò con cui l'utente interagisce (un motore di template è
  40. parte di questo strato). In symfony lo strato della Vista è principalmente costituito
  41. da template PHP. Queste sono memorizzate in varie cartelle `templates` come
  42. vedremo in seguito.
  43. * Il **~Controllore~** è la parte di codice che chiama il Modello per ottenere
  44. alcuni dati da passare alla Vista per visualizzarli attraverso il client.
  45. Quando abbiamo installato symfony il primo giorno abbiamo visto che tutte
  46. le richieste sono gestite dai front controller (`index.php` e `frontend_dev.php`).
  47. Questi front controller delegano il vero lavoro alle **azioni**. Come
  48. abbiamo visto ieri queste azioni sono raggruppate in **moduli**.
  49. ![MVC](http://www.symfony-project.org/images/jobeet/1_4/04/mvc.png)
  50. Oggi utilizzeremo il mockup definito il giorno 2 per personalizzare l'homepage
  51. e la pagina delle offerte di lavoro. Inoltre le renderemo dinamiche. Lungo la strada
  52. perfezioneremo molte cose in molti file differenti per mostrare la struttura
  53. delle cartelle di symfony e come separare il codice tra i vari strati.
  54. Il Layout
  55. ---------
  56. Per prima cosa se avete guardato con attenzione i mockup avrete notato che
  57. gran parte di ogni pagina sembra sempre la stessa. Sapere già che la duplicazione
  58. del codice è una cattiva pratica se stiamo parlando di codice HTML o PHP, perciò
  59. abbiamo bisogno di trovare un modo per prevenire il fatto che elementi comuni
  60. implichino duplicazione del codice.
  61. Un modo per risolvere questo problema è quello di definire un header e un footer
  62. includendoli in ogni template:
  63. ![Header e footer](http://www.symfony-project.org/images/jobeet/1_4/04/header_footer.png)
  64. Ma qui i file di header e footer non contengono HTML valido. Deve esserci una
  65. strada migliore. Invece di reinventate la ruota, utilizzeremo un altro design
  66. pattern per risolvere questo problema: il
  67. [design pattern ~decorator~](http://it.wikipedia.org/wiki/Decorator_pattern).
  68. Il design pattern decorator risolve il problema agendo al contrario: il template
  69. viene decorato dopo che il contenuto è stato reso da un template globale,
  70. in symfony questo è definito come un **~layout~**:
  71. ![Layout](http://www.symfony-project.org/images/jobeet/1_4/04/layout.png)
  72. Il layout di default di un'applicazione è chiamato `layout.php` e può
  73. essere trovato nella cartella `apps/frontend/templates/`. Questa cartella
  74. contiene tutti i template globali di un'applicazione.
  75. Rimpiazzate il layout di default di symfony con il seguente codice:
  76. [php]
  77. <!-- apps/frontend/templates/layout.php -->
  78. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  79. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  80. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  81. <head>
  82. <title>Jobeet - Your best job board</title>
  83. <link rel="shortcut icon" href="/favicon.ico" />
  84. <?php include_javascripts() ?>
  85. <?php include_stylesheets() ?>
  86. </head>
  87. <body>
  88. <div id="container">
  89. <div id="header">
  90. <div class="content">
  91. <h1><a href="<?php echo url_for('job/index') ?>">
  92. <img src="/images/logo.jpg" alt="Jobeet Job Board" />
  93. </a></h1>
  94. <div id="sub_header">
  95. <div class="post">
  96. <h2>Ask for people</h2>
  97. <div>
  98. <a href="<?php echo url_for('job/index') ?>">Post a Job</a>
  99. </div>
  100. </div>
  101. <div class="search">
  102. <h2>Ask for a job</h2>
  103. <form action="" method="get">
  104. <input type="text" name="keywords"
  105. id="search_keywords" />
  106. <input type="submit" value="search" />
  107. <div class="help">
  108. Enter some keywords (city, country, position, ...)
  109. </div>
  110. </form>
  111. </div>
  112. </div>
  113. </div>
  114. </div>
  115. <div id="content">
  116. <?php if ($sf_user->hasFlash('notice')): ?>
  117. <div class="flash_notice">
  118. <?php echo $sf_user->getFlash('notice') ?>
  119. </div>
  120. <?php endif; ?>
  121. <?php if ($sf_user->hasFlash('error')): ?>
  122. <div class="flash_notice">
  123. <?php echo $sf_user->getFlash('error') ?>
  124. </div>
  125. <?php endif; ?>
  126. <div class="content">
  127. <?php echo $sf_content ?>
  128. </div>
  129. </div>
  130. <div id="footer">
  131. <div class="content">
  132. <span class="symfony">
  133. <img src="/images/jobeet-mini.png" />
  134. powered by <a href="http://www.symfony-project.org/">
  135. <img src="/images/symfony.gif" alt="symfony framework" />
  136. </a>
  137. </span>
  138. <ul>
  139. <li><a href="">About Jobeet</a></li>
  140. <li class="feed"><a href="">Full feed</a></li>
  141. <li><a href="">Jobeet API</a></li>
  142. <li class="last"><a href="">Affiliates</a></li>
  143. </ul>
  144. </div>
  145. </div>
  146. </div>
  147. </body>
  148. </html>
  149. Un template di symfony è solamente un semplice file PHP. Nel template del
  150. layout ci sono chiamate a funzioni PHP e riferimenti a variabili PHP.
  151. `$sf_content` è la variabile più interessante: è definita dal framework stesso
  152. e contiene l'HTML generato dall'azione.
  153. Se navigate il modulo `job` (`http://jobeet.localhost/frontend_dev.php/job`)
  154. potete vedere che tutte le azioni sono decorate dal layout.
  155. I Fogli di stile, le Immagini e i Javascript
  156. --------------------------------------------
  157. Dato che questo tutorial non riguarda il web design, abbiamo già preparato
  158. tutte le risorse necessarie per Jobbet:
  159. organizzeremo un concorso per il miglior design il giorno 21, abbiamo
  160. [scaricate l'archivio delle immagini](http://www.symfony-project.org/get/jobeet/images.zip)
  161. e mettetele nella cartella `web/images`;
  162. [scarica tel'archivio dei fogli di stile](http://www.symfony-project.org/get/jobeet/css.zip)
  163. e metteteli nella cartella `web/css/`.
  164. >**NOTE**
  165. >Nel layout abbiamo incluso una *favicon*. Potete
  166. >[scaricare quella di Jobeet](http://www.symfony-project.org/images/jobeet/favicon.ico)
  167. >e metterla nella cartella `web/`.
  168. ![Il modulo job con layout ed elementi grafici](http://www.symfony-project.org/images/jobeet/1_4/04/job_layout_assets.png)
  169. >**TIP**
  170. >Di default, il task `generate:project` ha creato tre cartelle per i file degli
  171. >elementi grafici: `web/images/` per le immagini, `web/~css~/` per i ~fogli di stile~
  172. >`web/js/` per i ~Javascript~. Questa è una delle ~convenzioni~ definite da symfony,
  173. >ma potete salvarli ovunque vogliate all'interno della cartella `web/`.
  174. Il lettore più attento avrà notato che anche se il file `main.css` non è
  175. menzionato in nessun posto nel layout, è presente nell'HTML generato. Ma nessun
  176. altro file è presente. Com'è possibile?
  177. Il foglio di stile è stato incluso dalla funzione `~include_stylesheets~()`
  178. chiamata nel tag `<head>` all'interno del layout. La funzione `include_stylesheets()`
  179. è chiamata **helper**. Un helper è una funzione definita da symfony, che accetta
  180. dei parametri e restituisce codice HTML. La maggior parte delle volte, gli helper
  181. fanno risparmiare del tempo e racchiudono degli spezzoni di codice usati di
  182. frequente nei template. L'helper `include_stylesheets()` genera il tag `<link>`
  183. per i fogli di stile.
  184. Ma come fa l'helper a sapere quali fogli di stile includere?
  185. Lo strato della ~Vista~ può essere configurato modificando il file di configurazione
  186. dell'applicazione `~view.yml~`. Questo è quello generato di default dal comando
  187. `generate:app`:
  188. [yml]
  189. # apps/frontend/config/view.yml
  190. default:
  191. http_metas:
  192. content-type: text/html
  193. metas:
  194. #title: symfony project
  195. #description: symfony project
  196. #keywords: symfony, project
  197. #language: en
  198. #robots: index, follow
  199. stylesheets: [main.css]
  200. javascripts: []
  201. has_layout: true
  202. layout: layout
  203. Il file `view.yml` configura le impostazioni di `default` per ogni template
  204. dell'applicazione. Per esempio, l'elemento `stylesheets` definisce un array di
  205. fogli di stile da includere in ogni pagina dell'applicazione (l'inclusione è
  206. fatta dall'helper `include_stylesheets()`).
  207. >**NOTE**
  208. >Nel file `view.yml` di default, il file referenziato è `main.css` e non
  209. >`/css/main.css`. Comunque, le due definizioni sono equivalente in quanto
  210. >symfony aggiunge il prefisso `/~css~/` ai percorsi relativi.
  211. Se molti file sono definiti, symfony li includerà nello stesso ordine della
  212. definizione:
  213. [yml]
  214. stylesheets: [main.css, jobs.css, job.css]
  215. Si può anche cambiare l'attributo `media` e omettere il suffisso `.css`:
  216. [yml]
  217. stylesheets: [main.css, jobs.css, job.css, print: { media: print }]
  218. Questo file di configurazione sarà tradotto in:
  219. [php]
  220. <link rel="stylesheet" type="text/css" media="screen" href="/css/main.css" />
  221. <link rel="stylesheet" type="text/css" media="screen" href="/css/jobs.css" />
  222. <link rel="stylesheet" type="text/css" media="screen" href="/css/job.css" />
  223. <link rel="stylesheet" type="text/css" media="print" href="/css/print.css" />
  224. >**TIP**
  225. >Il file di configurazione `view.yml` definisce anche il ~layout~ usato dall'applicazione.
  226. >Di default, il nome è `layout`, così symfony decora ogni pagina con il file
  227. >`layout.php`. Si può anche disabilitare il processo di decorazione una volta per
  228. >tutte, impostando la proprietà `~has_layout~` a `false`.
  229. Funziona già così com'è, ma il file `jobs.css` è necessario solo per l'homepage,
  230. e il file `job.css` è necessario solo per la pagina del lavoro. Il file `view.yml`
  231. può essere personalizzato in ogni modulo. Cambiamo la chiave `stylesheets`
  232. del file `view.yml` dell'applicazione in modo che contenga solo il file `main.css`:
  233. [yml]
  234. # apps/frontend/config/view.yml
  235. stylesheets: [main.css]
  236. Per personalizzare la vista del modulo `job`, creiamo un file `view.yml`
  237. all'interno della cartella `apps/frontend/modules/job/config`:
  238. [yml]
  239. # apps/frontend/modules/job/config/view.yml
  240. indexSuccess:
  241. stylesheets: [jobs.css]
  242. showSuccess:
  243. stylesheets: [job.css]
  244. All'interno delle sezioni `indexSuccess` e `showSuccess` (sono i nomi dei file dei
  245. template associati alle azioni `index` e `show`, come vedremo in seguito), si può
  246. personalizzare ogni elemento all'interno della sezione `default` del file
  247. `view.yml` dell'applicazione. Tutti i nuovi elementi sono sostituiti a quelli
  248. definiti nella configurazione dell'applicazione. Si possono inoltre definire alcune
  249. configurazioni per tutte le azioni di un modulo con la sezione speciale `all`.
  250. >**SIDEBAR**
  251. >Principi di configurazione di symfony
  252. >
  253. >Per molti file di ~configurazione~ di symfony, la stessa impostazione può
  254. >essere definita in livelli differenti:
  255. >
  256. > * La configurazione di default è all'interno del framework
  257. > * La configurazione globale per il progetto (in `config/`)
  258. > * La configurazione locale per un'applicazione (in `apps/APP/config/`)
  259. > * La configurazione locale per un modulo (in `apps/APP/modules/MODULE/config/`)
  260. >
  261. >Nell'esecuzione, il sistema di configurazione unisce tutti i valori dei
  262. >differenti file se esistono e crea un copia cache dei risultati per migliorare
  263. >le performance.
  264. Solitamente, quando qualcosa è configurabile tramite un file di configurazione,
  265. lo è anche tramite codice PHP. al posto di create un file `view.yml` per il modulo
  266. `job` per esempio, potete anche usare l'helper `~use_stylesheet~()` per includere
  267. un foglio di stile da un template:
  268. [php]
  269. <?php use_stylesheet('main.css') ?>
  270. Si può anche usare questo helper nel layout per includere un foglio di stile globale.
  271. Scegliere tra un metodo è l'altro è solo un questione di gusti. Il file `view.yml`
  272. fornisce un modo per definire impostazioni per tutte le azioni di un modulo,
  273. che non è possibile in un template, ma la configurazione è statica. D'altro canto,
  274. usare l'~helper~ `use_stylehseet()` è più flessibile e, soprattutto, è tutto nello
  275. stesso posto: la definizione dello stile e il codice HTMl. Per Jobeet useremo
  276. l'helper `use_stylesheet()`, per cui potete eliminare il file `view.yml` che
  277. abbiamo appena creato e modificare i template `job` con le chiamate a `use_stylesheet()`:
  278. [php]
  279. <!-- apps/frontend/modules/job/templates/indexSuccess.php -->
  280. <?php use_stylesheet('jobs.css') ?>
  281. <!-- apps/frontend/modules/job/templates/showSuccess.php -->
  282. <?php use_stylesheet('job.css') ?>
  283. >**NOTE**
  284. >Simmetricamente, la configurazione di Javascript è eseguita dell'elemento `javascripts`
  285. >del file `view.yml` e l'helper `~use_javascript~()` definisce i file JavaScript
  286. >da includere in un template.
  287. L'Homepage di Jobeet
  288. --------------------
  289. Come visto nel giorno 3, la pagina dei lavori è generata dall'azione `index` del
  290. modulo `job`. L'azione `index` è la parte Controller della pagina e il template
  291. associato, `indexSuccess.php`, è la View:
  292. apps/
  293. frontend/
  294. modules/
  295. job/
  296. actions/
  297. actions.class.php
  298. templates/
  299. indexSuccess.php
  300. ### L'azione
  301. Ogni ~azione~ è rappresentata da un metodo di una classe. Per l'homepage di Jobeet,
  302. la classe è `jobActions` (il nome del modulo seguito dal suffisso `Actions`) ed
  303. il metodo è `executeIndex()` (`execute` seguito dal nome dell'azione).
  304. L'azione recupera tutti i lavori dal database:
  305. [php]
  306. // apps/frontend/modules/job/actions/actions.class.php
  307. class jobActions extends sfActions
  308. {
  309. public function executeFooBar(sfWebRequest $request)
  310. {
  311. <propel>
  312. $this->jobeet_jobs = JobeetJobPeer::doSelect(new Criteria());
  313. </propel>
  314. <doctrine>
  315. $this->jobeet_jobs = Doctrine::getTable('JobeetJob')
  316. ->createQuery('a')
  317. ->execute();
  318. </doctrine>
  319. }
  320. // ...
  321. }
  322. <propel>
  323. Diamo uno sguardo da vicino al codice: il metodo `executeIndex()` (il Controllore)
  324. chiama il Modello `JobeetJobPeer` per recuperare tutti i lavori (`new Criteria()`).
  325. Esso restituisce un array di oggetti `JobeetJob` che sono assegnati alla
  326. proprietà `jobeet_jobs`.
  327. </propel>
  328. <doctrine>
  329. Diamo uno sguardo da vicino al codice: il metodo `executeIndex()` (il Controllore)
  330. chiama il Modello `JobeetJob` per creare una query per recuperare tutti i
  331. lavori. Esso restituisce un `Doctrine_Collection` di oggetti `JobeetJob` che
  332. sono assegnati alla proprietà `jobeet_jobs`.
  333. </doctrine>
  334. Ognuna di queste proprietà è automaticamente passata al template (la Vista). Per
  335. passare dati dal Controllore alla Vista, basta creare una nuova proprietà:
  336. [php]
  337. public function executeIndex(sfWebRequest $request)
  338. {
  339. $this->foo = 'bar';
  340. $this->bar = array('bar', 'baz');
  341. }
  342. Questo codice renderà le variabili `$foo` e `$bar` accessibili dal template.
  343. ### Il Template
  344. Di default, il ~template~ associato a un'azione è dedotto da symfony grazie a
  345. una convenzione (il nome dell'azione seguito dal suffisso `Success`).
  346. Il template `indexSuccess.php` genera una tabella HTML per tutti i lavori.
  347. Ecco il codice attuale del template:
  348. [php]
  349. <!-- apps/frontend/modules/job/templates/indexSuccess.php -->
  350. <?php use_stylesheet('jobs.css') ?>
  351. <h1>Job List</h1>
  352. <table>
  353. <thead>
  354. <tr>
  355. <th>Id</th>
  356. <th>Category</th>
  357. <th>Type</th>
  358. <!-- more columns here -->
  359. <th>Created at</th>
  360. <th>Updated at</th>
  361. </tr>
  362. </thead>
  363. <tbody>
  364. <?php foreach ($jobeet_jobs as $jobeet_job): ?>
  365. <tr>
  366. <td>
  367. <a href="<?php echo url_for('job/show?id='.$jobeet_job->getId()) ?>">
  368. <?php echo $jobeet_job->getId() ?>
  369. </a>
  370. </td>
  371. <td><?php echo $jobeet_job->getCategoryId() ?></td>
  372. <td><?php echo $jobeet_job->getType() ?></td>
  373. <!-- more columns here -->
  374. <td><?php echo $jobeet_job->getCreatedAt() ?></td>
  375. <td><?php echo $jobeet_job->getUpdatedAt() ?></td>
  376. </tr>
  377. <?php endforeach; ?>
  378. </tbody>
  379. </table>
  380. <a href="<?php echo url_for('job/new') ?>">New</a>
  381. Nel codice del template, il `foreach` scorre attraverso la lista di oggetti `Job`
  382. (`$jobeet_jobs`) e, per ognuno di loro, ogni valore delle colonne è visualizzato.
  383. Ricordate che accedere al valore di una colonna è semplice come chiamare un metodo getter,
  384. il cui nome inizia con `get` ed è seguito dal nome della colonna in formato
  385. ~camelCase~ (per esempio il metodo `getCreatedAt()` per la colonna `created_at`).
  386. Ripuliamolo un po', per visualizzare solo un sottoinsieme di colonne disponibili:
  387. [php]
  388. <!-- apps/frontend/modules/job/templates/indexSuccess.php -->
  389. <?php use_stylesheet('jobs.css') ?>
  390. <div id="jobs">
  391. <table class="jobs">
  392. <?php foreach ($jobeet_jobs as $i => $job): ?>
  393. <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>">
  394. <td><?php echo $job->getLocation() ?></td>
  395. <td>
  396. <a href="<?php echo url_for('job/show?id='.$job->getId()) ?>">
  397. <?php echo $job->getPosition() ?>
  398. </a>
  399. </td>
  400. <td><?php echo $job->getCompany() ?></td>
  401. </tr>
  402. <?php endforeach; ?>
  403. </table>
  404. </div>
  405. ![Homepage](http://www.symfony-project.org/images/jobeet/1_4/04/homepage.png)
  406. La funzione `url_for()` in questo template è un helper che verrà discusso domani.
  407. Il template della pagina del lavoro
  408. -----------------------------------
  409. Personalizziamo ora il template della pagina del lavoro. Apriamo il file `showSuccess.php`
  410. e sostituiamo il suo contenuto con il codice seguente:
  411. [php]
  412. <!-- apps/frontend/modules/job/templates/showSuccess.php -->
  413. <?php use_stylesheet('job.css') ?>
  414. <?php use_helper('Text') ?>
  415. <div id="job">
  416. <h1><?php echo $job->getCompany() ?></h1>
  417. <h2><?php echo $job->getLocation() ?></h2>
  418. <h3>
  419. <?php echo $job->getPosition() ?>
  420. <small> - <?php echo $job->getType() ?></small>
  421. </h3>
  422. <?php if ($job->getLogo()): ?>
  423. <div class="logo">
  424. <a href="<?php echo $job->getUrl() ?>">
  425. <img src="/uploads/jobs/<?php echo $job->getLogo() ?>"
  426. alt="<?php echo $job->getCompany() ?> logo" />
  427. </a>
  428. </div>
  429. <?php endif; ?>
  430. <div class="description">
  431. <?php echo simple_format_text($job->getDescription()) ?>
  432. </div>
  433. <h4>How to apply?</h4>
  434. <p class="how_to_apply"><?php echo $job->getHowToApply() ?></p>
  435. <div class="meta">
  436. <propel>
  437. <small>posted on <?php echo $job->getCreatedAt('m/d/Y') ?></small>
  438. </propel>
  439. <doctrine>
  440. <small>posted on <?php echo $job->getDateTimeObject('created_at')->format('m/d/Y') ?></small>
  441. </doctrine>
  442. </div>
  443. <div style="padding: 20px 0">
  444. <a href="<?php echo url_for('job/edit?id='.$job->getId()) ?>">Edit</a>
  445. </div>
  446. </div>
  447. Questo template usa la variabile `$job`, passata dall'azione, per mostrare l'informazione
  448. sul lavoro. Poiché abbiamo rinominato la variabile passata al template da `$jobeet_job` a
  449. `$job`, dobbiamo riportare questo cambiamento nell'azione `show` (attenzione, ci
  450. sono due occorrenze della variabile):
  451. [php]
  452. // apps/frontend/jobeet/actions/actions.class.php
  453. public function executeShow(sfWebRequest $request)
  454. {
  455. <propel>
  456. $this->job = JobeetJobPeer::retrieveByPk($request->getParameter('id'));
  457. </propel>
  458. <doctrine>
  459. $this->job = Doctrine::getTable('JobeetJob')->find($request->getParameter('id'));
  460. </doctrine>
  461. $this->forward404Unless($this->job);
  462. }
  463. <propel>
  464. Notate che alcuni metodi di Propel accettano dei parametri. Siccome abbiamo definito la
  465. colonna `created_at` come un timestamp, il metodo `getCreatedAt()` accetta uno schema di
  466. formattazione della data come primo parametro.
  467. [php]
  468. $job->getCreatedAt('m/d/Y');
  469. </propel>
  470. <doctrine>
  471. Notate che le colonne di tipo date possono essere convertite a istanze dell'oggetto PHP DateTime.
  472. Così come abbiamo definito le colonne `created_at` come timestamp, è possibile convertire il
  473. valore della colonna a un oggetto DateTime usando il metodo `getDateTimeObject()`
  474. e dopo chiamando il metodo `format()` che prende un modello di formattazione della data come
  475. suo primo parametro:
  476. [php]
  477. $job->getDateTimeObject('created_at')->format('m/d/Y');
  478. </doctrine>
  479. >**NOTE**
  480. >La descrizione del lavoro usa l'helper `simple_format_text()` per formattarsi come HTML,
  481. >sostituendo ad esempio gli "a capo" con un `<br />`. Poiché tale helper appartiene al
  482. >gruppo di helper `Text`, che non è caricato di default, l'abbiamo caricato manualmente
  483. >usando l'helper `~use_helper~()`.
  484. ![Pagina del lavoro](http://www.symfony-project.org/images/jobeet/1_4/04/job.png)
  485. Gli ~slot~
  486. ----------
  487. Ed ora, il titolo di tutte le pagine è definito nel tag `<title>` del layout:
  488. [php]
  489. <title>Jobeet - Your best job board</title>
  490. Ma per la pagina del lavoro vogliamo fornire delle informazioni più dettagliate, come il
  491. nome della compagnia e la posizione del lavoro.
  492. In symfony, quando una zona del layout dipende dal template che deve essere
  493. visualizzato, occorre definire uno slot:
  494. ![Slot](http://www.symfony-project.org/images/jobeet/1_4/04/layout_slots.png)
  495. Aggiungiamo uno slot al layout per avere un titolo dinamico:
  496. [php]
  497. // apps/frontend/templates/layout.php
  498. <title><?php include_slot('title') ?></title>
  499. Ogni slot è definito da un nome (`title`) e può essere visualizzato usando l'helper
  500. `~include_slot~()`. Ora, all'inizio del template `showSuccess.php`, usiamo l'helper
  501. `slot()` per definire il contenuto dello slot per la pagina del lavoro:
  502. [php]
  503. // apps/frontend/modules/job/templates/showSuccess.php
  504. <?php slot('title', sprintf('%s is looking for a %s', $job->getCompany(), $job->getPosition())) ?>
  505. Se il titolo è complesso da generare, l'helper `slot()` può anche essere usato con un
  506. blocco di codice:
  507. [php]
  508. // apps/frontend/modules/job/templates/showSuccess.php
  509. <?php slot('title') ?>
  510. <?php echo sprintf('%s is looking for a %s', $job->getCompany(), $job->getPosition()) ?>
  511. <?php end_slot(); ?>
  512. Per alcune pagine, come la homepage, ci serve solo un titolo generico. Invece di
  513. ripetere lo stesso titolo più e più volte nei template, possiamo definire un titolo di
  514. default nel layout:
  515. [php]
  516. // apps/frontend/templates/layout.php
  517. <title>
  518. <?php include_slot('title', 'Jobeet - Your best job board') ?>
  519. </title>
  520. Il secondo parametro del metodo `include_slot()` è il valore predefinito per
  521. lo slot se non è stato definito. Se il valore predefinito è lungo o ha
  522. alcuni tag HTML, si può anche crearlo come mostrato nel seguente codice:
  523. [php]
  524. // apps/frontend/templates/layout.php
  525. <title>
  526. <?php if (!include_slot('title')): ?>
  527. Jobeet - Your best job board
  528. <?php endif; ?>
  529. </title>
  530. L'helper `include_slot()` restituisce `true` se lo slot è stato definito. Quindi, se
  531. abbiamo definito uno slot `title` in un template, verrà usato; altrimenti, verrà usato
  532. il titolo di default.
  533. >**TIP**
  534. >Abbiamo già visto alcuni helper che iniziano con `include_`. Questi helper
  535. >visualizzano l'HTML e in molti casi hanno una controparte `get_`, per restituire
  536. >solamente il contenuto:
  537. >
  538. > [php]
  539. > <?php include_slot('title') ?>
  540. > <?php echo get_slot('title') ?>
  541. >
  542. > <?php include_stylesheets() ?>
  543. > <?php echo get_stylesheets() ?>
  544. L'azione della pagina del lavoro
  545. --------------------------------
  546. La pagina del lavoro è generata dall'azione `show`, definita nel metodo `executeShow()`
  547. del modulo `job`:
  548. [php]
  549. class jobActions extends sfActions
  550. {
  551. public function executeShow(sfWebRequest $request)
  552. {
  553. <propel>
  554. $this->job = JobeetJobPeer::retrieveByPk($request->getParameter('id'));
  555. </propel>
  556. <doctrine>
  557. $this->job = Doctrine::getTable('JobeetJob')->find($request->getParameter('id'));
  558. </doctrine>
  559. $this->forward404Unless($this->job);
  560. }
  561. // ...
  562. }
  563. <propel>
  564. Come nell'azione `index`, la classe `JobeetJobPeer` è usata per recuperare un
  565. lavoro, stavolta usando il metodo `retrieveByPk()`. Il parametro di questo metodo
  566. è l'identificatore univoco di un lavoro, la sua ~chiave primaria~. La prossima sezione
  567. spiegherà perché l'istruzione `$request->getParameter('id')` restituisce la chiave
  568. primaria del lavoro.
  569. </propel>
  570. <doctrine>
  571. Come nell'azione `index`, la classe `JobeetJob` è usata per recuperare un
  572. lavoro, stavolta usando il metodo `find()`. Il parametro di questo metodo
  573. è l'identificatore univoco di un lavoro, la sua ~chiave primaria~. La prossima sezione
  574. spiegherà perché l'istruzione `$request->getParameter('id')` restituisce la chiave
  575. primaria del lavoro.
  576. </doctrine>
  577. <propel>
  578. >**TIP**
  579. >Le classi del modello generate contengono molti metodi utili per interagire
  580. >con gli oggetti del progetto. Prendetevi un po' di tempo per analizzare il codice
  581. > che si trova nella cartella `lib/om/` e per scoprire la potenza nascosta
  582. >in queste classi.
  583. </propel>
  584. Se il lavoro non esiste nel database, vogliamo rimandare l'utente a una pagina ~404~,
  585. che è esattamente ciò che fa il metodo `forward404Unless()`. Questo accetta un
  586. booleano come primo parametro e, a meno che non sia vero, ferma il flusso corrente
  587. dell'esecuzione. Poiché i metodi "forward" fermano l'esecuzione dell'azione
  588. sollevando un'eccezione `sfError404Exception`, non si ha bisogno di usare `return`
  589. successivamente.
  590. Come per le eccezioni, la pagina mostrata all'utente è diversa negli ~ambienti~
  591. `prod` e `dev`:
  592. ![404 errore in ambiente dev](http://www.symfony-project.org/images/jobeet/1_4/05/404_dev.png)
  593. ![404 errore in ambiente prod](http://www.symfony-project.org/images/jobeet/1_4/05/404_prod.png)
  594. >**NOTE**
  595. >Prima di pubblicare il sito Jobeet su un server di produzione, impareremo a
  596. >personalizzare la pagina 404 di default.
  597. -
  598. >**SIDEBAR**
  599. >La famiglia dei metodi "~forward~"
  600. >
  601. >Una chiamata a `forward404Unless` in realtà equivale a:
  602. >
  603. > [php]
  604. > $this->forward404If(!$this->job);
  605. >
  606. >che equivale anche a:
  607. >
  608. > [php]
  609. > if (!$this->job)
  610. > {
  611. > $this->forward404();
  612. > }
  613. >
  614. >Il metodo stesso `forward404()` è solo una scorciatoia per:
  615. >
  616. > [php]
  617. > $this->forward('default', '404');
  618. >
  619. >Il metodo `forward()` rimanda a un'altra azione della stessa applicazione;
  620. >negli esempi precedenti, all'azione `404` del modulo `default`. Il modulo
  621. >`default` è distribuito con symfony e fornisce delle azioni di default per le
  622. > pagine 404, secure e login.
  623. La richiesta e la risposta
  624. --------------------------
  625. Quando si visitano le pagine `/job` o `/job/show/id/1` nel proprio browser, si
  626. inizio a un viaggio nel server web. Il browser invia una **~richiesta~** e il
  627. server rimanda indietro una **~risposta~**.
  628. Abbiamo già visto che symfony incapsula la richiesta in un oggetto `sfWebRequest`
  629. (si veda il metodo `executeShow()`). E siccome symfony è un framework orientato
  630. agli oggetti, anche la risposta è un oggetto, della classe `sfWebResponse`.
  631. Si può accedere all'oggetto risposta in un'azione richiamando `$this->getResponse()`.
  632. Questi oggetti forniscono molti metodi utili per accedere alle informazioni dalle
  633. funzioni e dalle variabili globali di PHP.
  634. >**NOTE**
  635. >Perché symfony ha un wrap di funzionalità esistenti in PHP? Innanzitutto,
  636. >perché i metodi di symfony sono più potenti delle controparti PHP. Poi, perché
  637. >quando si testa un'applicazione, è più facile simulare un oggetto richiesta o
  638. >risposta piuttosto che trattare variabili globali o usare funzioni come
  639. >`header()`, che fanno troppe cose di nascosto.
  640. ### La richiesta
  641. La classe `sfWebRequest` è un wrapper per le array globali di PHP `~$_SERVER~`,
  642. `~$_COOKIE~`, `~$_GET~`, `~$_POST~` e `~$_FILES~`
  643. Nome del metodo | Equivalente PHP
  644. -------------------- | --------------------------------------------------
  645. `getMethod()` | `$_SERVER['REQUEST_METHOD']`
  646. `getUri()` | `$_SERVER['REQUEST_URI']`
  647. `getReferer()` | `$_SERVER['HTTP_REFERER']`
  648. `getHost()` | `$_SERVER['HTTP_HOST']`
  649. `getLanguages()` | `$_SERVER['HTTP_ACCEPT_LANGUAGE']`
  650. `getCharsets()` | `$_SERVER['HTTP_ACCEPT_CHARSET']`
  651. `isXmlHttpRequest()` | `$_SERVER['X_REQUESTED_WITH'] == 'XMLHttpRequest'`
  652. `getHttpHeader()` | `$_SERVER`
  653. `getCookie()` | `$_COOKIE`
  654. `isSecure()` | `$_SERVER['HTTPS']`
  655. `getFiles()` | `$_FILES`
  656. `getGetParameter()` | `$_GET`
  657. `getPostParameter()` | `$_POST`
  658. `getUrlParameter()` | `$_SERVER['PATH_INFO']`
  659. `getRemoteAddress()` | `$_SERVER['REMOTE_ADDR']`
  660. Abbiamo già avuto accesso ai parametri della richiesta usando il metodo
  661. `getParameter()`. Esso restituisce un valore dalla variabile globale `$_GET`
  662. o `$_POST`, oppure dalla variabile `~PATH_INFO~`.
  663. Se si vuole essere certi che un parametro della richiesta venga da una
  664. particolare di queste variabili, si devono usare rispettivamente i metodi
  665. `getGetParameter()`, `getPostParameter()` e `getUrlParameter()`.
  666. >**NOTE**
  667. >Se si vuole limitare un'azione per un ~metodo HTTP~ specifico, ad esempio se si
  668. >vuole essere sicuri che una form sia inviata come `POST`, si può usare
  669. >il metodo `isMethod()`: `$this->forwardUnless($request->isMethod('POST'));`.
  670. ### La risposta
  671. La classe `sfWebResponse` è un wrapper per le funzioni PHP `~header~()`
  672. e `setraw~cookie~()`:
  673. Nome del metodo | Equivalente PHP
  674. ----------------------------- | ----------------
  675. `setCookie()` | `setrawcookie()`
  676. `setStatusCode()` | `header()`
  677. `setHttpHeader()` | `header()`
  678. `setContentType()` | `header()`
  679. `addVaryHttpHeader()` | `header()`
  680. `addCacheControlHttpHeader()` | `header()`
  681. Ovviamente, la classe `sfWebResponse` fornisce anche un modo per impostare il
  682. contenuto della risposta (`setContent()`) e inviare la risposta al browser
  683. (`send()`).
  684. In questo giorno abbiamo visto come gestire i fogli di stile e i JavaScript
  685. sia nel file `view.yml` che nei template. Alla fine, entrambe le tecniche
  686. usano i metodi dell'oggetto risposta `addStylesheet()` e `addJavascript()`.
  687. >**TIP**
  688. >Le classi `sfAction`, `sfRequest` e `sfResponse` forniscono molti altri
  689. >metodi utili. Non esitate a consultare la
  690. [documentazione delle API](http://www.symfony-project.org/api/1_4/) per saperne
  691. >di più su tutte le classi interne di symfony.
  692. A domani
  693. --------
  694. Oggi abbiamo descritto alcuni design pattern usati da symfony. Speriamo che ora
  695. la struttura delle cartelle abbia più senso. Abbiamo giocato coi template,
  696. manipolando il layout e i file dei template. Li abbiamo anche resi un po' più
  697. dinamici, grazie agli slot e alle azioni.
  698. Domani impareremo di più sull'helper `url_for()` che abbiamo usato oggi,
  699. e sul sub-framework del routing associato con esso.
  700. __ORM__