Symfony 1.0, la guía definitiva

7.3. Configuración de la vista

En Symfony, la vista está formada por dos partes:

  • La presentación HTML del resultado de la acción (que se guarda en la plantilla, en el layout y en los fragmentos de plantilla)
  • El resto, que incluye entre otros los siguientes elementos:
    • Declaraciones <meta>: palabras clave (keywords), descripción (description), duración de la cache, etc.
    • El título de la página: no solo es útil para los usuarios que tienen abiertas varias ventanas del navegador, sino que también es muy importante para que los buscadores indexen bien la página.
    • Inclusión de archivos: de JavaScript y de hojas de estilos.
    • Layout: algunas acciones necesitan un layout personalizado (ventanas emergentes, anuncios, etc.) o puede que no necesiten cargar ningún layout (por ejemplo en las acciones relacionadas con Ajax).

En la vista, todo lo que no es HTML se considera configuración de la propia vista y Symfony permite 2 formas de manipular esa configuración. La forma habitual es mediante el archivo de configuración view.yml. Se utiliza cuando los valores de configuración no dependen del contexto o de alguna consulta a la base de datos. Cuando se trabaja con valores dinámicos que cambian con cada acción, se recurre al segundo método para establecer la configuración de la vista: añadir los atributos directamente en el objeto sfResponse durante la acción.

Nota Si un mismo parámetro de configuración se establece mediante el objeto sfResponse y mediante el archivo view.yml, tiene preferencia el valor establecido mediante el objeto sfResponse.

7.3.1. El archivo view.yml

Cada módulo contiene un archivo view.yml que define las opciones de su propia vista. De esta forma, es posible definir en un único archivo las opciones de la vista para todo el módulo entero y las opciones para cada vista. Las claves de primer nivel en el archivo view.yml son el nombre de cada módulo que se configura. El listado 7-16 muestra un ejemplo de configuración de la vista.

Listado 7-16 - ejemplo de archivo view.yml de módulo

editSuccess:
  metas:
    title: Edita tu perfil

editError:
  metas:
    title: Error en la edición del perfil

all:
  stylesheets: [mi_estilo]
  metas:
    title: Mi sitio web

Truco Se debe tener en cuenta que las claves principales del archivo view.yml son los nombres de las vistas, no los nombres de las acciones. Recuerda que el nombre de una vista se compone de un nombre de acción y un resultado de acción. Si por ejemplo la acción edit devuelve un valor igual a sfView::SUCCESS (o no devuelve nada, ya que este es el valor devuelto por defecto), el nombre de la vista sería editSuccess.

Las opciones por defecto para el módulo entero se definen bajo la clave all: en el archivo view.yml del módulo. Las opciones por defecto para todas las vistas de la aplicación se definen en el archivo view.yml de la aplicación. Una vez más, se tiene la configuración en cascada:

  • En apps/miaplicacion/modules/mimodulo/config/view.yml, las definiciones de cada vista solo se aplican a una vista y además sus valores tienen preferencia sobre las opciones generales del módulo.
  • En apps/miaplicacion/modules/mimodulo/config/view.yml, las definiciones bajo all: se aplican a todas las acciones del módulo y tienen preferencia sobre las definiciones de la aplicación.
  • En apps/miaplicacion/config/view.yml, las definiciones bajo default: se aplican a todos los módulos y todas las acciones de la aplicación.

Truco Por defecto no existen los archivos view.yml de cada módulo. Por tanto la primera vez que se necesita configurar una opción a nivel de módulo, se debe crear un nuevo archivo llamado view.yml en el directorio config/.

Después de ver la plantilla por defecto en el listado 7-5 y un ejemplo de la respuesta generada en el listado 7-6, puede que te preguntes dónde se definen las cabeceras de la página. En realidad, las cabeceras salen de las opciones de configuración por defecto definidas en el archivo view.yml de la aplicación que se muestra en el listado 7-17.

Listado 7-17 - Archivo de configuración por defecto de la vista de la aplicación, en apps/miaplicacion/config/view.yml

default:
  http_metas:
    content-type: text/html

  metas:
    title:        symfony project
    robots:       index, follow
    description:  symfony project
    keywords:     symfony, project
    language:     en

  stylesheets:    [main]

  javascripts:    [ ]

  has_layout:     on
  layout:         layout

Cada una de estas opciones se explica en detalle en la sección "Opciones de configuración de la vista".

7.3.2. El objeto respuesta (response)

