Symfony 1.0, la guía definitiva

14.4. Configuración del generador

El archivo de configuración del generador es muy poderoso, ya que permite modificar la administración generada automáticamente de muchas formas. Lo único malo de que tenga tantas posibilidades es que la descripción completa de su sintaxis es muy larga de leer y cuesta aprenderla, por lo que este capítulo es uno de los más largos del libro. El sitio web de Symfony dispone de un recurso adicional para aprender más fácilmente su sintaxis: la chuleta del generador de la administración, que se puede ver en la figura 14-7 y que se puede descargar desde http://www.symfony-project.org/uploads/assets/sfAdminGeneratorRefCard.pdf. Puede ser de utilidad tener la chuleta a mano cuando se leen los ejemplos de este capítulo.

Los ejemplos de esta sección modifican el módulo de administración article y también el módulo commnent basado en la definición de la clase Comment. Antes de modificar el módulo comment, es necesario crearlo mediante la tarea propel-init-admin:

> symfony propel-init-admin backend comment Comment
Chuleta del generador de administraciones

Figura 14.7 Chuleta del generador de administraciones

14.4.1. Campos

Por defecto, las columnas de la vista list y los campos de la vista edit son las columnas definidas en el archivo schema.yml. El archivo generator.yml permite seleccionar los campos que se muestran, los que se ocultan e incluso añadir campos propios (aunque no tengan una correspondencia directa con el modelo de objetos).

14.4.2. Opciones de los campos

El generador de la administración crea un field para cada columna del archivo schema.yml. Bajo la clave fields se puede definir la forma en la que se muestra cada campo, su formato, etc. El ejemplo que se muestra en el listado 14-7 define un valor propio para el atributo class y un tipo de campo propio para title, además de un título y un mensaje de ayuda para el campo content. Las siguientes secciones describen en detalle cómo funciona cada opción.

Listado 14-7 - Establecer un título propio a cada columna

generator:
  class:              sfPropelAdminGenerator
  param:
    model_class:      Article
    theme:            default

    fields:
      title:          { name: Título del artículo, type: textarea_tag, params: class=foo }
      content:        { name: Cuerpo, help: Rellene el cuerpo del artículo }

Además de las opciones globales para todas las vistas, se pueden redefinir las opciones de la clave fields para cada una de las vistas (list y edit en este ejemplo) tal y como muestra el listado 14-8.

Listado 14-8 - Redefiniendo las opciones globales en cada vista

generator:
  class:              sfPropelAdminGenerator
  param:
    model_class:      Article
    theme:            default

    fields:
      title:          { name: Título del artículo }
      content:        { name: Cuerpo }

    list:
      fields:
        title:        { name: Título }

    edit:
      fields:
        content:      { name: Cuerpo del artículo }

Este ejemplo se puede tomar como una regla general: cualquier opción establecida para todo el módulo mediante la clave fields, se puede redefinir en la configuración de cualquier vista (list y edit).

14.4.2.1. Mostrando nuevos campos

La sección fields permite definir para cada vista los campos que se muestran, los que se ocultan, la forma en la que se agrupan y las opciones para ordenarlos. Para ello se emplea la clave display. El código del listado 14-9 reordena los campos del módulo comment:

Listado 14-9 - Seleccionando los campos que se muestran, en modules/comment/config/generator.yml

generator:
  class:              sfPropelAdminGenerator
  param:
    model_class:      Comment
    theme:            default

    fields:
      article_id:     { name: Artículo }
      created_at:     { name: Pubicado en }
      content:        { name: Cuerpo }

    list:
      display:        [id, article_id, content]

    edit:
      display:
        NONE:         [article_id]
        Editable:     [author, content, created_at]

Con esta configuración, la vista list muetra 3 columnas, como se ve en la figura 14-8 y el formulario de la vista edit muestra 4 campos, agrupados en 2 secciones, como se ve en la figura 14-9.

Columnas seleccionadas para la vista "list" del módulo "comment"

Figura 14.8 Columnas seleccionadas para la vista "list" del módulo "comment"

Agrupando campos en la vista "edit" del módulo "comment"

Figura 14.9 Agrupando campos en la vista "edit" del módulo "comment"

De esta forma, la opción display tiene 2 propósitos:

  • Seleccionar las columnas que se muestran y el orden en el que lo hacen. Se utiliza un array simple con el nombre de los campos, como en la vista list anterior.
  • Agrupar los campos, para lo que se utiliza un array asociativo cuya clave es el nombre del grupo o NONE si se quiere definir un grupo sin nombre. Los campos se indican mediante un array simple con los nombres de los campos.

Truco Por defecto, las columnas que son clave primaria no aparecen en ninguna de las vistas.

14.4.2.2. Campos propios

