PageRenderTime 62ms CodeModel.GetById 2ms app.highlight 52ms RepoModel.GetById 2ms app.codeStats 0ms

/jobeet/es/04.markdown

https://github.com/rafaelgou/symfony1-docs
Markdown | 727 lines | 568 code | 159 blank | 0 comment | 0 complexity | 1e4e1a890140df4507c5d895224f2988 MD5 | raw file
  1Día 4: El Controlador y la Vista
  2==============================
  3
  4Ayer, hemos explorado cómo Symfony simplifica la gestión de bases de datos por abstracción entre los diferentes motores de bases de datos, y mediante la conversión de elementos relacionales con útiles clases orientadas a objetos. También hemos jugado con ##ORM## para describir el esquema de base de datos, crear las tablas, y llenar la base de datos con algunos datos iniciales.
  5
  6Hoy, vamos a personalizar el módulo básico `job` creado ayer. El módulo `job` existente tiene todo el código que necesitamos para Jobeet:
  7
  8 * Una página que lista todos los puestos de trabajo
  9 * Una página para crear un nuevo puesto de trabajo
 10 * Una página para actualizar un puesto de trabajo existente
 11 * Una página para eliminar un puesto de trabajo
 12
 13Aunque el código está listo para ser utilizado como esta, vamos a refactorizar las plantillas para adaptarlas lo más cerca a los mockups Jobeet.
 14
 15La arquitectura MVC
 16-------------------
 17
 18Si estás desarrollando con PHP sitios web sin ningún framework, probablemente uses el paradigma de un archivo PHP por página HTML. Estos archivos PHP probablemente contengan el mismo tipo de estructura: inicialización y configuración global, lógica de negocio relacionada con la página solicitada, busqueda de registros en la base, y finalmente el código HTML que arma la página.
 19
 20Puedes utilizar un motor de plantillas para separar la lógica del HTML.
 21Tal vez utilizas una capa de abstracción de la base de datos para separar el modelo de la lógica de negocio. Sin embargo, la mayoría de las veces, terminas con un montón de código que es una pesadilla para mantener. Es rápido para construir, pero con el tiempo, es más y más difícil de hacer cambios, especialmente porque nadie, excepto tú entiende cómo se construye y cómo funciona.
 22
 23Al igual que con todos los problemas, hay soluciones agradables. Para desarrollo web, la solución más común para la organización de su código de hoy en día es el [**patrón de diseño MVC**](http://en.wikipedia.org/wiki/Model-view-controller).
 24En resumen, el patrón de diseño MVC define una manera de organizar el código de acuerdo a su naturaleza. Este patrón separa el código en **tres capas**:
 25
 26  * La capa **Modelo** define la lógica de negocio (la base de datos pertenece a esta capa). Ya sabes que Symfony guarda todas las clases y archivos relacionados con el modelo en el directorio `lib/model/`.
 27
 28  * La **Vista** es con lo que el usuario interactúa (un motor de plantillas es parte de esta capa). En Symfony, la vista es principalmente la capa de plantillas PHP. Estas son guardadas en varios directorios `templates/` como veremos más adelante en el día de hoy.
 29
 30  * El **Controlador** es la pieza de código que llama al Modelo para obtener algunos datos que le pasa a la Vista para la presentación al cliente. Cuando instalamos Symfony el primer día, vimos que todas las solicitudes son gestionadas por un controlador frontal (`index.php` y `frontend_dev.php`). Estos controladores frontales delegadan la verdadera labor a las **acciones**. Como vimos ayer, estas acciones son, lógicamente, agrupadas en **módulos**.
 31
 32![MVC](http://www.symfony-project.org/images/jobeet/1_4/04/mvc.png)
 33
 34Hoy, usaremos el mockup definido el día 2 para personalizar la página principal y la página de puestos de trabajos. Vamos hacerlas dinámicas. A lo largo del camino, vamos a modificar un montón de cosas en diferentes archivos para demostrar la estructura de directorios symfony y la forma de separar el código entre las capas.
 35
 36El diseño
 37---------
 38
 39En primer lugar, si miraste de cerca los mockups, te darás cuenta de que gran parte de cada una de las páginas tiene el mismo aspecto. Ya sabes que la duplicación de código esta mal, ya sea si estamos hablando de código HTML o PHP, por lo que necesitamos encontrar una manera de prevenir estos elementos de vista común resultantes de la duplicación de código.
 40
 41Una forma de resolver el problema es definir un encabezado y un pie de página y lo incluyes en cada plantilla:
 42
 43![Header and footer](http://www.symfony-project.org/images/jobeet/1_4/04/header_footer.png)
 44
 45Pero los archivos de la cabecera y el pie de página no contienen HTML válido. Debe haber una mejor manera. En lugar de reinventar la rueda, vamos a utilizar otro patrón de diseño para resolver este problema: el 
 46[patrón de diseño decorador](http://en.wikipedia.org/wiki/Decorator_pattern).
 47El patrón de diseño decorador resuelve el problema al revés: la plantilla es decorada después de que el contenido es mostrado por una plantilla global, llamada **layout** en Symfony:
 48
 49![Layout](http://www.symfony-project.org/images/jobeet/1_4/04/layout.png)
 50
 51El layout de una aplicación se llama `layout.php` y se puede encontrar en el directorio `apps/frontend/templates/`. Este directorio contiene todas las plantillas globales para una aplicación.
 52
 53Reemplaza el layout por defecto de Symfony por el siguiente código:
 54
 55    [php]
 56    <!-- apps/frontend/templates/layout.php -->
 57    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 58     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 59    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 60      <head>
 61        <title>Jobeet - Your best job board</title>
 62        <link rel="shortcut icon" href="/favicon.ico" />
 63        <?php include_javascripts() ?>
 64        <?php include_stylesheets() ?>
 65      </head>
 66      <body>
 67        <div id="container">
 68          <div id="header">
 69            <div class="content">
 70              <h1><a href="<?php echo url_for('job/index') ?>">
 71                <img src="/images/logo.jpg" alt="Jobeet Job Board" />
 72              </a></h1>
 73
 74              <div id="sub_header">
 75                <div class="post">
 76                  <h2>Ask for people</h2>
 77                  <div>
 78                    <a href="<?php echo url_for('job/index') ?>">Post a Job</a>
 79                  </div>
 80                </div>
 81
 82                <div class="search">
 83                  <h2>Ask for a job</h2>
 84                  <form action="" method="get">
 85                    <input type="text" name="keywords"
 86                      id="search_keywords" />
 87                    <input type="submit" value="search" />
 88                    <div class="help">
 89                      Enter some keywords (city, country, position, ...)
 90                    </div>
 91                  </form>
 92                </div>
 93              </div>
 94            </div>
 95          </div>
 96
 97          <div id="content">
 98            <?php if ($sf_user->hasFlash('notice')): ?>
 99              <div class="flash_notice">
100                <?php echo $sf_user->getFlash('notice') ?>
101              </div>
102            <?php endif; ?>
103
104            <?php if ($sf_user->hasFlash('error')): ?>
105              <div class="flash_error">
106                <?php echo $sf_user->getFlash('error') ?>
107              </div>
108            <?php endif; ?>
109
110            <div class="content">
111              <?php echo $sf_content ?>
112            </div>
113          </div>
114
115          <div id="footer">
116            <div class="content">
117              <span class="symfony">
118                <img src="/images/jobeet-mini.png" />
119                powered by <a href="http://www.symfony-project.org/">
120                <img src="/images/symfony.gif" alt="symfony framework" />
121                </a>
122              </span>
123              <ul>
124                <li><a href="">About Jobeet</a></li>
125                <li class="feed"><a href="">Full feed</a></li>
126                <li><a href="">Jobeet API</a></li>
127                <li class="last"><a href="">Affiliates</a></li>
128              </ul>
129            </div>
130          </div>
131        </div>
132      </body>
133    </html>
134
135Una plantilla symfony es sólo un simple archivo PHP. En la plantilla layout, verás llamadas a funciones PHP y referencias a variables PHP. `$sf_content` es la variable más interesante: la define el mismo framework y contiene el código HTML generado por la acción.
136
137Si navegas el módulo `job` (`http://jobeet.localhost/frontend_dev.php/job`), verás que todas las acciones ahora son decoradas por el layout.
138
139Las Hojas de Estilo, Imágenes, y JavaScripts
140--------------------------------------------
141
142Ya que este tutorial no es acerca de diseño web, tenemos ya preparado todo lo necesario que usaremos para Jobeet:
143[descarga los archivo gráficos](http://www.symfony-project.org/get/jobeet/images.zip)
144y copiarlos dentro del directorio `web/images/`;
145[descarga los archivo de hojas de estilo](http://www.symfony-project.org/get/jobeet/css.zip)
146y copiarlos dentro del directorio `web/css/`.
147
148>**NOTE**
149>En el layout, hemos incluído un *favicon*. Puedes
150>[descargarlo de Jobeet](http://www.symfony-project.org/get/jobeet/favicon.ico)
151>y ponerlo bajo el directorio `web/`.
152
153![El módulo job con un layout y recursos](http://www.symfony-project.org/images/jobeet/1_4/04/job_layout_assets.png)
154
155>**TIP**
156>Por defecto, la tarea `generate:project` ha creado tres directorios para los recusos de 
157>project: `web/images/` para las imágenes, `web/css/` para las hojas de estilo, y 
158>`web/js/` para los Javascripts. Esta es una de las muchas convenciones definidas por
159>Symfony, pero por supuesto puedes almacenar en otro lugar bajo el directorio
160>`web/`.
161
162El astuto lector habrá notado que, incluso si el archivo `main.css` no es mencionado en cualquier lugar del layout por defecto, está sin duda presentes en el código HTML generado. Pero no los otros. ¿Cómo es esto posible?
163El archivo de estilo se ha incluido por la llamada a la función `include_stylesheets()` que encuentra la etiqueta `<head>`. La función `include_stylesheets()` es llamada un **helper**. Un helper es una función, definida por Symfony, que puede tener parámetros y devolver código HTML. La mayoría de las veces, los helpers te ahorran tiempo, ellos empaquetan código en snippets (porciones de código) utilizados con frecuencia en las plantillas. El helper `include_stylesheets()` genera una etiqueta `<link>` para las hojas de estilo.
164
165Pero, ¿cómo hace el helper para saber que hojas de estilo incluir?
166
167La capa de la Vista se puede configurar editando el archivo de configuración
168`view.yml` de la aplicación. Aquí está el archivo por defecto generado por la tarea `generate:app`:
169
170    [yml]
171    # apps/frontend/config/view.yml
172    default:
173      http_metas:
174        content-type: text/html
175
176      metas:
177        #title:        symfony project
178        #description:  symfony project
179        #keywords:     symfony, project
180        #language:     en
181        #robots:       index, follow
182
183      stylesheets:    [main.css]
184
185      javascripts:    []
186
187      has_layout:     true
188      layout:         layout
189
190El archivo `view.yml` establece la configuración `por defecto` para todas las plantillas de la aplicación. Por ejemplo, para las `hojas de estilo` define un array de archivos de estilo para incluir en todas las páginas de la aplicación (la inclusión se hace por el helper `include_stylesheets()`).
191
192>**NOTE**
193>En el archivo de configuración `view.yml` por defecto, se hace referencia al archivo
194>`main.css`, y no al `/css/main.css`. Como cuestión de hecho, ambas definiciones
195>son equivalentes pues el prefijo relativo symfony es `/css/`.
196
197Si muchos archivos se definen, Symfony los incluye en el mismo orden que la definición:
198
199    [yml]
200    stylesheets:    [main.css, jobs.css, job.css]
201
202También puedes cambiar el atributo `media` y omitir el sufijo `.css`:
203
204    [yml]
205    stylesheets:    [main.css, jobs.css, job.css, print: { media: print }]
206
207Esta configuración se presentará así:
208
209    [php]
210    <link rel="stylesheet" type="text/css" media="screen"
211      href="/css/main.css" />
212    <link rel="stylesheet" type="text/css" media="screen"
213      href="/css/jobs.css" />
214    <link rel="stylesheet" type="text/css" media="screen"
215      href="/css/job.css" />
216    <link rel="stylesheet" type="text/css" media="print"
217      href="/css/print.css" />
218
219>**TIP**
220>El archivo de configuración `view.yml` también define el layout por defecto usado por la
221>aplicación. Por defecto, el nombre es `layout`, y así Symfony decora cada
222>página con el archivo `layout.php`. Puedes también deshabilitar el proceso de decoración
223>completo cambiando el valor de `has_layout` a `false`.
224
225Funciona como está pero el archivo `jobs.css` es solo necesario en la página principal y el
226`job.css` sólo es necesario para la página job. El archivo `view.yml` se puede personalizar partiendo de una base por-módulo. Cambia el archivo `view.yml` de la aplicación sólo para tener el archivo `main.css`:
227
228    [yml]
229    # apps/frontend/config/view.yml
230    stylesheets:    [main.css]
231
232Para personalizar la vista del módulo `job`, crea un nuevo archivo `view.yml` en el directorio `apps/frontend/modules/job/config/`:
233
234    [yml]
235    # apps/frontend/modules/job/config/view.yml
236    indexSuccess:
237      stylesheets: [jobs.css]
238
239    showSuccess:
240      stylesheets: [job.css]
241
242Bajo las secciones `indexSuccess` y `showSuccess` (ellas son las plantillas asociadas a las acciones `index` y `show`, como veremos más adelante), puedes personalizar cualquiera de los items encontrados bajo la sección `default` del `view.yml` de la aplicación. Todos los items se fusionan con la configuración de la aplicación. También puedes definir algunas configuraciones para todas las acciones de un módulo con la sección especial `all`.
243
244>**SIDEBAR**
245>Principios de configuración en Symfony
246>
247>Para los muchos archivos de configuración de Symfony, la misma configuración se puede definir en
248>diferentes niveles:
249>
250>  * La configuración por defecto se encuentra en el framework
251>  * La configuración global para el proyecto (en `config/`)
252>  * La configuración local de una aplicación (en `apps/APP/config/`)
253>  * La configuración local limitada a un módulo (en
254>    `apps/APP/modules/MODULE/config/`)
255>
256>En tiempo de ejecución, la configuración del sistema combina todos los valores de los diferentes
257>archivos si existen y guarda en la memoria cache el resultado para un mejor rendimiento.
258
259Como regla empírica, cuando algo es configurable a través de un archivo de configuración, la misma puede realizarse con código PHP. En lugar de crear un archivo `view.yml` para el módulo `job` por ejemplo, también puedes utilizar el helper `use_stylesheet()` a fin de incluir una hoja de estilos a partir de una plantilla:
260
261    [php]
262    <?php use_stylesheet('main.css') ?>
263
264También puedes utilizar este helper en el layout a fin de incluir una hoja de estilo globalmente.
265
266Elegir entre un método u otro es realmente una cuestión de gusto. El archivo `view.yml` proporciona una manera de definir las cosas para todas las acciones de un módulo, las cuales no son posible en una plantilla, pero la configuración es bastante estático. Por otro lado, utilizando el helper `use_stylesheet()` es más flexible y además, todo está en el mismo lugar: la definición de estilos y el código HTML. Para Jobeet, vamos a utilizar el helper `use_stylesheet()`, osea puedes quitar el `view.yml` que recién creamos y actualiza la plantilla `job` con la llamada a `use_stylesheet()`:
267
268    [php]
269    <!-- apps/frontend/modules/job/templates/indexSuccess.php -->
270    <?php use_stylesheet('jobs.css') ?>
271
272    <!-- apps/frontend/modules/job/templates/showSuccess.php -->
273    <?php use_stylesheet('job.css') ?>
274
275>**NOTE**
276>En consecuencia, la configuración de JavaScript se realiza a través de la linea `javascripts`
277>del archivo de configuración `view.yml` y el helper `use_javascript()` 
278>define los archivos JavaScript a incluir para una plantilla.
279
280La Página Principal de Puesto de Trabajo
281----------------------------------------
282
283Como se observa en el día 3, la página principal de puestos de trabajo (job) es generada por la acción `index` del módulo `job`. La acción `index` es la parte del Controlador de la página y la plantilla asociada, `indexSuccess.php`, en la parte de la Vista:
284
285    apps/
286      frontend/
287        modules/
288          job/
289            actions/
290              actions.class.php
291            templates/
292              indexSuccess.php
293
294### La Acción
295
296Cada acción está representada por un método de una clase. Para la página principal de puestos de trabajo, la clase es `jobActions` (el nombre del módulo seguido de `Actions`) y el método es `executeIndex()` (`execute` seguido por el nombre de la acción).
297Esto recupera todos los puestos de trabajo de la base de datos:
298
299    [php]
300    // apps/frontend/modules/job/actions/actions.class.php
301    class jobActions extends sfActions
302    {
303      public function executeIndex(sfWebRequest $request)
304      {
305<propel>
306        $this->jobeet_jobs = JobeetJobPeer::doSelect(new Criteria());
307</propel>
308<doctrine>
309        $this->jobeet_jobs = Doctrine::getTable('JobeetJob')
310          ->createQuery('a')
311          ->execute();
312</doctrine>
313      }
314
315      // ...
316    }
317
318<propel>
319Echemos un vistazo más de cerca al código: el método `executeIndex()` (el Controlador) llama al Módelo `JobeetJobPeer` para recuperar todos los puestos de trabajo
320(`new Criteria()`). Devuelve un array de objetos `Job` que se asignan a la propiedad `jobeet_jobs` del objeto.
321</propel>
322<doctrine>
323Echemos un vistazo más de cerca al código: el método `executeIndex()` (el Controlador) llama a la Tabla `JobeetJob` para crear una consulta que recupere todos los puestos de trabajo. Devuelve una `Doctrine_Collection` de objetos `JobeetJob` que se asignan a la propiedad `jobeet_jobs` del objeto.
324</doctrine>
325
326Todas las propiedades del objeto luego se pasan automáticamente a la plantilla (la Vista). Para pasar los datos del Controlador a la Vista, solo crea una nueva propiedad:
327
328    [php]
329    public function executeFooBar(sfWebRequest $request)
330    {
331      $this->foo = 'bar';
332      $this->bar = array('bar', 'baz');
333    }
334
335Este código hará que las variables `$foo` y `$bar` sean accesibles en la plantilla.
336
337### La Plantilla
338
339De forma predeterminada, el nombre de plantilla asociado con una acción se deduce por Symfony gracias a un convención (el nombre de la acción seguida por `Success`).
340
341La plantilla `indexSuccess.php` genera una tabla HTML para todos los puestos de trabajo. Aquí esta el códido de la plantilla actual:
342
343    [php]
344    <!-- apps/frontend/modules/job/templates/indexSuccess.php -->
345    <?php use_stylesheet('jobs.css') ?>
346
347    <h1>Job List</h1>
348
349    <table>
350      <thead>
351        <tr>
352          <th>Id</th>
353          <th>Category</th>
354          <th>Type</th>
355    <!-- more columns here -->
356          <th>Created at</th>
357          <th>Updated at</th>
358        </tr>
359      </thead>
360      <tbody>
361        <?php foreach ($jobeet_jobs as $jobeet_job): ?>
362        <tr>
363          <td>
364            <a href="<?php echo url_for('job/show?id='.$jobeet_job->getId()) ?>">
365              <?php echo $jobeet_job->getId() ?>
366            </a>
367          </td>
368          <td><?php echo $jobeet_job->getCategoryId() ?></td>
369          <td><?php echo $jobeet_job->getType() ?></td>
370    <!-- more columns here -->
371          <td><?php echo $jobeet_job->getCreatedAt() ?></td>
372          <td><?php echo $jobeet_job->getUpdatedAt() ?></td>
373        </tr>
374        <?php endforeach; ?>
375      </tbody>
376    </table>
377
378    <a href="<?php echo url_for('job/new') ?>">New</a>
379
380En el código de plantilla, el `foreach` itera a través de la lista de objetos `Job` (`$jobeet_jobs`), y para cada puesto de trabajo, cada valor de la columna es mostrado.
381Recuerda, el acceso a un valor de una columna es tan simple como una llamada al método de acceso cuyo nombre comienza con `get` y el nombre de la columna en formato CamelCase (por ejemplo, el método `getCreatedAt()` para la columna `created_at`).
382
383Vamos a limpiar esto un poco para mostrar sólo un subconjunto de las columnas disponibles:
384
385    [php]
386    <!-- apps/frontend/modules/job/templates/indexSuccess.php -->
387    <?php use_stylesheet('jobs.css') ?>
388
389    <div id="jobs">
390      <table class="jobs">
391        <?php foreach ($jobeet_jobs as $i => $job): ?>
392          <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>">
393            <td class="location"><?php echo $job->getLocation() ?></td>
394            <td class="position">
395              <a href="<?php echo url_for('job/show?id='.$job->getId()) ?>">
396                <?php echo $job->getPosition() ?>
397              </a>
398            </td>
399            <td class="company"><?php echo $job->getCompany() ?></td>
400          </tr>
401        <?php endforeach; ?>
402      </table>
403    </div>
404
405![Página de inicio](http://www.symfony-project.org/images/jobeet/1_4/04/homepage.png)
406
407La función `url_for()` en esta plantilla es un helper symfony que vamos a discutir mañana.
408
409La Plantilla para los Puestos de Trabajo
410----------------------------------------
411
412Ahora vamos a personalizar la plantilla de la página de puestos de trabajo. Abre el archivo `showSuccess.php` y reemplaza su contenido con el siguiente código:
413
414    [php]
415    <!-- apps/frontend/modules/job/templates/showSuccess.php -->
416    <?php use_stylesheet('job.css') ?>
417    <?php use_helper('Text') ?>
418
419    <div id="job">
420      <h1><?php echo $job->getCompany() ?></h1>
421      <h2><?php echo $job->getLocation() ?></h2>
422      <h3>
423        <?php echo $job->getPosition() ?>
424        <small> - <?php echo $job->getType() ?></small>
425      </h3>
426
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
436      <div class="description">
437        <?php echo simple_format_text($job->getDescription()) ?>
438      </div>
439
440      <h4>How to apply?</h4>
441
442      <p class="how_to_apply"><?php echo $job->getHowToApply() ?></p>
443
444      <div class="meta">
445<propel>
446        <small>posted on <?php echo $job->getCreatedAt('m/d/Y') ?></small>
447</propel>
448<doctrine>
449        <small>posted on <?php echo $job->getDateTimeObject('created_at')->format('m/d/Y') ?></small>
450</doctrine>
451      </div>
452
453      <div style="padding: 20px 0">
454        <a href="<?php echo url_for('job/edit?id='.$job->getId()) ?>">
455          Edit
456        </a>
457      </div>
458    </div>
459
460Esta plantilla utiliza la variable `$job` pasada por la acción para mostrar la información del puesto de trabajo. Como hemos rebautizado el nombre de variable pasada a la plantilla de `$jobeet_job` a `$job`, es necesario también realizar este cambio en la acción `show` (tener cuidado, hay dos ocurrencias de la variable):
461
462    [php]
463    // apps/frontend/modules/job/actions/actions.class.php
464    public function executeShow(sfWebRequest $request)
465    {
466<propel>
467      $this->job =
468       ➥ JobeetJobPeer::retrieveByPk($request->getParameter('id'));
469</propel>
470<doctrine>
471      $this->job = Doctrine::getTable('JobeetJob')->
472       ➥ find($request->getParameter('id'));
473</doctrine>
474      $this->forward404Unless($this->job);
475    }
476
477<propel>
478Observa que algunos métodos de acceso de Propel toman argumentos. Como hemos definido la columna `created_at` como un timestamp, el método de acceso `getCreatedAt()` toma un día formateado como su primer argumento:
479
480    [php]
481    $job->getCreatedAt('m/d/Y');
482
483</propel>
484
485>**NOTE**
486>La descripción del trabajo usa el helper `simple_format_text()` para formatearlo como
487>HTML, sustituyendo los retornos de carro con `<br />` por ejemplo. Como este helper
488>pertenece al grupo de helper `Text`, que no se carga por defecto, tenemos
489>que cargarlo manualmente utilizando el helper `use_helper()`.
490
491![Página del Puesto de Trabajo](http://www.symfony-project.org/images/jobeet/1_4/04/job.png)
492
493Los Slots
494---------
495
496Ahora, el título de todas las páginas se define en la etiqueta `<title>` del layout:
497
498    [php]
499    <title>Jobeet - Your best job board</title>
500
501Sin embargo, para la página de puestos de trabajo, queremos darle más información útil, como el nombre de la empresa y el puesto de trabajo a ocupar.
502
503En Symfony, cuando una zona del layout depende de la plantilla para mostrarse, necesitas definir un slot:
504
505![Slots](http://www.symfony-project.org/images/jobeet/1_4/04/layout_slots.png)
506
507Añade un slot al layout para permitir que el título sea dinámico:
508
509    [php]
510    // apps/frontend/templates/layout.php
511    <title><?php include_slot('title') ?></title>
512
513Cada slot es definido por un nombre (`title`) y se pueden visualizar mediante el uso del helper `include_slot()`. Ahora, al comienzo de la plantilla `showSuccess.php`, usa el helper `slot()` para definir el contenido del slot para la página de puestos de trabajo:
514
515    [php]
516    // apps/frontend/modules/job/templates/showSuccess.php
517    <?php slot(
518      'title',
519      sprintf('%s is looking for a %s', $job->getCompany(), $job->getPosition()))
520    ?>
521
522Si el título es complejo de generar, el helper `slot()` también se puede utilizar con un bloque de código:
523
524    [php]
525    // apps/frontend/modules/job/templates/showSuccess.php
526    <?php slot('title') ?>
527      <?php echo sprintf('%s is looking for a %s', $job->getCompany(), $job->getPosition()) ?>
528    <?php end_slot(); ?>
529
530Para algunas páginas, como la página de inicio, sólo necesitamos un título genérico. En lugar de repetir el mismo título una y otra vez en las plantillas, podemos definir un título predeterminado en el layout:
531
532    [php]
533    // apps/frontend/templates/layout.php
534    <title>
535      <?php if (!include_slot('title')): ?>
536        Jobeet - Your best job board
537      <?php endif; ?>
538    </title>
539
540El helper `include_slot()` regresa `true` si el slot se ha definido. Por lo tanto, cuando defines el contenido del slot `title` en una plantilla, éste es usado; sino, el título predeterminado será utilizado.
541
542>**TIP**
543>Ya hemos visto bastantes helpers comenzando con `include_`. Estos helpers
544>generan el HTML y en la mayoría de los casos tienen un helper `get_` como contrapartida
545>solo para devolver el contenido:
546>
547>     [php]
548>     <?php include_slot('title') ?>
549>     <?php echo get_slot('title') ?>
550>
551>     <?php include_stylesheets() ?>
552>     <?php echo get_stylesheets() ?>
553
554La Acción de la Página de Puestos de Trabajo
555--------------------------------------------
556
557La página de puestos de trabajo es generada por la acción `show`, definida en el método `executeShow()` del módulo `job`:
558
559    [php]
560    class jobActions extends sfActions
561    {
562      public function executeShow(sfWebRequest $request)
563      {
564<propel>
565        $this->job =
566         ➥ JobeetJobPeer::retrieveByPk($request->getParameter('id'));
567</propel>
568<doctrine>
569        $this->job = Doctrine::getTable('JobeetJob')->
570         ➥ find($request->getParameter('id'));
571</doctrine>
572        $this->forward404Unless($this->job);
573      }
574
575      // ...
576    }
577
578<propel>
579Como en la acción `index`, la clase `JobeetJobPeer` es usada para obtener un puesto de trabajo, esta vez es mediante el uso del método `retrieveByPk()`. El parámetro de este método es el identificador único de un puesto de trabajo, su clave principal. La siguiente sección explicará el por qué la sentencia `$request->getParameter('id')` devuelve la clave principal del puesto de trabajo.
580</propel>
581<doctrine>
582Como en la acción `index`, la clase `JobeetJob` es usada para obtener un puesto de trabajo, esta vez es mediante el uso del método `find()`.  El parámetro de este método es el identificador único de un puesto de trabajo, su clave principal. La siguiente sección explicará el por qué la sentencia `$request->getParameter('id')` devuelve la clave principal del puesto de trabajo.
583</doctrine>
584
585<propel>
586>**TIP**
587>Las clases del modelo generadas tienen un montón de métodos útiles para interactuar con
588>los objetos del proyecto. Date un tiempo para navegar por el código ubicado en el directorio
589>`lib/om/` y descubre todo el poder de estas clases.
590</propel>
591
592Si el puesto de trabajo no existe en la base de datos, queremos llevar al usuario a una página 404, que es exactamente lo que hace el método `forward404Unless()`. Este toma un Booleano como primer argumento y, a menos que sea true, detiene el flujo de la actual ejecución. Como los métodos forward detienen la ejecución de la acción enseguida lanzando un `sfError404Exception`, no necesitas después volver atrás.
593
594En cuanto a las excepciones, la página que aparece al usuario es diferente en el entorno `prod` del entorno `dev`:
595
596![error 404 en el entorno dev](http://www.symfony-project.org/images/jobeet/1_4/05/404_dev.png)
597
598![error 404 en el entorno prod](http://www.symfony-project.org/images/jobeet/1_4/05/404_prod.png)
599
600>**NOTE**
601>Antes de implementar el sitio web Jobeet para el servidor de producción, aprenderás
602>cómo personalizar la página 404 por defecto.
603
604-
605
606>**SIDEBAR**
607>La Familia de Métodos "forward"
608>
609>El `forward404Unless` es en realidad equivalente a:
610>
611>     [php]
612>     $this->forward404If(!$this->job);
613>
614>que también es equivalente a:
615>
616>     [php]
617>     if (!$this->job)
618>     {
619>       $this->forward404();
620>     }
621>
622>El método `forward404()` en sí mismo es sólo un atajo para:
623>
624>     [php]
625>     $this->forward('default', '404');
626>
627>El método `forward()` hace un forward a otra acción de la misma aplicación;
628>en el ejemplo anterior, para la acción `404` del módulo `default`.
629>El módulo `default` es incluído con Symfony y da acciones predeterminadas
630>para mostrar páginas 404, de seguridad, y de login.
631
632La Petición y la Respuesta
633--------------------------
634
635Cuando navegas por las páginas `/job` o `/job/show/id/1` en tu navegador, estás iniciando un viaje de ida y vuelta al servidor web. El navegador está enviando una **Petición** y el servidor devuelve una **Respuesta**.
636
637Ya hemos visto que Symfony encapsula la petición en un object `sfWebRequest` (mirá el método `executeShow()`). Y como Symfony es un Framework Orientado a Objetos, la respuesta es también un objeto, de la clase `sfWebResponse`. Puedes acceder al objeto respuesta en la acción llamando a `$this->getResponse()`.
638
639Estos objetos proporcionan una gran cantidad de métodos convenientes para acceder a la información de funciones PHP y variables globales PHP.
640
641>**NOTE**
642>¿Por qué Symfony envuelve funcionalidades PHP existentes? En primer lugar, porque 
643>Symfony y sus métodos son más poderosos que su homólogo PHP. Luego, porque
644>cuando se prueba una aplicación, es mucho más fácil para simular un request o
645>un response con Objetos que tratar de ver alrededor de variables globales o trabajar
646>con funciones PHP como `header()` la cuales hacen demasiado magia por detrás.
647
648### La Petición
649
650La clase `sfWebRequest` envuelve a los arrays PHP globales `$_SERVER`, `$_COOKIE`, `$_GET`, `$_POST`,
651y `$_FILES`:
652
653 Nombre del método    | PHP equivalente
654 -------------------- | --------------------------------------------------
655 `getMethod()`        | `$_SERVER['REQUEST_METHOD']`
656 `getUri()`           | `$_SERVER['REQUEST_URI']`
657 `getReferer()`       | `$_SERVER['HTTP_REFERER']`
658 `getHost()`          | `$_SERVER['HTTP_HOST']`
659 `getLanguages()`     | `$_SERVER['HTTP_ACCEPT_LANGUAGE']`
660 `getCharsets()`      | `$_SERVER['HTTP_ACCEPT_CHARSET']`
661 `isXmlHttpRequest()` | `$_SERVER['X_REQUESTED_WITH'] == 'XMLHttpRequest'`
662 `getHttpHeader()`    | `$_SERVER`
663 `getCookie()`        | `$_COOKIE`
664 `isSecure()`         | `$_SERVER['HTTPS']`
665 `getFiles()`         | `$_FILES`
666 `getGetParameter()`  | `$_GET`
667 `getPostParameter()` | `$_POST`
668 `getUrlParameter()`  | `$_SERVER['PATH_INFO']`
669 `getRemoteAddress()` | `$_SERVER['REMOTE_ADDR']`
670
671Ya hemos accedido a parámetros de solicitud usando el método `getParameter()`. Devuelve un valor de la variable global `$_GET` o `$_POST`, o de la variable `PATH_INFO`.
672
673Si deseas asegurarte de que un parámetro de la petición procede de uno particular de estas variables, necesitas utilizar los métodos  `getGetParameter()`, `getPostParameter()`,
674y `getUrlParameter()` respectivamente.
675
676>**NOTE**
677>Si deseas restringir la acción de un método específico, por ejemplo, cuando
678>que deseas asegurarte de que un formulario es enviado como `POST`, puede utilizar el 
679>método `isMethod()`: 
680`$this->forwardUnless($request->isMethod('POST'));`.
681
682### La Respuesta
683
684La clase `sfWebResponse` envuelve a los métodos PHP `header()` y `setrawcookie()`:
685
686 Nombre del método             | PHP equivalente
687 ----------------------------- | ----------------
688 `setCookie()`                 | `setrawcookie()`
689 `setStatusCode()`             | `header()`
690 `setHttpHeader()`             | `header()`
691 `setContentType()`            | `header()`
692 `addVaryHttpHeader()`         | `header()`
693 `addCacheControlHttpHeader()` | `header()`
694
695Por supuesto, que la clase `sfWebResponse` también proporciona una manera de configurar el contenido de la respuesta (`setContent()`) y enviar la respuesta al navegador (`send()`).
696
697Hoy hemos visto cómo gestionar hojas de estilo y JavaScripts tanto en el `view.yml` como en las plantillas. Al final, ambas técnicas utilizan el objeto Response y sus métodos `addStylesheet()` y `addJavascript()`.
698
699>**TIP**
700>Las clases `sfAction`, `sfRequest`, y `sfResponse` dan muchos otros
701>métodos útiles. No dudes en navegar por la
702>[API documentation](http://www.symfony-project.org/api/1_2/) para aprender más
703>acerca de todas las clases internas de Symfony.
704>**TIP**
705>Las clases [`sfAction`](http://www.symfony-project.org/api/1_4/sfAction),
706>[`sfRequest`](http://www.symfony-project.org/api/1_4/sfRequest), y
707>[`sfResponse`](http://www.symfony-project.org/api/1_4/sfResponse) 
708>dan muchos otros métodos útiles. No dudes en navegar por la 
709>[documentación API](http://www.symfony-project.org/api/1_4/) para aprender más
710>acerca de todas las clases internas de Symfony.
711
712Nos vemos mañana
713----------------
714
715Hoy, hemos descrito algunos patrones de diseño utilizados por Symfony. Esperemos que la estructura de directorios del proyecto ahora tenga más sentido. Hemos tocado con las plantillas mediante la manipulación del layout y los archivos de plantilla. También lo hemos hecho un poco más dinámico gracias a slots y las acciones.
716
717Mañana, vamos a aprender más acerca del helper `url_for()` que hemos utilizado hoy, y el sub-framework de enrutamiento asociado a él.
718
719Feedback
720--------
721
722>**Tip**
723>Este capítulo ha sido traducido por **Roberto Germán Puentes Díaz**. 
724>Si encuentras algún error que deseas corregir o realizar algún comentario,
725>no dudes en enviarlo por correo a **puentesdiaz [arroba] gmail.com**
726
727__ORM__