Aunque el objeto response (objeto respuesta) es parte de la vista, normalmente se modifica en la acción. Las acciones acceden al objeto respuesta creado por Symfony, y llamado sfResponse, mediante el método getResponse(). El listado 7-18 muestra algunos de los métodos de sfResponse que se utilizan habitualmente en las acciones.

Listado 7-18 - Las acciones pueden acceder a los métodos del objeto sfResponse

class mimoduloActions extends sfActions
    {
      public function executeIndex()
      {
        $respuesta = $this->getResponse();

        // Cabeceras HTTP
        $respuesta->setContentType('text/xml');
        $respuesta->setHttpHeader('Content-Language', 'en');
        $respuesta->setStatusCode(403);
        $respuesta->addVaryHttpHeader('Accept-Language');
        $respuesta->addCacheControlHttpHeader('no-cache');

        // Cookies
        $respuesta->setCookie($nombre, $contenido, $expiracion, $ruta, $dominio);

        // Atributos Meta y cabecera de la página
        $respuesta->addMeta('robots', 'NONE');
        $respuesta->addMeta('keywords', 'palabra1 palabra2');
        $respuesta->setTitle('Mi Página de Ejemplo');
        $respuesta->addStyleSheet('mi_archivo_css');
        $respuesta->addJavaScript('mi_archivo_javascript');
      }
    }

Además de los métodos setter mostrados anteriormente para establecer el valor de las propiedades, la clase sfResponse también dispone de métodos getter que devuelven el valor de los atributos de la respuesta.

Los setters que establecen propiedades de las cabeceras de las páginas son uno de los puntos fuertes de Symfony. Como las cabeceras se envían lo más tarde posible (se envían en sfRenderingFilter) es posible modificar su valor todas las veces que sea necesario y tan tarde como haga falta. Además, incluyen atajos muy útiles. Por ejemplo, si no se indica el charset cuando se llama al método setContentType(), Symfony añade de forma automática el valor del charset definido en el archivo settings.yml.

$respuesta->setContentType('text/xml');
echo $respuesta->getContentType();
=> 'text/xml; charset=utf-8'

Los códigos de estado de las respuestas creadas por Symfony siguen la especificación de HTTP. De esta forma, los errores devuelven un código de estado igual a 500, las páginas que no se encuentran devuelven un código 404, las páginas normales devuelven el código 200, las páginas que no han sido modificadas se reducen a una simple cabecera con el código 304 (en el Capítulo 12 se explica con detalle), etc. Este comportamiento por defecto se puede redefinir para establecer códigos de estado personalizados, utilizando el método setStatusCode() sobre la respuesta. Se puede especificar un código propio junto con un mensaje personalizado o solamente un código, en cuyo caso Symfony añade el mensaje más común para ese código.

$respuesta->setStatusCode(404, 'Esta página no existe');

Truco Symfony normaliza el nombre de las cabeceras antes de enviarlas. De esta forma, no es necesario preocuparse si se ha escrito content-language en vez de Content-Language cuando se utiliza el método setHttpHeader(), ya que Symfony se encarga de transformar el primer nombre indicado en el segundo nombre, que es el correcto.

7.3.3. Opciones de configuración de la vista

Puede que hayas observador que existen 2 tipos diferentes de opciones para la configuración de la vista:

  • Las opciones que tienen un único valor (el valor es una cadena de texto en el archivo view.yml y el objeto respuesta utiliza un método set para ellas)
  • Las opciones que tienen múltiples valores (el archivo view.yml utiliza arrays para almacenar los valores y el objeto respuesta utiliza métodos de tipo add)

Hay que tener en cuenta por tanto que la configuración en cascada va sobreescribiendo los valores de las opciones de un solo valor y va añadiendo valores a las opciones que permiten valores múltiples. Este comportamiento se entiende mejor a medida que se avanza en este capítulo.

7.3.3.1. Configuración de las etiquetas <meta>

La información que almacenan las etiquetas <meta> de la respuesta no se muestra en el navegador, pero es muy útil para los buscadores. Además, permiten controlar la cache de cada página. Las etiquetas <meta> se pueden definir dentro de las claves http_metas: y metas: en el archivo view.yml, como se muestra en el listado 7-19, o utilizando los métodos addHttpMeta() y addMeta() del objeto respuesta dentro de la acción, como muestra el listado 7-20.

Listado 7-19 - Definir etiquetas <meta> en forma de clave: valor dentro del archivo view.yml

http_metas:
  cache-control: public

metas:
  description:   Página sobre economía en Francia
  keywords:      economía, Francia

Listado 7-20 - Definir etiquetas <meta> como opciones de la respuesta dentro de la acción