Los campos que se configuran en el archivo generator.yml ni siquiera tienen que corresponderse con alguna de las columnas definidas en el esquema. Si la clase relacionada incluye un método getter para el campo propio, este se puede utilizar como un campo más de la vista list; si además del getter existe un método setter, el campo también se puede utilizar en la vista edit. En el listado 14-10 se muestra un ejemplo que extiende el modelo de Article para añadir el método getNbComments() que obtiene el número de comentarios de un artículo.

Listado 14-10 - Añadiendo un getter propio en el modelo, en lib/model/Article.php

public function getNbComments()
{
  return $this->countComments();
}

Una vez definido este getter, el campo nb_comments está disponible como campo del módulo generado (el getter utiliza como nombre la habitual transformación camelCase del nombre del campo) como se muestra en el listado 14-11.

Listado 14-11 - Los getters propios permiten añadir más columnas a los módulos de administración, en backend/modules/article/config/generator.yml

generator:
  class:              sfPropelAdminGenerator
  param:
    model_class:      Article
    theme:            default

    list:
      display:        [id, title, nb_comments, created_at]

La vista list resultante se muestra en la figura 14-10.

Campo propio en la vista "list" del módulo "article"

Figura 14.10 Campo propio en la vista "list" del módulo "article"

Los campos propios también pueden devolver código HTML para mostrarlo directamente. El listado 14-12 por ejemplo extiende la clase Comment con un método llamado getArticleLink() y que devuelve el enlace al artículo.

Listado 14-12 - Añadiendo un getter propio que devuelve código HTML, en lib/model/Comment.class.php

public function getArticleLink()
{
  return link_to($this->getArticle()->getTitle(), 'article/edit?id='.$this->getArticleId());
}

Este nuevo getter se puede utilizar como un campo propio en la vista comment/list utilizando la misma sintaxis que en el listado 14-11. El resultado se muestra en el listado 14-13 y se ilustra en la figura 14-11, en la que se puede ver el código HTML generado por el getter (un enlace al artículo) en la segunda columna sustituyendo a la clave primaria del artículo.

Listado 14-13 - Los getter propios que devuelven código HTML también se pueden utilizar como columnas, en modules/comment/config/generator.yml

generator:
  class:              sfPropelAdminGenerator
  param:
    model_class:      Comment
    theme:            default

    list:
      display:        [id, article_link, content]
Campo propio en la vista "list" del módulo "comment"

Figura 14.11 Campo propio en la vista "list" del módulo "comment"

14.4.2.3. Campos parciales

El código del modelo debe ser independiente de su presentación. El método getArticleLink() de ejemplo anterior no respeta el principio de la separación en capas, porque la capa del modelo incluye cierto código correspondiente a la vista. Para conseguir el mismo efecto pero manteniendo la separación de capas, es mejor incluir el código que genera el HTML del campo propio en un elemento parcial. Afortunadamente, el generador de la administración permite utilizar elementos parciales si la declaración del nombre del campo contiene un guión bajo como primer carácter. De esta forma, el archivo generator.yml del listado 14-13 debería modificarse para ser como el del listado 14-14.

Listado 14-14 - Se pueden utilizar elementos parciales como campos, mediante el uso del prefijo _

list:
          display:        [id, _article_link, created_at]

Para que funcione la configuración anterior, es necesario crear un elemento parcial llamado _article_link.php en el directorio modules/comment/templates/, tal y como muestra el listado 14-15.

Listado 14-15 - Elemento parcial para la vista list del ejemplo, en modules/comment/templates/_article_link.php

<?php echo link_to($comment->getArticle()->getTitle(), 'article/edit?id='.$comment->getArticleId()) ?>

La plantilla de un elemento parcial tiene acceso al objeto actual mediante una variable que se llama igual que la clase ($comment en este ejemplo). Si se trabajara con un módulo construido para la clase llamada GrupoUsuario, el elemento parcial tiene acceso al objeto actual mendiante la variable $grupo_usuario.

El resultado de este ejemplo es idéntico al mostrado en la figura 14-11, salvo que en este caso se respeta la separación en capas. Si se acostumbra a separar el código en capas, el resultado será que las aplicaciones creadas son más fáciles de mantener.

Si se quieren definir los parámetros para un elemento parcial, se utiliza la misma sintaxis que para un campo normal. Bajo la clave field se indican los parámetros y en el nombre del campo no se debe incluir el guión bajo (_) inicial. El listado 14-16 muestra un ejemplo.

Listado 14-16 - Las propiedades de un elemento parcial se pueden definir bajo la clave fields

fields:
            article_link:   { name: Artículo }

Si el código del elemento parcial crece demasiado, es recomendable sustituirlo por un componente. Para definir un campo basado en un componente, solamente es necesario reemplazar el perfijo _ por el prefijo ~, como muestra el listado 14-17.

Listado 14-17 - Se pueden utilizar componentes en los campos, mediante el prefijo ~

...
        list:
          display:        [id, ~article_link, created_at]

En la plantilla que se genera, la configuración anterior resulta en una llamada al componente articleLink del módulo actual.

Nota Los campos propios y los campos creados con elementos parciales se pueden utilizar en las vistas list, edit y en los filtros. Si se utiliza el mismo elemento parcial en varias vistas, la variable $type almacena el contexto (list, edit o filter).

14.4.3. Modificando la vista

Si se quiere modificar el aspecto visual de las vistas edit y list, no se deben modificar las plantillas. Como se generan automáticamente, no es una buena idea modificarlas. En su lugar, se debe utilizar el archivo de configuración generator.yml, porque puede hacer prácticamente cualquier cosa que se necesite sin tener que sacrificar la modularidad de la aplicación.

14.4.3.1. Modificando el título de la vista

Además de una serie de campos propios, las páginas list y edit pueden mostrar un título específico. El listado 14-18 muestra cómo modificar el título de las vistas del módulo article. La vista edit resultante se ilustra en la figura 14-12.

Listado 14-18 - Estableciendo el título de cada vista, en backend/modules/article/config/generator.yml

list:
          title:          List of Articles
          ...

        edit:
          title:          Body of article %%title%%
          display:        [content]
Título propio en la vista "edit" del módulo "article"

Figura 14.12 Título propio en la vista "edit" del módulo "article"

Como los títulos por defecto utilizan el nombre de cada clase, normalmente no es necesario modificarlos (siempre que el modelo utilice nombres de clase explícitos).

Truco En los valores de las opciones del archivo generator.yml, se puede acceder al valor de un campo mediante su nombre encerrado entre los caracteres %%.

14.4.3.2. Añadiendo mensajes de ayuda

En las vistas list y edit, se pueden añadir "tooltips" o mensajes de ayuda para describir los campos que se muestran en los formularios. El listado 14-19 muestra como añadir un mensaje de ayuda para el campo article_id en la vista edit del módulo comment. Para ello, se utiliza la propiedad help bajo la clave fields. El resultado se muestra en la figura 14-13.

Listado 14-19 - Añadiendo un mensaje de ayuda en la vista edit, en modules/comment/config/generator.yml

edit:
          fields:
            ...
            article_id:   { help: The current comment relates to this article }
Mensaje de ayuda en la vista "edit" del módulo "comment"

Figura 14.13 Mensaje de ayuda en la vista "edit" del módulo "comment"

En la vista list, los mensajes de ayuda se muestran en la cabecera de la columna; en la vista edit los mensajes se muestran debajo de cada cuadro de texto.

14.4.3.3. Modificando el formato de la fecha

Las fechas se pueden mostrar siguiendo un formato propio si se utiliza la opción date_format, tal y como se muestra en el listado 14-20.

Listado 14-20 - Dando formato a la fecha en la vista list

list:
          fields:
            created_at:         { name: Published, params: date_format='dd/MM' }

La sintaxis que se utiliza es la misma que la del helper format_date() descrito en el capítulo anterior.

14.4.4. Opciones específicas para la vista "list"

La vista list' puede mostrar la información de cada fila en varias columnas o de forma conjunta en una sola línea. También puede mostrar opciones para filtrar los resultados, paginación de resultados y opciones para odenar los datos. Todas estas opciones se pueden modificar mediante los archivos de configuración, como se muestra en las siguientes secciones.

14.4.4.1. Modificando el layout

Por defecto, la unión entre la vista list y la vista edit se realiza mediante la columna que muestra la clave primaria. Si se observa de nuevo la figura 14-11, se ve que la columna id de la lista de comentarios no solo muestra la clave primaria de cada comentario, sino que incluye un enlace que permite a los usuarios acceder de forma directa a la vista edit.

Si se quiere mostrar en otra columna el enlace a los datos detallados, se añade el prefijo = al nombre de la columna que se utiliza en la clave display. El listado 14-21 elimina la columna id de la vista list de los comentarios y establece el enlace en el campo content. La figura 14-14 muestra el resultado de este cambio.

Listado 14-21 - Cambiando el enlace a la vista edit en la vista list, en modules/comment/config/generator.yml

list:
          display:    [article_link, =content]
Estableciendo el enlace a la vista <code>edit</code> en otra columna, en la vista <code>list</code> del módulo <code>comment</code>

Figura 14.14 Estableciendo el enlace a la vista edit en otra columna, en la vista list del módulo comment