$this->getResponse()->addHttpMeta('cache-control', 'public');
$this->getResponse()->addMeta('description', 'Página sobre economía en Francia');
$this->getResponse()->addMeta('keywords', 'economía, Francia');

Si se añade un nuevo valor a una clave que ya tenía establecido otro valor, se reemplaza el valor anterior por el nuevo valor establecido. Para las etiquetas <meta>, se puede añadir al método addHttpMeta() (y también a setHttpHeader()) un tercer parámetro con un valor de false para que añadan el valor indicado al valor que ya existía y así no lo reemplacen.

$this->getResponse()->addHttpMeta('accept-language', 'en');
$this->getResponse()->addHttpMeta('accept-language', 'fr', false);
echo $this->getResponse()->getHttpHeader('accept-language');
=> 'en, fr'

Para añadir las etiquetas <meta> en la página que se envía al usuario, se deben utilizar los helpers include_http_metas() y include_metas() dentro de la sección <head> (que es por ejemplo lo que hace el layout por defecto, como se vio en el listado 7-5). Symfony construye las etiquetas <meta> definitivas juntando de forma automática el valor de todas las opciones de todos los archivos view.yml (incluyendo el archivo por defecto mostrado en el listado 7-17) y el valor de todas las opciones establecidas mediante los métodos de la respuesta. Por tanto, el ejemplo del listado 7-19 acaba generando las etiquetas <meta> del listado 7-21.

Listado 7-21 - Etiquetas <meta> que se muestran en la página final generada

<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="cache-control" content="public" />
<meta name="robots" content="index, follow" />
<meta name="description" content="FPágina sobre economía en Francia" />
<meta name="keywords" content="economía, Francia" />

Como característica adicional, la cabecera HTTP de la respuesta incluye el contenido establecido en http-metas aunque no se utilice el helper include_http_metas() en el layout o incluso cuando no se utiliza ningún layout. Por ejemplo, si se necesita enviar el contenido de una página como texto plano, se puede utilizar el siguiente archivo de configuración view.yml:

http_metas:
  content-type: text/plain

has_layout: false

7.3.3.2. Configuración del título

El título de las páginas web es un aspecto clave para los buscadores. Además, es algo muy cómodo para los navegadores modernos que incluyen la navegación con pestañas. En HTML, el título se define como una etiqueta y como parte de la metainformación de la página, así que en el archivo view.yml el título aparece como descendiente de la clave metas:. El listado 7-22 muestra la definición del título en el archivo view.yml y el listado 7-23 muestra la definición en la acción.

Listado 7-22 - Definición del título en view.yml

indexSuccess:
  metas:
    title: Los tres cerditos

Listado 7-23 - Definición del título en la acción (es posible crear títulos dinámicamente)

$this->getResponse()->setTitle(sprintf('Los %d cerditos', $numero));

En la sección <head> del documento final, se incluye la etiqueta <meta name="title"> sólo si se utiliza el helper include_metas(), y se incluye la etiqueta <title> sólo si se utiliza el helper include_title(). Si se utilizan los dos helpers (como se muestra en el layout por defecto del listado 7-5) el título aparece dos veces en el documento (como en el listado 7-6), algo que es completamente correcto.

7.3.3.3. Configuración para incluir archivos

Como se muestra en los listados 7-24 y 7-25, es muy sencillo añadir una hoja de estilos concreta o un archivo de JavaScript en la vista.

Listado 7-24 - Incluir un archivo en view.yml

indexSuccess:
  stylesheets: [miestilo1, miestilo2]
  javascripts: [miscript]

Listado 7-25 - Incluir un archivo en la acción

$this->getResponse()->addStylesheet('miestilo1');
$this->getResponse()->addStylesheet('miestilo2');
$this->getResponse()->addJavascript('miscript');

En cualquier caso, el argumento necesario es el nombre del archivo. Si la extensión del archivo es la que le corresponde normalmente (.css para las hojas de estilos y .js para los archivos de JavaScript) se puede omitir la extensión. Si el directorio donde se encuentran los archivos también es el habitual (/css/ para las hojas de estilos y /js/ para los archivos de JavaScript) también se puede omitir. Symfony es lo bastante inteligente como para añadir la ruta y la extensión correcta.

Al contrario que lo que sucede en la definición de los elementos meta y title, no es necesario utilizar ningún helper en las plantillas o en el layout para incluir estos archivos. Por tanto, la configuración mostrada en los listados anteriores genera el código HTML mostrado en el listado 7-26, independientemente del contenido de la plantilla o del layout.

Listado 7-26 - Resultado de incluir los archivos - No es necesario llamar a ningún helper en el layout