La vista list muestra por defecto todos sus datos en varias columnas. También es posible mostrar de forma seguida todos los datos en una sola cadena de texto que ocupe toda la anchura de la tabla. El aspecto con el que se muestran los datos se denomina "layout" y la forma en la que se muestran todos seguidos se denomina stacked. Si se utiliza el layout stacked, la clave params debe contener el patrón que define el orden en el que se muestran los datos. El listado 14-22 muestra por ejemplo el layout deseado para la vista list del módulo comment. La figura 14-15 ilustra el resultado final.

Listado 14-22 - Uso del layout stacked en la vista list, en modules/comment/config/generator.yml

list:
          layout:  stacked
          params:  |
            %%=content%% <br />
            (sent by %%author%% on %%created_at%% about %%article_link%%)
          display:  [created_at, author, content]
Layout "stacked" en la vista "list" del módulo "comment"

Figura 14.15 Layout "stacked" en la vista "list" del módulo "comment"

El layout normal en varias columnas requiere un array con el nombre de los campos en la clave display; sin embargo, el layout stacked requiere que la clave params incluya el código HTML que se utilizará para mostrar cada fila de datos. No obstante, el array de la clave display también se utiliza en el layout stacked para determinar las cabeceras de columnas disponibles para reordenar los datos mostrados.

14.4.4.2. Filtrando los resultados

En la vista de tipo list, se pueden añadir fácilmente una serie de filtros. Con estos filtros, los usuarios pueden mostrar menos resultados y pueden obtener más rápidamente los que están buscando. Los filtros se configuran mediante un array con los nombres de los campos en la clave filters. El listado 14-23 muestra como incluir un filtro según los campos article_id, author y created_at en la vista list del módulo comment, y la figura 14-16 ilustra el resultado. Para que el ejemplo funcione correctamente, es necesario añadir un método __toString() a la clase Article (este método puede devolver, por ejemplo, el valor title del artículo).

Listado 14-23 - Incluyendo filtros en la vista list, en modules/comment/config/generator.yml

list:
          filters: [article_id, author, created_at]
          layout:  stacked
          params:  |
            %%=content%% <br />
            (sent by %%author%% on %%created_at%% about %%article_link%%)
          display:  [created_at, author, content]
Filtros en la vista "list" del módulo "comment"

Figura 14.16 Filtros en la vista "list" del módulo "comment"

Los filtros que muestra Symfony dependen del tipo de cada columna:

  • Para las columnas de texto (como el campo author en el módulo comment), el filtro es un cuadro de texto que permite realizar búsuqedas textuales incluso con comodines (*).
  • Para las claves externas (como el campo article_id en el módulo comment), el filtro es una lista desplegable con los datos de la columna correspondiente en la tabla asociada. Como sucede con el helper object_select_tag(), las opciones de la lista desplegable son las que devuelve el método __toString() de la clase relacionada.
  • Para las fechas (como el campo created_at en el módulo comment), el filtro está formado por un par de elementos para seleccionar fechas (que muestran un calendario) de forma que se pueda indicar un intervalo temporal.
  • Para las columnas booleanas, el filtro muestra una lista desplegable con los valores true, false y true or false (la última opción es para reinicializar el filtro).

De la misma forma que se pueden utilizar elementos parciales en las listas, también es posible utilizar filtros parciales que permitan definir filtrados que no realiza Symfony. En el siguiente ejemplo se utiliza un campo llamado state que solo puede contener dos valores (open y closed), pero estos valores se almacenan directamente en cada fila de la tabla (no se utiliza una relación con otra tabla). Un filtro de Symfony en este campo mostrará un cuadro de texto, pero lo más lógico sería mostrar una lista desplegable con los dos únicos valores permitidos. Mediante un filtro parcial es fácil mostrar esta lista desplegable. El listado 14-24 muestra un ejemplo de cómo realizar este filtro.

Listado 14-24 - Utilizando un filtro parcial

// El elemento parcial se define en templates/_state.php
    <?php echo select_tag('filters[state]', options_for_select(array(
      ` => `,
      'open' => 'open',
      'closed' => 'closed',
    ), isset($filters['state']) ? $filters['state'] : '')) ?>

    // Se añade el filtro parcial en la lista de filtros de config/generator.yml
        list:
          filters:        [date, _state]

El elemento parcial tiene acceso a la variable $filters, que es muy útil para obtener el valor actual del filtro.

Existe una última opción que es muy útil para buscar valores vacíos. Si se quiere filtrar por ejemplo la lista de comentarios para mostrar solamente los que no tienen autor, no se puede dejar vacío el filtro del autor, ya que en este caso se ignorará este filtro. La solución es establecer la opción filter_is_empty del campo a true, como en el listado 14-25, y el filtro mostrará un checkbox que permite buscar los valores vacíos, tal y como ilustra la figura 14-17.