<head>
...
<link rel="stylesheet" type="text/css" media="screen" href="/css/miestilo1.css" />
<link rel="stylesheet" type="text/css" media="screen" href="/css/miestilo2.css" />
<script language="javascript" type="text/javascript" src="/js/miscript.js">
</script>
</head>

Nota Para incluir las hojas de estilo y los archivos JavaScript, se utiliza un filtro llamado sfCommonFilter. El filtro busca la etiqueta <head> de la respuesta y añade las etiquetas <link> y <script> justo antes de cerrar la cabecera con la etiqueta </head>. Por tanto, no se pueden incluir este tipo de archivos si no existe una etiqueta <head> en el layout o en las plantillas.

Recuerda que se sigue aplicando la configuración en cascada, por lo que cualquier archivo que se incluya desde el archivo view.yml de la aplicación se muestra en cualquier página de la aplicación. Los listados 7-27, 7-28 y 7-29 muestran este funcionamiento.

Listado 7-27 - Ejemplo de archivo view.yml de aplicación

default:
  stylesheets: [principal]

Listado 7-28 - Ejemplo de archivo view.yml de módulo

indexSuccess:
  stylesheets: [especial]

all:
  stylesheets: [otra]

Listado 7-29 - Vista generada para la acción indexSuccess

<link rel="stylesheet" type="text/css" media="screen" href="/css/principal.css" />
<link rel="stylesheet" type="text/css" media="screen" href="/css/otra.css" />
<link rel="stylesheet" type="text/css" media="screen" href="/css/especial.css" />

Si no se quiere incluir un archivo definido en alguno de los niveles de configuración superiores, se puede añadir un signo - delante del nombre del archivo en la configuración de más bajo nivel, como se muestra en el listado 7-30.

Listado 7-30 - Ejemplo de archivo view.yml en el módulo y que evita incluir algunos de los archivos incluidos desde el nivel de configuración de la aplicación

indexSuccess:
  stylesheets: [-principal, especial]

all:
  stylesheets: [otra]

Para eliminar todas las hojas de estilos o todos los archivos de JavaScript, se puede utilizar la siguiente sintaxis:

indexSuccess:
  stylesheets: [-*]
  javascripts: [-*]

Se puede ser todavía más preciso al incluir los archivos, ya que se puede utilizar un parámetro adicional para indicar la posición en la que se debe incluir el archivo (sólo se puede indicar la posición primera o la última):

# En el archivo view.yml
indexSuccess:
  stylesheets: [especial: { position: first }]
// En la acción
$this->getResponse()->addStylesheet('especial', 'first');

Para modificar el atributo media de la hoja de estilos incluida, se pueden modificar las opciones por defecto de Symfony, como se muestra en los listados 7-31, 7-32 y 7-33.

Listado 7-31 - Definir el atributo media al añadir una hoja de estilos desde view.yml

indexSuccess:
  stylesheets: [principal, impresora: { media: print }]

Listado 7-32 - Definir el atributo media al añadir una hoja de estilos desde la acción

$this->getResponse()->addStylesheet('impresora', '', array('media' => 'print'));

Listado 7-33 - La vista que genera la configuración anterior

<link rel="stylesheet" type="text/css" media="print" href="/css/impresora.css" />

7.3.3.4. Configuración del layout

Dependiendo de la estructura gráfica del sitio web, pueden definirse varios layouts. Los sitios web clásicos tienen al menos dos layouts: el layout por defecto y el layout que muestran las ventanas emergentes.

Como se ha visto, el layout por defecto se define en miproyecto/apps/miaplicacion/templates/layout.php. Los layouts adicionales también se definen en el mismo directorio templates/. Para que una vista utilice un layout específico como por ejemplo miaplicacion/templates/mi_layout.php, se debe utilizar la sintaxis del listado 7-34 para los archivos view.yml o el listado 7-35 para definirlo en la acción.

Listado 7-34 - Definición del layout en view.yml

indexSuccess:
  layout: mi_layout

Listado 7-35 - Definición del layout en la acción

$this->setLayout('mi_layout');

Algunas vistas no requieren el uso de ningún layout (por ejemplo las páginas de texto y los canales RSS). En ese caso, se puede eliminar el uso del layout tal y como se muestra en los listados 7-36 y 7-37.

Listado 7-36 - Eliminar el layout en view.yml

indexSuccess:
  has_layout: false

Listado 7-37 - Eliminar el layout en la acción

$this->setLayout(false);

Nota Las vistas de las acciones que utilizan Ajax no tienen definido ningún layout por defecto.