Listado 14-25 - Filtrando los valores vacíos para el campo author en la vista list

list:
          fields:
            author:   { filter_is_empty: true }
          filters:    [article_id, author, created_at]
Permitiendo filtrar valores vacíos en el campo "author"

Figura 14.17 Permitiendo filtrar valores vacíos en el campo "author"

14.4.4.3. Ordenando el listado

Como muestra la figura 14-18, en la vista list las columnas que forman la cabecera de la tabla son enlaces que se pueden utilizar para reordenar los datos del listado. Las cabeceras se muestran tanto en el layout normal como en el layout stacked. Al pinchar en cualquiera de estos enlaces, se recarga la página añadiendo un parámetro sort que permite reordenar los datos de forma adecuada.

Las cabeceras de la tabla de la vista "list" permiten reordenar los datos

Figura 14.18 Las cabeceras de la tabla de la vista "list" permiten reordenar los datos

Se puede utilizar la misma sintaxis que emplea Symfony para incluir un enlace que apunte directamente a los datos ordenados de una forma determinada:

<?php echo link_to('Listado de comentarios ordenados por fecha', 'comment/list?sort=created_at&type=desc' ) ?>

También es posible indicar en el archivo generator.yml el orden por defecto para la vista list mediante el parámetro sort. El listado 14-26 muestra un ejemplo de la sintaxis que debe utilizarse.

Listado 14-26 - Estableciendo un orden por defecto en la vista list

list:
          sort:   created_at
          # Sintaxis alternativa para indicar la forma de ordenar
          sort:   [created_at, desc]

Nota Solamente se pueden reordenar los datos mediante los campos que se corresponden con columnas reales, no mediante los campos propios y los campos parciales.

14.4.4.4. Modificando la paginación

La administración generada automáticamente tiene en cuenta la posibilidad de que las tablas contengan muchos datos, por lo que la vista list incluye por defecto una paginación de datos. Si el número total de filas de la tabla es mayor que el número máximo de filas por página, entonces aparece la paginación al final del listado. La figura 14-19 muestra el ejemplo de un listado con 6 comentarios de prueba para el que el número máximo de comentarios por página es de 5. La paginación de datos asegura un buen rendimiento a la aplicación, porque solamente se obtienen los datos de las filas que se muestran, y permite una buena usabilidad, ya que hasta las filas que contienen millones de filas se pueden manejar con el módulo de administración.

La paginación se muestra cuando el listado es muy largo

Figura 14.19 La paginación se muestra cuando el listado es muy largo

El número máximo de filas que se muestran en cada página se controla mediante la opción max_per_page:

list:
          max_per_page:   5

14.4.4.5. Mejorando el rendimiento mediante una Join

El generador de la administración utiliza por defecto el método doSelect() para obtener las filas de datos. Sin embargo, si se utilizan objetos relacionados en el listado, el número de consultas a la base de datos puede aumentar demasiado. Si se quiere mostrar por ejemplo el nombre del artículo en el listado de comentarios, se debe hacer una consulta adicional por cada comentario para obtener el objeto Article relacionado. Afortunadamente, se puede indicar al paginador que utilice un método específico tipo doSelectJoinXXX() para optimizar el número de consultas necesario. La opción peer_method es la encargada de indicar el método a utilizar.

list:
          peer_method:   doSelectJoinArticle

En el Capítulo 18 se explica más detalladamente el concepto de Join.

14.4.5. Opciones específicas para la vista "edit"

La vista edit permite al usuario modificar el valor de cualquier columna de una fila de datos específica. En función del tipo de dato, Symfony determina automáticamente el tipo de campo de formulario que se muestra. Después, genera un helper de tipo object_*_tag() y le pasa el objeto y la propiedad a editar. Si por ejemplo la configuración de la vista edit del artículo estipula que el usuario puede editar el campo title:

edit:
          display: [title, ...]

Entonces, la página edit muestra un cuadro de texto normal para editar el campo title, ya que esta columna se define como de tipo varchar en el esquema.

<?php echo object_input_tag($article, 'getTitle') ?>

14.4.5.1. Modificando el tipo de campo de formulario

Las reglas que se utilizan por defecto para determinar el tipo de campo de formulario son las siguientes:

  • Las columnas de tipo integer, float, char, varchar(size) se muestran en la vista edit mediante object_input_tag().
  • Las columnas de tipo longvarchar aparecen como object_textarea_tag().
  • Una columna que es una clave externa, se muestra mediante object_select_tag().
  • Las columnas de tipo boolean aparecen como object_checkbox_tag().
  • Las columnas de tipo timestamp o date se muestran mediante object_input_date_tag().

En ocasiones, puede ser necesario saltarse estas reglas por defecto para indicar directamente el tipo de campo de formulario utilizado para una columna. Para ello, se utiliza la opción type bajo la clave fields con el nombre del helper que se quiere utilizar. Las opciones del helper object_*_tag() generado se pueden modificar con la opción params. El listado 14-27 muestra un ejemplo.

Listado 14-27 - Indicando un tipo especial de campo de formulario y sus opciones en la vista edit

generator:
      class:              sfPropelAdminGenerator
      param:
        model_class:      Comment
        theme:            default

        edit:
          fields:
                          ## No se muestra un cuadro de texto, solamente el texto
            id:           { type: plain }
                          ## El contenido del cuadro de texto no se puede modificar
            author:       { params: disabled=true }
                          ## El campo es un textarea (object_textarea_tag)
            content:      { type: textarea_tag, params: rich=true css=user.css tinymce_options=width:330 }
                          ## El campo es una lista desplegable (object_select_tag)
            article_id:   { params: include_custom=Choose an article }
             ...

La opciones indicadas en params se pasan directamente al helper object_*_tag() generado. La opción params del campo article_id en el ejemplo anterior produce el siguiente código en la plantilla:

<?php echo object_select_tag($comment, 'getArticleId', 'related_class=Article', 'include_custom=Choose an article') ?>

De esta forma, todas las opciones disponibles para los helpers de formulario se pueden utilizar para modificar la vista edit.

14.4.5.2. Manejando los campos parciales

Las vistas de tipo edit puede utilizar los mismos elementos parciales que se emplean en las vistas de tipo list. La única diferencia es que, en la acción, se debe realizar manualmente la actualización de la columna en función del valor enviado por el elemento parcial. Symfony puede procesar automáticamente los campos normales (los que se corresponden con columnas reales) pero no puede adivinar la forma de tratar los datos que utilizan campos parciales.

Si por ejemplo se define un modulo de administración para una clase User, los campos disponibles pueden ser id, nickname y password. El administrador del sitio web debe ser capaz de modificar la contraseña de un usuario si así se le solicita, pero la vista edit no debería mostrar el valor de la contraseña por motivos de seguridad. En su lugar, el formulario debería mostrar un cuadro de texto vacío para la contraseña y así el usuario puede introducir una nueva contraseña si desea cambiar su valor. Las opciones del archivo generator.yml para una vista edit de este tipo se muestran en el listado 14-28.

Listado 14-28 - Incluyendo un campo parcial en la vista edit

edit:
          display:        [id, nickname, _newpassword]
          fields:
            newpassword:  { name: Password, help: Introduce una contraseña para modificar su valor, dejalo vacío para mantener la contraseña actual }

El elemento parcial templates/_newpassword.php debe ser similar a:

<?php echo input_password_tag('newpassword', '') ?>

Este elemento parcial utiliza un helper de formulario sencillo y no un helper para objetos, ya que no es deseable obtener el valor de la contraseña a partir del objeto User actual, porque podría mostrar la contraseña del usuario.

A continuación, para utilizar el valor de este campo para actualizar el objeto en la acción, se debe extender el método updateUserFromRequest() de la acción. Para ello, se crea un método con el mismo nombre en la clase de la acción y se crea el código necesario para manejar el elemento parcial, como muestra el listado 14-29.

Listado 14-29 - Procesando un campo parcial en la acción, en modules/user/actions/actions.class.php

class userActions extends sfActions
{
  protected function updateUserFromRequest()
  {
    // Procesar los datos del campo parcial
    $password = $this->getRequestParameter('newpassword');

    if ($password)
    {
      $this->user->setPassword($password);
    }

    // Dejar que Symfony procese los otros datos
    parent::updateUserFromRequest();
  }
}

Nota En una aplicación real, la vista user/edit normalmente tendría 2 campos para la contraseña y el valor del segundo campo debe coincidir con el valor del primero para evitar los errores al escribir la contraseña. En la práctica, como se vio en el Capítulo 10, este comportamiento se se consigue mediante un validador. Los módulos generados automáticamente pueden hacer uso de este mecanismo de la misma forma que el resto de módulos.

14.4.6. Trabajando con claves externas

Si el esquema de la aplicación define relaciones entre tablas, los módulos generados para la administración pueden aprovechar esas relaciones para automatizar aun más los campos, simplificando enormemente la gestión de las relaciones entre tablas.

14.4.6.1. Relaciones uno-a-muchos

El generador de la administración se ocupa automáticamente de las relaciones de tablas de tipo 1-n. Como se muestra en la figura 14-1, la tabla blog_comment se relaciona con la tabla blog_article mediante el campo article_id. Si se utiliza el generador de administraciones para iniciar el módulo de la clase Comment, la acción comment/edit muestra automáticamente el campo article_id como una lista desplegable con los valores de los ID de todas las filas de datos de la tabla blog_article (la figura 14-9 también muestra una figura de esta relación).

Además, si se define un método __toString() en la clase Article, la lista desplegable puede mostrar otro texto para cada opción en vez del valor de la clave primaria de la fila.

Si se quiere mostrar la lista de comentarios relacionados con un artículo en el módulo article (relación 1-n) se debe modificar el módulo y utilizar un campo parcial.

14.4.6.2. Relaciones muchos-a-muchos

Symfony también se encarga de las relaciones n-n, pero como estas relaciones no se pueden definir en el esquema, es necesario añadir un par de opciones al archivo generator.yml.

Las relaciones muchos-a-muchos requieren una tabla intermedia. Si por ejemplo existe una relación n-n entre la tabla blog_article y la tabla blog_author (un artículo puede estar escrito por más de un autor y un mismo autor puede escribir varios artículos), la base de datos debe contener una tabla llamada blog_article_author o algo parecido, como se muestra en la figura Figure 14-20.

Uso de una tabla intermedia en las relaciones muchos-a-muchos

Figura 14.20 Uso de una tabla intermedia en las relaciones muchos-a-muchos

El modelo en este caso dispone de una clase llamada ArticleAuthor, que es el único dato que necesita el generador de la administración y que se indica en la opción through_class del campo adecuado.

En un módulo generado automáticamente a partir de la clase Article, se puede añadir un nuevo campo para crear una asociación n-n con la clase Author mediante las opciones del archivo generator.yml mostrado en el listado 14-30.

Listado 14-30 - Definiendo las relaciones muchos-a-muchos mediante la opción through_class

edit:
          fields:
            article_author: { type: admin_double_list, params: through_class=ArticleAuthor }

Este nuevo campo gestiona las relaciones entre los objetos existentes, por lo que no es suficiente con mostrar una lista deplegable. Este tipo de relaciones exige un tipo especial de campo para introducir los datos. Symfony incluye 3 tipos de campos especiales para relacionar los elementos de las 2 listas (que se muestran en la figura 14-21).

  • El tipo admin_double_list es un conjunto de 2 listas desplegables expandidas, además de los botones que permiten pasar elementos de la primera lista (elementos disponibles) a la segunda lista (elementos seleccionados).
  • El tipo admin_select_list es una lista desplegable expandida que permite seleccionar más de 1 elemento cada vez.
  • El tipo admin_check_list es una lista de elementos checkbox seleccionables.
Tipos de campos especiales disponibles para la gestión de las relaciones muchos-a-muchos

Figura 14.21 Tipos de campos especiales disponibles para la gestión de las relaciones muchos-a-muchos

14.4.7. Añadiendo nuevas interacciones

Los módulos de administración permiten a los usuarios realizar las operaciones CRUD habituales, aunque también es posible añadir otras interacciones diferentes o restringir las interacciones disponibles en una vista. La configuración que se muestra en el listado 14-31 habilita todas las operaciones CRUD habituales para el módulo article.

Listado 14-31 - Definiendo las interacciones de cada vista, en backend/modules/article/config/generator.yml

list:
          title:          List of Articles
          object_actions:
            _edit:         ~
            _delete:       ~
          actions:
            _create:       ~

        edit:
          title:          Body of article %%title%%
          actions:
            _list:         ~
            _save:         ~
            _save_and_add: ~
            _delete:       ~

En la vista de tipo list, existen 2 opciones relacionadas con las acciones: la lista de las acciones disponibles para todos los objetos y la lista de acciones disponibles para la página entera. La lista de interacciones definidas en el listado 14-31 producen el resultado de la figura 14-22. Cada fila de datos muestra un botón para modificar la información y un botón para eliminar ese registro. Al final de la lista se muestra un botón para crear nuevos elementos.

Interacciones de la vista "list"

Figura 14.22 Interacciones de la vista "list"

En la vista edit, como solamente se modifica un registro de datos cada vez, solamente se define un conjunto de acciones. Las interacciones definidas en el listado 14-31 se muestran con el aspecto de la figura 14-23. Tanto la acción save (guardar) como la acción save_and_add (guardar_y_añadir) guardan los cambios realizados en los datos. La única diferencia es que la acción save vuelve a mostrar la vista edit con los nuevos datos y la acción save_and_add muestra la vista edit con un formulario vacío para añadir otro elemento. Por tanto, la acción save_and_add es un atajo muy útil cuando se están añadiendo varios elementos de forma consecutiva. El botón de la acción delete (borrar) se encuentra lo suficientemente alejado de los otros 2 botones como para que no sea pulsado por accidente.

Los nombres de las interacciones que empiezan con un guión bajo (_) son reconocidos por Symfony y por tanto, utilizan el mismo icono y realizan la misma acción que las interacciones por defecto. El generador de la administración es capaz de reconocer las acciones _edit, _delete, _create, _list, _save, _save_and_add y _create.

Interacciones de la vista "edit"

Figura 14.23 Interacciones de la vista "edit"

También es posible definir interacciones propias, para lo que se debe especificar un nombre que no empiece por guión bajo, tal y como se muestra en el listado 14-32.

Listado 14-32 - Definiendo una interacción propia

list:
          title:          List of Articles
          object_actions:
            _edit:        -
            _delete:      -
            addcomment:   { name: Add a comment, action: addComment, icon: backend/addcomment.png }

Ahora, cada artículo que aparece en el listado muestra un botón con la imagen addcomment.png, tal y como se muestra en la figura 14-24. Al pinchar sobre ese botón, se ejecuta la acción addComment del módulo actual. La clave primaria del objeto relacionado se pasa automáticamente a los parámetros de la petición que se produce.

Interacciones propias en la vista "list"

Figura 14.24 Interacciones propias en la vista "list"

La acción addComment puede ser tan sencilla como la que muestra el listado 14-33.

Listado 14-33 - Acción para una interacción propia, en actions/actions.class.php

public function executeAddComment()
{
  $comment = new Comment();
  $comment->setArticleId($this->getRequestParameter('id'));
  $comment->save();

  $this->redirect('comment/edit?id='.$comment->getId());
}

Por último, si se quieren eliminar todas las acciones para una determinada vista, se utiliza una lista vacía como la del listado 14-34.

Listado 14-34 - Eliminando todas las acciones en la vista list

list:
          title:          List of Articles
          actions:        {}

14.4.8. Validación de formularios

Si se observa el código de la plantilla _edit_form.php generada, que se encuentra en el directorio cache/ del proyecto, se puede ver que los campos del formulario utilizan una nombrado especial. En la vista edit generada, los nombres de los campos del formulario se definen como la concatenación del nombre del módulo (utilizando guiones bajos) y el nombre del campo encerrado entre corchetes.

Si la vista edit de la clase Article tiene un campo llamado title, la plantilla será similar a la del listado 14-35 y el campo se identifica como article[title].

Listado 14-35 - Sintaxis de los nombres generados para los campos

// generator.yml
    generator:
      class:              sfPropelAdminGenerator
      param:
        model_class:      Article
        theme:            default
        edit:
          display: [title]
// Plantilla _edit_form.php generada
    <?php echo object_input_tag($article, 'getTitle', array('control_name' => 'article[title]')) ?>
// Código HTML generado
    <input type="text" name="article[title]" id="article_title" value="My Title" />

El uso de estos nombres de campos facilita el procesamiento de los formularios. Sin embargo, como se explica en el Capítulo 10, complica un poco la configuración del validador, por lo que se deben cambiar los corchetes [ ] por llaves { } en la clave fields del archivo de validación. Además, cuando se utiliza el nombre de un campo como parámetro del validador, se debe utilizar el nombre tal y como aparece en el código HTML (es decir, con los corchetes, pero entre comillas). El listado 14-36 muestra un ejemplo de la sintaxis especial que se debe utilizar para el validador de los formularios generados automáticamente.

Listado 14-36 - Sintaxis del archivo de validación para los formularios generados automáticamente

## Se reemplazan los corchetes por comillas en la lista de campos
    fields:
      article{title}:
        required:
          msg: You must provide a title
        ## Para los parámetros del validador se utiliza el nombre original del campo entre comillas
        sfCompareValidator:
          check:        "user[newpassword]"
          compare_error: The password confirmation does not match the password.

14.4.9. Restringiendo las acciones del usuario mediante credenciales

Los campos y las interacciones disponibles en un módulo de administración pueden variar en función de las credenciales del usuario conectado (el Capítulo 6 describe las opciones de seguridad de Symfony).

Los campos definidos en el generador pueden incluir una opción credentials para restringir su acceso solamente a los usuarios con la credencial adecuada. Esta característica se puede utilizar tanto en la vista list como en la vista edit. Además, el generador puede ocultar algunas interacciones en función de la credenciales del usuario. El listado 14-37 muestra estas opciones.

Listado 14-37 - Utilizando credenciales en generator.yml

## La columna id solamente se muestra para los usuarios con la credencial "admin"
        list:
          title:          List of Articles
          layout:         tabular
          display:        [id, =title, content, nb_comments]
          fields:
            id:           { credentials: [admin] }

    ## La interacción "addcomment" se restringe a los usuarios con la credencial "admin"
        list:
          title:          List of Articles
          object_actions:
            _edit:        -
            _delete:      -
            addcomment:   { credentials: [admin], name: Add a comment, action: addComment, icon: backend/addcomment.png }