SlideShare una empresa de Scribd logo
www.librosweb.es




Symfony
       la guía definitiva




              Fabien Potencier, François Zaninotto
Symfony, la guía definitiva




Sobre este libro...
      ▪ Esta versión impresa se creó el 20 de febrero de 2008 y todavía está in-
        completa. La versión más actualizada de los contenidos de este libro se puede
        encontrar en http://www.librosweb.es/symfony

      ▪ Si quieres aportar sugerencias, comentarios, críticas o informar sobre errores,
        puedes contactarnos en contacto@librosweb.es




www.librosweb.es                                                                     2
Symfony, la guía definitiva


  Capítulo 1. Introducción a Symfony............................................................... 13
    1.1. Symfony en pocas palabras ...................................................................... 13
       1.1.1. Características de Symfony ................................................................ 13
       1.1.2. ¿Quién ha desarrollado Symfony y por qué motivo? ...............................15
       1.1.3. La comunidad Symfony ...................................................................... 16
       1.1.4. ¿Es adecuado Symfony para mí? ......................................................... 16
    1.2. Conceptos básicos................................................................................... 17
       1.2.1. PHP 5 .............................................................................................. 17
       1.2.2. Programación Orientada a Objetos (OOP) ............................................. 17
       1.2.3. Métodos mágicos .............................................................................. 18
       1.2.4. PEAR (PHP Extension and Application Repository) ..................................18
       1.2.5. Mapeo de Objetos a Bases de datos (ORM) ...........................................19
       1.2.6. Desarrollo rápido de aplicaciones (RAD) ............................................... 20
       1.2.7. YAML ............................................................................................... 22
  Capítulo 2. Explorando el interior de Symfony ............................................... 24
    2.1. El patrón MVC ........................................................................................ 24
       2.1.1. Las capas de la arquitectura MVC ........................................................ 25
       2.1.2. Separación en capas más allá del MVC ................................................. 29
       2.1.3. La implementación del MVC que realiza Symfony ...................................32
       2.1.4. Las clases que forman el núcleo de Symfony.........................................35
    2.2. Organización del código ........................................................................... 35
       2.2.1. Estructura del proyecto: Aplicaciones, Módulos y Acciones ......................35
       2.2.2. Estructura del árbol de archivos .......................................................... 37
    2.3. Herramientas comunes ............................................................................ 41
       2.3.1. Contenedores de parámetros .............................................................. 41
       2.3.2. Constantes ....................................................................................... 43
       2.3.3. Carga automática de clases ................................................................ 43
  Capítulo 3. Ejecutar aplicaciones Symfony .................................................... 45
    3.1. Instalando el entorno de pruebas .............................................................. 45
    3.2. Instalando las librerías de Symfony ........................................................... 47
       3.2.1. Instalando Symfony con PEAR............................................................. 48
       3.2.2. Obtener Symfony mediante el repositorio SVN ......................................49
    3.3. Crear una aplicación web ......................................................................... 49
       3.3.1. Crear el Proyecto .............................................................................. 49
       3.3.2. Crear la Aplicación............................................................................. 50
    3.4. Configurar el servidor web ....................................................................... 51
       3.4.1. Configurar los servidores virtuales ....................................................... 51
       3.4.2. Configurar un servidor compartido....................................................... 52
    3.5. Resolución de problemas.......................................................................... 54
       3.5.1. Problemas típicos .............................................................................. 54
       3.5.2. Recursos relacionados con Symfony..................................................... 55
    3.6. Versionado del código fuente .................................................................... 55

www.librosweb.es                                                                                                   3
Symfony, la guía definitiva


  Capítulo 4. Introducción a la creación de páginas ......................................... 58
    4.1. Crear el esqueleto del módulo................................................................... 58
       4.1.1. Añadir una página ............................................................................. 60
       4.1.2. Transfiriendo información de la acción a la plantilla ................................63
    4.2. Obteniendo información del usuario a través de formularios..........................63
    4.3. Enlazando a otra acción ........................................................................... 65
    4.4. Obteniendo información de la petición........................................................ 66
  Capítulo 5. Configurar Symfony ..................................................................... 69
    5.1. El sistema de configuración ...................................................................... 69
       5.1.1. Sintaxis YAML y convenciones de Symfony ...........................................70
       5.1.2. ¡Socorro, los archivos YAML han roto la aplicación! ................................73
    5.2. Un vistazo general a los archivos de configuración .......................................74
       5.2.1. Configuración del Proyecto ................................................................. 74
       5.2.2. Configuración de la Aplicación ............................................................. 75
       5.2.3. Configuración de los Módulos .............................................................. 79
    5.3. Entornos................................................................................................ 80
       5.3.1. ¿Qué es un entorno?.......................................................................... 80
       5.3.2. Configuration en cascada ................................................................... 82
    5.4. La cache de configuración ........................................................................ 84
    5.5. Accediendo a la configuración desde la aplicación ........................................85
       5.5.1. La clase sfConfig ............................................................................... 85
       5.5.2. El archivo app.yml y la configuración propia de la aplicación ...................87
    5.6. Trucos para los archivos de configuración ................................................... 87
       5.6.1. Uso de constantes en los archivos de configuración YAML .......................88
       5.6.2. Uso de programación en los archivos de configuración ...........................88
       5.6.3. Utilizar tu propio archivo YAML ............................................................ 89
  Capítulo 6. El Controlador.............................................................................. 91
    6.1. El Controlador Frontal.............................................................................. 91
       6.1.1. El Trabajo del Controlador Frontal en Detalle ........................................91
       6.1.2. El Controlador Frontal por defecto ....................................................... 92
       6.1.3. Llamando a Otro Controlador Frontal para Cambiar el Entorno.................92
       6.1.4. Archivos por Lotes............................................................................. 93
    6.2. Acciones ................................................................................................ 94
       6.2.1. La clase de la acción .......................................................................... 94
       6.2.2. Sintaxis alternativa para las clases de las Acciones ................................96
       6.2.3. Obteniendo Información en las Acciones............................................... 96
       6.2.4. Terminación de las Acciones ............................................................... 97
       6.2.5. Saltando a Otra Acción....................................................................... 99
       6.2.6. Repitiendo Código para varias Acciones de un Modulo .......................... 101
    6.3. Accediendo a la Petición......................................................................... 101
    6.4. Sesiones de Usuario .............................................................................. 104
       6.4.1. Accediendo a la Sesión de Usuario ..................................................... 104


www.librosweb.es                                                                                                   4
Symfony, la guía definitiva


       6.4.2. Atributos Flash................................................................................ 105
       6.4.3. Manejo de Sesiones ......................................................................... 106
    6.5. Seguridad de la Acción .......................................................................... 107
       6.5.1. Restricción de Acceso ...................................................................... 108
       6.5.2. Otorgando Acceso ........................................................................... 109
       6.5.3. Credenciales Complejas ................................................................... 111
    6.6. Métodos de Validación y Manejo de Errores .............................................. 111
    6.7. Filtros.................................................................................................. 113
       6.7.1. La Cadena de Filtros ........................................................................ 113
       6.7.2. Construyendo Tu Propio Filtro ........................................................... 116
       6.7.3. Activación de Filtros y Parámetros ..................................................... 117
       6.7.4. Filtros de Ejemplo ........................................................................... 118
    6.8. Configuración del Módulo ....................................................................... 119
  Capítulo 7. La Vista ..................................................................................... 121
    7.1. Plantillas .............................................................................................. 121
       7.1.1. Helpers .......................................................................................... 122
       7.1.2. Layout de las páginas ...................................................................... 125
       7.1.3. Atajos de plantilla ........................................................................... 126
    7.2. Fragmentos de código ........................................................................... 127
       7.2.1. Elementos parciales......................................................................... 128
       7.2.2. Componentes ................................................................................. 129
       7.2.3. Slots ............................................................................................. 132
    7.3. Configuración de la vista ........................................................................ 134
       7.3.1. El archivo view.yml ......................................................................... 135
       7.3.2. El objeto respuesta (response).......................................................... 136
       7.3.3. Opciones de configuración de la vista ................................................. 137
    7.4. Slots de componentes ........................................................................... 142
    7.5. Mecanismo de escape ............................................................................ 144
       7.5.1. Activar el mecanismo de escape ........................................................ 145
       7.5.2. Opción escaping_strategy................................................................. 146
       7.5.3. Los helpers útiles para el mecanismo de escape .................................. 147
       7.5.4. Aplicando el mecanismo de escape a los arrays y los objetos ................ 147
  Capítulo 8. El modelo................................................................................... 150
    8.1. ¿Por qué utilizar un ORM y una capa de abstracción? ................................. 150
    8.2. Esquema de base de datos de Symfony ................................................... 152
       8.2.1. Ejemplo de esquema ....................................................................... 152
       8.2.2. Sintaxis básica de los esquemas........................................................ 153
    8.3. Las clases del modelo ............................................................................ 154
       8.3.1. Clases base y clases personalizadas ................................................... 155
       8.3.2. Clases objeto y clases "peer" ............................................................ 155
    8.4. Acceso a los datos................................................................................. 156
       8.4.1. Obtener el valor de una columna ....................................................... 157


www.librosweb.es                                                                                                    5
Symfony, la guía definitiva


       8.4.2. Obtener los registros relacionados ..................................................... 157
       8.4.3. Guardar y borrar datos .................................................................... 158
       8.4.4. Obtener registros mediante la clave primaria ...................................... 159
       8.4.5. Obtener registros mediante Criteria ................................................... 160
       8.4.6. Uso de consultas con código SQL....................................................... 163
       8.4.7. Uso de columnas especiales de fechas................................................ 164
    8.5. Conexiones con la base de datos ............................................................. 165
    8.6. Extender el modelo ............................................................................... 167
       8.6.1. Añadir nuevos métodos.................................................................... 167
       8.6.2. Redefinir métodos existentes ............................................................ 168
       8.6.3. Uso de comportamientos en el modelo ............................................... 168
    8.7. Sintaxis extendida del esquema .............................................................. 169
       8.7.1. Atributos........................................................................................ 169
       8.7.2. Detalles de las columnas .................................................................. 171
       8.7.3. Claves externas .............................................................................. 172
       8.7.4. Índices .......................................................................................... 173
       8.7.5. Columnas vacías ............................................................................. 173
       8.7.6. Tablas i18n .................................................................................... 174
       8.7.7. Más allá del schema.yml: schema.xml................................................ 174
    8.8. No crees el modelo dos veces ................................................................. 175
       8.8.1. Construir la estructura SQL de la base de datos en función de un esquema
       existente ................................................................................................. 176
       8.8.2. Construir un modelo de datos en formato YAML a partir de una base de
       datos existente ........................................................................................ 176
  Capítulo 9. Enlaces y sistema de enrutamiento ........................................... 179
    9.1. ¿Qué es el enrutamiento?....................................................................... 179
       9.1.1. URL como instrucciones de servidor ................................................... 179
       9.1.2. URL como parte de la interfaz ........................................................... 180
       9.1.3. Cómo funciona................................................................................ 182
    9.2. Reescritura de URL................................................................................ 183
    9.3. Helpers de enlaces ................................................................................ 185
       9.3.1. Hiperenlaces, botones y formularios .................................................. 186
       9.3.2. Opciones de los helpers de enlaces .................................................... 187
       9.3.3. Opciones GET y POST falsas ............................................................. 187
       9.3.4. Forzando los parámetros de la petición como variables de tipo GET ........ 188
       9.3.5. Utilizando rutas absolutas................................................................. 189
    9.4. Configuración del sistema de enrutamiento............................................... 190
       9.4.1. Reglas y patrones ........................................................................... 191
       9.4.2. Restricciones en los patrones ............................................................ 192
       9.4.3. Asignando valores por defecto .......................................................... 194
       9.4.4. Acelerando el sistema de enrutamiento mediante el uso de los nombres de
       las reglas ................................................................................................ 194
       9.4.5. Añadiendo la extensión .html ............................................................ 195

www.librosweb.es                                                                                                    6
Symfony, la guía definitiva


       9.4.6. Creando reglas sin el archivo routing.yml ........................................... 196
    9.5. Trabajando con rutas en las acciones....................................................... 197
  Capítulo 10. Formularios ............................................................................. 198
    10.1. Helpers de formularios ......................................................................... 198
       10.1.1. Etiqueta principal de los formularios................................................. 198
       10.1.2. Elementos comunes de formulario ................................................... 199
       10.1.3. Campos para introducir fechas ........................................................ 202
       10.1.4. Editor de textos avanzado .............................................................. 203
       10.1.5. Selección de idioma y país .............................................................. 204
    10.2. Helpers de formularios para objetos....................................................... 205
       10.2.1. Llenando listas desplegables con objetos .......................................... 206
       10.2.2. Creando una lista desplegable a partir de una columna que es clave
       externa ................................................................................................... 207
       10.2.3. Modificando objetos ....................................................................... 208
    10.3. Validación de formularios ..................................................................... 209
       10.3.1. Validadores .................................................................................. 210
       10.3.2. Archivo de validación ..................................................................... 211
       10.3.3. Mostrando el formulario de nuevo .................................................... 212
       10.3.4. Mostrando los mensajes de error en el formulario .............................. 213
       10.3.5. Mostrando de nuevo los datos introducidos ....................................... 215
       10.3.6. Validadores estándar de Symfony .................................................... 216
       10.3.7. Validadores con nombre ................................................................. 219
       10.3.8. Restringiendo la validación a un método ........................................... 220
       10.3.9. ¿Cuál es el aspecto de un archivo de validación?................................ 220
    10.4. Validaciones complejas ........................................................................ 221
       10.4.1. Creando un validador propio ........................................................... 221
       10.4.2. Utilizando la sintaxis de los arrays para los campos de formulario ........ 223
       10.4.3. Ejecutando un validador en un campo vacío ...................................... 223
  Capítulo 11. Integración con Ajax ............................................................... 226
    11.1. Helpers básicos de JavaScript ............................................................... 226
       11.1.1. JavaScript en las plantillas .............................................................. 227
       11.1.2. Actualizando un elemento DOM ....................................................... 228
       11.1.3. Aplicaciones que se degradan correctamente..................................... 229
    11.2. Prototype ........................................................................................... 229
    11.3. Helpers de Ajax .................................................................................. 231
       11.3.1. Enlaces Ajax ................................................................................. 233
       11.3.2. Formularios Ajax ........................................................................... 233
       11.3.3. Ejecución periódica de funciones remotas ......................................... 236
    11.4. Parámetros para la ejecución remota ..................................................... 236
       11.4.1. Actualizar elementos diferentes en función del estado de la respuesta .. 236
       11.4.2. Actualizar un elemento según su posición ......................................... 237
       11.4.3. Actualizar un elemento en función de una condición ........................... 238


www.librosweb.es                                                                                                    7
Symfony, la guía definitiva


       11.4.4. Determinando el método de una petición Ajax ................................... 238
       11.4.5. Permitiendo la ejecución de un script ............................................... 239
       11.4.6. Creando callbacks.......................................................................... 239
    11.5. Creando efectos visuales ...................................................................... 240
    11.6. JSON ................................................................................................. 241
    11.7. Interacciones complejas con Ajax .......................................................... 243
       11.7.1. Autocompletar .............................................................................. 244
       11.7.2. Arrastrar y soltar ........................................................................... 245
       11.7.3. Listas ordenables........................................................................... 246
       11.7.4. Edición directa de contenidos .......................................................... 247
  Capítulo 12. Uso de la cache........................................................................ 249
    12.1. Guardando la respuesta en la cache....................................................... 249
       12.1.1. Opciones de la cache global ............................................................ 249
       12.1.2. Guardando una acción en la cache ................................................... 250
       12.1.3. Guardando un elemento parcial, un componente o un slot de componentes
       en la cache .............................................................................................. 252
       12.1.4. Guardando un fragmento de plantilla en la cache ............................... 253
       12.1.5. Configuración dinámica de la cache.................................................. 255
       12.1.6. Uso de la cache super rápida........................................................... 257
    12.2. Eliminando elementos de la cache ......................................................... 258
       12.2.1. Borrando toda la cache .................................................................. 258
       12.2.2. Borrando partes de la cache............................................................ 258
       12.2.3. Estructura del directorio de la cache................................................. 261
       12.2.4. Borrado manual de la cache ............................................................ 261
    12.3. Probando y monitorizando la cache ........................................................ 262
       12.3.1. Creando un entorno de ejecución intermedio ..................................... 262
       12.3.2. Monitorizando el rendimiento .......................................................... 263
       12.3.3. Pruebas de rendimiento (benchmarking)........................................... 264
       12.3.4. Identificando elementos de la cache................................................. 264
    12.4. HTTP 1.1 y la cache del lado del cliente .................................................. 264
       12.4.1. Uso de la cabecera ETag para evitar el envío de contenidos no
       modificados ............................................................................................. 265
       12.4.2. Añadiendo la cabecera Last-Modified para evitar el envío de contenidos
       todavía válidos......................................................................................... 265
       12.4.3. Añadiendo cabeceras Vary para permitir varias versiones de la página en la
       cache...................................................................................................... 266
       12.4.4. Añadiendo la cabecera Cache-Control para permitir la cache en el lado del
       cliente .................................................................................................... 266
  Capítulo 13. Internacionalización y localización .......................................... 268
    13.1. Cultura del usuario .............................................................................. 268
       13.1.1. Indicando la cultura por defecto ...................................................... 268
       13.1.2. Modificando la cultura de un usuario ................................................ 269
       13.1.3. Determinando la cultura de forma automática ................................... 270


www.librosweb.es                                                                                                     8
Symfony, la guía definitiva


    13.2. Estándares y formatos ......................................................................... 270
       13.2.1. Mostrando datos según la cultura del usuario .................................... 270
       13.2.2. Obteniendo información en una aplicación localizada .......................... 272
    13.3. Información textual en la base de datos ................................................. 272
       13.3.1. Creando un esquema para una aplicación localizada ........................... 272
       13.3.2. Usando los objetos i18n generados .................................................. 273
    13.4. Traducción de la interfaz ...................................................................... 274
       13.4.1. Configurando la traducción ............................................................. 275
       13.4.2. Usando el helper de traducción........................................................ 275
       13.4.3. Utilizando un archivo de diccionario ................................................. 275
       13.4.4. Trabajando con diccionarios ............................................................ 277
       13.4.5. Trabajando con otros elementos que requieren traducción .................. 277
       13.4.6. Cómo realizar traducciones complejas .............................................. 278
       13.4.7. Utilizando el helper de traducción fuera de una plantilla ...................... 279
  Capítulo 14. Generadores ............................................................................ 281
    14.1. Generación de código en función del modelo ........................................... 281
       14.1.1. Scaffolding y administración ........................................................... 281
       14.1.2. Generando e iniciando el código ...................................................... 282
       14.1.3. Modelo de datos de ejemplo............................................................ 283
    14.2. Scaffolding ......................................................................................... 283
       14.2.1. Generando el scaffolding ................................................................ 283
       14.2.2. Iniciando el scaffolding ................................................................... 286
    14.3. Creando la parte de administración de las aplicaciones ............................. 286
       14.3.1. Iniciando un módulo de administración ............................................. 287
       14.3.2. Un vistazo al código generado ......................................................... 288
       14.3.3. Conceptos básicos del archivo de configuración generator.yml ............. 289
    14.4. Configuración del generador ................................................................. 290
       14.4.1. Campos ....................................................................................... 291
       14.4.2. Opciones de los campos ................................................................. 291
       14.4.3. Modificando la vista ....................................................................... 297
       14.4.4. Opciones específicas para la vista "list"............................................. 299
       14.4.5. Opciones específicas para la vista "edit" ........................................... 305
       14.4.6. Trabajando con claves externas....................................................... 308
       14.4.7. Añadiendo nuevas interacciones ...................................................... 310
       14.4.8. Validación de formularios................................................................ 209
       14.4.9. Restringiendo las acciones del usuario mediante credenciales .............. 313
    14.5. Modificando el aspecto de los módulos generados .................................... 313
       14.5.1. Utilizando una hoja de estilos propia ................................................ 314
       14.5.2. Creando una cabecera y un pie propios ............................................ 314
       14.5.3. Modificando el tema....................................................................... 315
  Capítulo 15. Pruebas unitarias y funcionales ............................................... 318
    15.1. Automatización de pruebas................................................................... 318


www.librosweb.es                                                                                                 9
Symfony, la guía definitiva


       15.1.1. Pruebas unitarias y funcionales ....................................................... 318
       15.1.2. Desarrollo basado en pruebas ......................................................... 319
       15.1.3. El framework de pruebas Lime ........................................................ 320
    15.2. Pruebas unitarias ................................................................................ 321
       15.2.1. ¿Qué aspecto tienen las pruebas unitarias? ....................................... 321
       15.2.2. Métodos para las pruebas unitarias .................................................. 322
       15.2.3. Parámetros para las pruebas........................................................... 324
       15.2.4. La tarea test-unit .......................................................................... 325
       15.2.5. Stubs, Fixtures y carga automática de clases .................................... 326
    15.3. Pruebas funcionales ............................................................................. 329
       15.3.1. ¿Cómo son las pruebas funcionales? ................................................ 329
       15.3.2. Navegando con el objeto sfTestBrowser ............................................ 330
       15.3.3. Utilizando asertos .......................................................................... 333
       15.3.4. Utilizando los selectores CSS........................................................... 335
       15.3.5. Trabajando en el entorno de pruebas ............................................... 337
       15.3.6. La tarea test-functional .................................................................. 338
    15.4. Recomendaciones sobre el nombre de las pruebas ................................... 338
    15.5. Otras utilidades para pruebas ............................................................... 339
       15.5.1. Ejecutando las pruebas en grupos.................................................... 340
       15.5.2. Acceso a la base de datos ............................................................... 340
       15.5.3. Probando la cache ......................................................................... 341
       15.5.4. Probando las interacciones en el lado del cliente ................................ 342
  Capítulo 16. Herramientas para la administración de aplicaciones .............. 345
    16.1. Logs .................................................................................................. 345
       16.1.1. Logs de PHP ................................................................................. 345
       16.1.2. Logs de Symfony........................................................................... 346
    16.2. Depuración de aplicaciones................................................................... 349
       16.2.1. Modo debug de Symfony ................................................................ 350
       16.2.2. Excepciones Symfony..................................................................... 351
       16.2.3. Extensión Xdebug.......................................................................... 352
       16.2.4. Barra de depuración web ................................................................ 353
       16.2.5. Depuración manual........................................................................ 358
    16.3. Cargando datos en una base de datos .................................................... 359
       16.3.1. Sintaxis del archivo de datos........................................................... 360
       16.3.2. Importando los datos ..................................................................... 361
       16.3.3. Usando tablas relacionadas ............................................................. 361
    16.4. Instalando aplicaciones ........................................................................ 362
       16.4.1. Preparando un proyecto para transferirlo con FTP .............................. 362
       16.4.2. Usando rsync para transferir archivos incrementalmente..................... 363
       16.4.3. Ignorando los archivos innecesarios ................................................. 365
       16.4.4. Administrando una aplicación en producción...................................... 366
  Capítulo 17. Personalizar Symfony .............................................................. 369


www.librosweb.es                                                                                                 10
Symfony, la guía definitiva


    17.1. Mixins................................................................................................ 369
       17.1.1. Comprendiendo la herencia múltiple................................................. 369
       17.1.2. Clases de tipo mixing ..................................................................... 370
       17.1.3. Declarar que una clase se puede extender ........................................ 371
       17.1.4. Registrando las extensiones ............................................................ 373
       17.1.5. Extendiendo de forma más precisa................................................... 375
    17.2. Factorías ............................................................................................ 377
    17.3. Utilizando componentes de otros frameworks .......................................... 378
    17.4. Plugins .............................................................................................. 380
       17.4.1. Plugins disponibles para Symfony .................................................... 380
       17.4.2. Instalando un plugin ...................................................................... 382
       17.4.3. Estructura de un plugin .................................................................. 385
       17.4.4. Cómo crear un plugin..................................................................... 389
  Capítulo 18. Rendimiento ............................................................................ 396
    18.1. Optimizando el servidor ....................................................................... 396
    18.2. Optimizando el modelo ........................................................................ 397
       18.2.1. Optimizando la integración de Propel................................................ 397
       18.2.2. Limitando el número de objetos que se procesan ............................... 398
       18.2.3. Minimizando el número de consultas mediante Joins .......................... 399
       18.2.4. Evitar el uso de arrays temporales ................................................... 401
       18.2.5. Saltándose el ORM......................................................................... 403
       18.2.6. Optimizando la base de datos.......................................................... 404
    18.3. Optimizando la vista ............................................................................ 405
       18.3.1. Utilizando el fragmento de código más rápido.................................... 405
       18.3.2. Optimizando el sistema de enrutamiento .......................................... 406
       18.3.3. Saltándose la plantilla .................................................................... 406
       18.3.4. Reduciendo los helpers por defecto .................................................. 407
       18.3.5. Comprimiendo la respuesta............................................................. 407
    18.4. Optimizando la cache........................................................................... 408
       18.4.1. Borrando partes de la cache de forma selectiva ................................. 408
       18.4.2. Generando páginas para la cache .................................................... 409
       18.4.3. Guardando los datos de la cache en una base de datos ....................... 410
       18.4.4. Saltándose Symfony ...................................................................... 411
       18.4.5. Guardando en la cache el resultado de una función ............................ 411
       18.4.6. Guardando datos en la cache del servidor ......................................... 412
    18.5. Desactivando las características que no se utilizan ................................... 413
    18.6. Optimizando el código fuente ................................................................ 414
       18.6.1. Compilación del núcleo de Symfony ................................................. 414
       18.6.2. El plugin sfOptimizer ...................................................................... 415
  Capítulo 19. Configuración avanzada........................................................... 417
    19.1. Opciones de Symfony .......................................................................... 417
       19.1.1. Acciones y módulos por defecto....................................................... 417


www.librosweb.es                                                                                                11
Symfony, la guía definitiva


       19.1.2. Activando características opcionales ................................................. 419
       19.1.3. Configuración de cada característica................................................. 421
    19.2. Extendiendo la carga automática de clases ............................................. 424
    19.3. Estructura de archivos propia................................................................ 426
       19.3.1. La estructura de archivos básica ...................................................... 427
       19.3.2. Modificando la estructura de archivos ............................................... 428
       19.3.3. Modificando el directorio raíz del proyecto......................................... 429
       19.3.4. Enlazando las librerías de Symfony .................................................. 429
    19.4. Comprendiendo el funcionamiento de los manejadores de configuración ..... 430
       19.4.1. Manejadores de configuración por defecto......................................... 431
       19.4.2. Creando un manejador propio ......................................................... 432
    19.5. Controlando las opciones de PHP ........................................................... 434




www.librosweb.es                                                                                       12
Symfony, la guía definitiva                               Capítulo 1. Introducción a Symfony




Capítulo 1. Introducción a Symfony
¿Qué puedes hacer con Symfony? ¿Qué necesitas para utilizarlo? Este capítulo responde
a todas estas preguntas.


1.1. Symfony en pocas palabras
Un framework simplifica el desarrollo de una aplicación mediante la automatización de al-
gunos de los patrones utilizados para resolver las tareas comunes. Además, un frame-
work proporciona estructura al código fuente, forzando al desarrollador a crear código
más legible y más fácil de mantener. Por último, un framework facilita la programación
de aplicaciones, ya que encapsula operaciones complejas en instrucciones sencillas.

Symfony es un completo framework diseñado para optimizar, gracias a sus característi-
cas, el desarrollo de las aplicaciones web. Para empezar, separa la lógica de negocio, la
lógica de servidor y la presentación de la aplicación web. Proporciona varias herramientas
y clases encaminadas a reducir el tiempo de desarrollo de una aplicación web compleja.
Además, automatiza las tareas más comunes, permitiendo al desarrollador dedicarse por
completo a los aspectos específicos de cada aplicación. El resultado de todas estas venta-
jas es que no se debe reinventar la rueda cada vez que se crea una nueva aplicación
web.

Symfony está desarrollado completamente con PHP 5. Ha sido probado en numerosos pr-
oyectos reales y se utiliza en sitios web de comercio electrónico de primer nivel. Symfony
es compatible con la mayoría de gestores de bases de datos, como MySQL, PostgreSQL,
Oracle y SQL Server de Microsoft. Se puede ejecutar tanto en plataformas *nix (Unix, Li-
nux, etc.) como en plataformas Windows. A continuación se muestran algunas de sus
características.

1.1.1. Características de Symfony
Symfony se diseñó para que se ajustara a los siguientes requisitos:

      ▪ Fácil de instalar y configurar en la mayoría de plataformas (y con la garantía de
        que funciona correctamente en los sistemas Windows y *nix estándares)

      ▪ Independiente del sistema gestor de bases de datos

      ▪ Sencillo de usar en la mayoría de casos, pero lo suficientemente flexible como
        para adaptarse a los casos más complejos

      ▪ Basado en la premisa de “convenir en vez de configurar”, en la que el desarrolla-
        dor solo debe configurar aquello que no es convencional

      ▪ Sigue la mayoría de mejores prácticas y patrones de diseño para la web

      ▪ Preparado para aplicaciones empresariales y adaptable a las políticas y arquitec-
        turas propias de cada empresa, además de ser lo suficientemente estable como
        para desarrollar aplicaciones a largo plazo




www.librosweb.es                                                                         13
Symfony, la guía definitiva                                 Capítulo 1. Introducción a Symfony


      ▪ Código fácil de leer que incluye comentarios de phpDocumentor y que permite un
        mantenimiento muy sencillo

      ▪ Fácil de extender, lo que permite su integración con librerías desarrolladas por
        terceros

1.1.1.1. Automatización de características de proyectos web
Symfony automatiza la mayoría de elementos comunes de los proyectos web, como por
ejemplo:

      ▪ La capa de internacionalización que incluye Symfony permite la traducción de los
        datos y de la interfaz, así como la adaptación local de los contenidos.

      ▪ La capa de presentación utiliza plantillas y layouts que pueden ser creados por
        diseñadores HTML sin ningún tipo de conocimiento del framework. Los helpers in-
        cluidos permiten minimizar el código utilizado en la presentación, ya que encap-
        sulan grandes bloques de código en llamadas simples a funciones.

      ▪ Los formularios incluyen validación automatizada y relleno automático de datos
        (“repopulation”), lo que asegura la obtención de datos correctos y mejora la ex-
        periencia de usuario.

      ▪ Los datos incluyen mecanismos de escape que permiten una mejor protección
        contra los ataques producidos por datos corruptos.

      ▪ La gestión de la caché reduce el ancho de banda utilizado y la carga del servidor.

      ▪ La autenticación y la gestión de credenciales simplifican la creación de secciones
        restringidas y la gestión de la seguridad de usuario.

      ▪ El sistema de enrutamiento y las URL limpias permiten considerar a las direccio-
        nes de las páginas como parte de la interfaz, además de estar optimizadas para
        los buscadores.

      ▪ El soporte de e-mail incluido y la gestión de APIs permiten a las aplicaciones web
        interactuar más allá de los navegadores.

      ▪ Los listados son más fáciles de utilizar debido a la paginación automatizada, el fil-
        trado y la ordenación de datos.

      ▪ Los plugins, las factorías (patrón de diseño “Factory”) y los “mixin” permiten rea-
        lizar extensiones a medida de Symfony.

      ▪ Las interacciones con Ajax son muy fáciles de implementar mediante los helpers
        que permiten encapsular los efectos JavaScript compatibles con todos los nave-
        gadores en una única línea de código.

1.1.1.2. Entorno de desarrollo y herramientas
Symfony puede ser completamente personalizado para cumplir con los requisitos de las
empresas que disponen de sus propias políticas y reglas para la gestión de proyectos y la
programación de aplicaciones. Por defecto incorpora varios entornos de desarrollo dife-
rentes e incluye varias herramientas que permiten automatizar las tareas más comunes
de la ingeniería del software:

www.librosweb.es                                                                           14
Symfony, la guía definitiva                               Capítulo 1. Introducción a Symfony


      ▪ Las herramientas que generan automáticamente código han sido diseñadas para
        hacer prototipos de aplicaciones y para crear fácilmente la parte de gestión de las
        aplicaciones.

      ▪ El framework de desarrollo de pruebas unitarias y funcionales proporciona las he-
        rramientas ideales para el desarrollo basado en pruebas (“test-driven develop-
        ment”).

      ▪ La barra de depuración web simplifica la depuración de las aplicaciones, ya que
        muestra toda la información que los programadores necesitan sobre la página en
        la que están trabajando.

      ▪ La interfaz de línea de comandos automatiza la instalación de las aplicaciones en-
        tre servidores.

      ▪ Es posible realizar cambios “en caliente” de la configuración (sin necesidad de
        reiniciar el servidor).

      ▪ El completo sistema de log permite a los administradores acceder hasta el último
        detalle de las actividades que realiza la aplicación.

1.1.2. ¿Quién ha desarrollado Symfony y por qué motivo?
La primera versión de Symfony fue publicada en Octubre de 2005 por Fabien Potencier,
fundador del proyecto y coautor de este libro. Fabien es el presidente de Sensio
(http://www.sensio.com/), una empresa francesa de desarrollo de aplicaciones web conoci-
da por sus innovaciones en este campo.

En el año 2003, Fabien realizó una investigación sobre las herramientas de software libre
disponibles para el desarrollo de aplicaciones web con PHP. Fabien llegó a la conclusión
de que no existía ninguna herramienta con esas características. Después del lanzamiento
de la versión 5 de PHP, decidió que las herramientas disponibles habían alcanzado un
grado de madurez suficiente como para integrarlas en un framework completo. Fabien
empleó un año entero para desarrollar el núcleo de Symfony, basando su trabajo en el
framework Mojavi (que también era un framework que seguía el funcionamiento MVC),
en la herramienta Propel para el mapeo de objetos a bases de datos (conocido como
ORM, de “object-relational mapping”) y en los helpers empleados por Ruby on Rails en
sus plantillas.

Fabien desarrolló originalmente Symfony para utilizarlo en los proyectos de Sensio, ya
que disponer de un framework efectivo es la mejor ayuda para el desarrollo eficiente y
rápido de las aplicaciones. Además, el desarrollo web se hace más intuitivo y las aplicac-
iones resultantes son más robustas y más fáciles de mantener. El framework se utilizó
por primera vez en el desarrollo de un sitio de comercio electrónico para un vendedor de
lencería y posteriormente se utilizó en otros proyectos.

Después de utilizar Symfony en algunos proyectos, Fabien decidió publicarlo bajo una li-
cencia de software libre. Sus razones para liberar el proyecto fueron para donar su traba-
jo a la comunidad, aprovechar la respuesta de los usuarios, mostrar la experiencia de
Sensio y porque considera que es divertido hacerlo.




www.librosweb.es                                                                         15
Symfony, la guía definitiva                                       Capítulo 1. Introducción a Symfony


  NOTA
  ¿Por qué lo llamaron “Symfony” y no “CualquierNombreFramework”? Porque Fabien quería una
  nombre corto que tuviera una letra ‘s’ (de Sensio) y una letra ‘f’ (de framework), que fuera fácil de
  recordar y que no estuviera asociado a otra herramienta de desarrollo. Además, no le gustan las
  mayúsculas. “Symfony” era muy parecido a lo que estaba buscando, aunque no es una palabra co-
  rrecta en el idioma inglés (la palabra correcta es “symphony”), y además estaba libre como nombre
  de proyecto. La otra alternativa era “baguette”.

Para que Symfony fuera un proyecto de software libre exitoso, debía tener una documen-
tación amplia y en inglés, para aumentar la incorporación de usuarios al proyecto. Fabien
pidió a su compañero de trabajo François Zaninotto, el otro coautor de este libro, que in-
vestigara el código fuente del programa y escribiera un libro sobre Symfony. Aunque el
proceso fue arduo, cuando el proyecto se lanzó públicamente, la documentación era sufi-
ciente como para atraer a muchos desarrolladores. El resto es historia.

1.1.3. La comunidad Symfony
En cuanto se abrió al público el sitio web de Symfony (http://www.symfony-project.org/)
muchos desarrolladores de todo el mundo se descargaron e instalaron el framework, co-
menzaron a leer la documentación y construyeron sus primeras aplicaciones con Sym-
fony, aumentando poco a poco la popularidad de Symfony.

En ese momento, los frameworks para el desarrollo de aplicaciones web estaban en pleno
apogeo, y era muy necesario disponer de un completo framework realizado con PHP.
Symfony proporcionaba una solución irresistible a esa carencia, debido a la calidad de su
código fuente y a la gran cantidad de documentación disponible, dos ventajas muy im-
portantes sobre otros frameworks disponibles. Los colaboradores aparecieron en seguida
proponiendo parches y mejoras, detectando los errores de la documentación y realizando
otras tareas muy importantes.

El repositorio público de código fuente y el sistema de notificación de errores y mejoras
mediante tickets permite varias formas de contribuir al proyecto y todos los voluntarios
son bienvenidos. Fabien continua siendo el mayor contribuidor de código al repositorio y
se encarga de garantizar la calidad del código.

Actualmente, el foro de Symfony, las listas de correo y el IRC ofrecen otras alternativas
válidas para el soporte del framework, con el que cada pregunta suele obtener una media
de 4 respuestas. Cada día nuevos usuarios instalan Symfony y el wiki y la sección de
fragmentos de código almacenan una gran cantidad de documentación generada por los
usuarios. Cada semana el número de aplicaciones conocidas desarrolladas con Symfony
se incrementa en 5 y el aumento continua.

La comunidad Symfony es el tercer pilar del framework y esperamos que tu también te
unas a ella después de leer este libro.

1.1.4. ¿Es adecuado Symfony para mí?
Independientemente de que seas un experto programador de PHP 5 o un principiante en
el desarrollo de aplicaciones web, podrás utilizar Symfony de forma sencilla. El principal
argumento para decidir si deberías o no utilizar Symfony es el tamaño del proyecto.

www.librosweb.es                                                                                    16
Symfony, la guía definitiva                                    Capítulo 1. Introducción a Symfony


Si tu proyecto consiste en desarrollar un sitio web sencillo con 5 o 10 páginas diferentes,
acceso simple a bases de datos y no es importante asegurar un gran rendimiento o una
documentación adecuada, deberías realizar tu proyecto solo con PHP. En ese caso, no
vas a obtener grandes ventajas por utilizar un framework de desarrollo de aplicaciones
web, además de que utilizar objetos y el modelo MVC (Modelo Vista Controlador) sola-
mente va a ralentizar el desarrollo de tu proyecto. Además, Symfony no está optimizado
para ejecutarse de forma eficiente en un servidor compartido en el que los scripts de PHP
se ejecutan solamente mediante CGI (Common Gateway Interface).

Por otra parte, si desarrollas aplicaciones web complejas con mucha lógica de negocio, no
es recomendable utilizar solo PHP. Para asegurar el mantenimiento y las ampliaciones fu-
turas de la aplicación, es necesario que el código sea ligero, legible y efectivo. Si quieres
incorporar los últimos avances en interacción con usuarios (como por ejemplo Ajax),
puedes acabar escribiendo cientos de líneas de JavaScript. Si quieres desarrollar aplicac-
iones de forma divertida y muy rápida, no es aconsejable utilizar solo PHP. En todos es-
tos casos, deberías utilizar Symfony.

Si eres un desarrollador web profesional, ya conoces todas las ventajas de utilizar un fra-
mework de desarrollo de aplicaciones web y solo necesitas un framework que sea madu-
ro, bien documentado y con una gran comunidad que lo apoye. En este caso, deberías
dejar de buscar porque Symfony es lo que necesitas.

  SUGERENCIA
  Si quieres ver una demostración visual de las posibilidades de Symfony, deberías ver los vídeos o
  screencasts que están disponibles en el sitio web de Symfony. En estas demostraciones se ve lo
  rápido y divertido que es desarrollar aplicaciones web con Symfony.


1.2. Conceptos básicos
Antes de empezar con Symfony, deberías conocer algunos conceptos básicos. Puedes sal-
tarte esta sección si conoces el significado de OOP, ORM, RAD, DRY, KISS, TDD, YAML y
PEAR.

1.2.1. PHP 5
Symfony está programado en PHP 5 (http://www.php.net/) y está enfocado al desarrollo de
aplicaciones web en el mismo lenguaje de programación. Por este motivo, es obligatorio
disponer de unos conocimientos avanzados de PHP 5 para sacar el máximo partido al
framework.

Los programadores que conocen PHP 4 pero que no han trabajado con PHP 5 deberían
centrarse en el nuevo modelo orientado a objetos de PHP.

1.2.2. Programación Orientada a Objetos (OOP)
La programación orientada a objetos (OOP, por sus siglas en inglés Object-oriented pro-
gramming) no va a ser explicada en este capítulo, ya que se necesitaría un libro entero
para ello. Como Symfony hace un uso continuo de los mecanismos orientados a objetos



www.librosweb.es                                                                                17
Symfony, la guía definitiva                                 Capítulo 1. Introducción a Symfony


disponibles en PHP 5, es un requisito obligatorio el conocer la OOP antes de aprender
Symfony.

En la Wikipedia se explica la OOP de la siguiente manera:

     “ La idea de la programación orientada a objetos es que una aplicación se puede
     considerar como una colección de unidades individuales, llamadas objetos, que
     interactúan entre sí. Los programas tradicionales pueden considerarse como una
     colección de funciones o como una lista de instrucciones de programación.”

PHP 5 incluye los conceptos de clase, objeto, método, herencia y muchos otros propios
de la programación orientada a objetos. Aquellos que no estén familiarizados con estos
conceptos, deberían consultar la documentación oficial de PHP disponible en
http://www.php.net/manual/es/language.oop5.basic.php .


1.2.3. Métodos mágicos
Uno de los puntos fuertes de los objetos de PHP es la utilización de los “métodos mági-
cos”. Este tipo de métodos permiten redefinir el comportamiento de las clases sin modifi-
car el código externo. Con estos métodos es posible que la sintaxis de PHP sea más con-
cisa y más fácil de extender. Además, estos métodos son fáciles de reconocer ya que sus
nombres siempre empiezan con 2 guiones bajos seguidos (__).

Por ejemplo, al mostrar un objeto, PHP busca de forma implícita un método llamado __-
toString() en ese objeto y que permite comprobar si se ha creado una visualización per-
sonalizada para ese objeto:
   $miObjeto = new miClase();
   echo $miObjeto;

   // Se busca el método mágico
   echo $miObjeto->__toString();

Symfony utiliza los métodos mágicos de PHP, por lo que deberías conocer su funcionam-
iento. La documentación oficial de PHP también explica los métodos mágicos
(http://www.php.net/manual/es/language.oop5.magic.php )


1.2.4. PEAR (PHP Extension and Application Repository)
PEAR es un “framework y sistema de distribución para componentes PHP reutilizables”.
PEAR permite descargar, instalar, actualizar y desinstalar scripts de PHP. Si se utiliza un
paquete de PEAR, no es necesario decidir donde guardar los scripts, cómo hacer que se
puedan utilizar o cómo extender la línea de comandos (CLI).

PEAR es un proyecto creado por la comunidad de usuarios de PHP, está desarrollado con
PHP y se incluye en las distribuciones estándar de PHP.

  SUGERENCIA
  El sitio web de PEAR, http://pear.php.net/, incluye documentación y muchos paquetes agrupa-
  dos en categorías.



www.librosweb.es                                                                           18
Symfony, la guía definitiva                                Capítulo 1. Introducción a Symfony


PEAR es el método más profesional para instalar librerías externas en PHP. Symfony
aconseja el uso de PEAR para disponer de una instalación única y centralizada que pueda
ser utilizada en varios proyectos. Los plugins de Symfony son paquetes de PEAR con una
configuración especial. El propio framework Symfony también está disponible como paq-
uete de PEAR.

Afortunadamente, no es necesario conocer la sintaxis de PEAR para utilizar Symfony. Lo
único necesario es entender su funcionamiento y tenerlo instalado. Para comprobar si
PEAR está instalado en el sistema, se puede escribir lo siguiente en una línea de
comandos:
   > pear info pear

El comando anterior muestra la versión de PEAR instalada en el sistema.

El proyecto Symfony dispone de su propio repositorio PEAR, también llamado canal. Los
canales de PEAR solamente se pueden utilizar a partir de la versión 1.4.0, por lo que es
necesario actualizar PEAR si se dispone de una versión anterior. Para actualizar PEAR, se
debe ejecutar el siguiente comando:
   > pear upgrade PEAR


1.2.5. Mapeo de Objetos a Bases de datos (ORM)
Las bases de datos siguen una estructura relacional. PHP 5 y Symfony por el contrario
son orientados a objetos. Por este motivo, para acceder a la base de datos como si fuera
orientada a objetos, es necesario una interfaz que traduzca la lógica de los objetos a la
lógica relacional. Esta interfaz se denomina “mapeo de objetos a bases de datos” (ORM,
de sus siglas en inglés “object-relational mapping”).

Un ORM consiste en una serie de objetos que permiten acceder a los datos y que contie-
nen en su interior cierta lógica de negocio.

Una de las ventajas de utilizar estas capas de abstracción de objetos/relacional es que
evita utilizar una sintaxis específica de un sistema de bases de datos concreto. Esta capa
transforma automáticamente las llamadas a los objetos en consultas SQL optimizadas
para el sistema gestor de bases de datos que se está utilizando en cada momento.

De esta forma, es muy sencillo cambiar a otro sistema de bases de datos completamente
diferente en mitad del desarrollo de un proyecto. Estas técnicas son útiles por ejemplo
cuando se debe desarrollar un prototipo rápido de una aplicación y el cliente aun no ha
decidido el sistema de bases de datos que más le conviene. El prototipo se puede realizar
utilizando SQLite y después se puede cambiar fácilmente a MySQL, PostgreSQL u Oracle
cuando el cliente se haya decidido. El cambio se puede realizar modificando solamente
una línea en un archivo de configuración.

La capa de abstracción utilizada encapsula toda la lógica de los datos. El resto de la apli-
cación no tiene que preocuparse por las consultas SQL y el código SQL que se encarga
del acceso a la base de datos es fácil de encontrar. Los desarrolladores especializados en
la programación con bases de datos pueden localizar fácilmente el código.

Utilizar objetos en vez de registros y clases en vez de tablas tiene otra ventaja: se pue-
den definir nuevos métodos de acceso a las tablas. Por ejemplo, si se dispone de una

www.librosweb.es                                                                          19
Symfony, la guía definitiva                                 Capítulo 1. Introducción a Symfony


tabla llamada Cliente con 2 campos, Nombre y Apellido, puede que sea necesario acce-
der directamente al nombre completo (NombreCompleto). Con la programación orientada a
objetos, este problema se resuelve añadiendo un nuevo método de acceso a la clase Cl-
iente de la siguiente forma:
   public function getNombreCompleto()
   {
     return $this->getNombre().' '.$this->getApellido();
   }

Todas las funciones comunes de acceso a los datos y toda la lógica de negocio relaciona-
da con los datos se puede mantener dentro de ese tipo de objetos. Por ejemplo, la sigu-
iente clase CarritoCompra almacena los productos (que son objetos). Para obtener el pre-
cio total de los productos del carrito y así realizar el pago, se puede añadir un método
llamado getTotal() de la siguiente forma:
   public function getTotal()
   {
     $total = 0;
     foreach ($this->getProductos() as $producto)
     {
       $total += $producto->getPrecio() * $item->getCantidad();
     }
     return $total;
   }

Y eso es todo. Imagina cuanto te hubiera costado escribir una consulta SQL que hiciera lo
mismo.

Propel, que también es un proyecto de software libre, es una de las mejores capas de
abstracción de objetos/relacional disponibles en PHP 5. Propel está completamente inte-
grado en Symfony, por lo que la mayoría de las manipulaciones de datos realizadas en
este libro siguen la sintaxis de Propel. En el libro se describe la utilización de los objetos
de Propel, pero se puede encontrar una referencia más completa en el sitio web de Pro-
pel (http://propel.phpdb.org/trac/).


1.2.6. Desarrollo rápido de aplicaciones (RAD)
Durante mucho tiempo, la programación de aplicaciones web fue un tarea tediosa y muy
lenta. Siguiendo los ciclos habituales de la ingeniería del software (como los propuestos
por el Proceso Racional Unificado o Rational Unified Process) el desarrollo de una aplica-
ción web no puede comenzar hasta que se han establecido por escrito una serie de requi-
sitos, se han creado los diagramas UML (Unified Modeling Language) y se ha producido
abundante documentación sobre el proyecto. Este modelo se veía favorecido por la baja
velocidad de desarrollo, la falta de versatilidad de los lenguajes de programación (antes
de ejecutar el programa se debe construir, compilar y reiniciar) y sobre todo por el hecho
de que los clientes no estaban dispuestos a adaptarse a otras metodologías.

Hoy en día, las empresas reaccionan más rápidamente y los clientes cambian de opinión
constantemente durante el desarrollo de los proyectos. De este modo, los equipos de de-
sarrollo deben adaptarse a esas necesidades y tienen que poder cambiar la estructura de
una aplicación de forma rápida. Afortunadamente, el uso de lenguajes de script como

www.librosweb.es                                                                           20
Symfony, la guía definitiva                                     Capítulo 1. Introducción a Symfony


Perl y PHP permiten seguir otras estrategias de programación, como RAD (desarrollo rá-
pido de aplicaciones) y el desarrollo ágil de software.

Una de las ideas centrales de esta metodología es que el desarrollo empieza lo antes po-
sible para que el cliente pueda revisar un prototipo que funciona y pueda indicar el cami-
no a seguir. A partir de ahí, la aplicación se desarrolla de forma iterativa, en la que cada
nueva versión incorpora nuevas funcionalidades y se desarrolla en un breve espacio de
tiempo.

Las consecuencias de estas metodologías para el desarrollador son numerosas. El progra-
mador no debe pensar acerca de las versiones futuras al incluir una nueva funcionalidad.
Los métodos utilizados deben ser lo más sencillos y directos posibles. Estas ideas se re-
sumen en el principio denominado KISS: ¡Haz las cosas sencillas, idiota! (Keep It Simple,
Stupid)

Cuando se modifican los requisitos o cuando se añade una nueva funcionalidad, normal-
mente se debe reescribir parte del código existente. Este proceso se llama refactorización
y sucede a menudo durante el desarrollo de una aplicación web. El código suele moverse
a otros lugares en función de su naturaleza. Los bloques de código repetidos se refactori-
zan en un único lugar, aplicando el principio DRY: No te repitas (Don’t Repeat Yourself).

Para asegurar que la aplicación sigue funcionando correctamente a pesar de los cambios
constantes, se necesita una serie de pruebas unitarias que puedan ser automatizadas. Si
están bien escritas, las pruebas unitarias permiten asegurar que nada ha dejado de func-
ionar después de haber refactorizado parte del código de la aplicación. Algunas metodo-
logías de desarrollo de aplicaciones obligan a escribir las pruebas antes que el propio có-
digo, lo que se conoce como TDD: desarrollo basado en pruebas (test-driven develop-
ment).

  NOTA
  Existen otros principios y hábitos relacionados con el desarrollo ágil de aplicaciones. Una de las
  metodologías más efectivas se conoce como XP: programación extrema (Extreme Programming).
  La documentación relacionada con XP puede enseñarte mucho sobre el desarrollo rápido y efectivo
  de las aplicaciones. Una buena forma de empezar con XP son los libros escritos por Kent Beck en
  la editorial Addison-Wesley.

Symfony es la herramienta ideal para el RAD. De hecho, el framework ha sido desarrolla-
do por una empresa que aplica el RAD a sus propios proyectos. Por este motivo, apren-
der a utilizar Symfony no es como aprender un nuevo lenguaje de programación, sino
que consite en aprender a tomar las decisiones correctas para desarrollar las aplicaciones
de forma más efectiva.

El sitio web del proyecto Symfony incluye un tutorial en el que se explica paso a paso el
desarrollo de una aplicación utilizando las técnicas de desarrollo ágil de aplicaciones. La
aplicación se llama Askeet (http://www.symfony-project.org/askeet) y su lectura es muy re-
comendada para todos aquellos que quieran adentrarse en el desarrollo ágil de
aplicaciones.




www.librosweb.es                                                                                 21
Symfony, la guía definitiva                                Capítulo 1. Introducción a Symfony


1.2.7. YAML
Según el sitio web oficial de YAML (http://www.yaml.org/), YAML es “un formato para seria-
lizar datos que es fácil de procesar por las máquinas, fácil de leer para las personas y fá-
cil de interactuar con los lenguajes de script”. Dicho de otra forma, YAML es un lenguaje
muy sencillo que permite describir los datos como en XML, pero con una sintaxis mucho
más sencilla. YAML es un formato especialmente útil para describir datos que pueden ser
transformados en arrays simples y asociativos, como por ejemplo:
   $casa = array(
      'familia' => array(
         'apellido' => 'García',
         'padres' => array('Antonio', 'María'),
         'hijos'   => array('Jose', 'Manuel', 'Carmen')
      ),
      'direccion' => array(
         'numero'        => 34,
         'calle'         => 'Gran Vía',
         'ciudad'        => 'Cualquiera',
         'codigopostal' => '12345'
      )
   );

Este array de PHP se puede crear directamente procesando esta cadena de texto en for-
mato YAML:
   casa:
     familia:
       apellido: García
       padres:
         - Antonio
         - María
       hijos:
         - Jose
         - Manuel
         - Carmen
     direccion:
       numero: 34
       calle: Gran Vía
       ciudad: Cualquiera
       codigopostal: "12345"

YAML utiliza la tabulación para indicar su estructura, los elementos que forman una sec-
uencia utilizan un guión medio y los pares clave/valor de los array asociativos se separan
con dos puntos. YAML también dispone de una notación resumida para describir la misma
estructura con menos líneas: los arrays simples se definen con [] y los arrays asociativos
se definen con {}. Por tanto, los datos YAML anteriores se pueden escribir de forma abre-
viada de la siguiente manera:
   casa:
     familia: { apellido: García, padres: [Antonio, María], hijos: [Jose, Manuel, Carmen] }
     direccion: { numero: 34, direccion: Gran Vía, ciudad: Cualquiera, codigopostal:
   "12345" }




www.librosweb.es                                                                          22
Symfony, la guía definitiva                                   Capítulo 1. Introducción a Symfony


YAML es el acrónimo de “YAML Ain’t Markup Language” (”YAML No es un Lenguaje de
Marcado”) y se pronuncia “yamel”. El formato se lleva utilizando desde 2001 y existen
utilidades para procesar YAML en una gran variedad de lenguajes de programación.

  SUGERENCIA
  La especificación completa del formato YAML se puede encontrar en http://www.yaml.org/.

Como se ha visto, YAML es mucho más rápido de escribir que XML (ya que no hacen falta
las etiquetas de cierre y el uso continuo de las comillas) y es mucho más poderoso que
los tradicionales archivos .ini (ya que estos últimos no soportan la herencia y las estruc-
turas complejas). Por este motivo, Symfony utiliza el formato YAML como el lenguaje
preferido para almacenar su configuración. Este libro contiene muchos archivos YAML,
pero como es tan sencillo, probablemente no necesites aprender más detalles de este
formato.


1.3. Resumen
Symfony es un framework para desarrollar aplicaciones web creado con PHP 5. Añade
una nueva capa por encima de PHP y proporciona herramientas que simplifican el desa-
rrollo de las aplicaciones web complejas. Este libro contiene todos los detalles del funcio-
namiento de Symfony y para entenderlo, solamente es necesario estar familiarizado con
los conceptos básicos de la programación moderna, sobre todo la programación orientada
a objetos (OOP), el mapeo de objetos a bases de datos (ORM) y el desarrollo rápido de
aplicaciones (RAD). El único requisito técnico obligatorio es el conocimiento de PHP 5.




www.librosweb.es                                                                             23
Symfony, la guía definitiva                      Capítulo 2. Explorando el interior de Symfony




Capítulo 2. Explorando el interior de Symfony
La primera vez que se accede al código fuente de una aplicación realizada con Symfony,
puede desanimar un poco a los nuevos desarrolladores. El código está dividido en mu-
chos directorios y muchos scripts y los archivos son un conjunto de clases PHP, código
HTML e incluso una mezcla de los dos. Además, existen referencias a clases que no se
pueden encontrar dentro del directorio del proyecto y la anidación de directorios puede
llegar hasta los seis niveles. Sin embargo, cuando comprendas las razones que están
detrás de esta aparente complejidad, lo verás como algo completamente natural y no
querrás cambiar la estructura de una aplicación Symfony por ninguna otra. En este capí-
tulo se explica con detalle toda esa estructura.


2.1. El patrón MVC
Symfony está basado en un patrón clásico del diseño web conocido como arquitectura
MVC, que está formado por tres niveles:

      ▪ El modelo representa la información con la que trabaja la aplicación, es decir, su
        lógica de negocio.

      ▪ La vista transforma el modelo en una página web que permite al usuario interact-
        uar con ella.

      ▪ El controlador se encarga de procesar las interacciones del usuario y realiza los
        cambios apropiados en el modelo o en la vista.

La Figura 2-1 ilustra el funcionamiento del patrón MVC.

La arquitectura MVC separa la lógica de negocio (el modelo) y la presentación (la vista)
por lo que se consigue un mantenimiento más sencillo de las aplicaciones. Si por ejemplo
una misma aplicación debe ejecutarse tanto en un navegador estándar como un un nave-
gador de un dispositivo móvil, solamente es necesario crear una vista nueva para cada
dispositivo; manteniendo el controlador y el modelo original. El controlador se encarga de
aislar al modelo y a la vista de los detalles del protocolo utilizado para las peticiones
(HTTP, consola de comandos, email, etc.). El modelo se encarga de la abstracción de la
lógica relacionada con los datos, haciendo que la vista y las acciones sean independientes
de, por ejemplo, el tipo de gestor de bases de datos utilizado por la aplicación.




www.librosweb.es                                                                           24
Symfony, la guía definitiva                      Capítulo 2. Explorando el interior de Symfony




                                Figura 2.1. El patrón MVC



2.1.1. Las capas de la arquitectura MVC
Para poder entender las ventajas de utilizar el patrón MVC, se va a transformar una apli-
cación simple realizada con PHP en una aplicación que sigue la arquitectura MVC. Un
buen ejemplo para ilustrar esta explicación es el de mostrar una lista con las últimas en-
tradas o artículos de un blog.

2.1.1.1. Programación simple
Utilizando solamente PHP normal y corriente, el script necesario para mostrar los artícu-
los almacenados en una base de datos se muestra en el siguiente listado:

Listado 2-1 - Un script simple
   <?php

   // Conectar con la base de datos y seleccionarla
   $conexion = mysql_connect('localhost', 'miusuario', 'micontrasena');
   mysql_select_db('blog_db', $conexion);

   // Ejecutar la consulta SQL
   $resultado = mysql_query('SELECT fecha, titulo FROM articulo', $conexion);

   ?>

   <html>
     <head>
       <title>Listado de Artículos</title>
     </head>
     <body>
      <h1>Listado de Artículos</h1>
      <table>
        <tr><th>Fecha</th><th>Titulo</th></tr>


www.librosweb.es                                                                           25
Symfony, la guía definitiva                        Capítulo 2. Explorando el interior de Symfony

   <?php
   // Mostrar los resultados con HTML
   while ($fila = mysql_fetch_array($resultado, MYSQL_ASSOC))
   {
   echo "t<tr>n";
   printf("tt<td> %s </td>n", $fila['fecha']);
   printf("tt<td> %s </td>n", $fila['titulo']);
   echo "t</tr>n";
   }
   ?>
        </table>
      </body>
   </html>

   <?php

   // Cerrar la conexion
   mysql_close($conexion);

   ?>

El script anterior es fácil de escribir y rápido de ejecutar, pero muy difícil de mantener y
actualizar. Los principales problemas del código anterior son:

        ▪ No existe protección frente a errores (¿qué ocurre si falla la conexión con la base
          de datos?).

        ▪ El código HTML y el código PHP están mezclados en el mismo archivo e incluso en
          algunas partes están entrelazados.

        ▪ El código solo funciona si la base de datos es MySQL.

2.1.1.2. Separando la presentación
Las llamadas a echo y printf del listado 2-1 dificultan la lectura del código. De hecho,
modificar el código HTML del script anterior para mejorar la presentación es un follón de-
bido a cómo está programado. Así que el código va a ser dividido en dos partes. En pri-
mer lugar, el código PHP puro con toda la lógica de negocio se incluye en el script del
controlador, como se muestra en el listado 2-2.

Listado 2-2 - La parte del controlador, en index.php
   <?php

   // Conectar con la base de datos y seleccionarla
   $conexion = mysql_connect('localhost', 'miusuario', 'micontrasena');
   mysql_select_db('blog_db', $conexion);

   // Ejecutar la consulta SQL
   $resultado = mysql_query('SELECT fecha, titulo FROM articulo', $conexion);

   // Crear el array de elementos para la capa de la vista
   $articulos = array();
   while ($fila = mysql_fetch_array($resultado, MYSQL_ASSOC))
   {
     $articulos[] = $fila;

www.librosweb.es                                                                             26
Symfony, la guía definitiva                       Capítulo 2. Explorando el interior de Symfony

   }

   // Cerrar la conexión
   mysql_close($conexion);

   // Incluir la lógica de la vista
   require('vista.php');

   ?>

El código HTML, que contiene cierto código PHP a modo de plantilla, se almacena en el
script de la vista, como se muestra en el listado 2-3.

Listado 2-3 - La parte de la vista, en vista.php
   <html>
     <head>
       <title>Listado de Artículos</title>
     </head>
     <body>
       <h1>Listado de Artículos</h1>
       <table>
          <tr><th>Fecha</th><th>Título</th></tr>
       <?php foreach ($articulos as $articulo): ?>
          <tr>
            <td><?php echo $articulo['fecha'] ?></td>
            <td><?php echo $articulo['titulo'] ?></td>
          </tr>
       <?php endforeach; ?>
       </table>
     </body>
   </html>

Una buena regla general para determinar si la parte de la vista está suficientemente lim-
pia de código es que debería contener una cantidad mínima de código PHP, la suficiente
como para que un diseñador HTML sin conocimientos de PHP pueda entenderla. Las ins-
trucciones más comunes en la parte de la vista suelen ser echo, if/else, foreach/endfo-
reach y poco más. Además, no se deben incluir instrucciones PHP que generen etiquetas
HTML.

Toda la lógica se ha centralizado en el script del controlador, que solamente contiene có-
digo PHP y ningún tipo de HTML. De hecho, y como puedes imaginar, el mismo controla-
dor se puede reutilizar para otros tipos de presentaciones completamente diferentes, co-
mo por ejemplo un archivo PDF o una estructura de tipo XML.

2.1.1.3. Separando la manipulación de los datos
La mayor parte del script del controlador se encarga de la manipulación de los datos. Pe-
ro, ¿qué ocurre si se necesita la lista de entradas del blog para otro controlador, por
ejemplo uno que se dedica a generar el canal RSS de las entradas del blog? ¿Y si se quie-
ren centralizar todas las consultas a la base de datos en un único sitio para evitar duplici-
dades? ¿Qué ocurre si cambia el modelo de datos y la tabla articulo pasa a llamarse ar-
ticulo_blog? ¿Y si se quiere cambiar a PostgreSQL en vez de MySQL? Para poder hacer


www.librosweb.es                                                                            27
Symfony, la guía definitiva                         Capítulo 2. Explorando el interior de Symfony


todo esto, es imprescindible eliminar del controlador todo el código que se encarga de la
manipulación de los datos y ponerlo en otro script, llamado el modelo, tal y como se
muestra en el listado 2-4.

Listado 2-4 - La parte del modelo, en modelo.php
   <?php

   function getTodosLosArticulos()
   {
     // Conectar con la base de datos y seleccionarla
     $conexion = mysql_connect('localhost', 'miusuario', 'micontrasena');
     mysql_select_db('blog_db', $conexion);

        // Ejecutar la consulta SQL
        $resultado = mysql_query('SELECT fecha, titulo FROM articulo', $conexion);

        // Crear el array de elementos para la capa de la vista
        $articulos = array();
        while ($fila = mysql_fetch_array($resultado, MYSQL_ASSOC))
        {
          $articulos[] = $fila;
        }

        // Cerrar la conexión
        mysql_close($conexion);

        return $articulos;
   }

   ?>

El controlador modificado se puede ver en el listado 2-5.

Listado 2-5 - La parte del controlador, modificada, en index.php
   <?php

   // Incluir la lógica del modelo
   require_once('modelo.php');

   // Obtener la lista de artículos
   $articulos = getTodosLosArticulos();

   // Incluir la lógica de la vista
   require('vista.php');

   ?>

Ahora el controlador es mucho más fácil de leer. Su única tarea es la de obtener los da-
tos del modelo y pasárselos a la vista. En las aplicaciones más complejas, el controlador
se encarga además de procesar las peticiones, las sesiones de los usuarios, la autentica-
ción, etc. El uso de nombres apropiados para las funciones del modelo hacen que sea in-
necesario añadir comentarios al código del controlador.




www.librosweb.es                                                                              28
Symfony, la guía definitiva                        Capítulo 2. Explorando el interior de Symfony


El script del modelo solamente se encarga del acceso a los datos y puede ser reorganiza-
do a tal efecto. Todos los parámetros que no dependen de la capa de datos (como por
ejemplo los parámetros de la petición del usuario) se deben obtener a través del contro-
lador y por tanto, no se puede acceder a ellos directamente desde el modelo. Las funcio-
nes del modelo se pueden reutilizar fácilmente en otros controladores.

2.1.2. Separación en capas más allá del MVC
El principio más importante de la arquitectura MVC es la separación del código del pro-
grama en tres capas, dependiendo de su naturaleza. La lógica relacionada con los datos
se incluye en el modelo, el código de la presentación en la vista y la lógica de la aplica-
ción en el controlador.

La programación se puede simplificar si se utilizan otros patrones de diseño. De esta for-
ma, las capas del modelo, la vista y el controlador se pueden subidividir en más capas.

2.1.2.1. Abstracción de la base de datos
La capa del modelo se puede dividir en la capa de acceso a los datos y en la capa de abs-
tracción de la base de datos. De esta forma, las funciones que acceden a los datos no
utilizan sentencias ni consultas que dependen de una base de datos, sino que utilizan
otras funciones para realizar las consultas. Así, si se cambia de sistema gestor de bases
de datos, solamente es necesario actualizar la capa de abstracción de la base de datos.

El listado 2-6 muestra una capa de acceso a datos específica para MySQL y el listado 2-7
muestra una capa sencilla de abstracción de la base de datos.

Listado 2-6 - La parte del modelo correspondiente a la abstracción de la base de
datos
   <?php

   function crear_conexion($servidor, $usuario, $contrasena)
   {
     return mysql_connect($servidor, $usuario, $contrasena);
   }

   function cerrar_conexion($conexion)
   {
     mysql_close($conexion);
   }

   function consulta_base_de_datos($consulta, $base_datos, $conexion)
   {
     mysql_select_db($base_datos, $conexion);

       return mysql_query($consulta, $conexion);
   }

   function obtener_resultados($resultado)
   {
     return mysql_fetch_array($resultado, MYSQL_ASSOC);
   }


www.librosweb.es                                                                             29
Symfony, la guía definitiva                           Capítulo 2. Explorando el interior de Symfony


Listado 2-7 - La parte del modelo correspondiente al acceso a los datos
   function getTodosLosArticulos()
   {
     // Conectar con la base de datos
     $conexion = crear_conexion('localhost', 'miusuario', 'micontrasena');

     // Ejecutar la consulta SQL
     $resultado = consulta_base_de_datos('SELECT fecha, titulo FROM articulo', 'blog_db',
   $conexion);

        // Crear el array de elementos para la capa de la vista
        $articulos = array();
        while ($fila = obtener_resultados($resultado))
        {
           $articulos[] = $fila;
        }

        // Cerrar la conexión
        cerrar_conexion($conexion);

        return $articulos;
   }

   ?>

Como se puede comprobar, la capa de acceso a datos no contiene funciones dependien-
tes de ningún sistema gestor de bases de datos, por lo que es independiente de la base
de datos utilizada. Además, las funciones creadas en la capa de abstracción de la base de
datos se pueden reutilizar en otras funciones del modelo que necesiten acceder a la base
de datos.

  NOTA
  Los ejemplos de los listados 2-6 y 2-7 no son completos, y todavía hace falta añadir algo de código
  para tener una completa abstracción de la base de datos (abstraer el código SQL mediante un
  constructor de consultas independiente de la base de datos, añadir todas las funciones a una clase,
  etc.) El propósito de este libro no es mostrar cómo se puede escribir todo ese código, ya que en el
  capítulo 8 se muestra cómo Symfony realiza de forma automática toda la abstracción.

2.1.2.2. Los elementos de la vista
La capa de la vista también puede aprovechar la separación de código. Las páginas web
suelen contener elementos que se muestran de forma idéntica a lo largo de toda la apli-
cación: cabeceras de la página, el layout genérico, el pie de página y la navegación glo-
bal. Normalmente sólo cambia el interior de la página. Por este motivo, la vista se separa
en un layout y en una plantilla. Normalmente, el layout es global en toda la aplicación o
al menos en un grupo de páginas. La plantilla sólo se encarga de visualizar las variables
definidas en el controlador. Para que estos componentes interaccionen entre sí correcta-
mente, es necesario añadir cierto código. Siguiendo estos principios, la parte de la vista
del listado 2-3 se puede separar en tres partes, como se muestra en los listados 2-8, 2-9
y 2-10.


www.librosweb.es                                                                                  30
Symfony, la guía definitiva                         Capítulo 2. Explorando el interior de Symfony


Listado 2-8 - La parte de la plantilla de la vista, en miplantilla.php
   <h1>Listado de Artículos</h1>
   <table>
   <tr><th>Fecha</th><th>Título</th></tr>
   <?php foreach ($articulos as $articulo): ?>
     <tr>
       <td><?php echo $articulo['fecha'] ?></td>
       <td><?php echo $articulo['titulo'] ?></td>
     </tr>
   <?php endforeach; ?>
   </table>

Listado 2-9 - La parte de la lógica de la vista
   <?php

   $titulo = 'Listado de Artículos';
   $contenido = include('miplantilla.php');

   ?>

Listado 2-10 - La parte del layout de la vista
   <html>
     <head>
       <title><?php echo $titulo ?></title>
     </head>
     <body>
       <?php echo $contenido ?>
     </body>
   </html>


2.1.2.3. Acciones y controlador frontal
En el ejemplo anterior, el controlador no se encargaba de realizar muchas tareas, pero
en las aplicaciones web reales el controlador suele tener mucho trabajo. Una parte im-
portante de su trabajo es común a todos los controladores de la aplicación. Entre las ta-
reas comunes se encuentran el manejo de las peticiones del usuario, el manejo de la se-
guridad, cargar la configuración de la aplicación y otras tareas similares. Por este motivo,
el controlador normalmente se divide en un controlador frontal, que es único para cada
aplicación, y las acciones, que incluyen el código específico del controlador de cada
página.

Una de las principales ventajas de utilizar un controlador frontal es que ofrece un punto
de entrada único para toda la aplicación. Así, en caso de que sea necesario impedir el ac-
ceso a la aplicación, solamente es necesario editar el script correspondiente al controla-
dor frontal. Si la aplicación no dispone de controlador frontal, se debería modificar cada
uno de los controladores.

2.1.2.4. Orientación a objetos
Los ejemplos anteriores utilizan la programación procedimental. Las posibilidades que
ofrecen los lenguajes de programación modernos para trabajar con objetos permiten
simplificar la programación, ya que los objetos pueden encapsular la lógica, pueden

www.librosweb.es                                                                              31
Symfony, la guía definitiva                            Capítulo 2. Explorando el interior de Symfony


heredar métodos y atributos entre diferentes objetos y proporcionan una serie de con-
venciones claras sobre la forma de nombrar a los objetos.

La implementación de una arquitectura MVC en un lenguaje de programación que no está
orientado a objetos puede encontrarse con problemas de namespaces y código duplicado,
dificultando la lectura del código de la aplicación.

La orientación a objetos permite a los desarrolladores trabajar con objetos de la vista,
objetos del controlador y clases del modelo, transformando las funciones de los ejemplos
anteriores en métodos. Se trata de un requisito obligatorio para las arquitecturas de tipo
MVC.

  SUGERENCIA
  Si quieres profundizar en el tema de los patrones de diseño para las aplicaciones web en el contex-
  to de la orientación a objetos, puedes leer “Patterns of Enterprise Application Architecture” de Mar-
  tin Fowler (Addison-Wesley, ISBN: 0-32112-742-0). El código de ejemplo del libro de Fowler está
  escrito en Java y en C#, pero es bastante fácil de leer para los programadores de PHP.


2.1.3. La implementación del MVC que realiza Symfony
Piensa por un momento cuántos componentes se necesitarían para realizar una página
sencilla que muestre un listado de las entradas o artículos de un blog. Como se muestra
en la figura 2-2, son necesarios los siguientes componentes:

      ▪ La capa del Modelo

               ▪ Abstracción de la base de datos

               ▪ Acceso a los datos

      ▪ La capa de la Vista

               ▪ Vista

               ▪ Plantilla

               ▪ Layout

      ▪ La capa del Controlador

               ▪ Controlador frontal

               ▪ Acción

En total son siete scripts, lo que parecen muchos archivos para abrir y modificar cada vez
que se crea una página. Afortunadamente, Symfony simplifica este proceso. Symfony to-
ma lo mejor de la arquitectura MVC y la implementa de forma que el desarrollo de aplica-
ciones sea rápido y sencillo.

En primer lugar, el controlador frontal y el layout son comunes para todas las acciones de
la aplicación. Se pueden tener varios controladores y varios layouts, pero solamente es
obligatorio tener uno de cada. El controlador frontal es un componente que sólo tiene có-
digo relativo al MVC, por lo que no es necesario crear uno, ya que Symfony lo genera de
forma automática.



www.librosweb.es                                                                                    32
Symfony, la guía definitiva                       Capítulo 2. Explorando el interior de Symfony


La otra buena noticia es que las clases de la capa del modelo también se generan au-
tomáticamente, en función de la estructura de datos de la aplicación. La librería Propel se
encarga de esta generación automática, ya que crea el esqueleto o estructura básica de
las clases y genera automáticamente el código necesario. Cuando Propel encuentra res-
tricciones de claves foráneas (o externas) o cuando encuentra datos de tipo fecha, crea
métodos especiales para acceder y modificar esos datos, por lo que la manipulación de
datos se convierte en un juego de niños. La abstracción de la base de datos es completa-
mente invisible al programador, ya que la realiza otro componente específico llamado
Creole. Así, si se cambia el sistema gestor de bases de datos en cualquier momento, no
se debe reescribir ni una línea de código, ya que tan sólo es necesario modificar un pará-
metro en un archivo de configuración.

Por último, la lógica de la vista se puede transformar en un archivo de configuración sen-
cillo, sin necesidad de programarla.




                          Figura 2.2. El flujo de trabajo de Symfony


Considerando todo lo anterior, el ejemplo de la página que muestra un listado con todas
las entradas del blog solamente requiere de tres archivos en Symfony, que se muestran
en los listados 2-11, 2-12 y 2-13.

Listado 2-11 - Acción listado, en miproyecto/apps/miaplicacion/modules/weblog/act-
ions/actions.class.php


www.librosweb.es                                                                            33
Symfony, la guía definitiva                      Capítulo 2. Explorando el interior de Symfony

   <?php
   class weblogActions extends sfActions
   {
     public function executeListado()
     {
       $this->articulos = ArticuloPeer::doSelect(new Criteria());
     }
   }

   ?>

Listado 2-12 - Plantilla listado, en miproyecto/apps/miaplicacion/modules/weblog/
templates/listadoSuccess.php
   <h1>Listado de Artículos</h1>
   <table>
   <tr><th>Fecha</th><th>Título</th></tr>
   <?php foreach ($articulos as $articulo): ?>
     <tr>
       <td><?php echo $articulo->getFecha() ?></td>
       <td><?php echo $articulo->getTitulo() ?></td>
     </tr>
   <?php endforeach; ?>
   </table>

Listado 2-13 - Vista listado, en miproyecto/apps/miaplicacion/modules/weblog/con-
fig/view.yml
   listadoSuccess:
     metas: { title: Listado de Artículos }

También es necesario definir el layout, como el del listado 2-14, pero el mismo layout se
puede reutilizar muchas veces.

Listado 2-14 - Layout, en miproyecto/apps/miaplicacion/templates/layout.php
   <html>
     <head>
       <?php echo include_title() ?>
     </head>
     <body>
       <?php echo $sf_data->getRaw('sf_content') ?>
     </body>
   </html>

Estos scripts son todo lo que necesita la aplicación del ejemplo. El código mostrado es el
necesario para crear la misma página que generaba el script simple del listado 2-1. Sym-
fony se encarga del resto de tareas, como hacer que los componentes interactuen entre
sí. Si se considera el número de líneas de código, el listado de entradas de blog creado
según la arquitectura MVC no requiere más líneas ni más tiempo de programación que un
script simple. Sin embargo, la arquitectura MVC proporciona grandes ventajas, como la
organización del código, la reutilización, la flexibilidad y una programación mucho más
entretenida. Por si fuera poco, crear la aplicación con Symfony permite crear páginas
XHTML válidas, depurar fácilmente las aplicaciones, crear una configuración sencilla,



www.librosweb.es                                                                           34
Symfony, la guía definitiva                             Capítulo 2. Explorando el interior de Symfony


abstracción de la base de datos utilizada, enrutamiento con URL limpias, varios entornos
de desarrollo y muchas otras utilidades para el desarrollo de aplicaciones.

2.1.4. Las clases que forman el núcleo de Symfony
La implementación que realiza Symfony de la arquitectura MVC incluye varias clases que
se mencionan una y otra vez a lo largo del libro:

      ▪ sfController es la clase del controlador. Se encarga de decodificar la petición y
         transferirla a la acción correspondiente.

      ▪ sfRequest almacena todos los elementos que forman la petición (parámetros, co-
         okies, cabeceras, etc.)

      ▪ sfResponse contiene las cabeceras de la respuesta y los contenidos. El contenido
         de este objeto se transforma en la respuesta HTML que se envía al usuario.

      ▪ El singleton de contexto (que se obtiene mediante sfContext::getInstance()) al-
         macena una referencia a todos los objetos que forman el núcleo de Symfony y
         puede ser accedido desde cualquier punto de la aplicación.

El capítulo 6 explica en detalle todos estos objetos.

Como se ha visto, todas las clases de Symfony utilizan el prefijo sf, como también hacen
todas las variables principales de Symfony en las plantillas. De esta forma, se evitan las
colisiones en los nombres de clases y variables de Symfony y los nombres de tus propias
clases y variables, además de que las clases del framework son más fáciles de reconocer.

  NOTA
  Entre las normas seguidas por el código de Symfony, se encuentra el estándar “UpperCamelCase”
  para el nombre de las clases y variables. Solamente existen dos excepciones: las clases del núcleo
  de Symfony empiezan por sf (por tanto en minúsculas) y las variables utilizadas en las plantillas
  que utilizan la sintaxis de separar las palabras con guiones bajos.

  Nota del traductor La notación “CamelCase” consiste en escribir frases o palabras compuestas eli-
  minando los espacios intermedios y poniendo en mayúscula la primera letra de cada palabra. La
  variante “UpperCamelCase” también pone en mayúscula la primera letra de todas.


2.2. Organización del código
Ahora que ya conoces los componentes que forman una aplicación de Symfony, a lo me-
jor te estás preguntando sobre cómo están organizados. Symfony organiza el código
fuente en una estructura de tipo proyecto y almacena los archivos del proyecto en una
estructura estandarizada de tipo árbol.

2.2.1. Estructura del proyecto: Aplicaciones, Módulos y Acciones
Symfony considera un proyecto como “un conjunto de servicios y operaciones disponibles
bajo un determinado nombre de dominio y que comparten el mismo modelo de objetos”.

Dentro de un proyecto, las operaciones se agrupan de forma lógica en aplicaciones. Nor-
malmente, una aplicación se ejecuta de forma independiente respecto de otras

www.librosweb.es                                                                                  35
Symfony, la guía definitiva                            Capítulo 2. Explorando el interior de Symfony


aplicaciones del mismo proyecto. Lo habitual es que un proyecto contenga dos aplicacio-
nes: una para la parte pública y otra para la parte de gestión, compartiendo ambas la
misma base de datos. También es posible definir proyectos que estén formados por var-
ios sitios web pequeños, cada uno de ellos considerado como una aplicación. En este ca-
so, es importante tener en cuenta que los enlaces entre aplicaciones se deben indicar de
forma absoluta.

Cada aplicación está formada por uno o más módulos. Un módulo normalmente repre-
senta a una página web o a un grupo de páginas con un propósito relacionado. Por ejem-
plo, una aplicación podría tener módulos como home, articulos, ayuda, carritoCompra,
cuenta, etc.

Los módulos almacenan las acciones, que representan cada una de las operaciones que
se puede realizar en un módulo. Por ejemplo el módulo carritoCompra puede definir acc-
iones como anadir, mostrar y actualizar. Normalmente las acciones se describen med-
iante verbos. Trabajar con acciones es muy similar a trabajar con las páginas de una
aplicación web tradicional, aunque en este caso dos acciones diferentes pueden acabar
mostrando la misma página (como por ejemplo la acción de añadir un comentario a una
entrada de un blog, que acaba volviendo a mostrar la página de la entrada con el nuevo
comentario).

  NOTA
  Nota del traductor En el párrafo anterior, la acción del carrito se llama anadir y no añadir, ya que
  el nombre de una acción también se utiliza como parte del nombre de un fichero y como parte del
  nombre de una función, por lo que se recomienda utilizar exclusivamente caracteres ASCII, y por
  tanto, no debería utilizarse la letra ñ.

  SUGERENCIA
  Si crees que todo esto es demasiado complicado para tu primer proyecto con Symfony, puedes
  agrupar todas las acciones en un único módulo, para simplificar la estructura de archivos. Cuando
  la aplicación se complique, puedes reorganizar las acciones en diferentes módulos. Como se co-
  menta en el capítulo 1, la acción de reescribir el código para mejorar su estructura o hacerlo más
  sencillo (manteniendo siempre su comportamiento original) se llama refactorización, y es algo muy
  común cuando se aplican los principios del RAD (“desarrollo rápido de aplicaciones”).

La figura 2-3 muestra un ejemplo de organización del código para un proyecto de un
blog, siguiendo la estructura de proyecto / aplicación / módulo / acción. No obstante, la
estructura de directorios real del proyecto es diferente al esquema mostrado por esa
figura.




www.librosweb.es                                                                                   36
Symfony, la guía definitiva                       Capítulo 2. Explorando el interior de Symfony




                        Figura 2.3. Ejemplo de organización del código



2.2.2. Estructura del árbol de archivos
Normalmente, todos los proyectos web comparten el mismo tipo de contenidos, como por
ejemplo:

      ▪ Una base de datos, como MySQL o PostgreSQL

      ▪ Archivo estáticos (HTML, imágenes, archivos de JavaScript, hojas de estilos, etc.)

      ▪ Archivos subidos al sitio web por parte de los usuarios o los administradores

      ▪ Clases y librerías PHP

      ▪ Librerías externas (scripts desarrollados por terceros)

      ▪ Archivos que se ejecutan por lotes (batch files) que normalmente son scripts que
        se ejecutan vía línea de comandos o mediante cron

      ▪ Archivos de log (las trazas que generan las aplicaciones y/o el servidor)

      ▪ Archivos de configuración

Symfony proporciona una estructura en forma de árbol de archivos para organizar de for-
ma lógica todos esos contenidos, además de ser consistente con la arquitectura MVC uti-
lizada y con la agrupación proyecto / aplicación / módulo. Cada vez que se crea un nuevo
proyecto, aplicación o módulo, se genera de forma automática la parte correspondiente
de esa estructura. Además, la estructura se puede personalizar completamente, para re-
organizar los archivos y directorios o para cumplir con las exigencias de organización de
un cliente.

2.2.2.1. Estructura de la raíz del proyecto
La raíz de cualquier proyecto Symfony contiene los siguientes directorios:
   apps/
     frontend/
     backend/
   batch/
   cache/


www.librosweb.es                                                                            37
Symfony, la guía definitiva                           Capítulo 2. Explorando el interior de Symfony

   config/
   data/
     sql/
   doc/
   lib/
     model/
   log/
   plugins/
   test/
     unit/
     functional/
   web/
     css/
     images/
     js/
     uploads/

La tabla 2-1 describe los contenidos de estos directorios

Tabla 2-1. Directorios en la raíz de los proyectos Symfony

Directorio Descripción

            Contiene un directorio por cada aplicación del proyecto (normalmente, frontend y
apps/
            backend para la parte pública y la parte de gestión respectivamente)

            Contiene los scripts de PHP que se ejecutan mediante la línea de comandos o mediante
batch/
            la programación de tareas para realizar procesos en lotes (batch processes)

            Contiene la versión cacheada de la configuración y (si está activada) la versión
            cacheada de las acciones y plantillas del proyecto. El mecanismo de cache (que se
cache/      explica en el Capítulo 12) utiliza los archivos de este directorio para acelerar la
            respuesta a las peticiones web. Cada aplicación contiene un subdirectorio que guarda
            todos los archivos PHP y HTML preprocesados

config/     Almacena la configuración general del proyecto

            En este directorio se almacenan los archivos relacionados con los datos, como por
data/       ejemplo el esquema de una base de datos, el archivo que contiene las instrucciones
            SQL para crear las tablas e incluso un archivo de bases de datos de SQLite

            Contiene la documentación del proyecto, formada por tus propios documentos y por la
doc/
            documentación generada por PHPdoc

            Almacena las clases y librerías externas. Se suele guardar todo el código común a
lib/        todas las aplicaciones del proyecto. El subdirectorio model/ guarda el modelo de
            objetos del proyecto (como se describe en el Capítulo 8)

            Guarda todos los archivos de log generados por Symfony. También se puede utilizar
            para guardar los logs del servidor web, de la base de datos o de cualquier otro
log/
            componente del proyecto. Symfony crea un archivo de log por cada aplicación y por
            cada entorno (los archivos de log se ven detalladamente en el Capítulo 16)

            Almacena los plugins instalados en la aplicación (el Capítulo 17 aborda el tema de los
plugins/
            plugins)

www.librosweb.es                                                                                     38
Symfony, la guía definitiva                            Capítulo 2. Explorando el interior de Symfony


             Contiene las pruebas unitarias y funcionales escritas en PHP y compatibles con el
test/        framework de pruebas de Symfony (que se explica en el capítulo 15). Cuando se crea
             un proyecto, Symfony crea algunos pruebas básicas

             La raíz del servidor web. Los únicos archivos accesibles desde Internet son los que se
web/
             encuentran en este directorio


2.2.2.2. Estructura de cada aplicación
Todas las aplicaciones de Symfony tienen la misma estructura de archivos y directorios:
   apps/
     [nombre aplicacion]/
       config/
       i18n/
       lib/
       modules/
       templates/
         layout.php
         error.php
         error.txt

La tabla 2-2 describe los subdirectorios de una aplicación

Tabla 2-2. Subdirectorios de cada aplicación Symfony

Directorio    Descripción

              Contiene un montón de archivos de configuración creados con YAML. Aquí se
              almacena la mayor parte de la configuración de la aplicación, salvo los parámetros
config/       propios del framework. También es posible redefinir en este directorio los parámetros
              por defecto si es necesario. El Capítulo 5 contiene más detalles sobre la configuración
              de las aplicaciones

              Contiene todos los archivos utilizados para la internacionalización de la aplicación,
              sobre todo los archivos que traducen la interfaz (el Capítulo 13 detalla la
i18n/
              internacionalización). La internacionalización también se puede realizar con una base
              de datos, en cuyo caso este directorio no se utilizaría

lib/          Contiene las clases y librerías utilizadas esclusivamente por la aplicación

modules/      Almacena los módulos que definen las características de la aplicación

           Contiene las plantillas globales de la aplicación, es decir, las que utilizan todos los
templates/ módulos. Por defecto contiene un archivo llamado layout.php, que es el layout
              principal con el que se muestran las plantillas de los módulos


  NOTA
  En las aplicaciones recién creadas, los directorios i18n/, lib/ y modules/ están vacíos.

Las clases de una aplicación no pueden acceder a los métodos o atributos de otras aplica-
ciones del mismo proyecto. Además, los enlaces entre 2 aplicaciones de un mismo pro-
yecto se deben indicar de forma absoluta. Esta última restricción es importante durante


www.librosweb.es                                                                                      39
Symfony, la guía definitiva                           Capítulo 2. Explorando el interior de Symfony


la inicialización del proyecto, que es cuando debes elegir como dividir el proyecto en
aplicaciones.

2.2.2.3. Estructura de cada módulo
Cada aplicación contiene uno o más módulos. Cada módulo tiene su propio subdirectorio
dentro del directorio modules y el nombre del directorio es el que se elige durante la crea-
ción del módulo.

Esta es la estructura de directorios típica de un módulo:
   apps/
     [nombre aplicacion]/
       modules/
         [nombre modulo]/
             actions/
                actions.class.php
             config/
             lib/
             templates/
                indexSuccess.php
             validate/

La tabla 2-3 describe los subirectorios de un módulo.

Tabla 2-3. Subdirectorios de cada módulo

Directorio    Descripción

              Normalmente contiene un único archivo llamado actions.class.php y que
actions/      corresponde a la clase que almacena todas las acciones del módulo. También es
              posible crear un archivo diferente para cada acción del módulo

              Puede contener archivos de configuración adicionales con parámetros exclusivos del
config/
              módulo

lib/          Almacena las clases y librerías utilizadas exclusivamente por el módulo

              Contiene las plantillas correspondientes a las acciones del módulo. Cuando se crea
templates/
              un nuevo módulo, automáticamente se crea la plantilla llamada indexSuccess.php

              Contiene archivos de configuración relacionados con la validación de formularios (que
validate/
              se verá en el Capítulo 10)


  NOTA
  En los módulos recién creados, los directorios config/, lib/ y validate/ están vacíos.


2.2.2.4. Estructura del sitio web
Existen pocas restricciones sobre la estructura del directorio web, que es el directorio que
contiene los archivos que se pueden acceder de forma pública. Si se utilizan algunas con-
venciones básicas en los nombres de los subdirectorios, se pueden simplificar las planti-
llas. La siguiente es una estructura típica del directorio web:



www.librosweb.es                                                                                   40
Symfony, la guía definitiva                             Capítulo 2. Explorando el interior de Symfony

   web/
     css/
     images/
     js/
     uploads/

Normalmente, los archivos estáticos se organizan según los directorios de la tabla 2-4.

Tabla 2-4. Subdirectorios habituales en la carpeta web

Directorio Descripción

css/         Contiene los archivos de hojas de estilo creados con CSS (archivos con extensión .css

images/      Contiene las imágenes del sitio con formato .jpg, .png o .gif

js/          Contiene los archivos de JavaScript con extensión .js

             Se almacenan los archivos subidos por los usuarios. Aunque normalmente este
             directorio contiene imágenes, no se debe confundir con el directorio que almacena las
uploads/
             imágenes del sitio (images/). Esta distinción permite sincronizar los servidores de
             desarrollo y de producción sin afectar a las imágenes subidas por los usuarios


  NOTA
  Aunque es muy recomendable mantener la estructura definida por defecto, es posible modificarla
  para adaptarse a las necesidades específicas de cada proyecto, como por ejemplo los proyectos
  que se ejecutan en servidores con sus propias estructuras de directorios definidas y con otras políti-
  cas para el desarrollo de las aplicaciones. El Capítulo 19 explica en detalle cómo modificar la es-
  tructura de directorios definida por Symfony.


2.3. Herramientas comunes
Algunas técnicas se utilizan una y otra vez en Symfony, por lo que es fácil encontrarse
con ellas a lo largo de este libro y en el desarrollo de tus proyectos. Entre estas técnicas
se encuentran los contenedores de parámetros (parameter holders), las constantes y la
carga automática de clases.

2.3.1. Contenedores de parámetros
Muchas de las clases de Symfony contienen algún contenedor de parámetros. Se trata de
una forma eficiente de encapsular los atributos y así poder utilizar métodos getter y set-
ter sencillos. La clase sfResponse por ejemplo incluye un contenedor de parámetros que
se puede obtener mediante el método getParameterHolder(). Todos los contenedores de
parámetros almacenan sus datos de la misma forma, como se muestra en el listado
2-15.

Listado 2-15 - Uso del contenedor de parámetros de sfResponse
   $respuesta->getParameterHolder()->set('parametro', 'valor');

   echo $respuesta->getParameterHolder()->get('parametro');
    => 'valor'



www.librosweb.es                                                                                     41
Symfony, la guía definitiva                      Capítulo 2. Explorando el interior de Symfony


La mayoría de clases que contienen contenedores de parámetros proporcionan métodos
abreviados para las operaciones de tipo get/set. La clase sfResponse es una de esas cla-
ses, ya que el código abreviado del listado 2-16 obtiene el mismo resultado que el código
original del listado 2-15.

Listado 2-16 - Uso de los métodos abreviados del contenedor de parámetros de
sfResponse
   $respuesta->setParameter('parametro', 'valor');

   echo $respuesta->getParameter('parametro');
    => 'valor'

El método getter del contenedor de parámetros permite la definición de un segundo
parámetro que actua de valor por defecto. De esta manera, se obtiene una protección
efectiva y sencilla frente a los errores. El listado 2-17 contiene un ejemplo de su uso.

Listado 2-17 - Uso de valores por defecto en las funciones de tipo getter
   // El parámetro llamado 'parametro' no está definido, por lo que el getter devuelve un
   valor vacío
   echo $respuesta->getParameter('parametro');
    => null

   // El valor por defecto se puede obtener con sentencias condicionales
   if ($respuesta->hasParameter('parametro'))
   {
      echo $respuesta->getParameter('parametro');
   }
   else
   {
      echo 'valor_por_defecto';
   }
     => 'valor_por_defecto'

   // El siguiente método es mucho más rápido
   echo $respuesta->getParameter('parametro', 'valor_por_defecto');
    => 'valor_por_defecto'

Los contenedores de parámetros permiten la utilización de namespaces. Si se utiliza un
tercer parámetro en un getter o en un setter, ese parámetro se utiliza como namespace
del parámetro y por tanto, el parámetro sólo estará definido dentro de ese namespace. El
listado 2-18 muestra un ejemplo.

Listado 2-18 - Uso de un namespace en el contenedor de parámetros de
sfResponse
   $respuesta->setParameter('parametro', 'valor1');
   $respuesta->setParameter('parametro', 'valor2', 'mi/namespace');
   echo $respuesta->getParameter('parametro');
    => 'valor1'
   echo $respuesta->getParameter('parametro', null, 'mi/namespace');
    => 'valor2'




www.librosweb.es                                                                           42
Symfony, la guía definitiva                        Capítulo 2. Explorando el interior de Symfony


También es posible añadir contenedores de parámetros a tus propias clases, para apro-
vechar las ventajas de su sintaxis. El listado 2-19 muestra un ejemplo de cómo definir
una clase con un contenedor de parámetros.

Listado 2-19 - Añadir un contenedor de parámetros a una clase
   class MiClase
   {
     protected $contenedor_parametros = null;

       public function initialize ($parametros = array())
       {
         $this->contenedor_parametros = new sfParameterHolder();
         $this->contenedor_parametros->add($parametros);
       }

       public function getContenedorParametros()
       {
         return $this->contenedor_parametros;
       }
   }


2.3.2. Constantes
Aunque pueda parecer sorprendente, Symfony no define casi ninguna constante. La
razón es que las constantes tienen un inconveniente en PHP: no se puede modificar su
valor una vez definidas. Por este motivo, Symfony utiliza su propio objeto para almace-
nar la configuración, llamado sfConfig, y que reemplaza a las constantes. Este objeto
proporciona métodos estáticos para poder acceder a los parámetros desde cualquier pun-
to de la aplicación. El listado 2-20 muestra el uso de los métodos de la clase sfConfig.

Listado 2-20 - Uso de los métodos de la clase sfConfig en vez de constantes
   // En vez de constantes de PHP,
   define('MI_CONSTANTE', 'valor');
   echo MI_CONSTANTE;

   // Symfony utiliza el objeto sfConfig
   sfConfig::set('mi_constante', 'valor');
   echo sfConfig::get('mi_constante');

Los métodos de sfConfig permiten definir valores por defecto y se puede invocar el mé-
todo sfConfig::set() más de una vez sobre el mismo parámetro para modificar su valor.
El capítulo 5 detalla el uso de los métodos de sfConfig.


2.3.3. Carga automática de clases
Normalmente, cuando se utiliza un método de una clase o cuando se crea un objeto en
PHP, se debe incluir antes la definición de esa clase.
   include 'clases/MiClase.php';
   $miObjeto = new MiClase();

Sin embargo, en los proyectos complejos con muchas clases y una estructura de directo-
rios con muchos niveles, requiere mucho trabajo incluir todas las clases necesarias

www.librosweb.es                                                                             43
Symfony, la guía definitiva                            Capítulo 2. Explorando el interior de Symfony


indicando correctamente la ruta de cada clase. Symfony incluye una función spl_autolo-
ad_register() para evitar la necesidad de los include y así poder escribir directamente:
   $miObjeto = new MiClase();

En este caso, Symfony busca la definición de la clase MiClase en todos los archivos con
extensión .php que se encuentran en alguno de los directorios lib/ del proyecto. Si se
encuentra la definición de la clase, se incluye de forma automática.

De esta forma, si se guardan todas las clases en los directorios lib/, no es necesario in-
cluir las clases de forma explícita. Por este motivo, los proyectos de Symfony no suelen
incluir instrucciones de tipo include o require.

  NOTA
  Para mejorar el rendimiento, la carga automática de clases de Symfony busca durante la primera
  petición en una serie de directorios (que se definen en un archivo interno de configuración). Una
  vez realizada la búsqueda en los directorios, se guarda el nombre de todas las clases encontradas
  y su ruta de acceso en un array asociativo de PHP. Así, las siguientes peticiones no tienen que vol-
  ver a mirar todos los directorios en busca de las clases. Este comportamiento implica que se debe
  borrar la cache de Symfony cada vez que se añade o se mueve una clase del proyecto (salvo en el
  entorno de desarrollo, donde no es necesario). El comando utilizado para borrar la cache es sym-
  fony clear-cache. El Capítulo 12 explica con detalle el mecanismo de cache y la configuración de
  la carga automática de clases se muestra en el capítulo 19.


2.4. Resumen
El uso de un framework que utiliza MVC obliga a dividir y organizar el código de acuerdo
a las convenciones establecidas por el framework. El código de la presentación se guarda
en la vista, el código de manipulación de datos se guarda en el modelo y la lógica de pro-
cesamiento de las peticiones constituye el controlador. Aplicar el patrón MVC a una apli-
cación resulta bastante útil además de restrictivo.

Symfony es un framework de tipo MVC escrito en PHP 5. Su estructura interna se ha di-
señado para obtener lo mejor del patrón MVC y la mayor facilidad de uso. Gracias a su
versatilidad y sus posibilidades de configuración, Symfony es un framework adecuado pa-
ra cualquier proyecto de aplicación web.

Ahora que ya has aprendido la teoría que está detrás de Symfony, estas casi preparado
para desarrollar tu primera aplicación. Pero antes de eso, necesitas tener instalado Sym-
fony en tu servidor de desarrollo.




www.librosweb.es                                                                                   44
Symfony, la guía definitiva                               Capítulo 3. Ejecutar aplicaciones Symfony




Capítulo 3. Ejecutar aplicaciones Symfony
Como se ha visto en los capítulos anteriores, el framework Symfony está formado por un
conjunto de archivos escritos en PHP. Los proyectos realizados con Symfony utilizan es-
tos archivos, por lo que la instalación de Symfony consiste en obtener esos archivos y
hacer que estén disponibles para los proyectos.

Como Symfony es un framework creado con PHP 5, es obligatorio disponer de la versión
5 de PHP. Por tanto, es necesario asegurarse de que se encuentra instalado, para lo cual
se puede ejecutar el siguiente comando en la línea de comandos del sistema operativo:
   > php -v

   PHP 5.2.0 (cli) (built: Nov 2 2006 11:57:36)
   Copyright (c) 1997-2006 The PHP Group
   Zend Engine v2.2.0, Copyright (c) 1998-2006 Zend Technologies

Si el número de la versión que se muestra es 5.0 o superior, ya es posible realizar la ins-
talación de Symfony que se describe en este capítulo.


3.1. Instalando el entorno de pruebas
Si lo único que quieres es comprobar lo que puede dar de sí Symfony, lo mejor es que te
decantes por la instalación rápida. En este caso, se utiliza el “entorno de pruebas” o
sandbox.

El entorno de pruebas está formado por un conjunto de archivos. Contiene un proyecto
vacío de Symfony e incluye todas las librerías necesarias (Symfony, Pake, Lime, Creole,
Propel y Phing), una aplicación de prueba y la configuración básica. No es necesario reali-
zar ninguna configuración en el servidor ni instalar ningún paquete adicional para que
funcione correctamente.

Para instalar el entorno de pruebas, se debe descargar su archivo comprimido desde
http://www.symfony-project.org/get/sf_sandbox.tgz. Una vez descargado el archivo, es esen-
cial asegurarse que tiene la extensión .tgz, ya que de otro modo no se descomprimirá
correctamente. La extensión .tgz no es muy común en sistemas operativos tipo Win-
dows, pero programas como WinRAR o 7-Zip lo pueden descomprimir sin problemas. A
continuación, se descomprime su contenido en el directorio raíz del servidor web, que
normalmente es web/ o www/. Para asegurar cierta uniformidad en la documentación, en
este capítulo se supone que se ha descomprimido el entorno de pruebas en el directorio
sf_sandbox/.

  ATENCIÓN
  Para hacer pruebas en un servidor local, se pueden colocar todos los archivos en la raíz del servi-
  dor web. Sin embargo, se trata de una mala práctica para los servidores de producción, ya que los
  usuarios pueden ver el funcionamiento interno de la aplicación.

Se puede comprobar si se ha realizado correctamente la instalación del entorno de prue-
bas mediante los comandos proporcionados por Symfony. Entra en el directorio sf_sand-
box/ y ejecuta el siguiente comando en los entornos *nix:

www.librosweb.es                                                                                  45
Symfony, la guía definitiva                            Capítulo 3. Ejecutar aplicaciones Symfony

   > ./symfony -V

En los sistemas Windows, ejecuta el siguiente comando:
   > symfony -V

El resultado del comando debería mostrar la versión del entorno de pruebas:
   symfony version 1.0.0

A continuación, se prueba si el servidor web puede acceder al entorno de pruebas med-
iante la siguiente URL:
   http://localhost/sf_sandbox/web/frontend_dev.php/

Si todo ha ido bien, deberías ver una página de bienvenida como la que se muestra en la
figura 3-1, con lo que la instalación rápida se puede dar por concluida. Si no se muestra
esa página, se mostrará un mensaje de error que te indica los cambios necesarios en la
configuración. También puedes consultar la sección “Resolución de problemas” que se
encuentra más adelante en este capítulo.




www.librosweb.es                                                                             46
Symfony, la guía definitiva                          Capítulo 3. Ejecutar aplicaciones Symfony


                   Figura 3.1. Página de bienvenida del entorno de pruebas


El entorno de pruebas está pensado para que practiques con Symfony en un servidor lo-
cal, no para desarrollar aplicaciones complejas que acaban siendo publicadas en la web.
No obstante, la versión de Symfony que está incluida en el entorno de pruebas es com-
pletamente funcional y equivalente a la que se instala vía PEAR.

Para desinstalar el entorno de pruebas, borra el directorio sf_sandbox/ de la carpeta web/
de tu servidor.


3.2. Instalando las librerías de Symfony
Al desarrollar aplicaciones con Symfony, es probable que tengas que instalarlo dos veces:
una para el entorno de desarrollo y otra para el servidor de producción (a no ser que el
servicio de hosting que utilices tenga Symfony preinstalado). En cada uno de los servido-
res lo lógico es evitar duplicidades juntando todos los archivos de Symfony en un único
directorio, independientemente de que desarrolles una o varias aplicaciones.

Como el desarrollo de Symfony evoluciona rápidamente, es posible que esté disponible
una nueva versión estable del framework unos días después de la primera instalación. La
actualización del framework es algo a tener muy en cuenta, por lo que se trata de otra
razón de peso para juntar en un único directorio todas las librerías de Symfony.

Existen dos alternativas para instalar las librerías necesarias para el desarrollo de las
aplicaciones:

      ▪ La instalación que utiliza PEAR es la recomendada para la mayoría de usuarios.
        Con este método, la instalación es bastante sencilla, además de ser fácil de com-
        partir y de actualizar.

      ▪ La instalación que utiliza Subversion (SVN) solamente se recomienda para los
        programadores de PHP más avanzados y es el método con el que pueden obtener
        los últimos parches, pueden añadir sus propias características al framework y
        pueden colaborar con el proyecto Symfony.

Symfony integra algunos paquetes externos:

      ▪ pake es una utilidad para la línea de comandos.

      ▪ lime es una utilidad para las pruebas unitarias.

      ▪ Creole es un sistema de abstracción de la base de datos. Se trata de un sistema
        similar a los PHP Data Objects (PDO) y proporciona una interfaz entre el código
        PHP y el código SQL de la base de datos, permitiendo cambiar fácilmente de sis-
        tema gestor de bases de datos.

      ▪ Propel se utiliza para el ORM. Proporciona persistencia para los objetos y un ser-
        vicio de consultas.

      ▪ Phing es una utilidad que emplea Propel para generar las clases del modelo.




www.librosweb.es                                                                           47
Symfony, la guía definitiva                                 Capítulo 3. Ejecutar aplicaciones Symfony


Pake y lime han sido desarrollados por el equipo de Symfony. Creole, Propel y Phing han
sido creados por otros equipos de desarrollo y se publican bajo la licencia GNU Lesser Pu-
blic General License (LGPL). Todos estos paquetes están incluidos en Symfony.

3.2.1. Instalando Symfony con PEAR
El paquete PEAR de Symfony incluye las librerías propias de Symfony y todas sus depen-
dencias. Además, también contiene un script que permite extender la línea de comandos
del sistema para que funcione el comando symfony.

Para instalar Symfony de esta manera, en primer lugar se debe añadir el canal Symfony
a PEAR mediante este comando:
   > pear channel-discover pear.symfony-project.com

Para comprobar las librerías disponibles en ese canal, se puede ejecutar lo siguiente:
   > pear remote-list -c symfony

Una vez añadido el canal, ya es posible instalar la última versión estable de Symfony me-
diante el siguiente comando:
   > pear install symfony/symfony

   downloading symfony-1.0.0.tgz ...
   Starting to download symfony-1.0.0.tgz (1,283,270 bytes)
   .................................................................
   .................................................................
   .............done: 1,283,270 bytes
   install ok: channel://pear.symfony-project.com/symfony-1.0.0

Y la instalación ya ha terminado. Los archivos y las utilidades de línea de comandos de
Symfony ya se han instalado. Para asegurarte de que se ha instalado correctamente, pr-
ueba a ejecutar el comando symfony para que te muestre la versión de Symfony que se
encuentra instalada:
   > symfony -V

   symfony version 1.0.0


  SUGERENCIA
  Si prefieres instalar la versión beta más reciente, que tiene las últimas correcciones de errores y las
  últimas mejoras, puedes ejecutar el comando pear install symfony/symfony-beta. Sin embar-
  go, las versiones beta no son estables y por tanto no se recomiendan para los servidores de
  producción.

Después de la instalación, las librerías de Symfony se encuentran en los siguientes
directorios:

      ▪ $php_dir/symfony/ contiene las principales librerías.

      ▪ $data_dir/symfony/ contiene la estructura básica de las aplicaciones Symfony;
         los módulos por defecto; y la configuración, datos para i18 (internacionalización),
         etc.



www.librosweb.es                                                                                      48
Symfony, la guía definitiva                                Capítulo 3. Ejecutar aplicaciones Symfony


      ▪ $doc_dir/symfony/ contiene la documentación.

      ▪ $test_dir/symfony/ contiene las pruebas unitarias.

Las variables que acaban en _dir se definen en la configuración de PEAR. Para ver sus
valores, puedes ejecutar el siguiente comando:
   > pear config-show


3.2.2. Obtener Symfony mediante el repositorio SVN
En los servidores de producción, o cuando no es posible utilizar PEAR, se puede descar-
gar la última versión de las librerías Symfony directamente desde el repositorio Subvers-
ion que utiliza Symfony:
   > mkdir /ruta/a/symfony
   > cd /ruta/a/symfony
   > svn checkout http://svn.symfony-project.com/tags/RELEASE_1_0_0/ .

El comando symfony, que solamente está disponible en las instalaciones PEAR, en reali-
dad es una llamada al script que se encuentra en /ruta/a/symfony/data/bin/symfony. Por
tanto, en una instalación realizada con SVN, el comando symfony -V es equivalente a:
   > php /ruta/a/symfony/data/bin/symfony -V

   symfony version 1.0.0

Probablemente ya tenías creado algún proyecto de Symfony antes de realizar la instala-
ción mediante SVN. En este caso, es necesario modificar el valor de 2 variables en el ar-
chivo de configuración config/config.php del proyecto:
   <?php

   $sf_symfony_lib_dir = '/ruta/a/symfony/lib/';
   $sf_symfony_data_dir = '/ruta/a/symfony/data/';

El Capítulo 19 muestra otras opciones para enlazar un proyecto con una instalación de
Symfony, incluyendo el uso de enlaces simbólicos y rutas relativas.

  SUGERENCIA
  Otra   forma    de   instalar   Symfony    es    bajar   directamente    el   paquete    de    PEAR
  (http://pear.symfony-project.com/get/symfony-1.0.0.tgz) y descomprimirlo en algún directorio.
  El resultado de esta instalación es el mismo que si se instala mediante el repositorio de Subversion.


3.3. Crear una aplicación web
Como se vio en el Capítulo 2, Symfony agrupa las aplicaciones relacionadas en proyec-
tos. Todas las aplicaciones de un proyecto comparten la misma base de datos. Por tanto,
para crear una aplicación web en primer lugar se debe crear un proyecto.

3.3.1. Crear el Proyecto
Los proyectos de Symfony siguen una estructura de directorios predefinida. Los coman-
dos que proporciona Symfony permiten automatizar la creación de nuevos proyectos, ya

www.librosweb.es                                                                                    49
Symfony, la guía definitiva                               Capítulo 3. Ejecutar aplicaciones Symfony


que se encargan de crear la estructura de directorios básica del proyecto y con los permi-
sos adecuados. Por tanto, para crear un proyecto se debe crear un directorio y decirle a
Symfony que cree un proyecto en su interior.

Si has utilizado la instalación con PEAR, ejecuta los siguientes comandos:
   > mkdir ~/miproyecto
   > cd ~/miproyecto
   > symfony init-project miproyecto

Si has instalado Symfony mediante SVN, puedes crear un proyecto con los siguientes
comandos:
   > mkdir ~/miproyecto
   > cd ~/miproyecto
   > php /path/to/symfony/data/bin/symfony init-project miproyecto

El comando symfony siempre debe ejecutarse en el directorio raíz del proyecto (en este
ejemplo, miproyecto/) ya que todas las tareas que realiza este comando son específicas
para cada proyecto.

La estructura de directorios creada por Symfony se muestra a continuación:
   apps/
   batch/
   cache/
   config/
   data/
   doc/
   lib/
   log/
   plugins/
   test/
   web/


  SUGERENCIA
  La tarea init-project añade un script llamado symfony en el directorio raíz del proyecto. Este
  script es idéntico al comando symfony que instala PEAR, por lo que se puede utilizar la instrucción
  php symfony en vez del comando symfony cuando no se dispone de las utilidades de la línea de
  comandos (lo que sucede cuando se instala Symfony mediante SVN).


3.3.2. Crear la Aplicación
El proyecto recién creado está incompleto, ya que requiere por lo menos de una aplica-
ción. Para crear la aplicación, se utiliza el comando symfony init-app, al que se le tiene
que pasar como argumento el nombre de la nueva aplicación:
   > symfony init-app miaplicacion

El comando anterior crea un directorio llamado miaplicacion/ dentro del directorio apps/
que se encuentra en la raíz del proyecto. Por defecto se crea una configuración básica de
la aplicación y una serie de directorios:
   apps/
     miaplicacion/


www.librosweb.es                                                                                  50
Symfony, la guía definitiva                            Capítulo 3. Ejecutar aplicaciones Symfony

        config/
        i18n/
        lib/
        modules/
        templates/

En el directorio web del proyecto también se crean algunos archivos PHP correspondientes
a los controladores frontales de cada uno de los entornos de ejecución de la aplicación:
   web/
     index.php
     miaplicacion_dev.php

El archivo index.php es el controlador frontal de producción de la nueva aplicación. Como
se trata de la primera aplicación, Symfony crea un archivo llamado index.php en vez de
miaplicacion.php (si después se crea una nueva aplicación llamada por ejemplo minueva-
aplicacion, el controlador frontal del entorno de producción que se crea se llamará min-
uevaaplicacion.php). Para ejecutar la aplicación en el entorno de desarrollo, se debe eje-
cutar el controlador frontal llamado miaplicacion_dev.php. El Capítulo 5 explica en detalle
los distintos entornos de ejecución.


3.4. Configurar el servidor web
Los scripts que se encuentran en el directorio web/ son los únicos puntos de entrada a la
aplicación. Por este motivo, debe configurarse el servidor web para que puedan ser acce-
didos desde Internet. En el servidor de desarrollo y en los servicios de hosting profesio-
nales, se suele tener acceso a la configuración completa de Apache para poder configurar
servidores virtuales (virtual host). En los servicios de alojamiento compartido, lo normal
es tener acceso solamente a los archivos .htaccess.


3.4.1. Configurar los servidores virtuales
El listado 3-1 muestra un ejemplo de la configuración necesaria para crear un nuevo ser-
vidor virtual en Apache mediante la modificación del archivo httpd.conf.

Listado 3-1 - Ejemplo de configuración de Apache, en apache/conf/httpd.conf
        <VirtualHost *:80>
          ServerName miaplicacion.ejemplo.com
          DocumentRoot "/home/steve/miproyecto/web"
          DirectoryIndex index.php
          Alias /sf /$sf_symfony_data_dir/web/sf
          <Directory "/$sf_symfony_data_dir/web/sf">
            AllowOverride All
            Allow from All
          </Directory>
          <Directory "/home/steve/miproyecto/web">
            AllowOverride All
            Allow from All
          </Directory>
        </VirtualHost>




www.librosweb.es                                                                             51
Symfony, la guía definitiva                                 Capítulo 3. Ejecutar aplicaciones Symfony


En la configuración del listado 3-1, se debe sustituir la variable $sf_symfony_data_dir por
la ruta real del directorio de datos de Symfony. Por ejemplo, la ruta en un sistema *nix
en el que se ha instalado Symfony mediante PEAR sería:
        Alias /sf /usr/local/lib/php/data/symfony/web/sf


  NOTA
  No es obligatorio el alias al directorio web/sf/. La finalidad del alias es permitir que Apache pueda
  encontrar las imágenes, hojas de estilos y archivos de JavaScript utilizados en la barra de depura-
  ción, en el generador automático de aplicaciones de gestión, en las páginas propias de Symfony y
  en las utilidades de Ajax. La alternativa a crear este alias podría ser la de crear un enlace simbólico
  (symlink) o copiar directamente los contenidos del directorio /$sf_symfony_data_dir/web/sf/ al
  directorio miproyecto/web/sf/.

No te olvides reiniciar Apache para que los cambios surtan efecto. La aplicación recién
creada ya se puede acceder con cualquier navegador en esta dirección:
   http://localhost/miaplicacion_dev.php/

Al acceder a la aplicación, se debería mostrar una imagen similar a la mostrada en la fi-
gura 3-1.

  Reescritura de URL (URL Rewriting)

  Symfony utiliza la reescritura de URL para mostrar “URL limpias” en la aplicación, es decir, URL
  con mucho sentido, optimizadas para buscadores y que ocultan a los usuarios los detalles técnicos
  internos de la aplicación. El Capítulo 9 explica en detalle el sistema de enrutamiento utilizado por
  Symfony y su implicación en las URL de las aplicaciones.

  Para que funcione correctamente la reescritura de URL, es necesario que Apache esté compilado
  con el módulo mod_rewrite o al menos que esté instalado el módulo mod_rewrite como módulo
  DSO. En este último caso, la configuración de Apache debe contener las siguientes líneas en el ar-
  chivo httpd.conf:
      AddModule mod_rewrite.c
      LoadModule rewrite_module modules/mod_rewrite.so

  Para los servidores IIS (Internet Information Services) es necesario disponer de isapi/rewrite
  instalado y activado. El sitio web del proyecto Symfony (http://www.symfony-project.org) dispone
  de más documentación sobre la instalación de Symfony en servidores IIS.


3.4.2. Configurar un servidor compartido
En los servidores de alojamiento compartido es un poco más complicado instalar las apli-
caciones creadas con Symfony, ya que los servidores suelen tener una estructura de di-
rectorios que no se puede modificar.

  ATENCIÓN
  No es recomendable hacer las pruebas y el desarrollo directamente en un servidor compartido. Una
  de las razones es que la aplicación es pública incluso cuando no está terminada, pudiendo mostrar
  su funcionamiento interno y pudiendo provocar problemas de seguridad. El otro motivo es que el
  rendimiento de los servidores compartidos habituales no es suficiente como para depurar la


www.librosweb.es                                                                                      52
Symfony, la guía definitiva                                Capítulo 3. Ejecutar aplicaciones Symfony


  aplicación con las utilidades de Symfony. Por este motivo, no se recomienda comenzar el desarrollo
  de una aplicación en un servidor compartido, sino que debería desarrollarse en un servidor local y
  subirla al servidor compartido una vez terminada la aplicación. En el Capítulo 16 se muestran técni-
  cas y herramientas para la instalación de las aplicaciones.

Imaginemos que el servidor compartido llama a la carpeta web www/ en vez de web/ y que
no es posible modificar el archivo de configuración httpd.conf, sino que solo es posible
acceder a un archivo de tipo .htaccess en ese directorio.

Los proyectos creados con Symfony permiten configurar cada ruta de cada directorio. En
el Capítulo 19 se detalla la configuración de los directorios, pero mientras tanto, se va a
renombrar el directorio web a www y se va a modificar la configuración de la aplicación pa-
ra que lo tenga en cuenta. El listado 3-2 muestra los cambios que es preciso añadir al fi-
nal del archivo config.php.

Listado 3-2 - Modificación de la estructura de directorios por defecto, en apps/
miaplicacion/config/config.php
   $sf_root_dir = sfConfig::get('sf_root_dir');
   sfConfig::add(array(
     'sf_web_dir_name' => $sf_web_dir_name = 'www',
     'sf_web_dir'       => $sf_root_dir.DIRECTORY_SEPARATOR.$sf_web_dir_name,
     'sf_upload_dir'    =>
   $sf_root_dir.DIRECTORY_SEPARATOR.$sf_web_dir_name.DIRECTORY_SEPARATOR.sfConfig::get('sf_upload_dir_n
   ));

La carpeta web de la raíz del servidor contiene por defecto un archivo de tipo .htaccess.
El listado 3-3 muestra su contenido, que debe ser modificado de acuerdo a los requerim-
ientos del servidor compartido.

Listado 3-3 - Configuración por defecto de .htaccess, ahora guardado en mipro-
yecto/www/.htaccess
        Options +FollowSymLinks +ExecCGI

        <IfModule mod_rewrite.c>
          RewriteEngine On

          # we skip all files with .something
          RewriteCond %{REQUEST_URI} ..+$
          RewriteCond %{REQUEST_URI} !.html$
          RewriteRule .* - [L]

          # we check if the .html version is here (caching)
          RewriteRule ^$ index.html [QSA]
          RewriteRule ^([^.]+)$ $1.html [QSA]
          RewriteCond %{REQUEST_FILENAME} !-f

          # no, so we redirect to our front web controller
          RewriteRule ^(.*)$ index.php [QSA,L]
        </IfModule>

        # big crash from our front web controller



www.librosweb.es                                                                                   53
Symfony, la guía definitiva                              Capítulo 3. Ejecutar aplicaciones Symfony

       ErrorDocument 500 "<h2>Application error</h2>symfony applicationfailed to start
   properly"

Después de realizar los cambios, ya debería ser posible acceder a la aplicación. Comprue-
ba si se muestra la página de bienvenida accediendo a esta dirección:
        http://www.ejemplo.com/miaplicacion_dev.php/

  Otras configuraciones de servidor

  Symfony permite realizar otras configuraciones de servidor. Por ejemplo se puede acceder a las
  aplicaciones Symfony utilizando alias en vez de servidores virtuales. También es posible ejecutar
  las aplicaciones Symfony en servidores IIS. Existen tantas técnicas como posibles configuraciones,
  aunque el propósito de este libro no es explicarlas todas.

  Para encontrar ayuda sobre las distintas configuraciones de servidor, puedes consultar el wiki del
  proyecto Symfony (http://trac.symfony-project.com/) en el que existen varios tutoriales con ex-
  plicaciones detalladas paso a paso.


3.5. Resolución de problemas
Si se producen errores durante la instalación, lo mejor es intentar mostrar los mensajes
de error en el navegador o en la consola de comandos. Normalmente los errores mues-
tran pistas sobre su posible causa y hasta pueden contener enlaces a algunos recursos
disponibles en Internet sobre ese problema.

3.5.1. Problemas típicos
Si continuan los problemas con Symfony, puedes comprobar los siguientes errores
comunes:

      ▪ Algunas instalaciones de PHP incluyen tanto PHP 4 como PHP 5. En este caso,
        suele ser habitual que el comando de PHP 5 sea php5, por lo que se debe ejecutar
        la instrucción php5 symfony en vez de symfony. Puede que también sea necesario
        añadir la directiva SetEnv PHP_VER 5 en el archivo de configuración .htaccess e
        incluso puede que tengas que renombrar los scripts del directorio web/ para que
        tengan una extensión .php5 en vez de la tradicional extensión .php. Cuando se
        intenta ejecutar Symfony con PHP 4, el error que se muestra es similar al
        siguiente:
   Parse error, unexpected ',', expecting '(' in .../symfony.php on line 19.

      ▪ El límite de memoria utilizado por PHP se define en el archivo de configuración
        php.ini y debería valer por lo menos 16M (equivalente a 16 MB). El síntoma
        común de este problema es cuando se muestra un mensaje de error al instalar
        Symfony mediante PEAR o cuando se utiliza la línea de comandos:
   Allowed memory size of 8388608 bytes exhausted

      ▪ La directiva zend.ze1_compatibility_mode del archivo de configuración de PHP
        (php.ini) debe tener un valor igual a off. Si no es así, cuando se intenta acceder
        a cualquier script, se muestra el siguiente mensaje de error:



www.librosweb.es                                                                                 54
Symfony, la guía definitiva                             Capítulo 3. Ejecutar aplicaciones Symfony

   Strict Standards: Implicit cloning object of class 'sfTimer'because of
   'zend.ze1_compatibility_mode'

      ▪ Los directorios log/ y cache/ del proyecto deben tener permiso de escritura para
         el servidor web. Si se ejecuta una aplicación sin estos permisos, se muestra la si-
         guiente excepción:
   sfCacheException [message] Unable to write cache file"/usr/miproyecto/cache/frontend/
   prod/config/config_config_handlers.yml.php"

      ▪ La ruta del sistema debe incluir la ruta al comando php, y la directiva include_-
         path del archivo de configuración de PHP (php.ini) debe contener una ruta a
         PEAR (en el caso de que se utilice PEAR).

      ▪ En ocasiones, existe más de un archivo php.ini en el sistema (por ejemplo cuan-
         do se instala PHP mediante el paquete WAMP). En estos casos, se puede realizar
         una llamada a la función phpinfo() de PHP para saber la ruta exacta del archivo
         php.ini que está utilizando la aplicación.

  NOTA
  Aunque no es obligatorio, es muy recomendable por motivos de rendimiento establecer el valor off
  a las directivas magic_quotes_gpc y register_globals del archivo de configuración de PHP
  (php.ini).


3.5.2. Recursos relacionados con Symfony
Existen varias formas de encontrar soluciones a los problemas típicos y que ya les han
ocurrido a otros usuarios:

      ▪ El foro de instalación de Symfony (http://www.symfony-project.org/forum/) contiene
         muchas preguntas sobre configuraciones en diferentes plataformas, entornos y
         servidores.

      ▪ La lista de correo de usuarios de Symfony (http://groups.google.es/group/symfony-
         es) permite buscar en sus archivos de mensajes, por lo que es posible que enc-
         uentres a otros usuarios que han pasado por los mismos problemas.

      ▪ El wiki de Symfony (http://trac.symfony-project.com/#Installingsymfony) contiene
         tutoriales detallados paso a paso sobre la instalación de Symfony que han sido
         creados por otros usuarios.

Si no encuentras la respuesta en esos recursos, puedes preguntar a la comunidad de
Symfony. Las preguntas puedes hacerlas en el foro, en la lista de correo e incluso en el
canal IRC de Symfony (#symfony) para obtener la respuesta de los miembros más activos
de la comunidad.


3.6. Versionado del código fuente
Una vez creada la aplicación, se recomienda empezar con el versionado del código fuente
(también llamado control de versiones). El versionado almacena todas las modificaciones
realizadas en el código, permite acceder a las versiones anteriores de cualquier archivo,


www.librosweb.es                                                                               55
Symfony, la guía definitiva                           Capítulo 3. Ejecutar aplicaciones Symfony


simplifica la creación de parches y permite trabajar en equipo de forma eficiente. Sym-
fony soporta de forma nativa el uso de CVS, aunque recomienda el uso de Subversion
(http://subversion.tigris.org/). Los ejemplos que se muestran a continuación utilizan co-
mandos de Subversion y presuponen que existe un servidor de Subversion instalado y
que se va a crear un nuevo repositorio para el proyecto. Para los usuarios de Windows,
se recomienda utilizar TortoiseSVN (http://tortoisesvn.tigris.org/) como cliente de Sub-
version. La documentación oficial de Subversion es un buen recurso para ampliar los co-
nocimientos sobre el versionado del código y sobre los comandos que utilizan los siguien-
tes ejemplos.

Los siguientes ejemplos requieren que exista una variable de entorno llamada
$SVNREP_DIR y cuyo valor es la ruta completa al repositorio. Si no es posible definir la var-
iable de entorno, en los siguientes comandos se debe escribir la ruta completa al reposi-
torio en vez de $SVNREP_DIR.

En primer lugar se crea un nuevo repositorio para el proyecto miproyecto:
   > svnadmin create $SVNREP_DIR/miproyecto

Después se crea el layout o estructura básica del repositorio mediante los directorios
trunk, tags y branches. El comando necesario es bastante largo:
   > svn mkdir -m "Creacion del layout" file:///$SVNREP_DIR/miproyecto/trunk
   file:///$SVNREP_DIR/miproyecto/tags file:///$SVNREP_DIR/miproyecto/branches

A continuación se realiza la primera versión, para lo que es necesario importar todos los
archivos del proyecto salvo los archivos temporales de cache/ y log/:
   >   cd ~/miproyecto
   >   rm -rf cache/*
   >   rm -rf log/*
   >   svn import -m "Primera importacion" . file:///$SVNREP_DIR/miproyecto/trunk

El siguiente comando permite comprobar si se han subido correctamente los archivos:
   > svn ls file:///$SVNREP_DIR/miproyecto/trunk/

Por el momento todo va bien, ya que ahora el repositorio SVN contiene una versión de
referencia de todos los archivos del proyecto. De esta forma, los archivos del directorio
miproyecto/ deben hacer referencia a los que almacena el repositorio. Para ello, renom-
bra el directorio miproyecto/ (si todo funciona correctamente lo podrás borrar) y descar-
ga los contenidos del repositorio en un nuevo directorio:
   >   cd ~
   >   mv miproyecto miproyecto.original
   >   svn co file:///$SVNREP_DIR/miproyecto/trunk miproyecto
   >   ls miproyecto

Y eso es todo. Ahora ya es posible trabajar con los archivos que se encuentran en el di-
rectorio miproyecto/ y subir todos los cambios al repositorio. Puedes borrar el directorio
miproyecto.original/ porque ya no se utiliza.

Solamente es necesario realizar una última configuración. Si se suben todos los archivos
del directorio al repositorio, se van a copiar algunos archivos innecesarios, como los que
se encuentran en los directorios cache/ y log/. Subversion permite establecer una lista

www.librosweb.es                                                                            56
Symfony, la guía definitiva                              Capítulo 3. Ejecutar aplicaciones Symfony


de archivos que se ignoran al subir los contenidos al repositorio. Además, es preciso es-
tablecer de nuevo los permisos correctos a los directorios cache/ y log/:
   >   cd ~/miproyecto
   >   chmod 777 cache
   >   chmod 777 log
   >   svn propedit svn:ignore log
   >   svn propedit svn:ignore cache

Al ejecutar los comandos anteriores, Subversion muestra el editor de textos configurado
por defecto. Si no se muestra nada, configura el editor de textos que utiliza Subversion
por defecto mediante el siguiente comando:
   > export SVN_EDITOR=<nombre_del_editor_de_textos>
   > svn propedit svn:ignore log
   > svn propedit svn:ignore cache

Para incluir todos los archivos de los directorios, se debe escribir lo siguiente cuando se
muestre el editor de textos:
   *

Para finalizar, guarda los cambios y cierra el editor.


3.7. Resumen
Para probar y jugar con Symfony en un servidor local, la mejor opción es instalar el en-
torno de pruebas o sandbox, que contiene un entorno de ejecución preconfigurado para
Symfony.

Para desarrollar aplicaciones web reales o para instalarlo en un servidor de producción,
se puede optar por la instalación via PEAR o mediante el repositorio de Subversion. Estos
métodos instalan las librerías de Symfony, pero se deben crear manualmente los proyec-
tos y las aplicaciones. El último paso de la configuración de las aplicaciones es la configu-
ración del servidor web, que puede realizarse de muchas formas. Symfony funciona muy
bien con los servidores virtuales y de hecho es el método recomendado.

Si se producen errores durante la instalación, existen muchos tutoriales y preguntas frec-
uentes en el sitio web de Symfony. Incluso es posible trasladar tu problema a la comuni-
dad Symfony para obtener una respuesta en general rápida y efectiva.

Después de crear el proyecto, se recomienda empezar con el versionado del código fuen-
te para realizar el control de versiones.

Una vez que ya se puede utilizar Symfony, es un buen momento para desarrollar la pri-
mera aplicación web básica.




www.librosweb.es                                                                               57
Symfony, la guía definitiva                      Capítulo 4. Introducción a la creación de páginas




Capítulo 4. Introducción a la creación de páginas
Curiosamente, el primer tutorial que utilizan los programadores para aprender cualquier
lenguaje de programación o framework es el que muestra por pantalla el mensaje “¡Hola
Mundo!” (del inglés Hello, world!). Resulta extraño creer que un ordenador pueda ser ca-
paz de saludar a todo el mundo, ya que todos los intentos que ha habido hasta ahora en
el campo de la inteligencia artificial han resultado en unos sistemas artificiales de conver-
sación bastante pobres. No obstante, Symfony no es más tonto que cualquier otro frame-
work, y la prueba es que se puede crear una página que muestre el mensaje “Hola,
<tu_nombre>”.

En este capítulo se muestra como crear un módulo, que es el elemento que agrupa a las
páginas. También se aprende cómo crear una página, que a su vez se divide en una ac-
ción y una plantilla, siguiendo la arquitectura MVC. Las interacciones básicas con las pá-
ginas se realizan mediante enlaces y formularios, por lo que también se muestra como
incluirlos en las plantillas y como manejarlos en las acciones.


4.1. Crear el esqueleto del módulo
Como se vio en el Capítulo 2, Symfony agrupa las páginas en módulos. Por tanto, antes
de crear una página es necesario crear un módulo, que inicialmente no es más que una
estructura vacía de directorios y archivos que Symfony puede reconocer.

La línea de comandos de Symfony automatiza la creación de los módulos. Sólo se necesi-
ta llamar a la tarea init-module indicando como argumentos el nombre de la aplicación y
el nombre del nuevo módulo. En el capítulo anterior se creo una aplicación llamada mia-
plicacion. Para añadirle un módulo llamado mimodulo, se deben ejecutar los siguientes
comandos:
   > cd ~/miproyecto
   > symfony init-module miaplicacion mimodulo

   >>   dir+       ~/miproyecto/apps/miaplicacion/modules/mimodulo
   >>   dir+       ~/miproyecto/apps/miaplicacion/modules/mimodulo/actions
   >>   file+      ~/miproyecto/apps/miaplicacion/modules/mimodulo/actions/actions.class.php
   >>   dir+       ~/miproyecto/apps/miaplicacion/modules/mimodulo/config
   >>   dir+       ~/miproyecto/apps/miaplicacion/modules/mimodulo/lib
   >>   dir+       ~/miproyecto/apps/miaplicacion/modules/mimodulo/templates
   >>   file+      ~/miproyecto/apps/miaplicacion/modules/mimodulo/templates/indexSuccess.php
   >>   dir+       ~/miproyecto/apps/miaplicacion/modules/mimodulo/validate
   >>   file+      ~/miproyecto/test/functional/miaplicacion/mimoduloActionsTest.php
   >>   tokens     ~/miproyecto/test/functional/miaplicacion/mimoduloActionsTest.php
   >>   tokens     ~/miproyecto/apps/miaplicacion/modules/mimodulo/actions/actions.class.php
   >>   tokens     ~/miproyecto/apps/miaplicacion/modules/mimodulo/templates/indexSuccess.php

Además de los directorios actions/, config/, lib/, templates/ y validate/, este coman-
do crea 3 archivos. El archivo que se encuentra en el directorio test/ tiene relación con
las pruebas unitarias, que se ven en el Capítulo 15. El archivo actions.class.php (que se
muestra en el listado 4-1) redirige la acción a la página de bienvenida del módulo por de-
fecto. Por último, el archivo templates/indexSuccess.php está vacío.

www.librosweb.es                                                                               58
Symfony, la guía definitiva                        Capítulo 4. Introducción a la creación de páginas


Listado 4-1 - La acción generada por defecto, en actions/actions.class.php
   <?php

   class mimoduloActions extends sfActions
   {
     public function executeIndex()
     {
       $this->forward('default', 'module');
     }
   }
  NOTA
  Si se abre el archivo actions.class.php generado realmente, su contenido es mucho mayor que
  las pocas líneas mostradas anteriormente, incluyendo un montón de comentarios. Symfony recom-
  ienda utilizar comentarios de PHP para documentar el proyecto y por tanto añade a cada archivo de
  cada clase comentarios que son compatibles con el formato de la herramienta phpDocumentor
  (http://www.phpdoc.org/).

En cada nuevo módulo, Symfony crea una acción por defecto llamada index. La acción
completa se compone del método executeIndex de la acción y del archivo de su plantilla
llamada indexSuccess.php. El significado del prefijo execute y del sufijo Success se expli-
can detalladamente en los Capítulos 6 y 7 respectivamente. Por el momento se puede
considerar que esta forma de nombrar a los archivos y métodos es una convención que
sigue Symfony. Para visualizar la página generada (que se muestra en la figura 4-1) se
debe acceder a la siguiente dirección en un navegador:
   http://localhost/miaplicacion_dev.php/mimodulo/index

En este capítulo no se utiliza la acción index, por lo que se puede borrar el método exe-
cuteIndex() del archivo actions.class.php y también se puede borrar el archivo in-
dexSuccess.php del directorio templates/.

  NOTA
  Symfony permite crear los módulos sin necesidad de utilizar la línea de comandos. Uno de esos
  métodos es crear manualmente todos los directorios y archivos necesarios. En otras ocasiones, las
  acciones y las plantillas de un módulo se emplean para manipular los datos de una tabla de la base
  de datos. Como el código necesario para crear, obtener, actualizar y borrar los datos casi siempre
  es el mismo, Symfony incorpora una técnica llamada scaffolding (literalmente traducido como “an-
  damiaje”) que permite generar de forma automática todo el código PHP del módulo. El Capítulo 14
  contiene los detalles de esta técnica.




www.librosweb.es                                                                                 59
Symfony, la guía definitiva                   Capítulo 4. Introducción a la creación de páginas




                 Figura 4.1. La página de índice generada automáticamente



4.1.1. Añadir una página
En Symfony la lógica o código de las páginas se define en la acción y la presentación se
define en las plantillas. Las páginas estáticas que no requieren de ninguna lógica necesi-
tan definir una acción vacía.

4.1.1.1. Añadir una acción
La página que muestra el mensaje “¡Hola Mundo!” será accedida mediante una acción lla-
mada miAccion. Para crearla, solamente es necesario añadir el método executeMiAccion
en la clase mimoduloActions, como muestra el Listado 4-2.

Listado 4-2 - Añadir una acción es equivalente a añadir un método de tipo exe-
cute en la clase de la acción
   <?php

   class mimoduloActions extends sfActions
   {


www.librosweb.es                                                                            60
Symfony, la guía definitiva                         Capítulo 4. Introducción a la creación de páginas

       public function executeMiAccion()
       {
       }
   }

El nombre del método de la acción siempre es execute + Xxxxxxx + (), donde la segunda
parte del nombre es el nombre de la acción con la primera letra en mayúsculas.

Por tanto, si ahora se accede a la siguiente dirección:
   http://localhost/miaplicacion_dev.php/mimodulo/miAccion

Symfony mostrará un mensaje de error indicando que la plantilla miAccionSuccess.php no
existe. Se trata de un error normal por el momento, ya que las páginas siempre están
formadas por una acción y una plantilla.

  ATENCIÓN
  Las URL (no los dominios) distinguen mayúsculas y minúsculas, y por tanto también las distingue
  Symfony, (aunque el nombre de los métodos en PHP no distingue mayúsculas de minúsculas). Por
  tanto, si se añade un método llamado executemiaccion() o executeMiaccion(), y se intenta ac-
  ceder desde el navegador a miAccion, Symfony muestra un mensaje de error de tipo 404 (Página
  no encontrada).

  Las URL son parte de la respuesta

  Symfony incluye un sistema de enrutamiento que permite separar completamente el nombre real de
  la acción y la forma de la URL que se utiliza para llamar a la acción. De esta forma, es posible per-
  sonalizar las URL como si fueran una parte más de la respuesta. La estructura de directorios del
  servidor o los parámetros de la petición ya no son obstáculos para construir URL con cualquier for-
  mato; la URL de una acción puede construirse siguiendo cualquier formato. Por ejemplo, la URL tí-
  pica de la acción index de un módulo llamado articulo suele tener el siguiente aspecto:
       http://localhost/miaplicacion_dev.php/articulo/index?id=123

  Esta URL se emplea para obtener un artículo almacenado en la base de datos. En el ejemplo anter-
  ior, se obtiene un artículo cuyo identificador es 123, que pertenece a la sección de artículos de Eu-
  ropa y que trata sobre la economía en Francia. Con un simple cambio en el archivo routing.yml,
  la URL anterior se puede construir de la siguiente manera:
       http://localhost/articulos/europa/francia/economia.html

  La URL que se obtiene no solo es mejor desde el punto de vista de los buscadores, sino que es
  mucho más significativa para el usuario medio, que incluso puede utilizar la barra de direcciones
  como si fuera una especie de línea de comandos para realizar consultas a medida, como por ejem-
  plo la siguiente URL:
       http://localhost/articulos/etiquetas/economia+francia+euro

  Symfony es capaz de procesar y generar este tipo de URL inteligentes. El sistema de enrutamiento
  es capaz de extraer automáticamente los parámetros de la petición y ponerlos a disposición de la
  acción. También es capaz de formatear los enlaces incluidos en la respuesta para que también se-
  an enlaces de tipo inteligente. El Capítulo 9 explica en detalle el sistema de enrutamiento.

  En resumen, el nombrado de las acciones no se debe realizar teniendo en cuenta la URL que se
  utilizará para acceder a ellas, sino que se deberían nombrar según la función de la acción dentro de

www.librosweb.es                                                                                    61
Symfony, la guía definitiva                        Capítulo 4. Introducción a la creación de páginas


  la aplicación. El nombre de la acción explica su funcionalidad, por lo que suele ser un verbo en su
  forma de infinitivo (como por ejemplo ver, listar, modificar, etc.). El nombre de las acciones se
  puede ocultar a los usuarios, por lo que si es necesario, se pueden utilizar nombres muy explícitos
  para las acciones (como por ejemplo listarPorNombre o verConComentarios). Con este tipo de
  nombres, no son necesarios demasiados comentarios para explicar la funcionalidad de la acción y
  el código fuente resultante es mucho más fácil de comprender.

4.1.1.2. Añadir una plantilla
La acción espera una plantilla para mostrarse en pantalla. Una plantilla es un archivo que
está ubicado en el directorio templates/ de un módulo, y su nombre está compuesto por
el nombre de la acción y el resultado de la misma. El resultado por defecto es success (e-
xitoso), por lo que el archivo de plantilla que se crea para la acción miAccion se llamará
miAccionSuccess.php.

Se supone que las plantillas sólo deben contener código de presentación, así que procura
mantener la menor cantidad de código PHP en ellas como sea posible. De hecho, una pá-
gina que muestre “¡Hola, mundo!” puede tener una plantilla tan simple como la del Lista-
do 4-3.

Listado 4-3 - La plantilla mimodulo/templates/miAccionSuccess.php
   <p>¡Hola, mundo!</p>

Si necesitas ejecutar algún código PHP en la plantilla, es mejor evitar la sintaxis usual de
PHP, como se muestra en el Listado 4-4. En su lugar, es preferible escribir las plantillas
utilizando la sintaxis alternativa de PHP, mostrada en el Listado 4-5, para mantener el
código entendible para personas sin conocimientos de PHP. De esta forma, no sólo el có-
digo final estará correctamente indentado, sino que además ayudará a mantener el códi-
go complejo de PHP en la acción, dado que sólo las estructuras de control (if, foreach,
while y demás) poseen una sintaxis alternativa.

Listado 4-4 - La sintaxis tradicional de PHP, buena para las acciones, pero mala
para las plantillas
   <p>¡Hola, mundo!</p>
   <?php

   if ($prueba)
   {
     echo "<p>".time()."</p>";
   }

   ?>

Listado 4-5 - La sintaxis alternativa de PHP, buena para las plantillas
   <p>¡Hola, mundo!</p>
   <?php if ($prueba): ?>
   <p><?php echo time(); ?></p>
   <?php endif; ?>




www.librosweb.es                                                                                  62
Symfony, la guía definitiva                          Capítulo 4. Introducción a la creación de páginas


  SUGERENCIA
  Una buena regla para comprobar si la sintaxis de la plantilla es lo suficientemente legible, es que el
  archivo no debe contener código HTML generado por PHP mediante la función echo, ni tampoco
  llaves. Y en la mayoría de los casos, al abrir una etiqueta <?php, debería cerrarse con ?> en la mis-
  ma línea.


4.1.2. Transfiriendo información de la acción a la plantilla
La tarea de la acción es realizar los cálculos complejos, obtener datos, realizar compro-
baciones, y crear o inicializar las variables necesarias para que se presenten o se utilicen
en la plantilla. Symfony hace que los atributos de la clase de la acción (disponibles vía
$this->nombreDeVariable en la acción), estén directamente accesibles en la plantilla en el
ámbito global (vía $nombreVariable). Los listados 4-6 y 4-7 muestran cómo pasar infor-
mación de la acción a la plantilla.

Listado 4-6 - Configurando un atributo de acción dentro de ella para hacerlo dis-
ponible para la plantilla
   <?php

   class mimoduloActions extends sfActions
   {
     public function executeMiAccion()
     {
       $hoy = getdate();
       $this->hora = $hoy['hours'];
     }
   }

Listado 4-7 - La plantilla tiene acceso directo a los atributos de la acción
   <p>¡Hola, Mundo!</p>
   <?php if ($hora >= 18): ?>
   <p>Quizás debería decir buenas tardes. Ya son las <?php echo $hora ?>.</p>
   <?php endif; ?>


  NOTA
  La plantilla es capaz de acceder a algunos datos sin necesidad de definir variables en la acción.
  Cada plantilla puede invocar métodos de los objetos $sf_context, $sf_request, sf_params y
  $sf_user. Esos métodos contienen datos relacionados con el contexto actual, la petición y sus
  parámetros, y la sesión. Muy pronto se muestra cómo utilizarlos de manera eficiente.


4.2. Obteniendo información del usuario a través de formularios
Los formularios constituyen un buen método para obtener información del usuario. Di-
señar formularios y sus elementos en HTML a veces puede ser tedioso, especialmente si
se quieren obtener páginas que validen en XHTML. Se pueden incluir elementos de for-
mulario en las plantillas de Symfony de manera tradicional, como se muestra en el Lista-
do 4-8, pero Symfony provee helpers que hacen mucho más sencilla esta tarea.

Listado 4-8 - Las plantillas pueden incluir código HTML tradicional


www.librosweb.es                                                                                     63
Symfony, la guía definitiva                    Capítulo 4. Introducción a la creación de páginas

   <p>¡Hola, Mundo!</p>
   <?php if ($hora >= 18): ?>
   <p>Quizás debería decir buenas tardes. Ya son las <?php echo $hora ?>.</p>
   <?php endif; ?>
   <form method="post" action="/miaplicacion_dev.php/mimodulo/otraAccion">
     <label for="nombre">¿Cómo te llamas?</label>
     <input type="text" name="nombre" id="nombre" value="" />
     <input type="submit" value="Ok" />
   </form>

Un helper es una función PHP definida en Symfony que está pensada para ser utilizada
desde las plantillas. Devuelven algo de código HTML y es más rápido de usar que insertar
el código HTML manualmente. Usando los helpers de Symfony, se puede obtener el re-
sultado del Listado 4-8 con el código que se muestra en el Listado 4-9.

Listado 4-9 - Es más rápido y simple utilizar helpers que utilizar etiquetas HTML
   <p>¡Hola, Mundo!</p>
   <?php if ($hora >= 18): ?>
   <p>Quizás debería decir buenas tardes. Ya son las <?php echo $hora ?>.</p>
   <?php endif; ?>
   <?php echo form_tag('mimodulo/otraAccion') ?>
     <?php echo label_for('nombre', '¿Cómo te llamas?') ?>
     <?php echo input_tag('nombre') ?>
     <?php echo submit_tag('Ok') ?>
   </form>

[side|Los helpers están aquí para ayudarte] Si en el ejemplo del Listado 4-9 crees que
utilizar helpers no es tan rápido como escribir HTML, considera este otro ejemplo:
    <?php
   $lista_tarjetas = array(
      'VISA' => 'Visa',
      'MAST' => 'MasterCard',
      'AMEX' => 'American Express',
      'DISC' => 'Discover');
   echo select_tag('tipo_tarjeta', options_for_select($lista_tarjetas, 'AMEX'));
   ?>

El código anterior genera el siguiente código HTML:
   <select name="tipo_tarjeta" id="tipo_tarjeta">
     <option value="VISA">Visa</option>
     <option value="MAST">MasterCard</option>
     <option value="AMEX" selected="selected">American Express</option>
     <option value="DISC">Discover</option>
   </select>

La ventaja de utilizar helpers en las plantillas es que aumentan la velocidad de escritura
del código, mejoran su claridad y lo hacen más conciso. El único inconveniente es el
tiempo necesario para aprender a utilizarlos, aunque no te costará más tiempo que el de-
dicado a leer este libro. Además, para escribir la instrucción <?php echo ?> necesaria,
puedes utilizar los atajos que seguramente incorpora tu editor de texto favorito. En defi-
nitiva, aunque es posible crear plantillas sin utilizar los helpers de Symfony, sería un gran
error escribir directamente el código HTML en las plantillas y sería mucho menos diverti-
do. [/note]

www.librosweb.es                                                                             64
Symfony, la guía definitiva                      Capítulo 4. Introducción a la creación de páginas


Por otra parte, el uso de etiquetas cortas de apertura (<?=, equivalente a <?php echo) no
se recomienda para aplicaciones web profesionales, debido a que el servidor web de pro-
ducción puede ser capaz de entender más de un lenguaje de script, y por tanto, confun-
dirse. Además, las etiquetas cortas de apertura no funcionan con la configuración por de-
fecto de PHP y necesitan de ajustes en el servidor para ser activadas. Por último, a la ho-
ra de lidiar con XML y validación, fallará inmediatamente porque <? tiene un significado
especial en XML.

Los formularios merecen un capítulo completo para ellos, debido a que Symfony provee
muchas herramientas, sobre todo helpers, para facilitar las cosas. Aprenderás sobre es-
tos helpers en el Capítulo 10.


4.3. Enlazando a otra acción
Ya se ha comentado que existe una independencia total entre el nombre de la acción y la
URL utilizada para llamarla, por lo que si se crea un enlace a otraAccion en una plantilla
como en el Listado 4-10, sólo funcionará con el enrutamiento establecido por defecto. Si
más tarde se decide modificar la manera de mostrar las URL, entonces será necesario ve-
rificar todas las plantillas para modificar los hipervínculos.

Listado 4-10 - Hipervínculos, la forma clásica
   <a href="/miaplicacion_dev.php/mimodulo/otraAccion?nombre=anonimo">
     Nunca digo mi nombre
   </a>

Para evitar este inconveniente, es necesario siempre utilizar el helper link_to() para cre-
ar enlaces a las acciones de la aplicación. El Listado 4-11 demuestra el uso del helper de
hipervínculos.

Listado 4-11 - El helper link_to()
   <p>¡Hola, Mundo!</p>
   <?php if ($hora >= 18): ?>
   <p>Quizás debería decir buenas tardes. Ya son las <?php echo $hora ?>.</p>
   <?php endif; ?>
   <?php echo form_tag('mimodulo/otraAccion') ?>
     <?php echo label_for('nombre', '¿Cómo te llamas?') ?>
     <?php echo input_tag('nombre') ?>
     <?php echo submit_tag('Ok') ?>
     <?php echo link_to('Nunca digo mi nombre','mimodulo/otraAccion?nombre=anonimo') ?>
   </form>

El código HTML resultante será el mismo que el anterior, excepto que, al modificar las re-
glas de enrutamiento, todas las plantillas funcionarán correctamente y modificarán las
URL como corresponde.

El helper link_to(), al igual que muchos otros, acepta un argumento para opciones es-
peciales y atributos de etiqueta adicionales. El Listado 4-12 muestra un ejemplo de un
argumento option y su HTML resultante. El argumento option puede ser tanto un array
asociativo como una simple cadena de texto mostrando pares de clave=valor separadas
por espacios.


www.librosweb.es                                                                               65
Symfony, la guía definitiva                      Capítulo 4. Introducción a la creación de páginas


Listado 4-12 - La mayoría de los helpers aceptan un argumento option
   // Argumento "option" como un array asociativo
   <?php echo link_to('Nunca digo mi nombre', 'mimodulo/otraAccion?nombre=anonimo',
     array(
       'class'    => 'enlace_especial',
       'confirm' => '¿Estás seguro?',
       'absolute' => true
   )) ?>

   // Argumento "option" como una cadena de texto
   <?php echo link_to('Nunca digo mi nombre', 'mimodulo/otraAccion?nombre=anonimo',
     'class=enlace_especial confirm=¿Estás seguro? absolute=true') ?>

   // Las 2 funciones devuelven el mismo resultado
    => <a class="enlace_especial" onclick="return confirm('¿Estás seguro?');"
       href="http://localhost/miaplicacion_dev.php/mimodulo/otraAccion/nombre/anonimo">
       Nunca digo mi nombre</a>

Siempre que se utiliza un helper de Symfony que devuelve una etiqueta HTML, es posible
insertar atributos de etiqueta adicionales (como el atributo class en el ejemplo del Lista-
do 4-12) en el argumento option. Incluso es posible escribir estos atributos a la vieja
usanza que utiliza HTML 4.0 (sin comillas dobles), y Symfony se encargará de mostrarlos
correctamente formateados en XHTML. Esta es otra razón por la que los helpers son más
rápidos de escribir que el HTML puro.

  NOTA
  Dado que requiere un procesado y transformación adicional, la sintaxis de cadena de texto es un
  poco más lenta que la sintaxis en forma de array.

Al igual que los helpers para formulario, los helpers de hipervínculo son muchos y tienen
muchas opciones. El Capítulo 9 los describirá en detalle.


4.4. Obteniendo información de la petición
El método getRequestParameter() del objeto sfActions permite recuperar desde la acción
los datos relacionados con la información que envía el usuario a través de un formulario
(normalmente en una petición POST) o a través de la URL (mediante una petición GET).
El Listado 4-13 muestra cómo es posible obtener el valor del parámetro name en
otraAccion.

Listado 4-13 - Recuperando datos de la petición desde el parámetro de petición,
dentro de una acción
   <?php

   class mimoduloActions extends sfActions
   {
     ...

      public function executeOtraAccion()
      {
        $this->nombre = $this->getRequestParameter('nombre');



www.librosweb.es                                                                               66
Symfony, la guía definitiva                           Capítulo 4. Introducción a la creación de páginas

       }
   }

Si la manipulación de datos es simple, ni siquiera es necesario utilizar la acción para re-
cuperar los parámetros de petición. La plantilla tiene acceso a un objeto llamado $sf_pa-
rams, el cual ofrece un método get() para recuperar los parámetros de la petición, tal y
como el método getRequestParameter() en la acción.

Dada una acción executeOtraAccion vacía, el Listado 4-14 muestra cómo la plantilla
otraAccionSuccess.php recupera el mismo parámetro name.

Listado 4-14 - Obteniendo datos del parámetro de petición directamente en la
plantilla
   <p>Hola, <?php echo $sf_params->get('nombre') ?>!</p>
  NOTA
  ¿Y por qué no utilizar en cambio las variables $_POST, $_GET, or $_REQUEST? Porque entonces las
  URL serían formateadas de manera diferente (como en http://localhost/articulos/europa/fran-
  cia/economia.html, sin ? ni =), las variables comunes de PHP ya no funcionarían, y sólo el sistema
  de enrutamiento sería capaz de recuperar los parámetros de petición. Además, seguramente quie-
  ras agregar un filtro a los datos del de la petición para prevenir código malicioso, lo cual sólo es po-
  sible si se mantienen todos los parámetros de petición en un contenedor de parámetros.

El objeto $sf_params es más potente que simplemente añadir una especie de getter a un
array. Por ejemplo, si sólo se desea probar la existencia de un parámetro de petición, se
puede utilizar simplemente el método $sf_parameter->has(), en lugar de comprobar el
valor en cuestión con get(), tal como en el Listado 4-15.

Listado 4-15 - Comprobando la existencia de un parámetro de petición en la
plantilla
   <?php if ($sf_params->has('nombre')): ?>
     <p>¡Hola, <?php echo $sf_params->get('nombre') ?>!</p>
   <?php else: ?>
     <p>¡Hola, Juan Pérez!</p>
   <?php endif; ?>

Como puede que hayas adivinado, el código anterior puede escribirse en una sola línea.
Al igual que la mayoría de los métodos getter de Symfony, tanto el método getReq-
uestParameter() en la acción, como el método $sf_params->get() en la plantilla (el cual,
de hecho, llama al mismo método en el mismo objeto), acepta un segundo argumento: el
valor por defecto a utilizar si dicho parámetro de petición no está presente.
   <p>¡Hola, <?php echo $sf_params->get('nombre', 'Juan Pérez') ?>!</p>


4.5. Resumen
En Symfony, las páginas están compuestas por una acción (un método del archivo act-
ions/actions.class.php con el prefijo execute) y una plantilla (un archivo en el directorio
templates/, normalmente terminado en Success.php). Las páginas se agrupan en módu-
los, de acuerdo a su función en la aplicación. Escribir plantillas es muy sencillo con la
ayuda de los helpers, funciones provistas por Symfony para generar código HTML.

www.librosweb.es                                                                                       67
Symfony, la guía definitiva                   Capítulo 4. Introducción a la creación de páginas


Además es necesario pensar que la URL es parte de la respuesta, por lo que se puede
formatear de cualquier forma que se necesite, sólo debes abstenerte de utilizar cualquier
referencia directa a la URL en el nombre de la acción o al recuperar un parámetro de
petición.

Una vez aprendidos estos principios básicos, es posible escribir una aplicación web com-
pleta con Symfony. Pero te costaría mucho tiempo, dado que casi cualquier tarea a com-
pletar durante el transcurso del desarrollo de la aplicación, se simplifica de una forma u
otra por alguna funcionalidad de Symfony...motivo por el que este libro aún no termina.




www.librosweb.es                                                                            68
Symfony, la guía definitiva                                  Capítulo 5. Configurar Symfony




Capítulo 5. Configurar Symfony
Para simplificar su uso, Symfony define una serie de convenciones o normas que se ajus-
tan a los requisitos habituales de las aplicaciones web estándar. De todas formas, los ar-
chivos de configuración, a pesar de ser tan sencillos de utilizar, son lo suficientemente
potentes como para personalizar cualquier aspecto del framework y la forma en que inte-
ractúan las aplicaciones. También es posible con estos archivos de configuración añadir
parámetros específicos para las aplicaciones.

En este capítulo se explica cómo funciona el mecanismo de configuración:

      ▪ La configuración de Symfony se guarda en archivos escritos con YAML, aunque se
        puede utilizar otro formato.

      ▪ En la estructura de directorios del proyecto, existen archivos de configuración a
        nivel de proyecto, de aplicación y de módulo.

      ▪ También es posible definir conjuntos de opciones de configuración. En Symfony,
        un conjunto de opciones de configuración se llama entorno.

      ▪ Desde cualquier punto del código de la aplicación se puede acceder a los valores
        establecidos en los archivos de configuración.

      ▪ Además, Symfony permite utilizar código PHP dentro de los archivos YAML y
        algún que otro truco más para hacer más flexible el sistema de configuración.


5.1. El sistema de configuración
La mayoría de aplicaciones web comparten una serie de características, independiente-
mente de su finalidad. Por ejemplo, es habitual restringir algunas partes de la aplicación
a una serie de usuarios, utilizar un layout común para mostrar todas las páginas, los for-
mularios deben volver a mostrar los datos que ha introducido el usuario después de una
validación errónea. El framework define el comportamiento básico de estas característi-
cas y el programador puede adaptar cada una mediante las opciones de configuración.
Esta forma de trabajar ahorra mucho tiempo de desarrollo, ya que muchos cambios im-
portantes no necesitan modificar ni siquiera una línea de código, aunque estos cambios
impliquen muchos cambios internos. Además se trata de una forma mucho más eficiente,
ya que permite asegurar que toda la configuración se encuentra en un lugar único y fácil-
mente localizable.

No obstante, este método también tiene dos inconvenientes muy importantes:

      ▪ Los programadores acaban escribiendo archivos XML complejos y muy largos.

      ▪ En una arquitectura basada en PHP, cada petición consumiría mucho más tiempo
        de proceso.

Considerando todas estas desventajas, Symfony utiliza solamente lo mejor de los archi-
vos de configuración. De hecho, el objetivo del sistema de configuración de Symfony es
ser:




www.librosweb.es                                                                        69
Symfony, la guía definitiva                                  Capítulo 5. Configurar Symfony


      ▪ Potente: todo lo que puede ser gestionado con archivos de configuración, se ges-
        tiona con archivos de configuración.

      ▪ Simple: muchas de las características de la configuración no se utilizan habitual-
        mente, por lo que las aplicaciones normales no tienen que tratar con ellas.

      ▪ Sencillo: los archivos de configuración son sencillos de leer, de modificar y de
        crear por parte de los desarrolladores.

      ▪ Personalizable: el lenguaje que se utiliza por defecto en los archivos de configu-
        ración es YAML, pero se puede cambiar por archivos INI, XML o cualquier otro
        formato que prefiera el programador.

      ▪ Rápido: la aplicación nunca procesa los archivos de configuración, sino que se
        encarga de ello el sistema de configuración, que compila todos los archivos de
        configuración en trozos de código PHP que se pueden procesar muy rápidamente.

5.1.1. Sintaxis YAML y convenciones de Symfony
Symfony utiliza por defecto el formato YAML para la configuración, en vez de los tradicio-
nales formatos INI y XML. El formato YAML indica su estructura mediante la tabulación y
es muy rápido de escribir. El Capítulo 1 ya describe algunas de sus ventajas y las reglas
más básicas. No obstante, se deben tener presentes algunas convenciones al escribir ar-
chivos YAML. En esta sección se mencionan las convenciones o normas más importantes.
El sitio web de YAML (http://www.yaml.org/) contiene la lista completa de normas del
formato.

En primer lugar, no se deben utilizar tabuladores en los archivos YAML, sino que
siempre se deben utilizar espacios en blanco. Los sistemas que procesan YAML no son
capaces de tratar con los tabuladores, por lo que la tabulación de los archivos se debe
crear con espacios en blanco como se muestra en el listado 5-1 (en YAML un tabulador se
indica mediante 2 espacios en blanco seguidos).

Listado 5-1 - Los archivos YAML no permiten los tabuladores
        # No utilices tabuladores
        all:
        -> mail:
        -> -> webmaster: webmaster@ejemplo.com

        # Utiliza espacios en blanco
        all:
          mail:
             webmaster: webmaster@ejemplo.com

Si los parámetros son cadenas de texto que contienen espacios en blanco al principio o al
final, se debe encerrar la cadena entera entre comillas simples. Si la cadena de texto
contiene caracteres especiales, también se encierran con comillas simples, como se
muestra en el listado 5-2.

Listado 5-2 - Las cadenas de texto especiales deben encerrarse entre comillas
simples




www.librosweb.es                                                                        70
Symfony, la guía definitiva                                      Capítulo 5. Configurar Symfony

   error1: Este campo es obligatorio
   error2: ' Este campo es obligatorio    '

   # Las comillas simples que aparecen dentro de las cadenas de
   # texto, se deben escribir dos veces
   error3: 'Este <nowiki>''campo''</nowiki> es obligatorio'

Se pueden escribir cadenas de texto muy largas en varias líneas, además de juntar cade-
nas escritas en varias líneas. En este último caso, se debe utilizar un carácter especial
para indicar que se van a escribir varias líneas (se puede utilizar > o |) y se debe añadir
una pequeña tabulación (dos espacios en blanco) a cada línea del grupo de cadenas de
texto. El listado 5-3 muestra este caso.

Listado 5-3 - Definir cadenas de texto muy largas y cadenas de texto multi-línea
   # Las cadenas de texto muy largas se pueden escribir en
   # varias líneas utilizando el carácter >
   # Posteriormente, cada nueva línea se transforma en un
   # espacio en blanco para formar la cadena de texto original.
   # De esta forma, el archivo YAML es más fácil de leer
   frase_para_recordar: >
     Vive como si fueras a morir mañana y
     aprende como si fueras a vivir para siempre.

   # Si un texto está formado por varias líneas, se utiliza
   # el carácter | para separar cada nueva línea. Los espacios
   # en blanco utilizados para tabular las líneas no se tienen
   # en cuenta.
   direccion: |
     Mi calle, número X
     Nombre de mi ciudad
     CP XXXXX

Los arrays se definen mediante corchetes que encierran a los elementos o mediante la
sintaxis expandida que utiliza guiones medios para cada elemento del array, como se
muestra en el listado 5-4.

Listado 5-4 - Sintaxis de YAML para incluir arrays
   # Sintaxis abreviada para los arrays
   idiomas: [ Alemán, Francés, Inglés, Italiano ]

   # Sintaxis expandida para los arrays
   idiomas:
     - Alemán
     - Francés
     - Inglés
     - Italiano

Para definir arrays asociativos, se deben encerrar los elementos mediante llaves ({ y }) y
siempre se debe insertar un espacio en blanco entre la clave y el valor de cada par cla-
ve: valor. También existe una sintaxis expandida que requiere indicar cada par clave:
valor en una nueva línea y con una tabulación (es decir, con 2 espacios en blanco delan-
te) como se muestra en el listado 5-5.

Listado 5-5 - Sintaxis de YAML para incluir arrays asociativos

www.librosweb.es                                                                            71
Symfony, la guía definitiva                                      Capítulo 5. Configurar Symfony

   # Sintaxis incorrecta, falta un espacio después de los 2 puntos
   mail: {webmaster:webmaster@ejemplo.com,contacto:contacto@ejemplo.com}

   # Sintaxis abreviada correcta para los array asociativos
   mail: { webmaster: webmaster@ejemplo.com, contacto: contacto@ejemplo.com }

   # Sintaxis expandida para los arrays asociativos
   mail:
     webmaster: webmaster@ejemplo.com
     contacto: contacto@ejemplo.com

Para los parámetros booleanos, se pueden utilizar los valores on, 1 o true para los valo-
res verdaderos y off, 0 o false para los valores falsos. El listado 5-6 muestra los posibles
valores booleanos.

Listado 5-6 - Sintaxis de YAML para los valores booleanos
   valores_verdaderos:        [ on, 1, true ]
   valores_falsos:            [ off, 0, false ]

Es recomendable añadir comentarios (que se definen mediante el carácter #) y todos los
espacios en blanco adicionales que hagan falta para hacer más fáciles de leer los archivos
YAML, como se muestra en el listado 5-7.

Listado 5-7 - Comentarios en YAML y espacios adicionales para alinear valores
   # Esta línea    es un comentario
   mail:
     webmaster:    webmaster@ejemplo.com
     contacto:     contacto@ejemplo.com
     admin:        admin@ejemplo.com    # se añaden espacios en blanco para alinear los valores

En algunos archivos de configuración de Symfony, se ven líneas que empiezan por # (y
por tanto se consideran comentarios y se ignoran) pero que parecen opciones de configu-
ración correctas. Se trata de una de las convenciones de Symfony: la configuración por
defecto, heredada de los archivos YAML del núcleo de Symfony, se repite en forma de lí-
neas comentadas a lo largo de los archivos de configuracion de cada aplicación, con el ú-
nico objetivo de informar al desarrollador. De esta forma, para modificar esa opción de
configuración, solamente es necesario eliminar el carácter de los comentarios y estable-
cer su nuevo valor. El listado 5-8 muestra un ejemplo.

Listado 5-8 - La configuración por defecto se muestra en forma de comentarios
   # Por defecto la cache está desactivada
   settings:
   # cache: off

   # Para modificar esta opción, se debe descomentar la línea
   settings:
     cache: on

En ocasiones, Symfony agrupa varias opciones de configuración en categorías. Todas las
opciones que pertenecen a una categoría se muestran tabuladas y bajo el nombre de esa
categoría. La configuración es más sencilla de leer si se agrupan las listas largas de pares
clave: valor. Los nombres de las categorías comienzan siempre con un punto (.) y el
listado 5-19 muestra un ejemplo de uso de categorías.

www.librosweb.es                                                                            72
Symfony, la guía definitiva                                             Capítulo 5. Configurar Symfony


Listado 5-9 - Los nombres de categorías son como los nombres de las clave, pe-
ro empiezan con un punto
   all:
     .general:
        impuestos:    19.6

   mail:
     webmaster:       webmaster@ejemplo.com

En el ejemplo anterior, mail es una clave y general sólo es el nombre de la categoría. En
realidad, el archivo YAML se procesa como si no existiera el nombre de la categoría, es
decir, como se muestra en el listado 5-10. El parámetro impuestos realmente es descend-
iente directo de la clave all.

Listado 5-10 - Los nombres de categorías solo se utilizan para hacer más fácil
de leer los archivos YAML y la aplicación los ignora
   all:
     impuestos:       19.6

   mail:
     webmaster:       webmaster@ejemplo.com

  Si no te gusta YAML

  YAML solamente es una interfaz para definir las opciones que utiliza el código PHP, por lo que la
  configuración definida mediante YAML se transforma en código PHP. Si ya has accedido al menos
  una vez a la aplicación, comprueba la cache de su configuración (por ejemplo en cache/miapli-
  cacion/dev/config/). En ese directorio se encuentran los archivos PHP generados por la confi-
  guración YAML. Más adelante se detalla la cache de la configuración.

  Lo mejor de todo es que si no quieres utilizar archivos YAML, puedes realizar la misma configura-
  ción a mano o mediante otros formatos (XML, INI, etc.) Más adelante en este libro se comentan
  otras formas alternativas a YAML para realizar la configuración e incluso se muestra como modifi-
  car las funciones de Symfony que se encargan de procesar la configuración (en el Capítulo 19). Uti-
  lizando estas técnicas, es posible evitar los archivos de configuración e incluso definir tu propio for-
  mato de configuración.


5.1.2. ¡Socorro, los archivos YAML han roto la aplicación!
Los archivos YAML se procesan y se transforman en array asociativos y arrays normales
de PHP. Después, estos valores transformados son los que se utilizan en la aplicación pa-
ra modificar el comportamiento de la vista, el modelo y el controlador. Por este motivo,
cuando existe un error en un archivo YAML, normalmente no se detecta hasta que se uti-
liza ese valor específico. Para complicar las cosas, el error o la excepción que se muestra
no siempre indica de forma clara que puede tratarse de un error en los archivos YAML de
configuración.

Si la aplicación deja de funcionar después de un cambio en la configuración, se debe
comprobar que no se ha cometido alguno de los errores típicos de los desarrolladores
principiantes con YAML:



www.librosweb.es                                                                                       73
Symfony, la guía definitiva                                     Capítulo 5. Configurar Symfony


      ▪ No existe un espacio en blanco entre la clave y su valor:
   clave1:valor1        # Falta un espacio después del :

      ▪ Alguna clave de una secuencia de valores está mal tabulada:
   all:
     clave1: valor1
      clave2: valor2    # Su tabulación no es igual que los otros miembros de la secuencia
     clave3: valor3

      ▪ Alguna clave o valor contiene caracteres reservados por YAML que no han sido
        encerrados por las comillas simples:
   mensaje: dile lo siguiente: hola      # :, [, ], { y } están reservados por YAML
   mensaje: 'dile lo siguiente: hola'    # Sintaxis correcta

      ▪ La línea que se modifica está comentada:
   # clave: valor       # No se tiene en cuenta porque empieza por #

      ▪ Existen 2 claves iguales con diferentes valores dentro del mismo nivel:
   clave1: valor1
   clave2: valor2
   clave1: valor3       # clave1 está definida 2 veces, solo se tiene en cuenta su último
   valor

      ▪ Todos los valores se consideran cadenas de texto, salvo que se convierta de for-
        ma explícita su valor:
   ingresos: 12,345     # El valor es una cadena de texto y no un número, salvo que se
   convierta


5.2. Un vistazo general a los archivos de configuración
La configuración de las aplicaciones realizadas con Symfony se distribuye en varios archi-
vos según su propósito. Los archivos contienen definiciones de parámetros, normalmente
llamadas opciones de configuración. Algunos parámetros se pueden redefinir en varios
niveles de la aplicación web (proyecto, aplicación y módulo) y otros parámetros son ex-
clusivos de algún nivel. En los siguientes capítulos se muestran los diversos archivos de
configuración relacionados con el tema de cada capítulo. En el Capítulo 19 se explica la
configuración avanzada.

5.2.1. Configuración del Proyecto
Symfony crea por defecto algunos archivos de configuración relacionados con el proyec-
to. El directorio miproyecto/config/ contiene los siguientes archivos:

      ▪ config.php: se trata del primer archivo que se ejecuta con cada petición o co-
        mando. Contiene la ruta a los archivos del framework y se puede modificar si se
        ha realizado una instalación personalizada. Se pueden añadir instrucciones defi-
        ne de PHP al final de este archivo para que esas constantes sean accesibles en
        cualquier aplicación del proyecto. El Capítulo 19 muestra el uso más avanzado de
        este archivo.



www.librosweb.es                                                                             74
Symfony, la guía definitiva                                     Capítulo 5. Configurar Symfony


      ▪ databases.yml: contiene la definición de los accesos a bases de datos y las opcio-
        nes de conexión de cada acceso (servidor, nombre de usuario, contraseña, nom-
        bre de base de datos, etc.) El Capítulo 8 lo explica en detalle. Sus parámetros se
        pueden redefinir en el nivel de la aplicación.

      ▪ properties.ini: contiene algunos parámetros que utiliza la herramienta de línea
        de comandos, como son el nombre del proyecto y las opciones para conectar con
        servidores remotos. El Capítulo 16 muestra las opciones de este archivo.

      ▪ rsync_exclude.txt: indica los directorios que se excluyen durante la sincroniza-
        ción entre servidores. El Capítulo 16 también incluye una explicación de este
        archivo.

      ▪ schema.yml y propel.ini: son los archivos de configuración que utiliza Propel pa-
        ra el acceso a los datos (recuerda que Propel es el sistema ORM que incorpora
        Symfony). Se utilizan para que las librerías de Propel puedan interactuar con las
        clases de Symfony y con los datos de la aplicación. schema.yml contiene la repre-
        sentación del modelo de datos relacional del proyecto. propel.ini se genera de
        forma automática y es muy probable que no necesites modificarlo. Si no se utiliza
        Propel, estos dos archivos son innecesarios. El Capítulo 8 explica en detalle el uso
        de estos dos archivos.

La mayoría de estos archivos los utilizan componentes externos o la línea de comandos e
incluso algunos son procesados antes de que se inicie la herramienta que procesa archi-
vos en formato YAML. Por este motivo, algunos de estos archivos no utilizan el formato
YAML.

5.2.2. Configuración de la Aplicación
La configuración de la aplicación es la parte más importante de la configuración. La confi-
guración se distribuye de la siguiente forma: el controlador frontal (que se encuentra en
el directorio web/) contiene la definición de las constantes principales, el directorio con-
fig/ de la aplicación contiene diversos archivos en formato YAML, los archivos de inter-
nacionalización se encuentran en el directorio i18n/ y también existen otros archivos del
framework que no son visibles pero que almacenan configuración adicional de la
aplicación.

5.2.2.1. Configuración del Controlador Frontal
La primera configuración de la aplicación se encuentra en su controlador frontal, que es
el primer script que se ejecuta con cada petición. El listado 5-11 muestra el código por
defecto del controlador frontal generado automáticamente:

Listado 5-11 - El controlador frontal de producción generado automáticamente
   <?php

   define('SF_ROOT_DIR',      realpath(dirname(__FILE__).'/..'));
   define('SF_APP',           'miaplicacion');
   define('SF_ENVIRONMENT',   'prod');
   define('SF_DEBUG',         false);


www.librosweb.es                                                                           75
Symfony, la guía definitiva                                           Capítulo 5. Configurar Symfony



   require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'

   sfContext::getInstance()->getController()->dispatch();

Después de definir el nombre de la aplicación (miaplicacion) y el entorno en el que se
ejecuta la aplicación (prod), se carga el archivo general de configuración y se despacha la
petición (dispatching). En este archivo se definen algunas constantes importantes:

      ▪ SF_ROOT_DIR: directorio raíz del proyecto (en general no hay que modificar su va-
        lor, salvo que se cambie la estructura de archivos del proyecto).

      ▪ SF_APP: el nombre de la aplicación. Es necesario para determinar las rutas de los
        archivos.

      ▪ SF_ENVIRONMENT: nombre del entorno en el que se ejecuta la aplicación (prod, dev
        o cualquier otro valor que se haya definido). Se utiliza para determinar las opcio-
        nes de configuración que se utilizan. Al final de este capítulo se explican los en-
        tornos de ejecución.

      ▪ SF_DEBUG: activa el modo de depuración de la aplicación (el Capítulo 16 contiene
        los detalles).

Cuando se quiere cambiar alguno de estos valores, normalmente se crea un nuevo con-
trolador frontal. El siguiente capítulo explica su funcionamiento y cómo crear nuevos
controladores.

  El directorio raíz puede estar en cualquier parte

  Los únicos archivos que se pueden acceder desde Internet son los que se encuentran en el directo-
  rio web del proyecto (es decir, el directorio llamado web/). Los scripts de los controladores fronta-
  les, las imágenes, las hojas de estilos y los archivos JavaScript son públicos. El resto de archivos
  deben estar fuera de la raíz del servidor web, por lo que pueden estar en cualquier sitio.

  El controlador frontal accede a los archivos que no son públicos mediante la ruta definida en
  SF_ROOT_DIR. Habitualmente el directorio raíz del proyecto se encuentra en el nivel inmediata-
  mente superior al directorio web/. Sin embargo, es posible definir una estructura de directorios com-
  pletamente diferente. Suponiendo que la estructura de directorios está formada por dos directorios
  principales, uno público y otro privado:
      symfony/    # Zona privada
        apps/
        batch/
        cache/
        ...
      www/        # Zona pública
        images/
        css/
        js/
        index.php

  En este caso, el directorio raíz es el directorio symfony/. Por tanto, en el controlador frontal in-
  dex.php se debe redefinir la constante SF_ROOT_DIR con el siguiente valor para que la aplicación
  funcione correctamente:


www.librosweb.es                                                                                    76
Symfony, la guía definitiva                                     Capítulo 5. Configurar Symfony

      define('SF_ROOT_DIR', realpath(dirname(__FILE__).'/../symfony'));

El Capítulo 19 explica en detalle cómo personalizar Symfony para que funcione con una
estructura de directorios a medida.

5.2.2.2. Configuración principal de la Aplicación
La configuración más importante de la aplicación se encuentra en el directorio miproyec-
to/apps/miaplicacion/config/:

      ▪ app.yml: contiene la configuración específica de la aplicación; por ejemplo se
        pueden almacenar variables globales que se utilizan en la lógica de negocio de la
        aplicación y que no se almacenan en una base de datos. Los ejemplos habituales
        de estas variables son los porcentajes de los impuestos como el IVA, los gastos
        de envío, direcciones de email de contacyo, etc. Por defecto el archivo está vacío.

      ▪ config.php: este archivo inicia la ejecucion de la aplicación, ya que realiza todas
        las inicializaciones necesarias para que la aplicación se pueda ejecutar. En este
        archivo se puede personalizar la estructura de directorios de la aplicación y se
        pueden añadir constantes que manejan las aplicaciones (el Capítulo 19 lo explica
        con más detalle). Comienza incluyendo el arhivo config.php del proyecto al que
        pertenece la aplicación.

      ▪ factories.yml: Symfony incluye sus propias clases para el manejo de la vista, de
        las peticiones, de las respuestas, de la sesión, etc. No obstante, es posible definir
        otras clases propias para realizar estas tareas. El Capítulo 17 lo explica más
        detalladamente.

      ▪ filters.yml: los filtros son trozos de código que se ejecutan con cada petición.
        En este archivo se definen los filtros que se van a procesar y cada módulo puede
        redefinir los filtros que se procesan. El Capítulo 6 explica en detalle los filtros.

      ▪ logging.yml: permite definir el nivel de detalle con el que se generan los archivos
        de log, utilizados para el mantenimiento y la depuración de las aplicaciones. El
        Capítulo 16 explica más profundamente esta configuración.

      ▪ routing.yml: almacena las reglas de enrutamiento, que permiten transformar las
        URL habituales de las aplicaciones web en URL limpias y sencillas de recordar.
        Cada vez que se crea una aplicación se crean unas cuantas reglas básicas por de-
        fecto. El Capítulo 9 está dedicado a los enlaces y el sistema de enrutamiento.

      ▪ settings.yml: contiene las principales opciones de configuración de una aplica-
        ción Symfony. Entre otras, permite especificar si la aplicación utiliza la internacio-
        nalización, el idioma por defecto de la aplicación, el tiempo de expiración de las
        peticiones y si se activa o no la cache. Un cambio en una única línea de configu-
        ración de este archivo permite detener el acceso a la aplicación para realizar ta-
        reas de mantenimiento o para actualizar alguno de sus componentes. Las opcio-
        nes más habituales y su uso se describen en el Capítulo 19.

      ▪ view.yml: establece la estructura inicial de la vista por defecto: el nombre del la-
        yout, el título de la página y las etiquetas <meta>; las hojas de estilos y los


www.librosweb.es                                                                           77
Symfony, la guía definitiva                                               Capítulo 5. Configurar Symfony


         archivos JavaScript que se incluyen; el Content-Type por defecto, etc. También
         permite definir el valor por defecto de las etiquetas <title> y <meta>. Cada mó-
         dulo puede redefinir el valor de todas estas opciones.

5.2.2.3. Configuración de la Internacionalización
Las aplicaciones con soporte de internacionalización son las que pueden mostrar una mis-
ma página en diferentes idiomas. Para conseguirlo, es necesario realizar una configura-
ción específica. Los dos sitios donde se configura la internacionalización en Symfony son:

      ▪ Archivo i18n.yml del directorio config/ de la aplicación: en este archivo se esta-
         blecen las opciones generales de traducción de páginas, como por ejemplo el id-
         ioma por defecto, si las traducciones se guardan en archivos o bases de datos y
         su formato.

      ▪ Los archivos de traducción en el directorio i18n/ de la aplicación: se trata de una
         especie de diccionarios que indican la traducción de cada frase que utilizan las
         plantillas de la aplicación de forma que cuando el usuario cambie de idioma sea
         posible mostrar las páginas en ese idioma.

Para activar el mecanismo de i18n, se debe modificar el archivo settings.yml. El Capítulo
13 profundiza en todas las características relacionadas con la i18n.

  NOTA
  Nota del traductor El término i18n es el más utilizado para referirse a la “internacionalización”. Su
  origen proviene de las 18 letras que existen entre la letra “i” y la letra “n” en la palabra “internaciona-
  lización”. Otras palabras siguen la misma técnica y así es habitual decir l10n para hablar de la “lo-
  calization” o adaptación local de los contenidos.

5.2.2.4. Configuración adicional de la Aplicación
Algunos archivos de configuración se encuentran en el directorio de instalación de Sym-
fony (en $sf_symfony_data_dir/config/) y por tanto no aparecen en los directorios de
configuración de las aplicaciones. Las opciones que se encuentran en esos archivos son
opciones para las que raramente se modifica su valor o que son globales a todos los pro-
yectos. De todas formas, si necesitas modificar alguna de estas opciones, crea un archivo
vacío con el mismo nombre en el directorio miproyecto/apps/miaplicacion/config/ y re-
define todas las opciones que quieras modificar. Las opciones definidas en una aplicación
siempre tienen preferencia respecto a las opciones definidas por el framework. Los archi-
vos de configuración que se encuentran en el directorio config/ de la instalación de Sym-
fony son los siguientes:

      ▪ autoload.yml: contiene las opciones relativas a la carga automática de clases. Es-
         ta opción permite utilizar clases propias sin necesidad de incluirlas previamente
         en el script que las utiliza, siempre que esas clases se encuentren en algunos di-
         rectorios determinados. El Capítulo 19 lo describe en detalle.




www.librosweb.es                                                                                         78
Symfony, la guía definitiva                                     Capítulo 5. Configurar Symfony


      ▪ constants.php: define la estructura de archivos y directorios por defecto. Para re-
        definir estos valores, se debe utilizar el archivo de configuración config.php de la
        aplicación, como se muestra en el Capítulo 19.

      ▪ core_compile.yml y bootstrap_compile.yml: define la lista de clases que se inclu-
        yen al iniciar la aplicación (en bootstrap_compile.yml) y las que se incluyen al
        procesar una petición (en core_compile.yml). Todas estas clases se concatenan
        en un único archivo PHP optimizado en el que se eliminan los comentarios y que
        acelera mucho la ejecución de la aplicación (ya que se reduce el número de ar-
        chivos que se acceden a uno solo desde los más de 40 archivos necesarios origi-
        nalmente para cada petición). Esta característica es muy necesaria cuando no se
        utiliza ningún acelerador de PHP. El Capítulo 18 incluye diversas técnicas para
        optimizar el rendimiento de las aplicaciones.

      ▪ config_handlers.yml: permite añadir o modificar los manejadores de archivos de
        configuración. El Capítulo 19 contiene todos los detalles.

      ▪ php.yml: este archivo se utiliza para comprobar que las directivas del archivo de
        configuración de PHP php.ini tienen los valores adecuados y permite redefinirlas
        si hace falta. Los detalles se explican en el Capítulo 19.

5.2.3. Configuración de los Módulos
Inicialmente los módulos no tienen ninguna configuración específica. En cualquier caso,
es posible modificar las opciones de la aplicación en cualquier módulo que así lo requiera.
Algunos de los usos típicos son los de cambiar la descripción HTML en todas las acciones
de un módulo o para incluir un archivo JavaScript específico. También se pueden añadir
nuevos parámetros exclusivamente para un módulo concreto.

Como se puede suponer, los archivos de configuración de los módulos se encuentran en
el directorio miproyecto/apps/miaplicacion/modules/mimodulo/config/. Los archivos dis-
ponibles son los siguientes:

      ▪ generator.yml: se utiliza para los módulos generados automáticamente a partir
        de una tabla de la base de datos, es decir, para los módulos utilizados en el scaf-
        folding y para las partes de administración creadas de forma automática. Contie-
        ne las opciones que definen como se muestran las filas y los registros en las pá-
        ginas generadas y también define las interacciones con el usuario: filtros, orde-
        nación, botones, etc. El Capítulo 14 explica todas estas características.

      ▪ module.yml: contiene la configuración de la acción y otros parámetros específicos
        del módulo (es un archivo equivalente al archivo app.yml de la aplicación). El
        Capítulo 6 lo explica en detalle.

      ▪ security.yml: permite restringir el acceso a determinadas acciones del módulo.
        En este archivo se configura que una página solamente pueda ser accedida por
        los usuarios registrados o por un grupo de usuarios registrados con permisos es-
        peciales. En el Capítulo 6 se detalla su funcionamiento.




www.librosweb.es                                                                           79
Symfony, la guía definitiva                                            Capítulo 5. Configurar Symfony


      ▪ view.yml: permite configurar las vistas de una o de todas las acciones del módu-
         lo. Redefine las opciones del archivo view.yml de la aplicación y su funcionamien-
         to se describe en el Capítulo 7.

      ▪ Archivos de validación de datos: aunque se encuentran en el directorio validate/
         en vez del directorio config/, se trata de archivos de configuración creados con
         YAML y que se emplean para controlar los datos introducidos en los formularios.
         En el Capítulo 10 se estudian en detalle.

Casi todos los archivos de configuración de los módulos permiten definir parámetros para
todas las vistas y/o acciones del módulo o solo para una serie de vistas y/o acciones.

  ¿No son demasiados archivos?

  Seguramente estás un poco abrumado por la cantidad de archivos de configuración que tiene la
  aplicación. Pero debes tener en cuenta que:

  Muy pocas veces tendrás que modificar la configuración, ya que las convenciones y normas esta-
  blecidas por Symfony suelen coincidir con los requerimientos habituales de las aplicaciones. Cada
  archivo de configuración se utiliza para alguna característica concreta, que se detallarán una a una
  en los siguientes capítulos. Cuando se estudia individualmente uno de los archivos, es muy fácil
  comprender su estructura y su finalidad. Para las aplicaciones más profesionales, es habitual modi-
  ficar la configuración por defecto. Los archivos de configuración permiten modificar fácilmente el
  funcionamiento de Symfony sin necesidad de añadir o modificar código a la aplicación. Imagina la
  cantidad de código PHP que se necesitaría para obtener un control similar al de Symfony. Si toda la
  configuración estuviera centralizada en un único archivo, además de ser un archivo enorme y muy
  difícil de leer, no sería posible redefinir el valor de las opciones en los diferentes niveles (como se
  ve más tarde en este capítulo en la sección “Configuraciones en cascada”).

  El mecanismo de configuración de Symfony es uno de sus puntos fuertes, ya que permite que el
  framework se pueda utilizar para crear cualquier tipo de aplicación y no solamente aquellas para las
  que se diseñó originalmente.


5.3. Entornos
Cuando se desarrolla una aplicación, es habitual disponer de varias configuraciones dis-
tintas pero relacionadas. Por ejemplo durante el desarrollo se tiene un archivo de confi-
guración con los datos de conexión a la base de datos de pruebas, mientras que en el
servidor de producción los datos de conexión necesarios son los de la base de datos de
producción. Symfony permite definir diferentes entornos de ejecución para poder mane-
jar de forma sencilla las diferentes configuraciones.

5.3.1. ¿Qué es un entorno?
Las aplicaciones de Symfony se pueden ejecutar en diferentes entornos. Todos los entor-
nos comparten el mismo código PHP (salvo el código del controlador frontal) pero pueden
tener configuraciones completamente diferentes. Cuando se crea una aplicación, Sym-
fony crea por defecto 3 entornos: producción (prod), pruebas (test) y desarrollo (dev).
También es posible añadir cualquier nuevo entorno que se considere necesario.



www.librosweb.es                                                                                      80
Symfony, la guía definitiva                                          Capítulo 5. Configurar Symfony


En cierta forma, un entorno y una configuración son sinónimos. Por ejemplo el entorno
de pruebas registra las alertas y los errores en el archivo de log, mientras que el entorno
de producción solamente registra los errores. En el entorno de desarrollo se suele desac-
tivar la cache, pero está activa en los entornos de pruebas y de producción. Los entornos
de pruebas y desarrollo normalmente trabajan con una base de datos que contiene datos
de prueba, mientras que el servidor de producción trabaja con la base de datos buena.
En este caso, la configuración de la base de datos varía en los diferentes entornos. Todos
los entornos pueden ejecutarse en una misma máquina, aunque en los servidores de pro-
ducción normalmente solo se instala el entorno de producción.

El entorno de desarrollo tiene activadas las opciones de log y de depuración de aplicacio-
nes, ya que es más importante el mantenimiento de la aplicación que su rendimiento. Sin
embargo, en el entorno de producción se ajustan las opciones de configuración para ob-
tener el máximo rendimiento, por lo que muchas características están desactivadas por
defecto. Una buena regla general suele ser la de utilizar el entorno de desarrollo hasta
que consideres que la funcionalidad de la aplicación en la que estás trabajando se enc-
uentra terminada y después pasarse al entorno de producción para comprobar su
rendimiento.

El entorno de pruebas varía respecto del de desarrollo y el de producción. La única forma
de interactuar con este entorno es mediante la línea de comandos para realizar pruebas
funcionales y ejecutar scripts. De esta forma, el entorno de pruebas es parecido al de
producción, pero no se accede a través de un navegador. De todas formas, simula el uso
de cookies y otros componentes específicos de HTTP.

Para ejecutar la aplicación en otro entorno, solamente es necesario cambiar de controla-
dor frontal. Hasta ahora, en todos los ejemplos se accedía al entorno de desarrollo, ya
que las URL utilizadas llamaban al controlador frontal de desarrollo:
   http://localhost/miaplicacion_dev.php/mimodulo/index

Sin embargo, si se quiere acceder a la aplicación en el entorno de producción, es necesa-
rio modificar la URL para llamar al controlador frontal de producción:
   http://localhost/index.php/mimodulo/index

Si el servidor web tiene habilitado el mod_rewrite, es posible utilizar las reglas de reescri-
tura de URL de Symfony, que se encuentran en web/.htaccess. Estas reglas definen que
el controlador frontal de producción es el script que se ejecuta por defecto en las peticio-
nes, por lo que se pueden utilizar URL como la siguiente:
   http://localhost/mimodulo/index

  Entornos y servidores

  No se deben confundir los entornos con los servidores. En Symfony, un entorno diferente es en rea-
  lidad una configuración diferente, que se corresponde con un controlador frontal determinado (que
  es el script que se encarga de procesar la petición). Sin embargo, un servidor diferente se corres-
  ponde con un nombre de dominio diferente en la dirección.
      http://localhost/miaplicacion_dev.php/mimodulo/index

      Servidor = localhost
      Entorno = miaplicacion_dev.php      (es decir, entorno de desarrollo)

www.librosweb.es                                                                                  81
Symfony, la guía definitiva                                          Capítulo 5. Configurar Symfony


  Normalmente, los desarrolladores programan y prueban sus aplicaciones en servidores de desarro-
  llo, que no son accesibles desde Internet y donde se puede modificar cualquier configuración de
  PHP o del propio servidor. Cuando la aplicación se va a lanzar de forma pública, se transfieren los
  archivos de la aplicación al servidor de producción y se permite el acceso a los usuarios.

  Por tanto, en cada servidor existen varios entornos de ejecución. Se puede ejecutar por ejemplo la
  aplicación en el entorno de producción aunque el servidor sea el de desarrollo. No obstante, suele
  ser habitual que en el servidor de producción solamente estén disponibles los entornos de ejecu-
  ción de producción, para eviar que los usuarios puedan ver la configuración del servidor o puedan
  comprometer la seguridad del sistema.

  Para crear un nuevo entorno de ejecución, no es necesario recurrir a la línea de comandos o crear
  nuevos directorios. Lo único que hay que hacer es crear un nuevo archivo de tipo controlador fron-
  tal (puedes copiar uno de los existentes) y modificar el nombre de su entorno de ejecución. Este
  nuevo entorno hereda todas las configuraciones por defecto del framework y todas las opciones co-
  munes a todos los entornos. En el siguiente capítulo se detalla como realizar esta operación.


5.3.2. Configuration en cascada
Una misma opción de configuración puede estar definida más de una vez en archivos di-
ferentes. De esta forma es posible por ejemplo definir que el tipo MIME de las páginas de
la aplicación sea text/html, pero que las páginas creadas con el módulo que se encarga
del RSS tengan un tipo MIME igual a text/xml. Symfony permite establecer el primer va-
lor en miaplicacion/config/view.yml y el segundo en miaplicacion/modules/rss/config/
view.yml. El sistema de configuración se encarga de que una opción establecida a nivel
de módulo tenga más prioridad que la opción definida a nivel de aplicación.

De hecho, Symfony define varios niveles de configuración:

      ▪ Niveles de granularidad:

               ▪ Configuración por defecto establecida por el framework

               ▪ Configuración global del proyecto (en miproyecto/config/)

               ▪ Configuración local de cada aplicación (en miproyecto/apps/miaplicac-
                 ion/config/)

               ▪ Configuración local de cada módulo (en miproyecto/apps/miaplicacion/
                 modules/mimodulo/config/)

      ▪ Niveles de entornos de ejecución:

               ▪ Específico para un solo entorno

               ▪ Para todos los entornos

Muchas de las opciones que se pueden establecer dependen del entorno de ejecución.
Por este motivo, los archivos de configuración YAML están divididos por entornos,
además de incluir una sección que se aplica a todos los entornos. De esta forma, un ar-
chivo de configuración típico de Symfony se parece al que se muestra en el listado 5-12.

Listado 5-12 - La estructura típica de los archivos de configuración de Symfony


www.librosweb.es                                                                                  82
Symfony, la guía definitiva                                 Capítulo 5. Configurar Symfony

   # Opciones para el entorno de producción
   prod:
     ...

   # Opciones para el entorno de desarrollo
   dev:
     ...

   # Opciones para el entorno de pruebas
   test:
     ...

   # Opciones para un entorno creado a medida
   mientorno:
     ...

   # Opciones para todos los entornos
   all:
     ...

Además de estas opciones, el propio framework define otros valores por defecto en archi-
vos que no se encuentran en la estructura de directorios del proyecto, sino que se enc-
uentran en el directorio $sf_symfony_data_dir/config/ de la instalación de Symfony. El
listado 5-13 muestra la configuración por defecto de estos archivos. Todas las aplicacio-
nes heredan estas opciones.

Listado 5-13 - La configuración por defecto, en $sf_symfony_data_dir/config/
settings.yml
   # Opciones por defecto:
   default:
     default_module:          default
     default_action:          index
     ...

Las opciones de estos archivos se incluyen como opciones comentadas en los archivos de
configuración del proyecto, la aplicación y los módulos, como se muestra en el listado 5-
14. De esta forma, se puede conocer el valor por defecto de algunas opciones y modifi-
carlo si es necesario.

Listado 5-14 - La configuración por defecto, repetida en varios archivos para co-
nocer fácilmente su valor, en miaplicacion/config/settings.yml
   #all:
     # default_module:           default
     # default_action:           index
     ...

El resultado final es que una misma opción puede ser definida varias veces y el valor que
se considera en cada momento se genera mediante la configuración en cascada. Una op-
ción definida en un entorno de ejecución específico tiene más prioridad sobre la misma
opción definida para todos los entornos, que también tiene preferencia sobre las opciones
definidas por defecto. Las opciones definidas a nivel de módulo tienen preferencia sobre
las mismas opciones definidas a nivel de aplicación, que a su vez tienen preferencia


www.librosweb.es                                                                       83
Symfony, la guía definitiva                                        Capítulo 5. Configurar Symfony


sobre las definidas a nivel de proyecto. Todas estas prioridades se resumen en la siguien-
te lista de prioridades, en el que el primer valor es el más prioritario de todos:

    1. Módulo

    2. Aplicación

    3. Proyecto

    4. Entorno específico

    5. Todos los entornos

    6. Opciones por defecto


5.4. La cache de configuración
Si cada nueva petición tuviera que procesar todos los archivos YAML de configuración y
tuviera que aplicar la configuración en cascada, se produciría una gran penalización en el
rendimiento de la aplicación. Symfony incluye un mecanismo de cache de configuración
para aumentar la velocidad de respuesta de las peticiones.

Unas clases especiales, llamadas manejadores, procesan todos los archivos de configura-
ción originales y los transforman en código PHP que se puede procesar de forma muy rá-
pida. En el entorno de desarrollo se prima la interactividad y no el rendimiento, por lo
que en cada petición se comprueba si se ha modificado la configuración. Como se proce-
san siempre los archivos modificados, cualquier cambio de un archivo YAML se refleja de
forma inmediata. Sin embargo, en el entorno de producción solamente se procesa la con-
figuración una vez durante la primera petición y se almacena en una cache el código PHP
generado, para que lo utilicen las siguientes peticiones. El rendimiento en el entorno pro-
ducción no se resiente, ya que las peticiones solamente ejecutan código PHP optimizado.

Si por ejemplo el archivo app.yml contiene lo siguiente:
   all:                       # Opciones para todos los entornos
     mail:
        webmaster:            webmaster@ejemplo.com

La carpeta cache/ del proyecto contendrá un archivo llamado config_app.yml.php y con el
siguiente contenido:
   <?php

   sfConfig::add(array(
     'app_mail_webmaster' => 'webmaster@ejemplo.com',
   ));

La consecuencia es que los archivos YAML raramente son procesados por el framework,
ya que se utiliza la cache de la configuración siempre que sea posible. Sin embargo, en el
entorno de desarrollo cada nueva petición obliga a Symfony a comparar las fechas de
modificación de los archivos YAML y las de los archivos almacenados en la cache. Sola-
mente se vuelven a procesar aquellos archivos que hayan sido modificados desde la peti-
ción anterior.




www.librosweb.es                                                                              84
Symfony, la guía definitiva                                    Capítulo 5. Configurar Symfony


Este mecanismo supone una gran ventaja respecto de otros frameworks de PHP, en los
que se compilan los archivos de configuración en cada petición, incluso en producción. Al
contrario de lo que sucede con Java, PHP no define un conexto de ejecución común a to-
das las peticiones. En otros frameworks de PHP, se produce una pérdida de rendimiento
importante al procesar toda la configuración con cada petición. Gracias al sistema de ca-
che, Symfony no sufre esta penalización ya que la pérdida de rendimiento provocada por
la configuración es muy baja.

La cache de la configuración implica una consecuencia muy importante. Si se modifica la
configuración en el entorno de producción, se debe forzar a Symfony a que vuelva a pro-
cesar los archivos de configuración para que se tengan en cuenta los cambios. Para ello,
solo es necesario borrar la cache, borrando todos los contenidos del directorio cache/ o
utilizando una tarea específica proporcionada por Symfony:
   > symfony clear-cache


5.5. Accediendo a la configuración desde la aplicación
Los archivos de configuración se transforman en código PHP y la mayoría de sus opciones
solamente son utilizadas por el framework. Sin embargo, en ocasiones es necesario acce-
der a los archivos de configuración desde el código de la aplicación (en las acciones,
plantillas, clases propias, etc.) Se puede acceder a todas las opciones definidas en los ar-
chivos settings.yml, app.yml, module.yml, logging.yml y i18n.yml mediante una clase
especial llamada sfConfig.


5.5.1. La clase sfConfig
Desde cualquier punto del código de la aplicación se puede acceder a las opciones de
configuración mediante la clase sfConfig. Se trata de un registro de opciones de configu-
ración que proporciona un método getter que puede ser utilizado en cualquier parte del
código:
   // Obtiene una opción
   opcion = sfConfig::get('nombre_opcion', $valor_por_defecto);

También se pueden crear o redefinir opciones desde el código de la aplicación:
   // Crear una nueva opción
   sfConfig::set('nombre_opcion', $valor);

El nombre de la opción se construye concatenando varios elementos y separándolos con
guiones bajos en este orden:

      ▪ Un prefijo relacionado con el nombre del archivo de configuración (sf_ para set-
        tings.yml, app_ para app.yml, mod_ para module.yml, sf_i18n_ para i18n.yml y
        sf_logging_ para logging.yml)

      ▪ Si existen, todas las claves ascendentes de la opción (y en minúsculas)

      ▪ El nombre de la clave, en minúsculas

No es necesario incluir el nombre del entorno de ejecución, ya que el código PHP solo tie-
ne acceso a los valores definidos para el entorno en el que se está ejecutando.


www.librosweb.es                                                                          85
Symfony, la guía definitiva                                      Capítulo 5. Configurar Symfony


El listado 5-16 muestra el código necesario para acceder a los valores de las opciones de-
finidas en el archivo app.yml mostrado en el listado 5-15.

Listado 5-15 - Ejemplo de configuración del archivo app.yml
   all:
     version:        1.5
     .general:
        impuestos:   19.6
     usuario_por_defecto:
        nombre:      Juan Pérez
     email:
        webmaster:   webmaster@ejemplo.com
        contacto:    contacto@ejemplo.com
   dev:
     email:
        webmaster:   otro@ejemplo.com
        contacto:    otro@ejemplo.com

Listado 5-16 - Acceso a las opciones de configuración desde el entorno de
desarrollo
   echo sfConfig::get('app_version');
   => '1.5'
   echo sfConfig::get('app_impuestos');      // Recuerda que se ignora el nombre de la
   categoría
                                             // Es decir, no es necesario incluir 'general'
   => '19.6'
   echo sfConfig::get('app_usuario_por_defecto_nombre');
   => 'Juan Pérez'
   echo sfConfig::get('app_email_webmaster');
   => 'otro@ejemplo.com'
   echo sfConfig::get('app_email_contacto');
   => 'otro@ejemplo.com'

Las opciones de configuración de Symfony tienen todas las ventajas de las constantes
PHP, pero sin sus desventajas, ya que se puede modificar su valor durante la ejecución
de la aplicación.

Considerando el funcionamiento que se ha mostrado, el archivo settings.yml que se uti-
liza para establecer las opciones del framework en cada aplicación, es equivalente a reali-
zar varias llamadas a la función sfConfig::set(). Así que el listado 5-17 se interpreta de
la misma forma que el listado 5-18.

Listado 5-17 - Extracto del archivo de configuración settings.yml
   all:
     .settings:
        available:             on
        path_info_array:       SERVER
        path_info_key:         PATH_INFO
        url_format:            PATH

Listado 5-18 - Forma en la que Symfony procesa el archivo settings.yml
   sfConfig::add(array(
     'sf_available' => true,


www.librosweb.es                                                                              86
Symfony, la guía definitiva                                          Capítulo 5. Configurar Symfony

     'sf_path_info_array' => 'SERVER',
     'sf_path_info_key' => 'PATH_INFO',
     'sf_url_format' => 'PATH',
   ));

El Capítulo 19 explica el significado de las opciones de configuración del archivo
settings.yml.


5.5.2. El archivo app.yml y la configuración propia de la aplicación
El archivo app.yml, que se encuentra en el directorio miproyecto/apps/miaplicacion/con-
fig/, contiene la mayoría de las opciones de configuración relacionadas con la aplicación.
Por defecto el archivo está vacío y sus opciones se configuran para cada entorno de eje-
cución. En este archivo se deben incluir todas las opciones que necesiten modificarse rá-
pidamente y se utiliza la clase sfConfig para acceder a sus valores desde el código de la
aplicación. El listado 5-19 muestra un ejemplo.

Listado 5-19 - Archivo app.yml que define los tipos de tarjeta de crédito acepta-
dos en un sitio
   all:
     tarjetascredito:
        falsa:                off
        visa:                 on
        americanexpress:      on

   dev:
     tarjetascredito:
        falsa:                on

Para saber si las tarjetas de crédito falsas se aceptan en el entorno de ejecución de la
aplicación, se debe utilizar la siguiente instrucción:
   sfConfig::get('app_tarjetascredito_falsa');


  SUGERENCIA
  Cuando vayas a definir una constante o una opción dentro de un script, piensa si no sería mejor in-
  cluir esa opción en el archivo app.yml. Se trata del lugar más apropiado para guardar todas las opc-
  iones de la configuración.

Los requerimientos de algunas aplicaciones complejas pueden dificultar el uso del archivo
app.yml. En este caso, se puede almacenar la configuración en cualquier otro archivo,
con el formato y la sintaxis que se prefiera y que sea procesado por un manejador reali-
zado completamente a medida. El Capítulo 19 explica en detalle el funcionamiento de los
manejadores de configuraciones.


5.6. Trucos para los archivos de configuración
Antes de empezar a crear los primeros archivos YAML, existen algunos trucos muy útiles
que es conveniente aprender. Estos trucos permiten evitar la duplicidad de la configura-
ción y permiten personalizar el formato YAML.



www.librosweb.es                                                                                   87
Symfony, la guía definitiva                                    Capítulo 5. Configurar Symfony


5.6.1. Uso de constantes en los archivos de configuración YAML
Algunas opciones de configuración dependen del valor de otras opciones. Para evitar es-
cribir 2 veces el mismo valor, Symfony permite definir constantes dentro de los archivos
YAML. Si el manejador de los archivos se encuentra con un nombre de opción todo en
mayúsculas y encerrado entre los símbolos % y %, lo reemplaza por el valor que tenga en
ese momento. El listado 5-20 muestra un ejemplo.

Listado 5-20 - Uso de constantes en los archivos YAML, ejemplo extraído del ar-
chivo autoload.yml
   autoload:
     symfony:
       name:              symfony
       path:              %SF_SYMFONY_LIB_DIR%
       recursive:         on
       exclude:           [vendor]

El valor de la opción path es el que devuelve en ese momento la llamada a sfConfig::-
get(’sf_symfony_lib_dir’). Si un archivo de configuración depende de otro archivo, es
necesario que el archivo del que se depende sea procesado antes (en el código de Sym-
fony se puede observar el orden en el que se procesan los archivos de configuración). El
archivo app.yml es uno de los últimos que se procesan, por lo que sus opciones pueden
depender de las opciones de otros archivos de configuración.

5.6.2. Uso de programación en los archivos de configuración
Puede ocurrir que los archivos de configuración dependan de parámetros externos (como
por ejemplo una base de datos u otro archivo de configuración). Para poder procesar es-
te tipo de casos, Symfony procesa los archivos de configuración como si fueran archivos
de PHP antes de procesarlos como archivos de tipo YAML. De esta forma, como se mues-
tra en el listado 5-21, es posible incluir código PHP dentro de un archivo YAML:

Listado 5-21 - Los archivos YAML puede contener código PHP
   all:
     traduccion:
        formato: <?php echo (sfConfig::get('sf_i18n') == true ? 'xliff' : 'none')."n" ?>

El único inconveniente es que la configuración se procesa al principio de la ejecución de
la petición del usuario, por lo que no están disponibles ninguno de los métodos y funcio-
nes de Symfony.

Además, como la instrucción echo no añade ningún retorno de carro por defecto, es nece-
sario añadirlo explícitamente mediante n o mediante el uso del helper echoln para cum-
plir con el formato YAML:
   all:
     traduccion:
        formato: <?php echoln sfConfig::get('sf_i18n') == true ? 'xliff' : 'none' ?>




www.librosweb.es                                                                          88
Symfony, la guía definitiva                                        Capítulo 5. Configurar Symfony


  ATENCIÓN
  Recuerda que en el entorno de producción, se utiliza una cache para la configuración, por lo que
  los archivos de configuración solamente se procesan (y en este caso, se ejecuta su código PHP)
  una vez después de borrar la cache.


5.6.3. Utilizar tu propio archivo YAML
La clase sfYaml permite procesar de forma sencilla cualquier archivo en formato YAML.
Se trata de un procesador (parser) de archivos YAML que los convierte en arrays asocia-
tivos de PHP. El listado 5-22 muestra un archivo YAML de ejemplo y el listado 5-23
muestra como transformarlo en código PHP:

Listado 5-22 - Archivo de prueba llamado prueba.yml
   casa:
     familia:
       apellido:        García
       padres:          [Antonio, María]
       hijos:           [Jose, Manuel, Carmen]
     direccion:
       numero:          34
       calle:           Gran Vía
       ciudad:          Cualquiera
       codigopostal:    12345

Listado 5-23 - Uso de la clase sfYaml para transformar el archivo YAML en un
array asociativo
        $prueba = sfYaml::load('/ruta/a/prueba.yml');
        print_r($prueba);

        Array(
          [casa] => Array(
            [familia] => Array(
               [apellido] => García
               [padres] => Array(
                 [0] => Antonio
                 [1] => María
               )
               [hijos] => Array(
                 [0] => Jose
                 [1] => Manuel
                 [2] => Carmen
               )
            )
            [direccion] => Array(
               [numero] => 34
               [calle] => Gran Vía
               [ciudad] => Cualquiera
               [codigopostal] => 12345
            )
          )
        )




www.librosweb.es                                                                               89
Symfony, la guía definitiva                                   Capítulo 5. Configurar Symfony


5.7. Resumen
El sistema de configuración de Symfony utiliza el lenguaje YAML por ser muy sencillo y
fácil de leer. Los desarrolladores cuentan con la posibilidad de definir varios entornos de
ejecución y con la opción de utilizar la configuración en cascada, lo que ofrece una gran
versatilidad a su trabajo. Las opciones de configuración se pueden acceder desde el códi-
go de la aplicación mediante el objeto sfConfig, sobre todo las opciones de configuración
de la aplicación que se definen en el archivo app.yml.

Aunque Symfony cuenta con muchos archivos de configuración, su ventaja es que así es
más adaptable. Además, recuerda que solo las aplicaciones que requieren de una confi-
guración muy personalizada tienen que utilizar estos archivos de configuración.




www.librosweb.es                                                                         90
Symfony, la guía definitiva                                          Capítulo 6. El Controlador




Capítulo 6. El Controlador
En Symfony, la capa del controlador, que contiene el código que liga la lógica de negocio
con la presentación, está dividida en varios componentes que se utilizan para diversos
propósitos:

      ▪ El controlador frontal es el único punto de entrada a la aplicación. Carga la confi-
        guración y determina la acción a ejecutarse.

      ▪ Las acciones contienen la lógica de la aplicación. Verifican la integridad de las pe-
        ticiones y preparan los datos requeridos por la capa de presentación.

      ▪ Los objetos request, response y session dan acceso a los parámetros de la peti-
          ción, las cabeceras de las respuestas y a los datos persistentes del usuario. Se
          utilizan muy a menudo en la capa del controlador.

      ▪ Los filtros son trozos de código ejecutados para cada petición, antes o después
        de una acción. Por ejemplo, los filtros de seguridad y validación son comúnmente
          utilizados en aplicaciones web. Puedes extender el framework creando tus prop-
          ios filtros.

Este capítulo describe todos estos componentes, pero no te abrumes porque sean mu-
chos componentes. Para una página básica, es probable que solo necesites escribir algu-
nas líneas de código en la clase de la acción, y eso es todo. Los otros componentes del
controlador solamente se utilizan en situaciones específicas.


6.1. El Controlador Frontal
Todas las peticiones web son manejadas por un solo controlador frontal, que es el punto
de entrada único de toda la aplicación en un entorno determinado.

Cuando el controlador frontal recibe una petición, utiliza el sistema de enrutamiento para
asociar el nombre de una acción y el nombre de un módulo con la URL escrita (o pincha-
da) por el usuario. Por ejemplo, la siguientes URL llama al script index.php (que es el
controlador frontal) y será entendido como llamada a la acción miAccion del módulo mi-
modulo:
   http://localhost/index.php/mimodulo/miAccion

Si no estás interesado en los mecanismos internos de Symfony, eso es todo que necesi-
tas saber sobre el controlador frontal. Es un componente imprescindible de la arquitectu-
ra MVC de Symfony, pero raramente necesitarás cambiarlo. Si no quieres conocer las tri-
pas del controlador frontal, puedes saltarte el resto de esta sección.

6.1.1. El Trabajo del Controlador Frontal en Detalle
El controlador frontal se encarga de despachar las peticiones, lo que implica algo más
que detectar la acción que se ejecuta. De hecho, ejecuta el código común a todas las ac-
ciones, incluyendo:

    1. Define las constantes del núcleo.


www.librosweb.es                                                                            91
Symfony, la guía definitiva                                          Capítulo 6. El Controlador


    2. Localiza la librería de Symfony

    3. Carga e inicializa las clases del núcleo del framework.

    4. Carga la configuración.

    5. Decodifica la URL de la petición para determinar la acción a ejecutar y los pará-
       metros de la petición.

    6. Si la acción no existe, redireccionará a la acción del error 404.

    7. Activa los filtros (por ejemplo, si la petición necesita autenticación).

    8. Ejecuta los filtros, primera pasada.

    9. Ejecuta la acción y produce la vista.

   10. Ejecuta los filtros, segunda pasada.

   11. Muestra la respuesta.

6.1.2. El Controlador Frontal por defecto
El controlador frontal por defecto, llamado index.php y ubicado en el directorio web/ del
proyecto, es un simple script, como lo muestra el Listado 6-1.

Listado 6-1 - El Controlador Frontal por Omisión
   <?php

   define('SF_ROOT_DIR',      realpath(dirname(__FILE__).'/..'));
   define('SF_APP',           'miaplicacion');
   define('SF_ENVIRONMENT',   'prod');
   define('SF_DEBUG',         false);



   require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'

   sfContext::getInstance()->getController()->dispatch();

La definición de las constantes corresponde al primer paso descrito en la sección anterior.
Después el controlador frontal incluye el config.php de la aplicación, que se ocupa de los
pasos 2 a 4. La llamada al método dispatch() del objeto sfController (que es el objeto
correspondiente al controlador del núcleo de la arquitectura MVC de Symfony) envía la
petición, ocupándose de los pasos 5 a 7. Los últimos pasos son manejados por la cadena
de filtros, según lo explicado más adelante este capítulo.

6.1.3. Llamando a Otro Controlador Frontal para Cambiar el Entorno
Cada entorno dispone de un controlador frontal. De hecho, es la existencia del controla-
dor frontal lo que define un entorno. El entorno se define en la constante SF_ENVIRONMENT.

Para cambiar el entorno en el que se está viendo la aplicación, simplemente se elige otro
controlador frontal. Los controladores frontales disponibles cuando creas una aplicación
con la tarea Symfony init-app son index.php para el entorno de producción y miaplicac-
ion_dev.php para el entorno de desarrollo (suponiendo que tu aplicación se llame


www.librosweb.es                                                                            92
Symfony, la guía definitiva                                         Capítulo 6. El Controlador


miaplicacion). La configuración por defecto de mod_rewrite utiliza index.php cuando la
URL no contiene el nombre de un script correspondiente a un controlador frontal. Así que
estas dos URL muestran la misma página (mimodulo/index) en el entorno de producción:
   http://localhost/index.php/mimodulo/index
   http://localhost/mimodulo/index

y esta URL muestra la misma página en el entorno de desarrollo:
   http://localhost/miaplicacion_dev.php/mimodulo/index

Crear un nuevo entorno es tan fácil como crear un nuevo controlador frontal. Por ejem-
plo, puede ser necesario un entorno llamado staging que permita a tus clientes probar la
aplicación antes de ir a producción. Para crear el entorno staging, simplemente copia
web/miaplicacion_dev.php en web/miaplicacion_staging.php y cambia el valor de la
constante SF_ENVIRONMENT a staging. Ahora en todos los archivos de configuración, pue-
des añadir una nueva sección staging: para establecer los valores específicos para este
entorno, como se muestra en el Listado 6-2

Listado 6-2 - Ejemplo de app.yml con valores específicos para el entorno staging
   staging:
     mail:
        webmaster:     falso@misitio.com
        contacto:      falso@misitio.com
   all:
     mail:
        webmaster:     webmaster@misitio.com
        contacto:      contacto@mysite.com

Si quieres ver como reacciona la aplicación en el nuevo entorno, llama al controlador
frontal asociado:
   http://localhost/miaplicacion_staging.php/mimodulo/index


6.1.4. Archivos por Lotes
En ocasiones es necesario ejecutar un script desde   la línea de comandos (o mediante una
tarea programada) con acceso a todas las clases y    características de Symfony, por ejem-
plo para realizar tareas como el envío programado    de correos electrónicos o para actuali-
zar periódicamente el modelo mediante una serie      de cálculos complejos. Para este tipo
de scripts, es necesario incluir al principio del archivo por lotes las mismas líneas que en
el controlador frontal. El listado 6-3 muestra el principio de un archivo por lotes de este
tipo.

Listado 6-3 - Ejemplo de archivo por lotes
   <?php

   define('SF_ROOT_DIR',      realpath(dirname(__FILE__).'/..'));
   define('SF_APP',           'miaplicacion');
   define('SF_ENVIRONMENT',   'prod');
   define('SF_DEBUG',         false);




www.librosweb.es                                                                           93
Symfony, la guía definitiva                                                Capítulo 6. El Controlador

   require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'

   // Agregar código aquí

Puedes ver que la única línea que falta es la que llama al método dispatch() del objeto
sfController, que solo puede ser utilizada con un navegador web, no en un proceso por
lotes. Definir una aplicación y un entorno te permite disponer de una configuración es-
pecífica. Incluir el archivo config.php inicializa el contexto y el cargado automático de las
clases.

  SUGERENCIA
  La interfaz de comandos de Symfony ofrece la tarea init-batch, que automáticamente crea un
  estructura básica (esqueleto) similar al que se encuentra en el Listado 6-3 en el directorio batch/.
  Simplemente indica como argumentos un nombre de aplicación, un nombre de entorno y un nom-
  bre para el archivo de lotes.


6.2. Acciones
Las acciones son el corazón de la aplicación, puesto que contienen toda la lógica de la
aplicación. Las acciones utilizan el modelo y definen variables para la vista. Cuando se
realiza una petición web en una aplicación Symfony, la URL define una acción y los pará-
metros de la petición.

6.2.1. La clase de la acción
Las acciones son métodos con el nombre executeNombreAccion de una clase llamada nom-
breModuloActions que hereda de la clase sfActions y se encuentran agrupadas por mó-
dulos. La clase que representa las acciones de un módulo se encuentra en el archivo act-
ions.class.php, en el directorio actions/ del módulo.

El listado 6-4 muestra un ejemplo de un archivo actions.class.php con una única acción
index para todo el módulo mimodulo.

Listado 6-4 - Ejemplo de la clase de la acción, en app/miaplicacion/modules/mimodu-
lo/actions/actions.class.php
   class mimoduloActions extends sfActions
   {
     public function executeIndex()
     {

       }
   }


  ATENCIÓN
  Aunque en PHP no se distinguen las mayúsculas y minúsculas de los nombres de los métodos,
  Symfony si los distingue. Así que se debe tener presente que los métodos de las acciones deben
  comenzar con execute en minúscula, seguido por el nombre exacto de la acción con la primera le-
  tra en mayúscula.



www.librosweb.es                                                                                   94
Symfony, la guía definitiva                                                 Capítulo 6. El Controlador


Para ejecutar un acción, se debe llamar al script del controlador frontal con el nombre del
módulo y de la acción como parámetros. Por defecto, se añade nombre_modulo/nombre_ac-
cion al script. Esto significa que la acción del listado 6-4 se puede ejecutar llamándola
con la siguiente URL:
   http://localhost/index.php/mimodulo/index

Añadir más acciones simplemente significa agregar más métodos execute al objeto sfAc-
tions, como se muestra en el listado 6-5.

Listado 6-5 - Clase con dos acciones, en miaplicacion/modules/mimodulo/actions/
actions.class.php
   class mimoduloActions extends sfActions
   {
     public function executeIndex()
     {
       ...
     }

       public function executeList()
       {
         ...
       }
   }

Si el tamaño de la clase de la acción crece demasiado, probablemente tendrás que refac-
torizar la clase para mover algo de codigo a la capa del modelo. El código de las acciones
debería ser muy corto (no mas que una pocas líneas), y toda la lógica del negocio de-
bería encontrarse en el modelo.

Aun así, el número de acciones en un módulo puede llegar a ser tan importante que sea
necesario dividirlas en 2 módulos.

  Normas del código de Symfony

  En los ejemplos de código dados en este libro, probablemente has notado que la apertura y cierre
  de llaves ({ y }) ocupan una línea cada una. Este estándar hace al código más fácil de leer.

  Entre otras normas que sigue el código de Symfony, la indentación se realiza siempre con 2 espac-
  ios en blanco; nunca se utilizan los tabuladores. La razón es que los tabuladores se muestran con
  distinta anchura en función del editor de textos utilizado, y porque el código que mezcla tabuladores
  con espacios en blanco es bastante difícil de leer.

  Los archivos PHP del núcleo de Symfony y los archivos generados no terminan con la etiqueta de
  cierre habitual ?>. La razón es que esta etiqueta no es obligatoria y puede provocar problemas con
  la salida producida si se incluyen por error espacios en blanco después de la etiqueta de cierre.

  Y si eres de los que te fijas en los detalles, verás que ninguna línea de código de Symfony termina
  con un espacio en blanco. En esta ocasión la razón no es técnica, sino que simplemente las líneas
  de código que terminan con espacios en blancos se ven feas en el editor de texto de Fabien.




www.librosweb.es                                                                                      95
Symfony, la guía definitiva                                        Capítulo 6. El Controlador


6.2.2. Sintaxis alternativa para las clases de las Acciones
Se puede utilizar una sintaxis alternativa para distribuir las acciones en archivos separa-
dos, un archivo por acción. En este caso, cada clase acción extiende sfAction (en lugar
de sfActions) y su nombre es nombreAccionAction. El nombre del método es simplemen-
te execute. El nombre del archivo es el mismo que el de la clase. Esto significa que el eq-
uivalente del Listado 6-5 puede ser escrito en dos archivos mostrados en los listados 6-6
y 6-7

Listado 6-6 - Archivo de una sola acción, en miaplicacion/modules/mimodulo/act-
ions/indexAction.class.php
   class indexAction extends sfAction
   {
     public function execute()
     {
       ...
     }
   }

Listado 6-7 - Archivo de una sola acción, en miaplicacion/modules/mimodulo/act-
ions/listAction.class.php
   class listAction extends sfAction
   {
     public function execute()
     {
       ...
     }
   }


6.2.3. Obteniendo Información en las Acciones
Las clases de las acciones ofrecen un método para acceder a la información relacionada
con el controlador y los objetos del núcleo de Symfony. El listado 6-8 muestra como
utilizarlos.

Listado 6-8 - Métodos comunes de sfActions
   class mimoduloActions extends sfActions
   {
     public function executeIndex()
     {
       // Obteniendo parametros de la petición
       $password      = $this->getRequestParameter('password');

        // Obteniendo información del controlador
        $nombreModulo = $this->getModuleName();
        $nombreAccion = $this->getActionName();

        // Obteniendo objetos del núcleo del framework
        $peticion      = $this->getRequest();
        $sesionUsuario = $this->getUser();
        $respuesta     = $this->getResponse();
        $controlador   = $this->getController();

www.librosweb.es                                                                          96
Symfony, la guía definitiva                                                   Capítulo 6. El Controlador

           $contexto         = $this->getContext();

           // Creando variables de la acción para pasar información a la plantilla
           $this->setVar('parametro', 'valor');
           $this->parametro = 'valor';           // Versión corta.

       }
   }

  El "singleton" del contexto

  En el controlador frontal ya se ha visto una llamada a sfContext::getInstance(). En una acción,
  el método getContext() devuelve el mismo singleton. Se trata de un objeto muy útil que guarda
  una referencia a todos los objetos del núcleo de Symfony relacionados con una petición dada, y
  ofrece un método accesor para cada uno de ellos:

           ▪ sfController: El objeto controlador (->getController())

           ▪ sfRequest: El objeto de la petición (->getRequest())

           ▪ sfResponse: El objeto de la respuesta (->getResponse())

           ▪ sfUser: El objeto de la sesión del usuario (->getUser())

           ▪ sfDatabaseConnection: La conexión a la base de datos (->getDatabaseConnection())

           ▪ sfLogger: El objeto para los logs (->getLogger())

           ▪ sfI18N: El objeto de internacionalización (->getI18N())

  Se puede llamar al singleton sfContext::getInstance() desde cualquier parte del código.


6.2.4. Terminación de las Acciones
Existen varias alternativas posibles cuando se termina la ejecución de una acción. El va-
lor retornado por el método de la acción determina como será producida la vista. Para
especificar la plantilla que se utiliza al mostrar el resultado de la acción, se emplean las
constantes de la clase sfView.

Si existe una vista por defecto que se debe llamar (este es el caso más común), la acción
debería terminar de la siguiente manera:
   return sfView::SUCCESS;

Symfony buscará entonces una plantilla llamada nombreAccionSuccess.php. Este compor-
tamiento se ha definido como el comportamiento por defecto, por lo que si omites la sen-
tencia return en el método de la acción, Symfony también buscará una plantilla llamada
nombreAccionSuccess.php. Las acciones vacías también siguen este comportamiento. El
listado 6-9 muestra un ejemplo de terminaciones exitosas de acciones.

Listado       6-9      -   Acciones   que   llaman    a   las    plantillas     indexSuccess.php      y
listSuccess.php
   public function executeIndex()
   {
     return sfView::SUCCESS;


www.librosweb.es                                                                                     97
Symfony, la guía definitiva                                          Capítulo 6. El Controlador

   }

   public function executeList()
   {
   }

Si existe una vista de error que se debe llamar, la acción deberá terminar de la siguiente
manera:
   return sfView::ERROR;

Symonfy entonces buscará un plantilla llamada nombreAccionError.php.

Para utilizar una vista personalizada, se debe utilizar el siguiente valor de retorno:
   return 'MiResultado';

Symfony entonces buscará una plantilla llamada nombreAccionMiResultado.php.

Si no se utiliza ninguna vista –por ejemplo, en el caso de una acción ejecutada en un ar-
chivo de lotes– la acción debe terminar de la siguiente forma:
   return sfView::NONE;

En este caso, no se ejecuta ninguna plantilla. De esta forma, se evita por completo la ca-
pa de vista y se establece directamente el código HTML producido por la acción. Como
muestra el Listado 6-10, Symfony provee un método renderText() específico para este
caso. Este método puede ser útil cuando se necesita una respuesta muy rápida en una
acción, como por ejemplo para las interacciones creadas con Ajax, como se verá en el
Capítulo 11.

Listado 6-10 - Evitando la vista mediante una respuesta directa y un valor de re-
torno sfView::NONE
   public function executeIndex()
   {
     $this->getResponse()->setContent("<html><body>¡Hola Mundo!</body></html>");

       return sfView::NONE;
   }

   // Es equivalente a
   public function executeIndex()
   {
     return $this->renderText("<html><body>¡Hola Mundo!</body></html>");
   }

En algunos casos, se necesita una respuesta vacía pero con algunas cabeceras definidas
(sobre todo la cabecera X-JSON). Para conseguirlo, se definen las cabeceras con el objeto
sfResponse, que se ve en el próximo capítulo, y se devuelve como valor de retorno la
constante sfView::HEADER_ONLY, como muestra el Listado 6-11.

Listado 6-11 - Evitando la producción de la vista y enviando solo cabeceras
   public function executeActualizar()
   {
     $salida = '<"titulo","Mi carta sencilla"],["nombre","Sr. Pérez">';
     $this->getResponse()->setHttpHeader("X-JSON", '('.$salida.')');

www.librosweb.es                                                                            98
Symfony, la guía definitiva                                             Capítulo 6. El Controlador



       return sfView::HEADER_ONLY;
   }

Si la acción debe ser producida por una plantilla específica, se debe prescindir de la sen-
tencia return y se debe utilizar el método setTemplate() en su lugar.
   $this->setTemplate('miPlantillaPersonalizada');


6.2.5. Saltando a Otra Acción
En algunos casos, la ejecución de un acción termina solicitando la ejecución de otra ac-
ción. Por ejemplo, una acción que maneja el envío de un formulario en una solicitud
POST normalmente redirecciona a otra acción después de actualizar la base de datos.
Otro ejemplo es el de crear un alias de una acción: la acción index normalmente se utili-
za para mostrar un listado y de hecho se suele redireccionar a la acción list.

La clase de la acción provee dos métodos para ejecutar otra acción:

       ▪ Si la acción pasa la llamada hacia otra acción (forward):
   $this->forward('otroModulo', 'index');

       ▪ Si la acción produce un redireccionamiento web (redirect):
   $this->redirect('otroModulo/index');
   $this->redirect('http://www.google.com/');


  NOTA
  El código que se encuentra después de una llamada a los métodos forward o redirect en una
  acción nunca se ejecuta. Se puede considerar que estas llamadas son equivalentes a la sentencia
  return. Estos métodos lanzan una excepción sfStopException para detener la ejecución de la
  acción; esta excepción es interceptada más adelante por Symfony y simplemente se ignora.

La elección entre redirect y forward es a veces engañosa. Para elegir la mejor solución,
ten en cuenta que un forward es una llamada interna a la aplicación y transparente para
el usuario. En lo que concierne al usuario, la URL mostrada es la misma que la solicitada.
Por el contrario, un redirect resulta en un mensaje al navegador del usuario, involucran-
do una nueva petición por parte del mismo y un cambio en la URL final resultante.

Si la acción es llamada desde un formulario enviado con method=”post”, deberías siempre
realizar un redirect. La principal ventaja es que si el usuario recarga la página resultan-
te, el formulario no será enviado nuevamente; además, el botón de retroceder funciona
como se espera, ya que muestra el formulario y no una alerta preguntando al usuario si
desea reenviar una petición POST.

Existe un tipo especial de forward que se utiliza comúnmente. El método forward404()
redirecciona a una acción de Página no encontrada. Este método se utiliza normalmente
cuando un parámetro necesario para la ejecución de la acción no está presente en la pe-
tición (por tanto detectando una URL mal escrita). El Listado 6-12 muestra un ejemplo de
una acción mostrar que espera un parámetro llamado id.

Listado 6-12 - Uso del método forward404()


www.librosweb.es                                                                               99
Symfony, la guía definitiva                                               Capítulo 6. El Controlador

   public function executeMostrar()
   {
     $articulo = ArticuloPeer::retrieveByPK($this->getRequestParameter('id'));
     if (!$articulo)
     {
       $this->forward404();
     }
   }


  SUGERENCIA
  Si estás buscando la acción y la plantilla del error 404, las puedes encontrar en el directorio
  $sf_symfony_data_dir/modules/default/. Se puede personalizar esta página agregado un mó-
  dulo default a la aplicación, sobrescribiendo el del framework, y definiendo una acción error404 y
  una plantilla error404Success dentro del nuevo módulo. Otro método alternativo es el de estable-
  cer las constantes error_404_module y error_404_action en el archivo settings.yml para utili-
  zar una acción existente.

La experiencia muestra que, la mayoría de las veces, una acción hace un redirect o un
forward después de probar algo, como en el listado 6-12. Por este motivo, la clase sfAct-
ions   tiene   algunos    métodos    más,    llamados    forwardIf(),    forwardUnless(),      for-
ward404If(), forward404Unless(), redirectIf() y redirectUnless(). Estos métodos sim-
plemente requieren un parámetro que representa la condición cuyo resultado se emplea
para ejecutar el método. El método se ejecuta si el resultado de la condición es true y el
método es de tipo xxxIf() o si el resultado de la condición es false y el método es de ti-
po xxxUnless(), como se muestra en el listado 6-13.

Listado 6-13 - Uso del método forward404If()
   // Esta acción es equivalente a la mostrada en el Listado 6-12
   public function executeMostrar()
   {
     $articulo = ArticuloPeer::retrieveByPK($this->getRequestParameter('id'));
     $this->forward404If(!$articulo);
   }

   // Esta acción también es equivalente
   public function executeMostrar()
   {
     $articulo = ArticuloPeer::retrieveByPK($this->getRequestParameter('id'));
     $this->forward404Unless($articulo);
   }

El uso de estos métodos permite mantener el código de las acciones muy corto y también
lo hacen más fácil de leer.

  SUGERENCIA
  Cuando la acción llama al método forward404() o alguno de sus similares, Symfony lanza una ex-
  cepción sfError404Exception que maneja la respuesta al error 404. Esto significa que si se quie-
  re mostrar un mensaje de error de tipo 404 desde cualquier parte del código desde donde no se qu-
  iere acceder al controlador, se puede lanzar una excepción similar.


www.librosweb.es                                                                               100
Symfony, la guía definitiva                                          Capítulo 6. El Controlador


6.2.6. Repitiendo Código para varias Acciones de un Modulo
La convención en el nombre de las acciones executeNombreAccion() (en el caso de una
clase de tipo sfActions) o execute() (en el caso de una clase sfAction) garantiza que
Symfony encontrará el método de la acción. Además, permite crear métodos propios que
no serán considerados como acciones, siempre que su nombre no empiece con execute.

Existe otra convención útil cuando se necesita ejecutar repetidamente en cada acción una
serie de sentencias antes de ejecutar la propia acción. Esas sentencias comunes se pue-
den colocar en el método preExecute() de la clase de la acción. De forma análoga, se
pueden definir sentencias que se ejecuten después de cada acción añadiéndolas al méto-
do postExecute(). La sintaxis de estos métodos se muestra en el Listado 6-14.

Listado 6-14 - Usando los métodos preExecute, postExecute, y métodos personali-
zados en la clase de la acción
   class mimoduloActions extends sfActions
   {
     public function preExecute()
     {
       // El código insertado aquí se ejecuta al principio de cada llamada a una acción
       ...
     }

       public function executeIndex()
       {
         ...
       }

       public function executeListar()
       {
         ...
         $this->miPropioMetodo(); // Se puede acceder a cualquier método de la clase acción
       }

       public function postExecute()
       {
         // El código insertado aquí se ejecuta al final de cada llamada a la acción
         ...
       }

       protected function miPropioMetodo()
       {
         // Se pueden crear métodos propios, siempre que su nombre no comience por "execute"
         // En ese case, es mejor declarar los métodos como protected o private
         ...
       }
   }


6.3. Accediendo a la Petición
Ya se ha presentado anteriormente el método getRequestParameter(’miparametro’), utili-
zado para obtener el valor del parámetro de una petición por su nombre. De hecho, este


www.librosweb.es                                                                          101
Symfony, la guía definitiva                                                 Capítulo 6. El Controlador


método es una forma rápida equivalente a la sucesión de llamadas al contenedor de los
parámetros de la petición getRequest()->getParameter(’miparametro’). La clase de la ac-
ción accede al objeto de la petición, llamado sfWebRequest en Symfony, y a todos sus
métodos, mediante el método getRequest(). La tabla 6-1 lista los métodos más útiles de
sfWebRequest.

Tabla 6-1. Métodos del objeto sfWebRequest

Nombre                               Función                     Ejemplo de salida producida

Información sobre la petición

                                                                 Devuelve la constante
getMethod()                          Método de la petición       sfRequest::GET o
                                                                 sfRequest::POST

                                     Nombre del método de
getMethodName()                                                  POST
                                     petición

                                     Valor de una cabecera       Apache/2.0.59 (Unix) DAV/2
getHttpHeader(’Server’)
                                     HTTP                        PHP/5.1.6

getCookie(’foo’)                     Valor de una cookie         valor

                                     ¿Es una petición
isXmlHttpRequest() (1)                                           true
                                     AJAX?

isSecure()                           ¿Es una petición SSL?       true

Parámetros de la petición

                                     ¿Existe el parámetro en
hasParameter(’parametro’)                                    true
                                     la petición?

getParameter(’parametro’)            Valor del parámetro         valor

                               Array de todos los
getParameterHolder()->getAll() parámetros de la
                               petición

Información relacionada con la URI

                                                                 http://localhost/
getUri()                             URI completa                miaplicacion_dev.php/mimodulo/
                                                                 miaccion

getPathInfo()                        Información de la ruta      /mimodulo/miaccion

                                     Valor del “referer” de la   http://localhost/
getReferer() (2)
                                     petición                    miaplicacion_dev.php/

getHost()                            Nombre del Host             localhost

                                     Nombre y ruta del
getScriptName()                                                  miaplicacion_dev.php
                                     controlador frontal


www.librosweb.es                                                                                 102
Symfony, la guía definitiva                                        Capítulo 6. El Controlador


Información del navegador del
cliente

                                  Array de los lenguajes   Array( [0] => fr [1] => fr_FR
getLanguages()
                                  aceptados                [2] => en_US [3] => en )

                                  Array de los juegos de   Array( [0] => ISO-8859-1 [1]
getCharsets()
                                  caracteres aceptados     => UTF-8 [2] => * )

                                  Array de los tipos de    Array( [0] => text/xml [1] =>
getAcceptableContentType()
                                  contenidos aceptados     text/html

(1) Funciona sólo con prototype (2) A veces es bloqueado por los proxy

La clase sfActions ofrece algunos atajos para acceder a los métodos de la petición más
rápidamente, como se muestra en el listado 6-15.

Listado 6-15 - Accediendo a los étodos del objeto sfRequest desde una acción
   class mimoduloActions extends sfActions
   {
     public function executeIndex()
     {
       $tieneParametro = $this->getRequest()->hasParameter('parametro');
       $tieneParametro = $this->hasRequestParameter('parametro'); // Versión corta
       $parametro      = $this->getRequest()->getParameter('parametro');
       $parametro      = $this->getRequestParameter('parametro'); // Versión corta
     }
   }

Para peticiones de tipo multipart utilizadas cuando el usuario adjunta archivos, el objeto
sfWebRequest provee medios para acceder y mover estos archivos, como se muestra en
el listado 6-16.

Listado 6-16 - El objeto sfWebRequest sabe cómo manejar archivos adjuntos
   class mimoduloActions extends sfActions
   {
     public function executeUpload()
     {
       if ($this->getRequest()->hasFiles())
       {
         foreach ($this->getRequest()->getFileNames() as $nombreArchivo)
         {
           $tamanoArchivo = $this->getRequest()->getFileSize($nombreArchivo);
           $tipoArchivo = $this->getRequest()->getFileType($nombreArchivo);
           $archivoErroneo = $this->getRequest()->hasFileError($nombreArchivo);
           $directorioSubidas = sfConfig::get('sf_upload_dir');
           $this->getRequest()->moveFile('file', $directorioSubidas.'/'.$nombreArchivo);
         }
       }
     }
   }




www.librosweb.es                                                                        103
Symfony, la guía definitiva                                                Capítulo 6. El Controlador


No tienes que preocuparte sobre si el servidor soporta las variables de PHP $_SERVER o
$_ENV, o acerca de valores por defecto o problemas de compatibilidad del servidor, ya
que los métodos de ‘sfWebRequest lo hacen todo por tí. Además sus nombres son tan evi-
dentes que no es necesario consultar la documentación de PHP para descubrir cómo ob-
tener información sobre la petición.


6.4. Sesiones de Usuario
Symfony maneja automáticamente las sesiones del usuario y es capaz de almacenar da-
tos de forma persistente entre peticiones. Utiliza el mecanismo de manejo de sesiones
incluido en PHP y lo mejora para hacerlo mas configurable y más fácil de usar.

6.4.1. Accediendo a la Sesión de Usuario
El objeto sesión del usuario actual se accede en la acción con el método getUser(), que
es una instancia de la clase sfUser. Esta clase dispone de un contenedor de parámetros
que permite guardar cualquier atributo del usuario en el. Esta información estará disponi-
ble en otras peticiones    hasta terminar la sesión del usuario, como se muestra en el Lista-
do 6-17. Los atributos     de usuarios pueden guardar cualquier tipo de información (cade-
nas de texto, arrays y     arrays asociativos). Se pueden utilizar para cualquier usuario, in-
cluso si ese usuario no    se ha identificado.

Listado 6-17 - El objeto sfUser puede contener atributos personalizados del us-
uario disponibles en todas las peticiones
   class mimoduloActions extends sfActions
   {
     public function executePrimeraPagina()
     {
       $nombre = $this->getRequestParameter('nombre');

           // Guardar información en la sesión del usuario
           $this->getUser()->setAttribute('nombre', $nombre);
       }

       public function executeSegundaPagina()
       {
         // Obtener información de la sesión del usuario con un valor por defecto
         $nombre = $this->getUser()->getAttribute('nombre', 'Anónimo');
       }
   }


  ATENCIÓN
  Puedes guardar objetos en la sesión del usuario, pero no se recomienda hacerlo. El motivo es que
  el objeto de la sesión es seralizado entre peticiones y se guarda en un archivo. Cuando la sesión se
  deserializa, la clase del objeto guardado debe haber sido previamente cargada y este no es siem-
  pre el caso. Además, puede haber objetos de tipo “stalled” si se guardan objetos de Propel.

Como muchos otros getters en Symfony, el método getAttribute() acepta un segundo
parámetro, especificando el valor por defecto a ser utilizado cuando el atributo no está
definido. Para verificar si un atributo ha sido definido para un usuario, se utiliza el

www.librosweb.es                                                                                 104
Symfony, la guía definitiva                                               Capítulo 6. El Controlador


método hasAttribute(). Los atributos se guardan en un contenedor de parámetros que
puede ser accedido por el método getAttributeHolder(). También permite un borrado
rápido de los atributos del usuario con los métodos usuales del contenedor de paráme-
tros, como se muestra en el listado 6-18.

Listado 6-18 - Eliminando información de la sesión del usuario
   class mimoduloActions extends sfActions
   {
     public function executeBorraNombre()
     {
       $this->getUser()->getAttributeHolder()->remove('nombre');
     }

       public function executeLimpia()
       {
         $this->getUser()->getAttributeHolder()->clear();
       }
   }

Los atributos de la sesión del usuario también están disponibles por defecto en las planti-
llas mediante la variable $sf_user, que almacena el objeto sfUser actual, como se mues-
tra en el listado 6-19.

Listado 6-19 - Las plantillas también tienen acceso a los atributos de la sesión
del usuario
   <p>
     Hola, <?php echo $sf_user->getAttribute('nombre') ?>
   </p>


  NOTA
  Si se necesita guardar la información solamente durante la petición actual (por ejemplo, para pasar
  información a través de una sucesión de llamadas a acciones) es preferible utilizar la clase sfReq-
  uest, que también tiene métodos getAttribute() y setAttribute(). Solo los atributos del obje-
  to sfUser son persistentes entre peticiones.


6.4.2. Atributos Flash
Un problema recurrente con los atributos del usuario es la limpieza de la sesión del usua-
rio una vez que el atributo no se necesita más. Por ejemplo, puede ser necesario mostrar
un mensaje de confirmación después de actualizar información mediante un formulario.
Como la acción que maneja el formulario realiza una redirección, la única forma de pasar
información desde esta acción a la acción que ha sido redireccionada es almacenar la in-
formación en la sesión del usuario. Pero una vez que se muestra el mensaje, es necesar-
io borrar el atributo; ya que de otra forma, permanecerá en la sesión hasta que esta
expire.

El atributo de tipo flash es un atributo fugaz que permite definirlo y olvidarse de el, sab-
iendo que desaparece automáticamente después de la siguiente petición y que deja la
sesión limpia para las futuras peticiones. En la acción, se define el atributo flash de la si-
guiente manera:


www.librosweb.es                                                                                105
Symfony, la guía definitiva                                                Capítulo 6. El Controlador

   $this->setFlash('atributo', $valor);

La plantilla se procesa y se envía al usuario, quien después realiza una nueva petición
hacia otra acción. En esta segunda acción, es posible obtener el valor del atributo flash
de esta forma:
   $valor = $this->getFlash('atributo');

Luego te puedes olvidar de ese parámetro. Después de mostrar la segunda página, el
atributo flash atributo desaparece automáticamente. Incluso si no se utiliza el atributo
durante la segunda acción, el atributo desaparece igualmente de la sesión.

Si se necesita acceder un atributo flash desde la plantilla, se puede utilizar el objeto
$sf_flash:
   <?php if ($sf_flash->has('atributo')): ?>
     <?php echo $sf_flash->get('atributo') ?>
   <?php endif; ?>

O simplemente:
   <?php echo $sf_flash->get('atributo') ?>

Los atributos de tipo flash son una forma limpia de pasar información a la próxima
petición.

6.4.3. Manejo de Sesiones
El manejo de sesiones de Symfony se encarga de gestionar automáticamente el almace-
namiento de los IDs de sesión tanto en el cliente como en el servidor. Sin embargo, si se
necesita modificar este comportamiento por defecto, es posible hacerlo. Se trata de algo
que solamente lo necesitan los usuarios más avanzados.

En el lado del cliente, las sesiones son manejadas por cookies. La cookie de Symfony se
llama Symfony, pero se puede cambiar su nombre editando el archivo de configuración
factories.yml, como se muestra en el Listado 6-20.

Listado 6-20 - Cambiando el nombre de la cookie de sesión, en apps/miaplicac-
ion/config/factories.yml
   all:
     storage:
        class: sfSessionStorage
        param:
          session_name: mi_nombre_cookie


  SUGERENCIA
  La sesión se inicializa (con la función de PHP session_start()) solo si el parámetro auto_start
  de factories.yml tiene un valor de true (que es el caso por defecto). Si se quiere iniciar la sesión
  manualmente, se debe cambiar el valor de esa opción de configuración del archivo
  factories.yml.

El manejo de sesiones de Symfony esta basado en las sesiones de PHP. Por tanto, si la
gestión de la sesión en la parte del cliente se quiere realizar mediante parámetros en la



www.librosweb.es                                                                                 106
Symfony, la guía definitiva                                        Capítulo 6. El Controlador


URL en lugar de cookies, se debe modificar el valor de la directiva use_trans_sid en el
archivo de configuración php.ini. No obstante, se recomienda no utilizar esta técnica.
   session.use_trans_sid = 1

En el lado del servidor, Symfony guarda por defecto las sesiones de usuario en archivos.
Se pueden almacenar en la base de datos cambiando el valor del parámetro class en
factories.yml, como se muestra en el Listado 6-21.

Listado 6-21 - Cambiando el almacenamiento de las sesiones en el servidor, en
apps/miaplicacion/config/factories.yml
   all:
     storage:
        class: sfMySQLSessionStorage
        param:
          db_table: SESSION_TABLE_NAME     # Nombre de la tabla que guarda las sesiones
          database: DATABASE_CONNECTION    # Nombre de la conexión a la base de datos que
   se utiliza

Las clases de almacenamiento de sesiones disponibles son sfMySQLSessionStorage,
sfPostgreSQLSessionStorage y sfPDOSessionStorage; la última es la preferida. La opción
database define la conexión a utilizar; Symfony luego utiliza databases.yml (ver Capítulo
8) para determinar los parámetros de la conexión (host, nombre de la base de datos, us-
uario, y password) para realizar la conexión.

La expiración de la sesión se produce automáticamente después de sf_timeout segundos.
El valor de esta constante es 30 minutos por defecto y puede ser modificado para cada
entorno en el archivo de configuración settings.yml, como se muestra en el Listado
6-22.

Listado 6-22 - Cambiando el tiempo de vida de la sesión, en apps/miaplicacion/
config/settings.yml
   default:
     .settings:
       timeout:       1800         # Tiempo de vida de la sesión en segundos


6.5. Seguridad de la Acción
La posibilidad de ejecutar una acción puede ser restringida a usuarios con ciertos privile-
gios. Las herramientas proporcionadas por Symfony para este propósito permiten la cre-
ación de aplicaciones seguras, en las que los usuarios necesitan estar autenticados antes
de acceder a alguna característica o a partes de la aplicación. Añadir esta seguridad a
una aplicación requiere dos pasos: declarar los requerimientos de seguridad para cada
acción y autenticar a los usuarios con privilegios para que puedan acceder estas acciones
seguras.




www.librosweb.es                                                                         107
Symfony, la guía definitiva                                          Capítulo 6. El Controlador


6.5.1. Restricción de Acceso
Antes de ser ejecutada, cada acción pasa por un filtro especial que verifica si el usuario
actual tiene privilegios de acceder a la acción requerida. En Symfony, los privilegios es-
tan compuestos por dos partes:

      ▪ Las acciones seguras requieren que los usuarios esten autenticados.

      ▪ Las credenciales son privilegios de seguridad agrupados bajo un nombre y que
        permiten organizar la seguridad en grupos.

Para restringir el acceso a una acción se crea y se edita un archivo de configuración YAML
llamado ‘security.yml en el directorio config/ del módulo. En este archivo, se pueden
especificar los requerimientos de seguridad que los usuarios deberán satisfacer para cada
acción o para todas (all) las acciones. El listado 6-23 muestra un ejemplo de
security.yml.

Listado 6-23 - Estableciendo restricciones de acceso, en apps/miaplicacion/modu-
les/mimodulo/config/security.yml
   ver:
     is_secure:     off       # Todos los usuarios pueden ejecutar la acción "ver"

   modificar:
     is_secure:     on        # La acción "modificar" es sólo para usuarios autenticados

   borrar:
     is_secure:   on          # Sólo para usuarios autenticados
     credentials: admin       # Con credencial "admin"

   all:
     is_secure:    off        # off es el valor por defecto

Las acciones no incluyen restricciones de seguridad por defecto, asi que cuando no existe
el archivo security.yml o no se indica ninguna acción en ese archivo, todas las acciones
son accesibles por todos los usuarios. Si existe un archivo security.yml, Syfmony busca
por el nombre de la acción y si existe, verifica que se satisfagan los requerimientos de
seguridad. Lo que sucede cuando un usuario trata de acceder una acción restringida de-
pende de sus credenciales:

      ▪ Si el usuario está autenticado y tiene las credenciales apropiadas, entonces la ac-
        ción se ejecuta.

      ▪ Si el usuario no está autenticado, es redireccionado a la acción de login.

      ▪ Si el usuario está autenticado, pero no posee las credenciales apropiadas, será
        redirigido a la acción segura por defecto, como muestra la figura 6-1.

Las páginas login y secure son bastante simples, por lo que seguramente será necesario
personalizarlas. Se puede configurar que acciones se ejecutan en caso de no disponer de
suficientes privilegios en el archivo settings.yml de la aplicación cambiando el valor de
las propiedades mostradas en el listado 6-24.




www.librosweb.es                                                                           108
Symfony, la guía definitiva                                           Capítulo 6. El Controlador




                   Figura 6.1. La página por defecto de la acción ''secure''


Listado 6-24 - Las acciones de seguridad por defecto se definen en apps/miapli-
cacion/config/settings.yml
   all:
     .actions:
        login_module:            default
        login_action:            login

        secure_module:           default
        secure_action:           secure


6.5.2. Otorgando Acceso
Para obtener acceso a áreas restringidas, los usuarios necesitan estar autenticados y/o
poseer ciertas credenciales. Puedes extender los privilegios del usuario mediante llama-
das a métodos del objeto sfUser. El estado autenticado se estable con el método
setAuthenticated() y se puede comprobar con el método isAuthenticated(). El listado
6-25 muestra un ejemplo sencillo de autenticación.

Listado 6-25 - Estableciendo el estado de autenticación del usuario
   class miCuentaActions extends sfActions
   {
     public function executeLogin()
     {
       if ($this->getRequestParameter('login') == 'valor')
       {
         $this->getUser()->setAuthenticated(true);
       }

www.librosweb.es                                                                           109
Symfony, la guía definitiva                                             Capítulo 6. El Controlador

       }

       public function executeLogout()
       {
         $this->getUser()->setAuthenticated(false);
       }
   }

Las credenciales son un poco más complejas de tratar, ya que se pueden verificar, agre-
gar, quitar y borrar las credenciales. El listado 6-26 describe los métodos de las credenc-
iales de la clase sfUser.

Listado 6-26 - Manejando las credenciales del usuario en la acción
   class miCuentaActions extends sfActions
   {
     public function executeEjemploDeCredenciales()
     {
       $usuario = $this->getUser();

           // Agrega una o más credenciales
           $usuario->addCredential('parametro');
           $usuario->addCredentials('parametro', 'valor');

           // Verifica si el usuario tiene una credencial
           echo $usuario->hasCredential('parametro');                          => true

           // Verifica si un usuario tiene una de las credenciales
           echo $usuario->hasCredential(array('parametro', 'valor'));          => true

           // Verifica si el usuario tiene ambas credenciales
           echo $usuario->hasCredential(array('parametro', 'valor'), true);    => true

           // Quitar una credencial
           $usuario->removeCredential('parametro');
           echo $usuario->hasCredential('parametro');                          => false

           // Elimina todas las credenciales (útil en el proceso de logout)
           $usuario->clearCredentials();
           echo $usuario->hasCredential('valor');                              => false
       }
   }

Si el usuario tiene la credencial “parametro”, entonces ese usuario podrá acceder a las
acciones para las cuales el archivo security.yml requiere esa credencial. Las credenciales
se pueden utilizar también para mostrar contenido autenticado en una plantilla, como se
muestra en el listado 6-27.

Listado 6-27 - Tratando con credenciales de usuario en una plantilla
   <ul>
     <li><?php echo link_to('seccion1', 'content/seccion1') ?></li>
     <li><?php echo link_to('seccion2', 'content/seccion2') ?></li>
     <?php if ($sf_user->hasCredential('seccion3')): ?>
     <li><?php echo link_to('seccion3', 'content/seccion3') ?></li>



www.librosweb.es                                                                             110
Symfony, la guía definitiva                                                 Capítulo 6. El Controlador

     <?php endif; ?>
   </ul>

Y para el estado de autenticación, las credenciales normalmente se dan a los usuarios
durante el proceso de login. Este es el motivo por el que el objeto sfUser normalmente
se extiende para añadir métodos de login y de logout, de forma que se pueda establecer
el estado de seguridad del usuario de forma centralizada.

  SUGERENCIA
  Entre los plugins de Symfony, sfGuardPlugin extiende la clase de sesión para facilitar el proceso de
  login y logout. El Capitulo 17 contiene más información al respecto.


6.5.3. Credenciales Complejas
La sintaxis YAML utilizada en el archivo security.yml permite restringir el acceso a usua-
rios que tienen una combinación de credenciales, usando asociaciones de tipo AND y OR.
Con estas combinaciones, se pueden definir flujos de trabajo y sistemas de manejo de
privilegios muy complejos – como por ejemplo, un sistema de gestión de contenidos
(CMS) cuya parte de gestión sea accesible solo a usuarios con credencial admin, donde
los artículos pueden ser editados solo por usuarios con credenciales de editor y publica-
dos solo por aquellos que tienen credencial de publisher. El listado 6-28 muestra este
ejemplo.

Listado 6-28 - Sintaxis de combinación de credenciales
   editarArticulo:
     credentials: [ admin, editor ]                    # admin AND editor

   publicarArticulo:
     credentials: [ admin, publisher ]                 # admin AND publisher

   gestionUsuarios:
     credentials: [[ admin, superuser ]]               # admin OR superuser

Cada vez que se añade un nuevo nivel de corchetes, la lógica cambia entre AND y OR.
Así que se pueden crear combinaciones muy complejas de credenciales, como la
siguiente:
   credentials: [[root, [supplier, [owner, quasiowner]] accounts]]
                # root OR (supplier AND (owner OR quasiowner)) OR accounts


6.6. Métodos de Validación y Manejo de Errores
La validando de los datos de la acción –normalmente los parámetros de la petición– es
una tarea repetitiva y tediosa. Symfony incluye un sistema de validación, utilizando mé-
todos de la clase acción.

Se ve en primer lugar un ejemplo. Cuando un usuario hace una petición a miAccion,
Symfony siempre busca primero un método llamado validateMiAccion(). Si lo encuentra,
Symfony ejecuta ese método. El valor de retorno de esta validación determina el siguien-
te método que se ejecuta: si devuelve true, entonces se ejecuta el método executeMiAc-
cion();    en   otro   caso,   se   ejecuta    handleErrorMiAccion().       En   el   caso   de   que

www.librosweb.es                                                                                  111
Symfony, la guía definitiva                                            Capítulo 6. El Controlador


handleErrorMiAccion() no exista, Symfony busca un método genérico llamado handleE-
rror(). Si tampoco existe, simplemente devuelve el valor sfView::ERROR para producir la
plantilla miAccionError.php. La Figura 6-2 ilustra este proceso.




                              Figura 6.2. El proceso de validación


La clave para un correcto funcionamiento de la validación es respetar la convención de
nombres para los métodos de la acción:

      ▪ validateNombreAccion es el método de validación, que devuelve true o false. Se
        trata del primer método buscado cuando se solicita la acción NombreAccion. Si no
        existe, la acción se ejecuta directamente.

      ▪ handleErrorNombreAccion es el método llamado cuando el método de validación
        falla. Si no existe, entonces se muestra la plantilla Error.

      ▪ executeNombreAccion es el método de la acción. Debe existir para todoas las
        acciones.

El listado 6-29 muestra un ejemplo de una acción con métodos de validación. Independ-
ientemente de si la validación falla o no falla, en el siguiente ejemplo se ejecuta la planti-
lla miAccionSuccess.php pero no con los mismos parámetros.

Listado 6-29 - Ejemplo de métodos de validación


www.librosweb.es                                                                            112
Symfony, la guía definitiva                                                  Capítulo 6. El Controlador

   class mimoduloActions extends sfActions
   {
     public function validateMiAccion()
     {
       return ($this->getRequestParameter('id') > 0);
     }

       public function handleErrorMiAccion()
       {
         $this->message = "Parámetros no válidos";

           return sfView::SUCCESS;
       }

       public function executeMiAccion()
       {
         $this->message = "Los parámetros son válidos";
       }
   }

Se puede incluir cualquier código en el método validate(). La única condición es que de-
vuelva un valor true o false. Como es un método de la clase sfActions, tiene acceso a
los objetos sfRequest y sfUser, que pueden ser realmente útiles para validación de los
datos de la petición y del contexto.

Se pueden utilizar este mecanismo para implementar la validación de los formularios (es-
to es, controlar los valores introducidos por el usuario en un formulario antes de proce-
sarlo), pero se trata de una tarea muy repetitiva para la que Symfony proporciona herra-
mientas automatizadas, como las descritas en el Capítulo 10.


6.7. Filtros
El mecanismo de seguridad puede ser entendido como un filtro, por el que debe pasar
cada petición antes de ejecutar la acción. Según las comprobaciones realizadas en el fil-
tro, se puede modificar el procesamiento de la petición –por ejemplo, cambiando la ac-
ción ejecutada (default/secure en lugar de la acción solicitada en el caso del filtro de se-
guridad). Symfony extiende esta idea a clases de filtros. Se puede especificar cualquier
número de clases de filtros a ser ejecutadas antes de que se procese la respuesta, y
además hacerlo de forma sistemática para todas las peticiones. Se pueden entender los
filtros como una forma de empaquetar cierto código de forma similar a preExecute() y
postExecute(), pero a un nivel superior (para toda una aplicación en lugar de para todo
un módulo).

6.7.1. La Cadena de Filtros
Symfony de hecho procesa cada petición como una cadena de filtros ejecutados de forma
sucesiva. Cuando el framework recibe una petición, se ejecuta el primer filtro (que siem-
pre es sfRenderingFilter). En algún punto, llama al siguiente filtro en la cadena, luego el
siguiente, y asi sucesivamente. Cuando se ejecuta el último filtro (que siempre es sfExe-
cutionFilter),      los   filtros   anteriores   pueden   finalizar,   y   asi   hasta   el   filtro    de


www.librosweb.es                                                                                       113
Symfony, la guía definitiva                                              Capítulo 6. El Controlador


sfRenderingFilter. La Figura 6-3 ilustra esta idea con un diagrama de secuencias, utili-
zando una cadena de filtros simplificada (la cadena real tiene muchos más filtros).




                              Figura 6.3. Ejemplo de cadena de filtros


Este proceso es la razón de la estructura de la clases de tipo filtro. Todas estas clases ex-
tienden la clase sfFilter y contienen un método execute() que espera un objeto de tipo
$filterChain como parámetro. En algún punto de este método, el filtro pasa al siguiente
filtro en la cadena, llamando a $filterChain->execute(). El listado 6-30 muestra un
ejemplo. Por lo tanto, los filtros se dividen en dos partes:

      ▪ El código que se encuentra antes de la llamada a $filterChain->execute() se
        ejecuta antes de que se ejecute la acción.

      ▪ El código que se encuentra después de la llamada a $filterChain->execute() se
        ejecuta después de la acción y antes de producir la vista.

Listado 6-30 - Estructura de la clase filtro
   class miFiltro extends sfFilter
   {
     public function execute ($filterChain)
     {
       // Código que se ejecuta antes de la ejecución de la acción
       ...

        // Ejecutar el siguiente filtro de la cadena
        $filterChain->execute();

       // Código que se ejecuta después de la ejecuciñon de la acción y antes de que se
   genere la vista
       ...



www.librosweb.es                                                                              114
Symfony, la guía definitiva                                            Capítulo 6. El Controlador

       }
   }

La cadena de filtros por defecto se define en el archivo de configurarcion de la aplicación
filters.yml, y su contenido se muestra en el listado 6-31. Este archivo lista los filtros
que se ejecutan para cada petición.

Listado 6-31 - Cadena de filtros por defecto, en miaplicacion/config/filters.yml
   rendering: ~
   web_debug: ~
   security: ~

   # Generalmente, se insertar los filtros propios aqui

   cache:         ~
   common:        ~
   flash:         ~
   execution:     ~

Estas declaraciones no tienen parámetros (el caracter tilde, ~, significa null en YAML),
porque heredan los parámetros definidos en el núcleo de Symfony. En su núcleo, Sym-
fony define las opciones class y param para cada uno de estos filtros. Por ejemplo, el lis-
tado 6-32 muestra los parámetros por defecto para el filtro rendering.

Listado 6-32 - Parámetros por defecto del filtro sfRenderingFilter, en $sf_Sym-
fony_data_dir/config/filters.yml
   rendering:
     class: sfRenderingFilter      # Clase del filtro
     param:                        # Parámetros del filtro
       type: rendering

Si se deja el valor vacío (~) en el archivo filters.yml de la aplicación, Symfony aplica el
filtro con las opciones por defecto definidas en su núcleo.

Se pueden personalizar la cadenas de filtros en varias formas:

       ▪ Desactivando algún filtro de la cadena agregando un parámetro enabled: off.
           Por ejemplo, para desactivar el filtro de depuración web (web_debug), se añade:
   web_debug:
     enabled: off

       ▪ No se deben borrar las entradas del archivo filters.yml para desactivar un filtro
           ya que Symfony lanzará una excepción.

       ▪ Se pueden añadir declaraciones propias en cualquier lugar de la cadena (normal-
         mente después del filtro security) para agregar un filtro propio (como se verá en
           la próxima sección). En cualquier caso, el filtro rendering debe ser siempre la pri-
           mera entrada, y el filtro execution debe ser siempre la ultima entrada en la cade-
           na de filtros.

       ▪ Redefinir la clase y los parámetros por defecto del filtro por defecto (normalmen-
         te para modificar el sistema de seguridad y utilizar un filtro de seguridad propio).


www.librosweb.es                                                                            115
Symfony, la guía definitiva                                               Capítulo 6. El Controlador


  SUGERENCIA
  El parámetro enabled: off funciona correctamente para desactivar los filtros propios, pero se
  pueden desactivar los filtros por defecto a través del archivo settings.myl, modificando los valo-
  res de las opciones web_debug, use_security, cache, y use_flash. El motivo es que cada uno de
  los filtros por defecto posee un parámetro condition que comprueba el valor de estas opciones.


6.7.2. Construyendo Tu Propio Filtro
Construir un filtro propio es bastante sencillo. Se debe crear una definición de una clase
similar a la demostrada en el listado 6-30, y se coloca en una de los directorios lib/ del
proyecto para aprovechar la carga automática de clases.

Como una acción puede pasar el control o redireccionar hacia otra acción y en consec-
uencia relanzar toda la cadena de filtros, quizás sea necesario restringir la ejecución de
los filtros propios a la primera acción de la petición. El método isFirstCall() de la clase
sfFilter retorna un valor booleano con este propósito. Esta llamada solo tiene sentido
antes de la ejecución de una acción.

Este concepto se puede entender fácilmente con un ejemplo. El listado 6-33 muestra un
filtro utilizado para auto-loguear a los usuarios con una cookie MiSitioWeb, que se supone
que se crea en la acción login. Se trata de una forma rudimentaria pero que funciona pa-
ra incluir la característica Recuérdame de un formulario de login.

Listado 6-33 - Ejemplo de archivo de clase de filtro, en apps/miaplicacion/lib/
rememberFilter.class.php
   class rememberFilter extends sfFilter
   {
     public function execute($filterChain)
     {
       // Ejecutar este filtro solo una vez
       if ($this->isFirstCall())
       {
         // Los filtros no tienen acceso directo a los objetos user y request.
         // Se necesita el contexto para obtenerlos
         $peticion = $this->getContext()->getRequest();
         $usuario = $this->getContext()->getUser();

               if ($peticion->getCookie('MiSitioWeb'))
               {
                 // logueado
                 $usuario->setAuthenticated(true);
               }
           }

           // Ejecutar el proximo filtro
           $filterChain->execute();
       }
   }

En ocasiones, en lugar de continuar con la ejecución de la cadena de filtros, se necesita
pasar el control a una acción específica al final de un filtro. sfFilter no tiene un método


www.librosweb.es                                                                               116
Symfony, la guía definitiva                                                 Capítulo 6. El Controlador


forward(), pero sfController si, por lo que simplemente se puede llamar al siguiente
método:
   return $this->getContext()->getController()->forward('mimodulo', 'miAccion');


  NOTA
  La clase sfFilter tiene un método initialize(), ejecutado cuando se crea el objeto filtro. Se
  puede redefinir en el filtro propio si se necesita trabajar de forma personalizada con los parámetros
  de los filtros (definidos en filters.yml, como se describe a continuación).


6.7.3. Activación de Filtros y Parámetros
Crear un filtro no es suficiente para activarlo. Se necesita agregar el filtro propio a la ca-
dena, y para eso, se debe declar la clase del filtro en el archivo filters.yml, localizado
en el directorio config/de la aplicación o del módulo, como se muestra en el listado 6-34.

Listado 6-34 - Ejemplo de archivo de activación de filtro, en apps/miaplicacion/
config/filters.yml
   rendering: ~
   web_debug: ~
   security: ~

   remember:                 # Los filtros requieren un nombre único
     class: rememberFilter
     param:
       cookie_name: MiSitioWeb
       condition:   %APP_ENABLE_REMEMBER_ME%

   cache:       ~
   common:      ~
   flash:       ~
   execution:   ~

Cuando se encuentra activo, el filtro se ejecuta en cada petición. El archivo de configura-
ción de los filtros puede contener una o más definiciones de parámetros en la sección pa-
ram. La clase filtro puede obtener estos parámetros con el método getParameter(). El lis-
tado 6-35 muestra como obtener los valores de los parámetros.

Listado 6-35 - Obteniendo el valor del parámetro, en apps/miaplicacion/lib/
rememberFilter.class.php
   class rememberFilter extends sfFilter
   {
     public function execute($filterChain)
     {
         ...
         if ($request->getCookie($this->getParameter('cookie_name')))
         ...
     }
   }

El parámetro condition se comprueba en la cadena de filtros para ver si el filtro debe ser
ejecutado. Por lo que las declaraciones del filtro propio puede basarse en la configuración

www.librosweb.es                                                                                  117
Symfony, la guía definitiva                                          Capítulo 6. El Controlador


de la aplicación, como muestra el listado 6-34. El filtro remeber se ejecuta solo si el archi-
vo app.yml incluye lo siguiente:
   all:
     enable_remember_me: on


6.7.4. Filtros de Ejemplo
Los filtros son útiles para repetir cierto código en todas las acciones. Por ejemplo, si se
utiliza un sistema remoto de estadísticas, puede ser necesario añadir un trozo de código
que realice una llamada a un script de las estadísticas en cada página. Este código se
puede colocar en el layout global, pero entonces estaría activo para toda la aplicación.
Otra forma es colocarlo en un filtro, como se muestra el listado 6-36, y activarlo en cada
módulo.

Listado 6-36 - Filtro para el sistema de estadísticas de Google Analytics
   class sfGoogleAnalyticsFilter extends sfFilter
   {
     public function execute($filterChain)
     {
       // No se hace nada antes de la acción
       $filterChain->execute();

        // Decorar la respuesta con el código de Google Analytics
        $codigoGoogle = '
   <script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
   </script>
   <script type="text/javascript">
          _uacct="UA-'.$this->getParameter('google_id').'";urchinTracker();
   </script>';
        $respuesta = $this->getContext()->getResponse();
        $respuesta->setContent(str_ireplace('</body>',
   $codigoGoogle.'</body>',$respuesta->getContent()));
      }
   }

No obstante, este filtro no es perfecto, ya que no se debería añadir el código de Google si
la respuesta no es de tipo HTML.

Otro ejemplo es el de un filtro que cambia las peticiones a SSL si no lo son, para hacer
más segura la comunicación, como muestra el Listado 6-37.

Listado 6-37 - Filtro de comunicación segura
   class sfSecureFilter extends sfFilter
   {
     public function execute($filterChain)
     {
       $contexto = $this->getContext();
       $peticion = $context->getRequest();
       if (!$peticion->isSecure())
       {
         $urlSegura = str_replace('http', 'https', $peticion->getUri());
         return $contexto->getController()->redirect($urlSegura);
         // No se continúa con la cadena de filtros

www.librosweb.es                                                                          118
Symfony, la guía definitiva                                             Capítulo 6. El Controlador

            }
            else
            {
              // La petición ya es segura, asi que podemos continuar
              $filterChain->execute();
            }
        }
   }

Los filtros se utilizan mucho en los plugins, porque permiten extender las características
de una aplicación de forma global. El Capítulo 17 incluye más información sobre los plu-
gins, y el wiki del proyecto Symfony (http://trac.symfony-project.com/) también tiene más
ejemplos de filtros.


6.8. Configuración del Módulo
Algunas características de los módulos dependen de la configuración. Para modificarlas,
se debe crear un archivo module.yml en el directorio config/ y se deben definir paráme-
tros para cada entorno (o en la sección all: para todos los entornos). El listado 6-38
muestra un ejemplo de un archivo module.yml para el módulo mimodulo.

Listing 6-38 - Configuración del módulo, en apps/miaplicacion/modules/mimodulo/
config/module.yml
   all:                 # Para todos los entornos
     enabled:     true
     is_internal: false
     view_class: sfPHP

El parámetro enabled permite desactivar todas las acciones en un módulo. En ese caso,
todas        las   acciones    se   redireccionan    a    la   acción   module_disabled_modu-
le/module_disabled_action (tal y como se define en el archivo settings.yml).

El parámetro is_internal permite restringir la ejecución de todas las acciones de un mó-
dulo a llamadas internas. Esto es útil por ejemplo para acciones de envío de correos
electrónicos que se deben llamar desde otras acciones para enviar mensajes de e-mail,
pero que no se deben llamar desde el exterior.

El parámetro view_class define la clase de la vista. Debe heredar de sfView. Sobreescri-
bir este valor permite utilizar otros sistemas de generación de vistas con otros motores
de plantillas, como por ejemplo Smarty.


6.9. Resumen
En Symfony, la capa del controlador esta dividida en dos partes: el controlador frontal,
que es el único punto de entrada a la aplicación para un entorno dado, y las acciones,
que contienen la lógia de las páginas. Una acción puede elegir la forma en la que se eje-
cuta su vista, devolviendo un valor correspondiente a una de las constantes de la clase
sfView. Dentro de una acción, se pueden manipular los diferentes elementos del contex-
to, incluidos el objeto de la petición (sfRequest) y el objeto de la sesión del usuario actual
(sfUser).


www.librosweb.es                                                                             119
Symfony, la guía definitiva                                      Capítulo 6. El Controlador


Combinando el poder del objeto de sesión, el objeto acción y las configuraciones de se-
guridad proporcionan sistema de seguridad completo, con restricciones de acceso y cre-
denciales. Los métodos especiales validate() y handleError() en la acciones permiten
gestionar la validación de las peticiones. Y si los métodos preExecute() y postExecute()
se diseñan para la reutilización de código dentro de un módulo, los filtros permiten la
misma reutilización para toda la aplicación ejecutando código del controlador para cada
petición.




www.librosweb.es                                                                      120
Symfony, la guía definitiva                                              Capítulo 7. La Vista




Capítulo 7. La Vista
La vista se encarga de producir las páginas que se muestran como resultado de las accio-
nes. La vista en Symfony está compuesta por diversas partes, estando cada una de ellas
especialmente preparada para que pueda ser fácilmente modificable por la persona que
normalmente trabaja con cada aspecto del diseño de las aplicaciones.

      ▪ Los diseñadores web normalmente trabajan con las plantillas (que son la presen-
        tación de los datos de la acción que se está ejecutando) y con el layout (que con-
        tiene el código HTML común a todas las páginas). Estas partes están formadas
        por código HTML que contiene pequeños trozos de código PHP, que normalmente
        son llamadas a los diversos helpers disponibles.

      ▪ Para mejorar la reutilización de código, los programadores suelen extraer trozos
        de las plantillas y los transforman en componentes y elementos parciales. De es-
        ta forma, el layout se modifica para definir zonas en las que se insertan compo-
        nentes externos. Los diseñadores web también pueden trabajar fácilmente con
        estos trozos de plantillas.

      ▪ Los programadores normalmente centran su trabajo relativo a la vista en los ar-
        chivos de configuración YAML (que permiten establecer opciones para las propie-
        dades de la respuesta y para otros elementos de la interfaz) y en el objeto resp-
        uesta. Cuando se trabaja con variables en las plantillas, deben considerarse los
        posibles riesgos de seguridad de XSS (cross-site scripting) por lo que es necesar-
        io conocer las técnicas de escape de los caracteres introducidos por los usuarios.

Independientemente del tipo de trabajo, existen herramientas y utilidades para simplifi-
car y acelerar el trabajo (normalmente tedioso) de presentar los resultados de las accio-
nes. En este capítulo se detallan todas estas herramientas.


7.1. Plantillas
El Listado 7-1 muestra el código típico de una plantilla. Su contenido está formado por
código HTML y algo de código PHP sencillo, normalmente llamadas a las variables defini-
das en la acción (mediante la instrucción $this->nombre_variable = ‘valor’;) y algunos
helpers.

Listado 7-1 - Plantilla de ejemplo indexSuccess.php
   <h1>Bienvenido</h1>
   <p>¡Hola de nuevo, <?php echo $nombre ?>!</p>
   <ul>¿Qué es lo que quieres hacer?
     <li><?php echo link_to('Leer los últimos artículos', 'articulo/leer') ?></li>
     <li><?php echo link_to('Escribir un nuevo artículo', 'articulo/escribir') ?></li>
   </ul>

Como se explica en el Capítulo 4, es recomendable utilizar la sintaxis alternativa de PHP
en las plantillas para hacerlas más fáciles de leer a aquellos desarrolladores que descono-
cen PHP. Se debería minimizar en lo posible el uso de código PHP en las plantillas, ya que
estos archivos son los que se utilizan para definir la interfaz de la aplicación, y muchas
veces son diseñados y modificados por otros equipos de trabajo especializados en el

www.librosweb.es                                                                         121
Symfony, la guía definitiva                                                       Capítulo 7. La Vista


diseño de la presentación y no de la lógica del programa. Además, incluir la lógica dentro
de las acciones permite disponer de varias plantillas para una sola acción sin tener que
duplicar el código.

7.1.1. Helpers
Los helpers son funciones de PHP que devuelven código HTML y que se utilizan en las
plantillas. En el listado 7-1, la función link_to() es un helper. A veces, los helpers sola-
mente se utilizan para ahorrar tiempo, agrupando en una sola instrucción pequeños tro-
zos de código utilizados habitualmente en las plantillas. Por ejemplo, es fácil imaginarse
la definición de la función que representa a este helper:
   <?php echo input_tag('nick') ?>
   => <input type="text" name="nick" id="nick" value="" />

La función debería ser como la que se muestra en el listado 7-2.

Listado 7-2 - Ejemplo de definición de helper
   function input_tag($name, $value = null)
   {
     return '<input type="text" name="'.$name.'" id="'.$name.'" value="'.$value.'" />';
   }

En realidad, la función input_tag() que incluye Symfony es un poco más complicada que
eso, ya que permite indicar un tercer parámetro que contiene otros atributos de la etiq-
ueta <input>. Se puede consultar su sintaxis completa y sus opciones en la documenta-
ción de la API: http://www.symfony-project.org/api/symfony.html .

La mayoría de las veces los helpers incluyen cierta inteligencia que evita escribir bastante
código:
   <?php echo auto_link_text('Por favor, visita nuestro sitio web www.ejemplo.com') ?>
   => Por favor, visita nuestro sitio web <a
   href="http://www.ejemplo.com">www.ejemplo.com</a>

Los helpers facilitan la creación de las plantillas y producen el mejor código HTML posible
en lo que se refiere al rendimiento y a la accesibilidad. Aunque se puede usar HTML nor-
mal y corriente, los helpers normalmente son más rápidos de escribir.

  SUGERENCIA
  Quizás te preguntes por qué motivo los helpers se nombran con la sintaxis de los guiones bajos en
  vez de utilizar el método camelCase que se utiliza en el resto de Symfony. El motivo es que los hel-
  pers son funciones, y todas las funciones de PHP utilizan la sintaxis de los guiones bajos.

7.1.1.1. Declarando los Helpers
Los archivos de Symfony que contienen los helpers no se cargan automáticamente (ya
que contienen funciones, no clases). Los helpers se agrupan según su propósito. Por
ejemplo el archivo llamado TextHelper.php contiene todas las funciones de los helpers re-
lacionados con el texto, que se llaman “grupo de helpers de Text”. De esta forma, si una
plantilla va a utilizar un helper, se debe cargar previamente el grupo al que pertenece el



www.librosweb.es                                                                                 122
Symfony, la guía definitiva                                                    Capítulo 7. La Vista


helper mediante la función use_helper(). El listado 7-3 muestra una plantilla que hace
uso del helper auto_link_text(), que forma parte del grupo Text.

Listado 7-3 - Declarando el uso de un helper
   // Esta plantilla utiliza un grupo de helpers específicos
   <?php use_helper('Text') ?>
   ...
   <h1>Descripción</h1>
   <p><?php echo auto_link_text($descripcion) ?></p>


  SUGERENCIA
  Si se necesita declarar más de un grupo de helpers, se deben añadir más argumentos a la llamada
  de la función use_helper(). Si por ejemplo se necesitan cargar los helpers Text y Javascript, la
  llamada a la función debe ser <?php echo use_helper(’Text’, ‘Javascript’) ?>.

Por defecto algunos de los helpers están disponibles en las plantillas sin necesidad de ser
declarados. Estos helpers pertenecen a los siguientes grupos:

      ▪ Helper: se necesita para incluir otros helpers (de hecho, la función use_helper()
        también es un helper)

      ▪ Tag: helper básico para etiquetas y que utilizan casi todos los helpers

      ▪ Url: helpers para la gestión de enlaces y URL

      ▪ Asset: helpers que añaden elementos a la sección <head> del código HTML y que
        proporcionan enlaces sencillos a elementos externos (imágenes, archivos JavaS-
        cript, hojas de estilo, etc.)

      ▪ Partial: helpers que permiten incluir trozos de plantillas

      ▪ Cache: manipulación de los trozos de código que se han añadido a la cache

      ▪ Form: helpers para los formularios

El archivo settings.yml permite configurar la lista de helpers que se cargan por defecto
en todas las plantillas. De esta forma, se puede modificar su configuración si se sabe por
ejemplo que no se van a usar los helpers relacionados con la cache o si se sabe que
siempre se van a necesitar los helpers relacionados con el grupo Text. Este cambio puede
aumentar ligeramente la velocidad de ejecución de la aplicación. Los 4 primeros helpers
de la lista anterior (Helper, Tag, Url y Asset) no se pueden eliminar, ya que son obligato-
rios para que funcione correctamente el mecanismo de las plantillas. Por este motivo ni
siquiera aparecen en la lista de helpers estándares.

  SUGERENCIA
  Si se quiere utilizar un helper fuera de una plantilla, se puede cargar un grupo de helpers desde
  cualquier punto de la aplicación mediante la función sfLoader::loadHelpers($helpers), donde
  la variable $helpers es el nombre de un grupo de helpers o un array con los nombres de varios
  grupos de helpers. Por tanto, si se quiere utilizar auto_link_text() dentro de una acción, es ne-
  cesario llamar primero a sfLoader::loadHelpers(’Text’).



www.librosweb.es                                                                              123
Symfony, la guía definitiva                                                 Capítulo 7. La Vista


7.1.1.2. Los helpers habituales
Algunos helpers se explican en detalle en los siguientes capítulos, en función de la carac-
terística para la que han sido creados. El listado 7-4 incluye un pequeña lista de los hel-
pers que más se utilizan y muestra también el código HTML que generan.

Listado 7-4 - Los helpers por defecto más utilizados
   // Grupo Helper
   <?php use_helper('NombreHelper') ?>
   <?php use_helper('NombreHelper1', 'NombreHelper2', 'NombreHelper3') ?>

   // Grupo Tag
   <?php echo tag('input', array('name' => 'parametro', 'type' => 'text')) ?>
   <?php echo tag('input', 'name=parametro type=text') ?> // Sintaxis alternativa para
   las opciones
   => <input name="parametro" type="text" />
   <?php echo content_tag('textarea', 'contenido de prueba', 'name=parametro') ?>
   => <textarea name="parametro">contenido de prueba</textarea>

   // Grupo Url
   <?php echo link_to('Pínchame', 'mimodulo/miaccion') ?>
   => <a href="/ruta/a/miaccion">Pínchame</a> // Depende del sistema de enrutamiento

   // Grupo Asset
   <?php echo image_tag('miimagen', 'alt=imagen size=200x100') ?>
   => <img src="/images/miimagen.png" alt="imagen" width="200" height="100"/>
   <?php echo javascript_include_tag('miscript') ?>
   => <script language="JavaScript" type="text/javascript" src="/js/miscript.js"></script>
   <?php echo stylesheet_tag('estilo') ?>
   => <link href="/stylesheets/estilo.css" media="screen" rel="stylesheet" type="text/css"
   />

Symfony incluye muchos otros helpers y describirlos todos requeriría de un libro entero.
La mejor referencia para estudiar los helpers es la documentación de la API, que se pue-
de consultar en http://www.symfony-project.org/api/symfony.html, donde todos los helpers
incluyen documentación sobre su sintaxis, opciones y ejemplos.

7.1.1.3. Crea tus propios helpers
Symfony incluye numerosos helpers que realizan distintas funcionalidades, pero si no se
encuentra lo que se necesita, es probable que tengas que crear un nuevo helper. Crear
un helper es muy sencillo.

Las funciones del helper (funciones normales de PHP que devuelven código HTML) se de-
ben guardar en un archivo llamado NombreHelper.php, donde Nombre es el nombre del
nuevo grupo de helpers. El archivo se debe guardar en el directorio apps/miaplicacion/
lib/helper/ (o en cualquier directorio helper/ que esté dentro de cualquier directorio
lib/ del proyecto) para que la función use_helper(’Nombre’) pueda encontrarlo de forma
automática y así poder incluirlo en la plantilla.




www.librosweb.es                                                                           124
Symfony, la guía definitiva                                                       Capítulo 7. La Vista


  SUGERENCIA
  Este mecanismo permite incluso redefinir los helpers de Symfony. Para redefinir por ejemplo todos
  los helpers del grupo Text, se puede crear un archivo llamado TextHelper.php y guardarlo en el
  directorio apps/miaplicacion/lib/helper/. Cada vez que se llame a la función use_hel-
  per(’Text’), Symfony carga el nuevo grupo de helpers en vez del grupo por defecto. Hay que ser
  cuidadoso con este método, ya que como el archivo original no se carga, el nuevo grupo de helpers
  debe redefinir todas y cada una de las funciones del grupo original, ya que de otra forma no estarán
  disponibles las funciones no definidas.


7.1.2. Layout de las páginas
La plantilla del listado 7-1 no es un documento XHTML válido. Le faltan la definición del
DOCTYPE y las etiquetas <html> y <body>. El motivo es que estos elementos se encuentran
en otro lugar de la aplicación, un archivo llamado layout.php que contiene el layout de la
página. Este archivo, que también se denomina plantilla global, almacena el código HTML
que es común a todas las páginas de la aplicación, para no tener que repetirlo en cada
página. El contenido de la plantilla se integra en el layout, o si se mira desde el otro pun-
to de vista, el layout decora la plantilla. Este comportamiento es una implementación del
patrón de diseño llamado “decorator” y que se muestra en la figura 7-1.

  SUGERENCIA
  Para obtener más información sobre el patrón “decorator” y sobre otros patrones de diseño, se pue-
  de consultar el libro “Patterns of Enterprise Application Architecture” escrito por Martin Fowler
  (Addison-Wesley, ISBN: 0-32112-742-0).




                          Figura 7.1. Plantilla decorada con un layout


El listado 7-5 muestra el layout por defecto, que se encuentra en el directorio
templates/.

Listado 7-5 - Layout por defecto, en miproyecto/apps/miaplicacion/templates/
layout.php
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/
   2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd">
   <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
   <head>
     <?php echo include_http_metas() ?>
     <?php echo include_metas() ?>
     <?php echo include_title() ?>
     <link rel="shortcut icon" href="/favicon.ico" />
   </head>
   <body>


www.librosweb.es                                                                                 125
Symfony, la guía definitiva                                               Capítulo 7. La Vista

   <?php echo $sf_data->getRaw('sf_content') ?>

   </body>
   </html>

Los helpers utilizados en la sección <head> obtienen información del objeto respuesta y
en la configuración de la vista. La etiqueta <body> muestra el resultado de la plantilla.
Utilizando este layout, la configuración por defecto y la plantilla de ejemplo del listado 7-
1, la vista generada sería la del listado 7-6.

Listado 7-6 - Unión del layout, la configuración de la vista y la plantilla
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/
   2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd">
   <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
   <head>
     <meta http-equiv="content-type" content="text/html; charset=utf-8" />
     <meta name="title" content="symfony project" />
     <meta name="robots" content="index, follow" />
     <meta name="description" content="symfony project" />
     <meta name="keywords" content="symfony, project" />
     <title>symfony project</title>
     <link rel="stylesheet" type="text/css" href="/css/main.css" />
     <link rel="shortcut icon" href="/favicon.ico">
   </head>
   <body>

   <h1>Bienvenido</h1>
   <p>¡Hola de nuevo, <?php echo $nombre ?>!</p>
   <ul>¿Qué es lo que quieres hacer?
     <li><?php echo link_to('Leer los últimos artículos', 'articulo/leer') ?></li>
     <li><?php echo link_to('Escribir un nuevo artículo', 'articulo/escribir') ?></li>
   </ul>

   </body>
   </html>

La plantilla global puede ser adaptada completamente para cada aplicación. Se puede
añadir todo el código HTML que sea necesario. Normalmente se utiliza el layout para
mostrar la navegación, el logotipo del sitio, etc. Incluso es posible definir más de un lay-
out y decidir en cada acción el layout a utilizar. No te preocupes ahora por la forma de
incluir archivos de JavaScript y hojas de estilos, ya que se explica en la sección “Configu-
ración de la Vista” más adelante en este capítulo.

7.1.3. Atajos de plantilla
Symfony incluye una serie de variables propias en todas las plantillas. Estas variables se
pueden considerar atajos que permiten el acceso directo a la información más utilizada
en las plantillas, mediante los siguientes objetos internos de Symfony:

      ▪ $sf_context: el objeto entero de contexto (instance of sfContext)

      ▪ $sf_request: el objeto petición (instance of sfRequest)

      ▪ $sf_params: los parámetros de la petición

www.librosweb.es                                                                         126
Symfony, la guía definitiva                                                   Capítulo 7. La Vista


      ▪ $sf_user: el objeto de sesión del usuario actual (instance of sfUser)

En el capítulo anterior se detallaban algunos métodos útiles de los objetos sfRequest y
sfUser. En las plantillas se pueden invocar todos esos métodos mediante las variables
$sf_request y $sf_user. Por ejemplo, si la petición incluye un parámetro llamado total,
desde la plantilla se puede acceder a su valor de la siguiente manera:
   // Método largo
   <?php echo $sf_request->getParameter('total'); ?>

   // Método corto (atajo)
   <?php echo $sf_params->get('total'); ?>

   // Son equivalentes al siguiente código de la acción
   echo $this->getRequestParameter('total');


7.2. Fragmentos de código
En ocasiones es necesario incluir cierto código HTML o PHP en varias páginas. Para no te-
ner que repetirlo, casi siempre es suficiente con utilizar la instrucción include().

Si por ejemplo varias de las plantillas de la aplicación utilizan el mismo fragmento de có-
digo, se puede guardar en un archivo llamado miFragmento.php en el directorio global de
plantillas (miproyecto/apps/miaplicacion/templates/) e incluirlo en las plantillas median-
te la instrucción siguiente:
   <?php include(sfConfig::get('sf_app_template_dir').'/miFragmento.php') ?>

Sim embargo, esta forma de trabajar con fragmentos de código no es muy limpia, sobre
todo porque puede que los nombres de las variables utilizadas no coincidan en el frag-
mento de código y en las distintas plantillas. Además, el sistema de cache de Symfony
(que se explica en el Capítulo 12) no puede detectar el uso de include(), por lo que no
se puede incluir en la cache el código del fragmento de forma independiente al de las
plantillas. Symfony define 3 alternativas al uso de la instrucción include() y que permi-
ten manejar de forma inteligente los fragmentos de código:

      ▪ Si el fragmento contiene poca lógica, se puede utilizar un archivo de plantilla al
        que se le pasan algunas variables. En este caso, se utilizan los elementos parcia-
        les (partial).

      ▪ Si la lógica es compleja (por ejemplo se debe acceder a los datos del modelo o se
        debe variar los contenidos en función de la sesión) es preferible separar la pre-
        sentación de la lógica. En este caso, se utilizan componentes (component).

      ▪ Si el fragmento va a reemplazar una zona específica del layout, para la que pue-
        de que exista un contenido por defecto, se utiliza un slot.

  NOTA
  Existe otro tipo de fragmento de código, llamado “slot de componentes”, que se utiliza cuando el
  fragmento depende del contexto (por ejemplo si el fragmento debe ser diferente para las acciones
  de un mismo módulo). Más tarde en este capítulo se explican los “slots de componentes”.




www.librosweb.es                                                                             127
Symfony, la guía definitiva                                                Capítulo 7. La Vista


Todos estos fragmentos se incluyen mediante los helpers del grupo llamado Partial. Es-
tos helpers están disponibles en cualquier plantilla de Symfony sin necesidad de declarar-
los al principio.

7.2.1. Elementos parciales
Un elemento parcial es un trozo de código de plantilla que se puede reutilizar. Por ejem-
plo, en una aplicación de publicación, el código de plantilla que se encarga de mostrar un
artículo se utiliza en la página de detalle del artículo, en la página que lista los mejores
artículo y en la página que muestra los últimos artículos. Se trata de un código perfecto
para definirlo como elemento parcial, tal y como muestra la figura 7-2.




              Figura 7.2. Reutilización de elementos parciales en las plantillas


Al igual que las plantillas, los elementos parciales son archivos que se encuentran en el
directorio templates/, y que contienen código HTML y código PHP. El nombre del archivo
de un elemento parcial siempre comienza con un guión bajo (_), lo que permite distinguir
a los elementos parciales de las plantillas, ya que todos se encuentran en el mismo direc-
torio templates/.

Una plantilla puede incluir elementos parciales independientemente de que estos se enc-
uentren en el mismo módulo, en otro módulo o en el directorio global templates/. Los
elementos parciales se incluyen mediante el helper include_partial(), al que se le pasa
como parámetro el nombre del módulo y el nombre del elemento parcial (sin incluir el
guión bajo del principio y la extensión .php del final), tal y como se muestra en el listado
7-7.

Listado 7-7 - Incluir elementos parciales en una plantilla del módulo mimodulo
   // Incluir el elemento pacial de miaplicacion/modules/mimodulo/templates/_miparcial1.php
   // Como la plantilla y el elemento parcial están en el mismo módulo,
   // se puede omitir el nombre del módulo
   <?php include_partial('miparcial1') ?>

   // Incluir el elemento parcial de miaplicacion/modules/otromodulo/templates/
   _miparcial2.php
   // En este caso es obligatorio indicar el nombre del módulo
   <?php include_partial('otromodulo/miparcial2') ?>

   // Incluir el elemento parcial de miaplicacion/templates/_miparcial3.php
   // Se considera que es parte del módulo 'global'
   <?php include_partial('global/miparcial3') ?>

Los elementos parciales pueden acceder a los helpers y atajos de plantilla que proporcio-
na Symfony. Pero como los elementos parciales se pueden llamar desde cualquier punto


www.librosweb.es                                                                          128
Symfony, la guía definitiva                                                    Capítulo 7. La Vista


de la aplicación, no tienen acceso automático a las variables definidas por la acción que
ha incluido la plantilla en la que se encuentra el elemento parcial, a no ser que se pase
esas variables explícitamente en forma de parámetro. Si por ejemplo se necesita que un
elemento parcial tenga acceso a una variable llamada $total, la acción pasa esa variable
a la plantilla y después la plantilla se la pasa al helper como el segundo parámetro de la
llamada a la función include_partial(), como se muestra en los listado 7-8, 7-9 y 7-10.

Listado 7-8 - La acción define una variable, en mimodulo/actions/actions.class.php
   class mimoduloActions extends sfActions
   {
     public function executeIndex()
     {
       $this->total = 100;
     }
   }

Listado 7-9 - La plantilla pasa la variable al elemento parcial, en mimodulo/templa-
tes/indexSuccess.php
   <p>¡Hola Mundo!</p>
   <?php include_partial('miparcial',
   array('mitotal' => $total)
   ) ?>

Listado 7-10 - El elemento parcial ya puede usar la variable, en mimodulo/templa-
tes/_miparcial.php
   <p>Total: <?php echo $mitotal ?></p>


  SUGERENCIA
  Hasta ahora, todos los helpers se llamaban con la función <?php echo nombreFuncion() ?>. Por
  el contrario, el helper utilizado con los elementos parciales se llama mediante <?php include_-
  partial() ?>, sin incluir el echo, para hacer su comportamiento más parecido a la instrucción de
  PHP include(). Si alguna vez se necesita obtener el contenido del elemento parcial sin mostrarlo,
  se puede utilizar la función get_partial(). Todos los helpers de tipo include_ de este capítulo,
  tienen una función asociada que comienza por get_ y que devuelve los contenidos que se pueden
  mostrar directamente con una instrucción echo.


7.2.2. Componentes
En el Capítulo 2, el primer script de ejemplo se dividía en dos partes para separar la lógi-
ca de la presentación. Al igual que el patrón MVC se aplica a las acciones y las plantillas,
es posible dividir un elemento parcial en su parte de lógica y su parte de presentación.
En este caso, se necesitan los componentes.

Un componente es como una acción, solo que mucho más rápido. La lógica del compo-
nente se guarda en una clase que hereda de sfComponents y que se debe guardar en el
archivo action/components.class.php. Su presentación se guarda en un elemento parcial.
Los métodos de la clase sfComponents empiezan con la palabra execute, como sucede con
las acciones, y pueden pasar variables a su presentación de la misma forma en la que se


www.librosweb.es                                                                              129
Symfony, la guía definitiva                                                    Capítulo 7. La Vista


pasan variables en las acciones. Los elementos parciales que se utilizan como presenta-
ción de un componente, se deben llamar igual que los componentes, sustituyendo la pa-
labra execute por un guión bajo. La tabla 7-1 compara las convenciones en los nombres
de las acciones y los componentes.

Tabla 7-1. Convenciones en el nombrado de las acciones y de los componentes

Convención                                 Acciones                  Componentes

Archivo de la lógica                       actions.class.php         components.class.php

Clase de la que hereda la lógica           sfActions                 sfComponents

Nombre de los métodos                      executeMiAccion()         executeMiComponente()

Nombre del archivo de presentación         miAccionSuccess.php       _miComponente.php


  SUGERENCIA
  De la misma forma que es posible separar los archivos de las acciones, la clase sfComponents dis-
  pone de una equivalente llamada sfComponent y que permite crear archivos individuales para cada
  componente siguiendo una sintaxis similar.

Por ejemplo, se puede definir una zona lateral que muestra las últimas noticias de un de-
terminado tema que depende del perfil del usuario y que se va a reutilizar en varias pági-
nas. Las consultas necesarias para mostrar las noticias son demasiado complejas como
para incluirlas en un elemento parcial, por lo que se deben incluir en un archivo similar a
las acciones, es decir, en un componente. La figura 7-3 ilustra este ejemplo.




                       Figura 7.3. Uso de componentes en las plantillas


En este ejemplo, mostrado en los listados 7-11 y 7-12, el componente se define en su
propio módulo (llamado news), pero se pueden mezclar componentes y acciones en un ú-
nico módulo, siempre que tenga sentido hacerlo desde un punto de vista funcional.

Listado    7-11    -   La     clase   de   los   componentes,       en   modules/news/actions/
components.class.php
        <?php

        class newsComponents extends sfComponents
        {
          public function executeHeadlines()
          {


www.librosweb.es                                                                              130
Symfony, la guía definitiva                                                Capítulo 7. La Vista

                $c = new Criteria();
                $c->addDescendingOrderByColumn(NewsPeer::PUBLISHED_AT);
                $c->setLimit(5);
                $this->news = NewsPeer::doSelect($c);
            }
        }

Listado 7-12 - El elemento parcial, en modules/news/templates/_headlines.php
        <div>
          <h1>Últimas noticias</h1>
          <ul>
          <?php foreach($news as $headline): ?>
            <li>
               <?php echo $headline->getPublishedAt() ?>
               <?php echo link_to($headline->getTitle(),'news/show?id='.$headline->getId())
   ?>
            </li>
          <?php endforeach ?>
          </ul>
        </div>

Ahora, cada vez que se necesite el componente en una plantilla, se puede incluir de la si-
guiente forma:
   <?php include_component('news', 'headlines') ?>

Al igual que sucede con los elementos parciales, se pueden pasar parámetros adicionales
a los componentes mediante un array asociativo. Dentro del elemento parcial se puede
acceder directamente a los parámetros mediante su nombre y en el componente se pue-
de acceder a ellos mediante el uso de $this. El listado 7-13 muestra un ejemplo.

Listado 7-13 - Paso de parámetros a un componente y a su plantilla
        // Llamada al componente
        <?php include_component('news', 'headlines', array('parametro' => 'valor')) ?>

        // Dentro del componente
        echo $this->parametro;
         => 'valor'

        // Dentro del elemento parcial _headlines.php
        echo $parametro;
         => 'valor'

Se pueden incluir componentes dentro de otros componentes y también en el layout glo-
bal como si fuera una plantilla normal. Al igual que en las acciones, los métodos execute
de los componentes pueden pasar variables a sus elementos parciales relacionados y
pueden tener acceso a los mismos atajos. Pero las similitudes se quedan solo en eso. Los
componentes no pueden manejar la seguridad ni la validación, no pueden ser llamados
desde Internet (solo desde la propia aplicación) y no tienen distintas posibilidades para
devolver sus resultados. Por este motivo, los componentes son más rápidos que las
acciones.




www.librosweb.es                                                                          131
Symfony, la guía definitiva                                                   Capítulo 7. La Vista


7.2.3. Slots
Los elementos parciales y los componentes están especialmente diseñados para reutilizar
código. Sin embargo, en muchas ocasiones se necesitan fragmentos de código que relle-
nen un layout con más de una zona variable. Por ejemplo se puede necesitar añadir etiq-
uetas personalizadas en la sección <head> del layout en función del contenido de la ac-
ción. También se puede dar el caso de un layout que tiene una zona de contenidos diná-
micos que se rellena con el resultado de la acción y muchas otras zonas pequeñas que
tienen un contenido por defecto definido en el layout pero que puede ser modificado en la
plantilla.

En los casos descritos anteriormente la solución más adecuada es un slot. Básicamente,
un slot es una zona que se puede definir en cualquier elemento de la vista (layout, plan-
tilla o elemento parcial). La forma de rellenar esa zona es similar a establecer el valor de
una variable. El código de relleno se almacena de forma global en la respuesta, por lo
que se puede definir en cualquier sitio (layout, plantilla o elemento parcial). Se debe defi-
nir un slot antes de utilizarlo y también hay que tener en cuenta que el layout se ejecuta
después de la plantilla (durante el proceso de decoración) y que los elementos parciales
se ejecutan cuando los llama una plantilla. Como todo esto suena demasiado abstracto,
se va a ver su funcionamiento con un ejemplo.

Imagina que se dispone de un layout con una zona para la plantilla y 2 slots: uno para el
lateral de la página y otro para el pie de página. El valor de los slots se define en la plan-
tilla. Durante el proceso de decoración, el layout integra en su interior el código de la
plantilla, por lo que los slots se rellenan con los valores que se han definido anteriormen-
te, tal y como muestra la figura 7-4. De esta forma, el lateral y el pie de página pueden
depender de la acción. Se puede aproximar a la idea de tener un layout con uno o más
agujeros que se rellenan con otro código.




                 Figura 7.4. La plantilla define el valor de los slots del layout


Su funcionamiento se puede comprender mejor viendo algo de código. Para incluir un
slot se utiliza el helper include_slot(). El helper has_slot() devuelve un valor true si el
slot ya ha sido definido antes, permitiendo de esta forma establecer un mecanismo de
protección frente a errores. El listado 7-14 muestra como definir la zona para el slot la-
teral en el layout y su contenido por defecto.

Listado 7-14 - Incluir un slot llamado lateral en el layout
        <div id="lateral">
        <?php if (has_slot('lateral')): ?>
          <?php include_slot('lateral') ?>
        <?php else: ?>
          <!-- código del lateral por defecto -->


www.librosweb.es                                                                             132
Symfony, la guía definitiva                                                    Capítulo 7. La Vista

          <h1>Zona cuyo contenido depende del contexto</h1>
          <p>Esta zona contiene enlaces e información sobre
          el contenido principal de la página.</p>
        <?php endif; ?>
        </div>

Las plantillas pueden definir los contenidos de un slot (e incluso los elementos parciales
pueden hacerlo). Como los slots se definen para mostrar código HTML, Symfony proporc-
iona métodos útiles para indicar ese código HTML: se puede escribir el código del slot en-
tre las llamadas a las funciones slot() y end_slot(), como se muestra en el listado 7-15.

Listado 7-15 - Redefiniendo el contenido del slot lateral en la plantilla
        ...
        <?php slot('lateral') ?>
          <!-- Código específico para el lateral de esta plantilla -->
          <h1>Detalles del usuario</h1>
          <p>Nombre: <?php echo $user->getName() ?></p>
          <p>Email: <?php echo $user->getEmail() ?></p>
        <?php end_slot() ?>

El código incluido entre las llamadas a los helpers del slot se ejecutan en el contexto de
las plantillas, por lo que tienen acceso a todas las variables definidas por la acción. Sym-
fony añade de forma automática en el objeto response el resultado del código anterior.
No se muestra directamente en la plantilla, sino que se puede acceder a su código med-
iante la llamada a la función include_slot(), como se muestra en el listado 7.14.

Los slots son muy útiles cuando se tienen que definir zonas que muestran contenido que
depende del contexto de la página. También se puede utilizar para añadir código HTML al
layout solo para algunas acciones. Por ejemplo, una plantilla que muestra la lista de las
últimas noticias puede necesitar incluir un enlace a un canal RSS dentro de la sección
<head> del layout. Esto se puede conseguir añadiendo un slot llamado feed en el layout y
que sea redefinido en la plantilla del listado de noticias.

  Dónde encontrar los fragmentos de plantillas

  Los usuarios que trabajan con plantillas normalmente son diseñadores web, que no conocen muy
  bien el funcionamiento de Symfony y que pueden tener problemas para encontrar los fragmentos
  de plantilla, ya que pueden estar desperdigados por todas la aplicación. Los siguientes consejos
  pueden hacer más fácil su trabajo con las plantillas de Symfony.

  En primer lugar, aunque los proyectos de Symfony contienen muchos directorios, todos los layouts,
  plantillas y fragmentos de plantillas son archivos que se encuentran en directorios llamados tem-
  plates/. Por tanto, en lo que respecta a un diseñador web, la estructura de un proyecto queda re-
  ducida a:
      miproyecto/
        apps/
          aplicacion1/
            templates/        # Layouts de la aplicacion 1
            modules/
              modulo1/
                templates/    # Plantillas y elementos parciales del modulo 1
              modulo2/


www.librosweb.es                                                                              133
Symfony, la guía definitiva                                                      Capítulo 7. La Vista

                 templates/    # Plantillas y elementos parciales del modulo 2
               modulo3/
                 templates/    # Plantillas y elementos parciales del modulo 3

  El resto de directorios pueden ser ignorados por el diseñador.

  Cuando se encuentren con una función del tipo include_partial(), los diseñadores web sólo tie-
  nen que preocuparse por el primer argumento de la función. La estructura del nombre de este argu-
  mento es nombre_modulo/nombre_elemento_parcial, lo que significa que el código se encuentra
  en el archivo modules/nombre_modulo/templates/_nombre_elemento_parcial.php.

  En los helpers de tipo include_component(), el nombre del módulo y el nombre del elemento par-
  cial son los dos primeros argumentos. Por lo demás, para empezar a diseñar plantillas de aplicacio-
  nes Symfony sólo es necesario tener una idea general sobre lo que son los helpers y cuales son los
  más utilizados en las plantillas.


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 lay-
                 out (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 Sym-
fony 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 de-
penden del contexto o de alguna consulta a la base de datos. Cuando se trabaja con va-
lores dinámicos que cambian con cada acción, se recurre al segundo método para esta-
blecer la configuración de la vista: añadir los atributos directamente en el objeto sfRes-
ponse 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.




www.librosweb.es                                                                                134
Symfony, la guía definitiva                                                     Capítulo 7. La Vista


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


  SUGERENCIA
  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 archi-
vo 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 ca-
        da vista solo se aplican a una vista y además sus valores tienen preferencia so-
        bre 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 de-
        finiciones 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.

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


www.librosweb.es                                                                               135
Symfony, la guía definitiva                                               Capítulo 7. La Vista


Después de ver la plantilla por defecto en el listado 7-5 y un ejemplo de la respuesta ge-
nerada 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 de-
finidas en el archivo view.yml de la aplicación que se muestra en el listado 7-17.

Listado 7-17 - Archivo de configuración de la vista de la aplicación, en apps/mia-
plicacion/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 configura-
ción de la vista”.

7.3.2. El objeto respuesta (response)
Aunque el objeto response (objeto respuesta) es parte de la vista, normalmente se modi-
fica en la acción. Las acciones acceden al objeto respuesta creado por Symfony, y llama-
do 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);




www.librosweb.es                                                                         136
Symfony, la guía definitiva                                                   Capítulo 7. La Vista

                // 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 nece-
sario 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 for-
ma 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 defec-
to 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 men-
saje más común para ese código.
   $respuesta->setStatusCode(404, 'Esta página no existe');


  SUGERENCIA
  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 configu-
ración de la vista:

      ▪ Las opciones que tienen un único valor (el valor es una cadena de texto en el ar-
        chivo view.yml y el objeto respuesta utiliza un método set para ellas)




www.librosweb.es                                                                             137
Symfony, la guía definitiva                                              Capítulo 7. La Vista


      ▪ 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 archi-
vo 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ña-
dir 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



www.librosweb.es                                                                        138
Symfony, la guía definitiva                                                 Capítulo 7. La Vista


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 esta-
blecido 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


www.librosweb.es                                                                           139
Symfony, la guía definitiva                                                        Capítulo 7. La Vista

   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 enc-
uentran 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 nece-
sario 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 mos-
trado 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 sfCommonFil-
  ter. 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 archi-
vo 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]


www.librosweb.es                                                                                  140
Symfony, la guía definitiva                                               Capítulo 7. La Vista


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 supe-
riores, 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 algu-
nos 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 utili-
zar 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 (solo 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 op-
ciones 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" />




www.librosweb.es                                                                         141
Symfony, la guía definitiva                                                        Capítulo 7. La Vista


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/tem-
plates/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 miaplicac-
ion/templates/mi_layout.php, se debe utilizar la sintaxis del listado 7-34 para los archi-
vos 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 por defecto no tienen definido ningún layout.


7.4. Slots de componentes
Si se combina el poder de los componentes que se han visto anteriormente y las opcio-
nes de configuración de la vista, se consigue un modelo de desarrollo de la vista comple-
tamente nuevo: el sistema de slots de componentes. Se trata de una alternativa a los
slots que se centra en la reutilización y en la separación en capas. De esta forma, los
slots de componentes están mucho más estructurados que los slots, pero son un poco
más lentos de ejecutar.

Al igual que los slots, los slots de componentes son zonas que se pueden definir en los
elementos de la vista. La principal diferencia reside en la forma en la que se decide qué
codigo rellena esas zonas. En un slot normal, el código se establece en otro elemento de
la vista; en un slot de componentes, el código es el resultado de la ejecución de un com-
ponente, y el nombre de ese componente se obtiene de la configuración de la vista. Des-
pués de verlos en la práctica, es sencillo entender el comportamiento de los slots de
componentes.



www.librosweb.es                                                                                  142
Symfony, la guía definitiva                                             Capítulo 7. La Vista


Para definir la zona del slot de componentes, se utiliza el helper include_component_s-
lot(). El parámetro de esta función es el nombre que se asigna al slot de componentes.
Imagina por ejemplo que el archivo layout.php de la aplicación tiene un lateral de conte-
nidos cuya información depende de la página en la que se muestra. El listado 7-38 mues-
tra como se incluiría este slot de componentes.

Listado 7-38 - Incluir un slot de componentes de nombre lateral
   ...
   <div id="lateral">
     <?php include_component_slot('lateral') ?>
   </div>

La correspondencia entre el nombre del slot de componentes y el nombre del propio
componente se define en la configuración de la vista. Por ejemplo, se puede establecer el
componente por defecto para el slot lateral debajo de la clave components del archivo
view.yml de la aplicación. La clave de la opción de configuración es el nombre del slot de
componentes; el valor de la opción es un array que contiene el nombre del módulo y el
nombre del componente. El listado 7-29 muestra un ejemplo.

Listado 7-39 - Definir el slot de componentes por defecto para lateral, en mia-
plicacion/config/view.yml
   default:
     components:
       lateral: [mimodulo, default]

De esta forma, cuando se ejecuta el layout, el slot de componentes lateral se rellena
con el resultado de ejecutar el método executeDefault() de la clase mimoduloComponents
del módulo mimodulo, y este método utiliza la vista del elemento parcial _default.php que
se encuentra en modules/mimodulo/templates/.

La configuración en cascada permite redefinir esta opción en cualquier módulo. Por ejem-
plo, en el módulo user puede ser más útil mostrar el nombre del usuario y el número de
artículos que ha publicado. En ese caso, se puede particularizar el slot lateral mediante
las siguientes opciones en el archivo view.yml del módulo, como se muestra en el listado
7-40.

Listado 7-40 - Particularizando el slot de componentes lateral, en miaplicacion/
modules/user/config/view.yml
   all:
     components:
        lateral: [mimodulo, user]

El listado 7-41 muestra el código del componente necesario para este slot.

Listado 7-41 - Componentes utilizados por el slot lateral, en modules/mimodulo/ac-
tions/components.class.php
   class mimoduloComponents extends sfComponents
   {
     public function executeDefault()
     {

www.librosweb.es                                                                       143
Symfony, la guía definitiva                                                 Capítulo 7. La Vista

       }

       public function executeUser()
       {
         $this->usuario_actual = $this->getUser()->getCurrentUser();
         $c = new Criteria();
         $c->add(ArticlePeer::AUTHOR_ID, $this->usuario_actual->getId());
         $this->numero_articulos = ArticlePeer::doCount($c);
       }
   }

El listado 7-42 muestra la vista de estos 2 componentes.

Listado 7-42 - Elementos parciales utilizados por el slot de componentes lateral,
en modules/mimodulo/templates/
   // _default.php
   <p>El contenido de esta zona depende de la página en la que se muestra.</p>

   // _user.php
   <p>Nombre de usuario: <?php echo $usuario_actual->getName() ?></p>
   <p><?php echo $numero_articulos ?> artículos publicados</p>

Los slots de componentes se pueden utilizar para añadir en las páginas web las “migas
de pan”, los menús de navegación que dependen de cada página y cualquier otro conte-
nido que se deba insertar de forma dinámica. Como componentes que son, se pueden
utilizar en el layout global y en cualquier plantilla, e incluso en otros componentes. La
configuración que indica el componente de un slot siempre se extrae de la configuración
de la última acción que se ejecuta.

Para evitar que se utilice un slot de componentes en un módulo determinado, se puede
declarar un par módulo/componente vacío, tal y como muestra el listado 7-43.

Listado 7-43 - Deshabilitar un slot de componentes en view.yml
   all:
     components:
        lateral: []


7.5. Mecanismo de escape
Cuando se insertan datos generados dinámicamente en una plantilla, se debe asegurar la
integridad de los datos. Por ejemplo, si se utilizan datos obtenidos mediante formularios
que pueden rellenar usuarios anónimos, existe un gran riesgo de que los contenidos pue-
dan incluir scripts y otros elementos maliciosos que se encargan de realizar ataques de
tipo XSS (cross-site scripting). Por tanto, se debe aplicar un mecanismo de escape a to-
dos los datos mostrados, de forma que ninguna etiqueta HTML pueda ser peligrosa.

Imagina por ejemplo que un usuario rellena un campo de formulario con el siguiente
valor:
   <script>alert(document.cookie)</script>

Si se muestran directamente los datos, el navegador ejecuta el código JavaScript intro-
ducido por el usuario, que puede llegar a ser mucho más peligroso que el ejemplo


www.librosweb.es                                                                           144
Symfony, la guía definitiva                                                Capítulo 7. La Vista


anterior que simplemente muestra un mensaje. Por este motivo, se deben aplicar meca-
nismos de escape a los valores introducidos antes de mostrarlos, para que se transfor-
men en algo como:
   &lt;script&gt;alert(document.cookie)&lt;/script&gt;

Los datos se pueden escapar manualmente utilizando la función htmlentities() de PHP,
pero es un método demasiado repetitivo y muy propenso a cometer errores. En su lugar,
Symfony incluye un sistema conocido como mecanismo de escape de los datos que se
aplica a todos los datos mostrados mediante las variables de las plantillas. El mecanismo
se activa mediante un único parámetro en el archivo settings.yml de la aplicación.


7.5.1. Activar el mecanismo de escape
El mecanismo de escape de datos se configura de forma global para toda la aplicación en
el archivo settings.yml. El sistema de escape se controla con 2 parámetros: la estrategia
(escaping_strategy) define la forma en la que las variables están disponibles en la vista y
el método (escaping_method) indica la función que se aplica a los datos.

En las siguientes secciones se describen estas opciones en detalle, pero básicamente lo
único necesario para activar el mecanismo de escape es establecer para la opción esca-
ping_strategy el valor both en vez de su valor por defecto bc, tal y como muestra el lis-
tado 7-44.

Listado 7-44 - Activar el mecanismo de escape, en miaplicacion/config/
settings.yml
   all:
     .settings:
        escaping_strategy: both
        escaping_method:   ESC_ENTITIES

Esta configuración aplica la función htmlentities() a los datos de todas las variables
mostradas. Si se define una variable llamada prueba en la acción con el siguiente
contenido:
   $this->prueba = '<script>alert(document.cookie)</script>';

Con el sistema de escape activado, al mostrar esta variable en una plantilla, se mos-
trarán los siguientes datos:
   echo $prueba;
   => &gt;&lt;script&gt;alert(document.cookie)&lt;/script&gt;

Si se activa el mecanismo de escape, desde cualquier plantilla se puede acceder a una
nueva variable llamada $sf_data. Se trata de un objeto contenedor que hace referencia a
todas las variables que se han modificado mediante el sistema de escape. De esta forma,
también es posible mostrar el contenido de la variable prueba de la siguiente manera:
   echo $sf_data->get('prueba');
   => &gt;&lt;script&gt;alert(document.cookie)&lt;/script&gt;




www.librosweb.es                                                                          145
Symfony, la guía definitiva                                                    Capítulo 7. La Vista


  SUGERENCIA
  El objeto $sf_data implementa la interfaz Array, por lo que en vez de utilizar la sinta-
  xis$sf_data->get(’mivariable’), se puede obtener la variable mediante $sf_data[’mivaria-
  ble’]. Sin embargo, no se trata realmente de un array, por lo que no se pueden utilizar funciones
  como por ejemplo print_r().

Mediante este objeto también se puede acceder a los datos originales o datos en crudo
de la variable. Se trata de una opción muy útil por ejemplo cuando la variable contiene
código HTML que se quiere incluir directamente en el navegador para que sea interpreta-
do en vez de mostrado (solo se debería utilizar esta opción si se confía plenamente en el
contenido de esa variable). Para acceder a los datos originales se puede utilizar el méto-
do getRaw().
   echo $sf_data->getRaw('prueba');
   => <script>alert(document.cookie)</script>

Si una variable almacena código HTML, cada vez que se necesita el código HTML original,
es necesario acceder a sus datos originales, de forma que el código HTML se interprete y
no se muestre en el navegador. Por este motivo el layout por defecto utiliza la instrucción
$sf_data->getRaw(’sf_content’) para incluir el contenido de la plantilla, en vez de utili-
zar directamente el método $sf_content, que provocaría resultados no deseados cuando
se activa el mecanismo de escape.

7.5.2. Opción escaping_strategy
La opción escaping_strategy determina la forma en la que se muestra el contenido de las
variables en las plantillas. Sus posibles valores son los siguientes:

      ▪ bc (backward compatible mode o modo retrocompatible): el contenido de las var-
        iables no se modifica, pero el contenedor $sf_data almacena una versión modifi-
        cada de cada variable. De esta forma, los datos de las variables se obtienen en
        crudo, a menos que se obtenga la versión modificada del objeto $sf_data. Se tra-
        ta del valor por defecto de la opción, aunque se trata del modo que permite los
        ataques de tipo XSS.

      ▪ both: a todas las variables se les aplica el mecanismo de escape. Los valores
        también están disponibles en el contenedor $sf_data. Se trata del valor recomen-
        dado, ya que solamente se está expuesto al riesgo si se utilizan de forma explíci-
        ta los datos originales. En ocasiones, se deben utilizar los valores originales, por
        ejemplo para incluir código HTML de forma que se interprete en el navegador y
        no se muestre el código HTML. Si una aplicación se encuentra medio desarrollada
        y se cambia la estrategia del mecanismo de escape a este valor, algunas funcio-
        nalidades pueden dejar de funcionar como se espera. Lo mejor es seleccionar es-
        ta opción desde el principio.

      ▪ on: los valores solamente están disponibles en el contenedor $sf_data. Se trata
        del método más seguro y más rapido de manejar el mecanismo de escape, ya
        que cada vez que se quiere mostrar el contenido de una variable, se debe elegir
        el método get() para los datos modificados o el método getRaw() para el


www.librosweb.es                                                                              146
Symfony, la guía definitiva                                              Capítulo 7. La Vista


        contenido original. De esta forma, siempre se recuerda la posibilidad de que los
        datos de la variable sean corruptos.

      ▪ off: deshabilita el mecanismo de escape. Las plantillas no pueden hacer uso del
        contenedor $sf_data. Si nunca se va a necesitar el sistema de escape, es mejor
        utilizar esta opción y no la opción por defecto bc, ya que la aplicación se ejecuta
        más rápidamente.

7.5.3. Los helpers útiles para el mecanismo de escape
Los helpers utilizados en el mecanismo de escape son funciones que devuelven el valor
modificado correspondiente al valor que se les pasa. Se pueden utilizar como valor de la
opción escaping_method en el archivo settings.yml o para especificar un método concreto
de escape para los datos de una vista. Los helpers disponibles son los siguientes:

      ▪ ESC_RAW: no modifica el valor original.

      ▪ ESC_ENTITIES: aplica la función htmlentities() de PHP al valor que se le pasa y
        utiliza la opción ENT_QUOTES para el estilo de las comillas.

      ▪ ESC_JS: modifica un valor que corresponde a una cadena de JavaScript que va a
        ser utilizada como HTML. Se trata de una opción muy útil para escapar valores
        cuando se emplea JavaScript para modificar de forma dinámica el contenido
        HTML de la página.

      ▪ ESC_JS_NO_ENTITIES: modifica un valor que va a ser utilizado en una cadena de
        JavaScript pero no le añade las entidades HTML correspondientes. Se trata de
        una opción muy útil para los valores que se van a mostrar en los cuadros de diá-
        logo (por ejemplo para una variable llamada miCadena en la instrucción javas-
        cript:alert(miCadena);).


7.5.4. Aplicando el mecanismo de escape a los arrays y los objetos
No solo las cadenas de caracteres pueden hacer uso del mecanismo de escape, sino que
también se puede aplicar a los arrays y los objetos. El mecanismo de escape se aplica en
cascada a todos los arrays u objetos. Si la estrategia empleada es both, el listado 7-45
muesta el mecanismo de escape aplicado en cascada.

Listado 7-45 - El sistema de escape se puede aplicar a los arrays y los objetos
        // Definición de la clase
        class miClase
        {
          public function pruebaCaracterEspecial($valor = '')
          {
            return '<'.$valor.'>';
          }
        }

        // En la acción
        $this->array_prueba = array('&', '<', '>');
        $this->array_de_arrays = array(array('&'));


www.librosweb.es                                                                        147
Symfony, la guía definitiva                                                 Capítulo 7. La Vista

        $this->objeto_prueba = new miClase();

        // En la plantilla
        <?php foreach($array_prueba as $valor): ?>
          <?php echo $valor ?>
        <?php endforeach; ?>
         => &amp; &lt; &gt;
        <?php echo $array_de_arrays[0][0] ?>
         => &amp;
        <?php echo $objeto_prueba->pruebaCaracterEspecial('&') ?>
         => &lt;&amp;&gt;

De hecho, el tipo de las variables en la plantilla no es el tipo que le correspondería a la
variable original. El mecanismo de escape “decora las variables y las transforma en obje-
tos especiales:
   <?php echo get_class($array_prueba) ?>
   => sfOutputEscaperArrayDecorator
   <?php echo get_class($objeto_prueba) ?>
   => sfOutputEscaperObjectDecorator

Esta es la razón por la que algunas funciones PHP habituales (como array_shift(),
print_r(), etc.) no funcionan en los arrays a los que se ha aplicado el mecanismo de es-
cape. No obstante, se puede seguir accediendo mediante [], se pueden recorrer con fo-
reach y proporcionan el dato correcto al utilizar la función count() (aunque count() solo
funciona con la versión 5.2 o posterior de PHP). Como en las plantillas los datos (casi)
siempre se acceden en modo solo lectura, la mayor parte de las veces se accede a los
datos mediante los métodos que sí funcionan.

De todas formas, todavía es posible acceder a los datos originales mediante el objeto
$sf_data. Además, los métodos de los objetos a los que se aplica el mecanismo de esca-
pe se modifican para que acepten un parámetro adicional: el método de escape. Así, se
puede utilizar un método de escape diferente cada vez que se accede al valor de una var-
iable en una plantilla, o incluso es posible utilizar el helper ESC_RAW para desactivar el sis-
tema de escape para una variable concreta. El listado 7-46 muestra un ejemplo.

Listado 7-46 - Los métodos de los objetos a los que se aplica el mecanismo de
escape aceptan un parámetro adicional
   <?php echo $objeto_prueba->pruebaCaracterEspecial('&') ?>
   => &lt;&amp;&gt;
   // Las siguientes 3 líneas producen el mismo resultado
   <?php echo $objeto_prueba->pruebaCaracterEspecial('&', ESC_RAW) ?>
   <?php echo $sf_data->getRaw('objeto_prueba')->pruebaCaracterEspecial('&') ?>
   <?php echo $sf_data->get('objeto_prueba', ESC_RAW)->pruebaCaracterEspecial('&') ?>
   => <&>

Si se incluyen muchos objetos en las plantillas, el truco de añadir un parámetro adicional
a los métodos se utiliza mucho, ya que es el método más rápido de obtener los datos ori-
ginales al ejecutar el método.




www.librosweb.es                                                                           148
Symfony, la guía definitiva                                                   Capítulo 7. La Vista


  ATENCIÓN
  Las variables de Symfony también se modifican al activar el mecanismo de escape. Por tanto, las
  variables $sf_user, $sf_request, $sf_param y $sf_context siguen funcionando, pero sus méto-
  dos devuelven sus datos modificados, a no ser que se utilice la opción ESC_RAW como último argu-
  mento de las llamadas a los métodos.


7.6. Resumen
Existen numerosas herramientas y utilidades para manipular la capa correspondiente a la
presentación. Las plantillas se pueden construir en pocos segundos, gracias al uso de los
helpers. Los layouts, los elementos parciales, los componentes y los slots de componen-
tes permiten aplicar los conceptos de modularidad y reutilización de componentes. La
configuración de la vista aprovecha la velocidad de YAML para manejar la mayoría de ca-
beceras de las páginas. La configuración en cascada evita tener que definir todas las opc-
iones para cada vista. Si una modificación de la presentación requiere el uso de datos
dinámicos, se puede realizar la modificación en la acción mediante el objeto sfResponse.
Además, la vista puede protegerse ante ataques de tipo XSS gracias al mecanismo de es-
cape de los datos de las variables.




www.librosweb.es                                                                             149
Symfony, la guía definitiva                                             Capítulo 8. El modelo




Capítulo 8. El modelo
Hasta ahora, la mayor parte de los contenidos se ha dedicado a la construcción de pági-
nas y al procesado de peticiones y respuestas. Sin embargo, la lógica de negocio de las
aplicaciones web depende casi siempre en su modelo de datos. El componente que se en-
carga por defecto de gestionar el modelo en Symfony es una capa de tipo ORM (object/
relational mapping) realizada mediante el proyecto Propel (http://propel.phpdb.org/). En
las aplicaciones Symfony, el acceso y la modificación de los datos almacenados en la ba-
se de datos se realiza mediante objetos; de esta forma nunca se accede de forma explíci-
ta a la base de datos. Este comportamiento permite un alto nivel de abstracción y permi-
te una fácil portabilidad.

En este capítulo se explica como crear el modelo de objetos de datos, y la forma en la
que se acceden y modifican los datos mediante Propel. Además, se muestra la integra-
ción de Propel en Symfony.


8.1. ¿Por qué utilizar un ORM y una capa de abstracción?
Las bases de datos son relacionales. PHP 5 y Symfony están orientados a objetos. Para
acceder de forma efectiva a la base de datos desde un contexto orientado a objetos, es
necesaria una interfaz que traduzca la lógica de los objetos a la lógica relacional. Como
se explicó en el Capítulo 1, esta interfaz se llama ORM (object-relational mapping) o “-
mapeo de objetos a bases de datos”, y está formada por objetos que permiten acceder a
los datos y que contienen en sí mismos el código necesario para hacerlo.

La principal ventaja que aporta el ORM es la reutilización, permitiendo llamar a los méto-
dos de un objeto de datos desde varias partes de la aplicación e incluso desde diferentes
aplicaciones. La capa ORM también encapsula la lógica de los datos; como por ejemplo,
el cálculo de la puntuación de un usuario de un foro en función de las aportaciones que
ha realizado al foro y en función del éxito de esas aportaciones. Cuando una página quie-
re mostrar esa puntuación de un usuario, simplemente invoca un método del modelo de
datos, sin preocuparse de cómo se realiza el cálculo. Si el método de cálculo sufre alguna
variación, solo es necesario modificar el método que calcula la puntuación en el modelo,
sin necesidad de modificar el resto de la aplicación.

La utilización de objetos en vez de registros y de clases en vez de tablas, tiene otra ven-
taja: permite añadir métodos accesores en los objetos que no tienen relación directa con
una tabla. Si se dispone por ejemplo de una tabla llamada cliente con 2 campos llama-
dos nombre y apellidos, puede que se necesite un dato llamado NombreCompleto que in-
cluya y combine el nombre y los apellidos. En el mundo orientado a objetos, es tan fácil
como añadir un método accesor a la clase Cliente, como se muestra en el listado 8-1.
Desde el punto de vista de la aplicación, no existen diferencias entre los atributos Nombre,
Apellidos, NombreCompleto de la clase Cliente. Solo la propia clase es capaz de determi-
nar si un atributo determinado se corresponde con una columna de la base de datos.

Listado 8-1 - Los métodos accesores en la clase del modelo permiten ocultar la
estructura real de la tabla de la base de datos


www.librosweb.es                                                                        150
Symfony, la guía definitiva                                                 Capítulo 8. El modelo

   public function getNombreCompleto()
   {
     return $this->getNombre().' '.$this->getApellidos();
   }

Todo el código repetitivo de acceso a los datos y toda la lógica de negocio de los propios
datos se puede almacenar en esos objetos. Imagina que se ha definido la clase Carri-
toCompra en la que se almacenan Productos (que son objetos). Para obtener el precio to-
tal del carrito de la compra antes de realizar el pago, se puede crear un método que en-
capsula el proceso de cálculo, tal y como se muestra en el listado 8-2.

Listado 8-2 - Los métodos accesores ocultan la lógica de los datos
   public function getTotal()
   {
     $total = 0;
     foreach ($this->getProductos() as $producto)
     {
       $total += $producto->getPrecio() * $producto->getCantidad();
     }

       return $total;
   }

Existe otra consideración importante que hay que tener en cuenta cuando se crean ele-
mentos de acceso a los datos: las empresas que crean las bases de datos utilizan varian-
tes diferentes del lenguaje SQL. Si se cambia a otro sistema gestor de bases de datos, es
necesario reescribir parte de las consultas SQL que se definieron para el sistema anterior.
Si se crean las consultas mediante una sintaxis independiente de la base de datos y un
componente externo se encarga de traducirlas al lenguaje SQL concreto de la base de
datos, se puede cambiar fácilmente de una base de datos a otra. Este es precisamente el
objetivo de las capas de abstracción de bases de datos. Esta capa obliga a utilizar una
sintaxis específica para las consultas y a cambio realiza el trabajo sucio de optimizar y
adaptar el lenguaje SQL a la base de datos concreta que se está utilizando.

La principal ventaja de la capa de abstracción es la portabilidad, porque hace posible el
cambiar la aplicación a otra base de datos, incluso en mitad del desarrollo de un proyec-
to. Si se debe desarrollar rápidamente un prototipo de una aplicación y el cliente no ha
decidido todavía la base de datos que mejor se ajusta a sus necesidades, se puede cons-
truir la aplicación utilizando SQLite y cuando el cliente haya tomado la decisión, cambiar
fácilmente a MySQL, PostgreSQL o Oracle. Solamente es necesario cambiar una línea en
un archivo de configuración y todo funciona correctamente.

Symfony utiliza Propel como ORM y Propel utiliza Creole como capa de abstracción de ba-
ses de datos. Estos 2 componentes externos han sido desarrollados por el equipo de Pro-
pel, y están completamente integrados en Symfony, por lo que se pueden considerar una
parte más del framework. Su sintaxis y sus convenciones, que se describen en este capí-
tulo, se han adaptado de forma que difieran lo menos posible de las de Symfony.

  NOTA
  En una aplicación de Symfony, todas las aplicaciones comparten el mismo modelo. Esa es precisa-
  mente la razón de ser de los proyectos: una agrupación de aplicaciones que dependen de un


www.librosweb.es                                                                            151
Symfony, la guía definitiva                                                  Capítulo 8. El modelo


  modelo común. Este es el motivo por el que el modelo es independiente de las aplicaciones y los
  archivos del modelo se guardan en el directorio lib/model/ de la raíz del proyecto.


8.2. Esquema de base de datos de Symfony
Para crear el modelo de objetos de datos que utiliza Symfony, se debe traducir el modelo
relacional de la base de datos a un modelo de objetos de datos. Para realizar ese mapeo
o traducción, el ORM necesita una descripción del modelo relacional, que se llama “esq-
uema” (schema). En el esquema se definen las tablas, sus relaciones y las características
de sus columnas.

La sintaxis que utiliza Symfony para definir los esquemas hace uso del formato YAML. Los
archivos schema.yml deben guardarse en el directorio miproyecto/config/.

  NOTA
  Symfony también puede trabajar con el formato nativo de los esquemas en Propel, que está basado
  en XML. Más adelante en este capítulo se explican los detalles en la sección “Más allá del sche-
  ma.yml: schema.xml”.


8.2.1. Ejemplo de esquema
¿Cómo se traduce la estructura de una base de datos a un esquema? La mejor forma de
entenderlo es mediante un ejemplo. En el ejemplo se supone que se tiene una base de
datos de un blog con 2 tablas: blog_article y blog_comment, con la estructura que se
muestra en la figura 8-1.




                Figura 8.1. Estructura de tablas de la base de datos del blog


En este caso, el archivo schema.yml debería ser el del listado 8-3.

Listado 8-3 - Ejemplo de schema.yml
   propel:
     blog_article:
       _attributes:   { phpName: Article }
       id:
       title:         varchar(255)
       content:       longvarchar
       created_at:
     blog_comment:
       _attributes:   { phpName: Comment }
       id:
       article_id:
       author:        varchar(255)
       content:       longvarchar
       created_at:



www.librosweb.es                                                                             152
Symfony, la guía definitiva                                                   Capítulo 8. El modelo


Observa como el nombre de la propia base de datos (blog) no aparece en el archivo
schema.yml. En su lugar, la base de datos se describe bajo el nombre de una conexión
(propel en el ejemplo anterior). El motivo es que las opciones de conexión con la base de
datos pueden depender del entorno en el que se está ejecutando la aplicación. Si se ac-
cede a la aplicación en el entorno de desarrollo, es posible que se acceda a la base de
datos de desarrollo (por ejemplo blog_dev) pero con el mismo esquema que en la base
de datos de producción. Las opciones de conexión con la base de datos se especifican en
el archivo databases.yml, que se describe más adelante en este capítulo en la sección
“Conexiones con la base de datos”. El esquema no contiene ningún tipo de opción para la
conexión a la base de datos, solo el nombre de la conexión, para mantener la abstracción
de la base de datos.

8.2.2. Sintaxis básica de los esquemas
En el archivo schema.yml, la primera clave representa el nombre de la conexión. Puede
contener varias tablas, cada una con varias columnas. Siguiendo la sintaxis de YAML, las
claves terminan con 2 puntos (:) y la estructura se define mediante la indentación con
espacios, no con tabuladores.

Cada tabla puede definir varios atributos, incluyendo el atributo phpName (que es el nom-
bre de la clase PHP que será generada para esa tabla). Si no se menciona el atributo ph-
pName para una tabla, Symfony crea una clase con el mismo nombre que la tabla al que
se aplica las normas del camelCase.

  SUGERENCIA
  La convención camelCase elimina los guiones bajos de las palabras y pasa a mayúsculas la prime-
  ra letra de cada palabra. Las versiones camelCase por defecto de blog_article y blog_comment
  son BlogArticle y BlogComment. El nombre de esta convención para generar nombres viene del
  aspecto de las mayúsculas en una palabra larga, parecido a las jorobas de un camello.

Las tablas contienen columnas y el valor de las columnas se puede definir de 3 formas
diferentes:

      ▪ Si no se indica nada, Symfony intenta adivinar los atributos más adecuados para
        la columna en función de su nombre y de una serie de convenciones que se expli-
        can en la sección “Columnas vacías” un poco más adelante en este Capítulo. Por
        ejemplo, en el listado 8-3 no es necesario definir la columna id. Symfony por de-
        fecto la trata como de tipo entero (integer), cuyo valor se auto-incrementa y
        además, clave principal de la tabla. En la tabla blog_comment, la columna arti-
        cle_id se trata como una clave externa a la tabla blog_article (las columnas
        que acaban en _id se consideran claves externas, y su tabla relacionada se de-
        termina automáticamente en función de la primera parte del nombre de la colum-
        na). Las columnas que se llaman created_at automáticamente se consideran de
        tipo timestamp. Para este tipo de columnas, no es necesario definir su tipo. Esta
        es una de las razones por las que es tan fácil crear archivos schema.yml.




www.librosweb.es                                                                              153
Symfony, la guía definitiva                                             Capítulo 8. El modelo


      ▪ Si solo se define un atributo, se considera que es el tipo de columna. Symfony
        entiende los tipos de columna habituales: boolean, integer, float, date, var-
        char(tamaño), longvarchar (que se convierte, por ejemplo, en tipo text en
        MySQL), etc. Para contenidos de texto de más de 256 caracteres, se utiliza el ti-
        po longvarchar, que no tiene tamaño definido (pero que no puede ser mayor que
        65KB en MySQL). Los tipos date y timestamp tienen las limitaciones habituales de
        las fechas de Unix y no pueden almacenar valores anteriores al 1 de Enero de
        1970. Como puede ser necesario almacenar fechas anteriores (por ejemplo para
        las fechas de nacimiento), existe un formato de fechas “anteriores a Unix” que
        son bu_date and bu_timestamp.

      ▪ Si se necesitan definir otros atributos a la columna (por ejemplo su valor por de-
        fecto, si es obligatorio o no, etc.), se indican los atributos como pares clave: va-
        lor. Esta sintaxis avanzada del esquema se describe más adelante en este
        capítulo.

Las columnas también pueden definir el atributo phpName, que es la versión modificada de
su nombre según las convenciones habituales (Id, Title, Content, etc) y que normalmen-
te no es necesario redefinir.

Las tablas también pueden definir claves externas e índices de forma explícita, además
de incluir definiciones específicas de su estructura para ciertas bases de datos. En la sec-
ción “Sintaxis avanzada del esquema” se detallan estos conceptos.


8.3. Las clases del modelo
El esquema se utiliza para construir las clases del modelo que necesita la capa del ORM.
Para reducir el tiempo de ejecución de la aplicación, estas clases se generan mediante
una tarea de línea de comandos llamada propel-build-model.
   > symfony propel-build-model

Al ejecutar ese comando, se analiza el esquema y se generan las clases base del modelo,
que se almacenan en el directorio lib/model/om/ del proyecto:

      ▪ BaseArticle.php

      ▪ BaseArticlePeer.php

      ▪ BaseComment.php

      ▪ BaseCommentPeer.php

Además, se crean las verdaderas clases del modelo de datos en el directorio lib/model/:

      ▪ Article.php

      ▪ ArticlePeer.php

      ▪ Comment.php

      ▪ CommentPeer.php



www.librosweb.es                                                                        154
Symfony, la guía definitiva                                           Capítulo 8. El modelo


Solo se han definido 2 tablas y se han generado 8 archivos. Aunque este hecho no es na-
da extraño, merece una explicación.

8.3.1. Clases base y clases personalizadas
¿Por qué es útil mantener 2 versiones del modelo de objetos de datos en 2 directorios
diferentes?

Puede ser necesario añadir métodos y propiedades personalizadas en los objetos del mo-
delo (piensa por ejemplo en el método getNombreCompleto() del listado 8-1). También es
posible que a medida que el proyecto se esté desarrollando, se añadan tablas o colum-
nas. Además, cada vez que se modifica el archivo schema.yml se deben regenerar las cla-
ses del modelo de objetos mediante el comando propel-build-model. Si se añaden los
métodos personalizados en las clases que se generan, se borrarían cada vez que se vuel-
ven a generar esas clases.

Las clases con nombre Base del directorio lib/model/om/ son las que se generan directa-
mente a partir del esquema. Nunca se deberían modificar esas clases, porque cada vez
que se genera el modelo, se borran todas las clases.

Por otra parte, las clases de objetos propias que están en el directorio lib/model heredan
de las clases con nombre Base. Estas clases no se modifican cuando se ejecuta la tarea
propel-build-model, por lo que son las clases en las que se añaden los métodos propios.

El listado 8-4 muestra un ejemplo de una clase propia del modelo creada la primera vez
que se ejecuta la tarea propel-build-model.

Listado 8-4 - Archivo de ejemplo de una clase del modelo, en lib/model/
Article.php
   <?php

   class Article extends BaseArticle
   {
   }

Esta clase hereda todos los métodos de la clase BaseArticle, pero no le afectan las modi-
ficaciones en el esquema.

Este mecanismo de clases personalizadas que heredan de las clases base permite empe-
zar a programar desde el primer momento, sin ni siquiera conocer el modelo relacional
definitivo de la base de datos. La estructura de archivos creada permite personalizar y
evolucionar el modelo.

8.3.2. Clases objeto y clases "peer"
Article y Comment son clases objeto que representan un registro de la base de datos.
Permiten acceder a las columnas de un registro y a los registros relacionados. Por tanto,
es posible obtener el título de un artículo invocando un método del objeto Article, como
se muestra en el listado 8-5.




www.librosweb.es                                                                      155
Symfony, la guía definitiva                                                   Capítulo 8. El modelo


Listado 8-5 - Las clases objeto disponen de getters para los registros de las
columnas
   $articulo = new Article();
   ...
   $titulo = $articulo->getTitle();

ArticlePeer y CommentPeer son clases de tipo “peer”; es decir, clases que tienen métodos
estáticos para trabajar con las tablas de la base de datos. Proporcionan los medios nece-
sarios para obtener los registros de las tablas. Sus métodos devuelven normalmente un
objeto o una colección de objetos de la clase objeto relacionada, como se muestra en el
listado 8-6.

Listado 8-6 - Las clases “peer” contienen métodos estáticos para obtener regis-
tros de la base de datos
   $articulos = ArticlePeer::retrieveByPks(array(123, 124, 125));
   // $articulos es un array de objetos de la clase Article


  NOTA
  Desde el punto de vista del modelo de datos, no puede haber objetos de tipo “peer”. Por este moti-
  vo los métodos de las clases “peer” se acceden mediante :: (para invocarlos de forma estática), en
  vez del tradicional -> (para invocar los métodos de forma tradicional).

La combinación de las clases objeto y las clases “peer” y las versiones básicas y persona-
lizadas de cada una hace que se generen 4 clases por cada tabla del esquema. En reali-
dad, existe una quinta clase que se crea en el directorio lib/model/map/ y que contiene
metainformación relativa a la tabla que es necesaria para la ejecución de la aplicación.
Pero como es una clase que seguramente no se modifica nunca, es mejor olvidarse de
ella.


8.4. Acceso a los datos
En Symfony, el acceso a los datos se realiza mediante objetos. Si se está acostumbrado
al modelo relacional y a utilizar consultas SQL para acceder y modificar los datos, los mé-
todos del modelo de objetos pueden parecer complicados. No obstante, una vez que se
prueba el poder de la orientación a objetos para acceder a los datos, probablemente te
gustará mucho más.

En primer lugar, hay que asegurarse de que se utiliza el mismo vocabulario. Aunque el
modelo relacional y el modelo de objetos utilizan conceptos similares, cada uno tiene su
propia nomenclatura:

Relacional                                     Orientado a objetos

Tabla                                          Clase

Fila, registro                                 Objeto

Campo, columna                                 Propiedad




www.librosweb.es                                                                               156
Symfony, la guía definitiva                                                  Capítulo 8. El modelo


8.4.1. Obtener el valor de una columna
Cuando Symfony construye el modelo, crea una clase de objeto base para cada una de
las tablas definidas en schema.yml. Cada una de estas clases contiene una serie de cons-
tructores y accesores por defecto en función de la definición de cada columna: los méto-
dos new, getXXX() y setXXX() permiten crear y obtener las propiedades de los objetos,
como se muestra en el listado 8-7.

Listado 8-7 - Métodos generados en una clase objeto
   $articulo = new Article();
   $articulo->setTitle('Mi primer artículo');
   $articulo->setContent('Este es mi primer artículo. n Espero que te guste.');

   $titulo   = $articulo->getTitle();
   $contenido = $articulo->getContent();


  NOTA
  La clase objeto generada se llama Article, que es el valor de la propiedad phpName para la tabla
  blog_article. Si no se hubiera definido la propiedad phpName, la clase se habría llamado BlogAr-
  ticle. Los métodos accesores (get y set) utilizan una variante de camelCase aplicada al nombre
  de las columnas, por lo que el método getTitle() obtiene el valor de la columna title.

Para establecer el valor de varios campos a la vez, se puede utilizar el método fromArr-
ay(), que también se genera para cada clase objeto, como se muestra en el listado 8-8.

Listado 8-8 - El método fromArray() es un setter múltiple
   $articulo->fromArray(array(
     'title'   => 'Mi primer artículo',
     'content' => 'Este es mi primer artículo. n Espero que te guste.'
   ));


8.4.2. Obtener los registros relacionados
La columna article_id de la tabla blog_comment define implícitamente una clave externa
a la tabla blog_article. Cada comentario está relacionado con un artículo y un artículo
puede tener muchos comentarios. Las clases generadas contienen 5 métodos que tradu-
cen esta relación a la forma orientada a objetos, de la siguiente manera:

      ▪ $comentario->getArticle(): para obtener el objeto Article relacionado

      ▪ $comentario->getArticleId(): para obtener el ID del objeto Article relacionado

      ▪ $comentario->setArticle($articulo): para definir el objeto Article relacionado

      ▪ $comentario->setArticleId($id): para definir el objeto Article relacionado a
         partir de un ID

      ▪ $articulo->getComments(): para obtener los objetos Comment relacionados

Los métodos getArticleId() y setArticleId() demuestran que se puede utilizar la co-
lumna article_id como una columna normal y que se pueden indicar las relaciones


www.librosweb.es                                                                             157
Symfony, la guía definitiva                                                   Capítulo 8. El modelo


manualmente, pero esto no es muy interesante. La ventaja de la forma orientada a obje-
tos es mucho más evidente en los otros 3 métodos. El listado 8-9 muestra como utilizar
los setters generados.

Listado 8-9 - Las claves externas se traducen en un setter especial
   $comentario = new Comment();
   $comentario->setAuthor('Steve');
   $comentario->setContent('¡Es el mejor artículo que he leído nunca!');

   // Añadir este comentario al anterior objeto $articulo
   $comentario->setArticle($articulo);

   // Sintaxis alternativa
   // Solo es correcta cuando el objeto artículo ya
   // ha sido guardado anteriormente en la base de datos
   $comentario->setArticleId($articulo->getId());

El listado 8-10 muestra como utilizar los getters generados automáticamente. También
muestra como encadenar varias llamadas a métodos en los objetos del modelo.

Listado 8-10 - Las claves externas se traducen en getters especiales
   // Relación de "muchos a uno"
   echo $comentario->getArticle()->getTitle();
    => Mi primer artículo
   echo $comentario->getArticle()->getContent();
    => Este es mi primer artículo.
       Espero que te guste.

   // Relación "uno a muchos"
   $comentarios = $articulo->getComments();

El método getArticle() devuelve un objeto de tipo Article, que permite utilizar el méto-
do accesor getTitle(). Se trata de una alternativa mucho mejor que realizar la unión de
las tablas manualmente, ya que esto último necesitaría varias líneas de código (empe-
zando con la llamada al método $comment->getArticleId()).

La variable $comentarios del listado 8-10 contiene un array de objetos de tipo Comment.
Se puede mostrar el primer comentario mediante $comentarios[0] o se puede recorrer la
colección entera mediante foreach ($comentarios as $comentario).

  NOTA
  Los objetos del modelo se definen siguiendo la convención de utilizar nombres en singular, y ahora
  se puede entender la razón. La clave externa definida en la tabla blog_comment crea el método
  getComments(), cuyo nombre se crea añadiendo una letra s al nombre del objeto Comment. Si el
  nombre del modelo fuera plural, la generación automática llamaría getCommentss() a ese método,
  lo cual no tiene mucho sentido.


8.4.3. Guardar y borrar datos
Al utilizar el constructor new se crea un nuevo objeto, pero no un registro en la tabla
blog_article. Si se modifica el objeto, tampoco se reflejan esos cambios en la base de


www.librosweb.es                                                                               158
Symfony, la guía definitiva                                                   Capítulo 8. El modelo


datos. Para guardar los datos en la base de datos, se debe invocar el método save() del
objeto.
   $articulo->save();

El ORM de Symfony es lo bastante inteligente como para detectar las relaciones entre ob-
jetos, por lo que al guardar el objeto $articulo también se guarda el objeto $comentario
relacionado. También detecta si ya existía el objeto en la base de datos, por lo que el
método save() a veces se traduce a una sentencia INSERT de SQL y otras veces se tradu-
ce a una sentencia UPDATE. La clave primaria se establece de forma automática al llamar
al método save(), por lo que después de guardado, se puede obtener la nueva clave pri-
maria del objeto mediante $articulo->getId().

  SUGERENCIA
  Para determinar si un objeto es completamente nuevo, se puede utilizar el método isNew(). Para
  detectar si un objeto ha sido modificado y por tanto se debe guardar en la base de datos, se puede
  utilizar el método isModified().

Si lees los comentarios que insertan los usuarios en tus artículos, puede que te desani-
mes un poco para seguir publicando cosas en Internet. Si además no captas la ironía de
los comentarios, puedes borrarlos fácilmente con el método delete(), como se muestra
en el listado 8-11.

Listado 8-11 - Borrar registros de la base de datos mediante el método delete()
del objeto relacionado
   foreach ($articulo->getComments() as $comentario)
   {
     $comentario->delete();
   }


  SUGERENCIA
  Después de ejecutar el método delete(), el objeto sigue disponible hasta que finaliza la ejecución
  de la petición actual. Para determinar si un objeto ha sido borrado de la base de datos, se puede
  utilizar el método isDeleted().


8.4.4. Obtener registros mediante la clave primaria
Si se conoce la clave primaria de un registro concreto, se puede utilizar el método retr-
ieveByPk() de la clase peer para obtener el objeto relacionado.
   $articulo = ArticlePeer::retrieveByPk(7);

El archivo schema.yml define el campo id como clave primaria de la tabla blog_article,
por lo que la sentencia anterior obtiene el artículo cuyo id sea igual a 7. Como se ha uti-
lizado una clave primaria, solo se obtiene un registro; la variable $articulo contiene un
objeto de tipo Article.

En algunos casos, la clave primaria está formada por más de una columna. Es esos ca-
sos, el método retrieveByPK() permite indicar varios parámetros, uno para cada columna
de la clave primaria.

www.librosweb.es                                                                               159
Symfony, la guía definitiva                                                     Capítulo 8. El modelo


También se pueden obtener varios objetos a la vez mediante sus claves primarias, invo-
cando el método retrieveByPKs(), que espera como argumento un array de claves
primarias.

8.4.5. Obtener registros mediante Criteria
Cuando se quiere obtener más de un registro, se debe utilizar el método doSelect() de la
clase peer correspondiente a los objetos que se quieren obtener. Por ejemplo, para obte-
ner objetos de la clase Article, se llama al método ArticlePeer::doSelect().

El primer parámetro del método doSelect() es un objeto de la clase Criteria, que es una
clase para definir consultas simples sin utilizar SQL, para conseguir la abstracción de ba-
se de datos.

Un objeto Criteria vacío devuelve todos los objetos de la clase. Por ejemplo, el código
del listado 8-12 obtiene todos los artículos de la base de datos.

Listado 8-12 - Obtener registros mediante Criteria con el método doSelect()
(Criteria vacío)
   $c = new Criteria();
   $articulos = ArticlePeer::doSelect($c);

   // Genera la siguiente consulta SQL
   SELECT blog_article.ID, blog_article.TITLE, blog_article.CONTENT,
          blog_article.CREATED_AT
   FROM   blog_article;

  Hydrating

  Invocar el método ::doSelect() es mucho más potente que una simple consulta SQL. En primer
  lugar, se optimiza el código SQL para la base de datos que se utiliza. En segundo lugar, todos los
  valores pasados a Criteria se escapan antes de insertarlos en el código SQL, para evitar los pro-
  blemas de SQL injection. En tercer lugar, el método devuelve un array de objetos y no un result set.
  El ORM crea y rellena de forma automática los objetos en función del result set de la base de datos.
  Este proceso se conoce con el nombre de hydrating.

Para las selecciones más complejas de objetos, se necesitan equivalentes a las sentenc-
ias WHERE, ORDER BY, GROUP BY y demás de SQL. El objeto Criteria dispone de métodos y
parámetros para indicar todas estas condiciones. Por ejemplo, para obtener todos los co-
mentarios escritos por el usuario Steve y ordenados por fecha, se puede construir un ob-
jeto Criteria como el del listado 8-13.

Listado 8-13 - Obtener registros mediante Criteria con el método doSelect()
(Criteria con condiciones)
   $c = new Criteria();
   $c->add(CommentPeer::AUTHOR, 'Steve');
   $c->addAscendingOrderByColumn(CommentPeer::CREATED_AT);
   $comentarios = CommentPeer::doSelect($c);

   // Genera la siguiente consulta SQL
   SELECT blog_comment.ARTICLE_ID, blog_comment.AUTHOR, blog_comment.CONTENT,


www.librosweb.es                                                                                 160
Symfony, la guía definitiva                                                   Capítulo 8. El modelo

          blog_comment.CREATED_AT
   FROM   blog_comment
   WHERE blog_comment.author = 'Steve'
   ORDER BY blog_comment.CREATED_AT ASC;

Las constantes de clase que se pasan como parámetros a los métodos add() hacen refe-
rencia a los nombres de las propiedades. Su nombre se genera a partir del nombre de las
columnas en mayúsculas. Por ejemplo, para indicar la columna content de la tabla
blog_article, se utiliza la constante de clase llamada ArticlePeer::CONTENT.

  NOTA
  ¿Por qué se utiliza CommentPeer::AUTHOR en vez de blog_comment.AUTHOR, que es en definitiva
  el valor que se va a utilizar en la consulta SQL? Supon que se debe modificar el nombre del campo
  de la tabla y en vez de author ahora se llama contributor. Si se hubiera utilizado el valor blog_-
  comment.AUTHOR, es necesario modificar ese valor en cada llamada al modelo. Sin embargo, si se
  utiliza el valorCommentPeer::AUTHOR, solo es necesario cambiar el nombre de la columna en el ar-
  chivo schema.yml, manteniendo el atributo phpName a un valor igual a AUTHOR y reconstruir el
  modelo.

La tabla 8-1 compara la sintaxis de SQL y del objeto Criteria.

Tabla 8-1 - Sintaxis de SQL y del objeto Criteria

SQL                                                  Criteria

WHERE columna = valor                                ->add(columna, valor);

                                                     ->add(columna, valor,
WHERE columna <> valor
                                                     Criteria::NOT_EQUAL);

Otros operadores de comparación

                                                     Criteria::GREATER_THAN,
> , <
                                                     Criteria::LESS_THAN

                                                     Criteria::GREATER_EQUAL,
>=, <=
                                                     Criteria::LESS_EQUAL

IS NULL, IS NOT NULL                                 Criteria::ISNULL, Criteria::ISNOTNULL

LIKE, ILIKE                                          Criteria::LIKE, Criteria::ILIKE

IN, NOT IN                                           Criteria::IN, Criteria::NOT_IN

Otras palabras clave de SQL

ORDER BY columna ASC                                 ->addAscendingOrderByColumn(columna);

ORDER BY columna DESC                                ->addDescendingOrderByColumn(columna);

LIMIT limite                                         ->setLimit(limite)

OFFSET desplazamiento                                ->setOffset(desplazamiento)



www.librosweb.es                                                                               161
Symfony, la guía definitiva                                                  Capítulo 8. El modelo


FROM tabla1, tabla2 WHERE tabla1.col1 =
                                                   ->addJoin(col1, col2)
tabla2.col2

FROM tabla1 LEFT JOIN tabla2 ON                    ->addJoin(col1, col2,
tabla1.col1 = tabla2.col2                          Criteria::LEFT_JOIN)

FROM tabla1 RIGHT JOIN tabla2 ON                   ->addJoin(col1, col2,
tabla1.col1 = tabla2.col2                          Criteria::RIGHT_JOIN)


  SUGERENCIA
  La mejor forma de descubrir y entender los métodos disponibles en las clases generadas automáti-
  camente es echar un vistazo a los archivos Base del directorio lib/model/om/. Los nombres de los
  métodos son bastante explícitos, aunque si se necesitan más comentarios sobre esos métodos, se
  puede establecer el parámetro propel.builder.addComments a true en el archivo de configura-
  ción config/propel.ini y después volver a reconstruir el modelo.

El listado 8-14 muestra otro ejemplo del uso de Criteria con condiciones múltiples. En el
ejemplo se obtienen todos los comentarios del usuario Steve en los artículos que contie-
nen la palabra enjoy y además, ordenados por fecha.

Listado 8-14 - Otro ejemplo para obtener registros mediante Criteria con el mé-
todo doSelect() (Criteria con condiciones)
   $c = new Criteria();
   $c->add(CommentPeer::AUTHOR, 'Steve');
   $c->addJoin(CommentPeer::ARTICLE_ID, ArticlePeer::ID);
   $c->add(ArticlePeer::CONTENT, '%enjoy%', Criteria::LIKE);
   $c->addAscendingOrderByColumn(CommentPeer::CREATED_AT);
   $comentarios = CommentPeer::doSelect($c);

   // Genera la siguiente consulta SQL
   SELECT blog_comment.ID, blog_comment.ARTICLE_ID, blog_comment.AUTHOR,
          blog_comment.CONTENT, blog_comment.CREATED_AT
   FROM   blog_comment, blog_article
   WHERE blog_comment.AUTHOR = 'Steve'
          AND blog_article.CONTENT LIKE '%enjoy%'
          AND blog_comment.ARTICLE_ID = blog_article.ID
   ORDER BY blog_comment.CREATED_AT ASC

De la misma forma que el lenguaje SQL es sencillo pero permite construir consultas muy
complejas, el objeto Criteria permite manejar condiciones de cualquier nivel de comple-
jodad. Sin embargo, como muchos programadores piensan primero en el código SQL y
luego lo traducen a las condiciones de la lógica orientada a objetos, es difícil comprender
bien el objeto Criteria cuando se utiliza las primeras veces. La mejor forma de aprender
es mediante ejemplos y aplicaciones de prueba. El sitio web del proyecto Symfony esá
lleno de ejemplos de cómo construir objetos de tipo Criteria para todo tipo de
situaciones.

Además del método doSelect(), todas las clases peer tienen un método llamado doC-
ount(), que simplemente cuenta el número de registros que satisfacen las condiciones
pasadas como parámetro y devuelve ese número como un entero. Como no se devuelve

www.librosweb.es                                                                             162
Symfony, la guía definitiva                                                  Capítulo 8. El modelo


ningún objeto, no se produce el proceso de hydrating y por tanto el método doCount() es
mucho más rápido que doSelect().

Las clases peer también incluyen métodos doDelete(), doInsert() y doUpdate() (todos
ellos requieren como parámetro un objeto de tipo Criteria). Estos métodos permiten re-
alizar consultas de tipo DELETE, INSERT y UPDATE a la base de datos. Se pueden consultar
las clases peer generadas automáticamente para descubrir más detalles de estos méto-
dos de Propel.

Por último, si solo se quiere obtener el primer objeto, se puede reemplazar el método
doSelect() por doSelectOne(). Es muy útil cuando se sabe que las condiciones de Crite-
ria solo van a devolver un resultado, y su ventaja es que el método devuelve directa-
mente un objeto en vez de un array de objetos.

  SUGERENCIA
  Cuando una consulta doSelect() devuelve un número muy grande de resultados, normalmente
  sólo se quieren mostrar unos pocos en la respuesta. Symfony incluye una clase especial para pagi-
  nar resultados llamada sfPropelPager, que realiza la paginación de forma automática y cuya do-
  cumentación y ejemplos de uso se puede encontrar en http://www.symfony-project.org/cookbook/
  trunk/pager.


8.4.6. Uso de consultas con código SQL
A veces, no es necesario obtener los objetos, sino que solo son necesarios algunos datos
calculados por la base de datos. Por ejemplo, para obtener la fecha de creación de todos
los artículos, no tiene sentido obtener todos los artículos y después recorrer el array de
los resultados. En este caso es preferible obtener directamente el resultado, ya que se
evita el proceso de hydrating.

Por otra parte, no deberían utilizarse instrucciones PHP de acceso a la base de datos,
porque se perderían las ventajas de la abstracción de bases de datos. Lo que significa
que se debe evitar el ORM (Propel) pero no la abstracción de bases de datos (Creole).

Para realizar consultas a la base de datos con Creole, es necesario realizar los siguientes
pasos:

    1. Obtener la conexión con la base de datos.

    2. Construir la consulta.

    3. Crear una sentencia con esa consulta.

    4. Iterar el result set que devuelve la ejecución de la sentencia.

Aunque lo anterior puede parecer un galimatías, el código del listado 8-15 es mucho más
explícito.

Listado 8-15 - Consultas SQL personalizadas con Creole
   $conexion = Propel::getConnection();
   $consulta = 'SELECT MAX(%s) AS max FROM %s';
   $consulta = sprintf($consulta, ArticlePeer::CREATED_AT, ArticlePeer::TABLE_NAME);
   $sentencia = $conexion->prepareStatement($consulta);


www.librosweb.es                                                                              163
Symfony, la guía definitiva                                                     Capítulo 8. El modelo

   $resultset = $sentencia->executeQuery();
   $resultset->next();
   $max = $resultset->getInt('max');

Al igual que sucede con las selecciones realizadas con Propel, las consultas con Creole
son un poco complicadas de usar al principio. Los ejemplos de las aplicaciones existentes
y de los tutoriales pueden ser útiles para descubrir la forma de hacerlas.

  SUGERENCIA
  Si se salta esa forma de acceder y se intenta acceder de forma directa a la base de datos, se corre
  el riesgo de perder la seguridad y la abstracción proporcionadas por Creole. Aunque es más largo
  hacerlo con Creole, es la forma de utilizar las buenas prácticas que aseguran un buen rendimiento,
  portabilidad y seguridad a la aplicación. Esta recomendación es especialmente útil para las consul-
  tas que contienen parámetros cuyo origen no es confiable (como por ejemplo un usuario de Inter-
  net). Creole se encarga de escapar los datos para mantener la seguridad de la base de datos. Si se
  accede directamente a la base de datos, se corre el riesgo de sufrir ataques de tipo SQL-injection.


8.4.7. Uso de columnas especiales de fechas
Normalmente, cuando una tabla tiene una columna llamada created_at, se utiliza para
almacenar un timestamp de la fecha de creación del registro. La misma idea se aplica a
las columnas updated_at, cuyo valor se debe actualizar cada vez que se actualiza el prop-
io registro.

La buena noticia es que Symfony reconoce estos nombres de columna y se ocupa de act-
ualizar su valor de forma automática. No es necesario establecer manualmente el valor
de las columnas created_at y updated_at; se actualizan automáticamente, tal y como
muestra el listado 8-16. Lo mismo se aplica a las columnas llamadas created_on y
updated_on.

Listado        8-16   -   Las    columnas       created_at      y   updated_at      se    gestionan
automáticamente
   $comentario = new Comment();
   $comentario->setAuthor('Steve');
   $comentario->save();

   // Muestra la fecha de creación
   echo $comentario->getCreatedAt();
    => [fecha de la operación INSERT de la base de datos]

Además, los getters de las columnas de fechas permiten indicar el formato de la fecha
como argumento:
   echo $comentario->getCreatedAt('Y-m-d');

  Refactorizando la capa de datos

  Cuando se desarrolla un proyecto con Symfony, normalmente se empieza escribiendo el código de
  la lógica de dominio en las acciones. Sin embargo, las consultas a la base de datos y la manipula-
  ción del modelo no se debería realizar en la capa del controlador. De forma que toda la lógica relac-
  ionada con los datos se debería colocar en la capa del modelo. Cada vez que se ejecuta el mismo


www.librosweb.es                                                                                  164
Symfony, la guía definitiva                                                        Capítulo 8. El modelo


  código en más de un sitio de las acciones, se debería mover ese código al modelo. De esta forma
  las acciones se mantienen cortas y fáciles de leer.

  Por ejemplo, imagina el caso de un blog que tiene que obtener los 10 artículos más populares rela-
  cionados con una etiqueta determinada (que se pasa como parámetro). Este código no debería es-
  tar en la acción, sino en el modelo. De hecho, si se tiene que mostrar en la plantilla la lista de artí-
  culos, la acción debería ser similar a la siguiente:
       public function executeMuestraArticulosPopularesParaEtiqueta()
       {
         $etiqueta = TagPeer::retrieveByName($this->getRequestParameter('tag'));
         $this->foward404Unless($etiqueta);
         $this->articulos = $etiqueta->getArticulosPopulares(10);
       }

  La acción crea un objeto de clase Tag a partir del parámetro de la petición. Después, todo el código
  necesario para realizar la consulta a la base de datos se almacena en el método getArticulosPo-
  pulares() de esta clase. La acción es más fácil de leer y el código del modelo se puede reutilizar
  fácilmente en otra acción.

  Mover el código a un lugar más apropiado es una de las técnicas de refactorización. Si se realiza
  habitualmente, el código resultante es más fácil de mantener y de entender por otros programado-
  res. Una buena regla general sobre cuando refactorizar la capa de los datos es que el código de
  una acción raramente debería tener más de 10 líneas de código PHP.


8.5. Conexiones con la base de datos
Aunque el modelo de datos es independiente de la base de datos utilizada, es necesario
utilizar una base de datos concreta. La información mínima que necesita Symfony para
realizar peticiones a la base de datos es su nombre, los datos de acceso y el tipo de base
de datos. Esta información se indica en el archivo databases.yml que se encuentra en el
directorio config/. El listado 8-17 muestra un ejemplo de ese archivo.

Listado 8-17 - Ejemplo de opciones de conexión con la base de datos, en mipro-
yecto/config/databases.yml
   prod:
     propel:
       param:
         hostspec:               miservidordatos
         username:               minombreusuario
         password:               xxxxxxxxxx

   all:
     propel:
        class:                   sfPropelDatabase
        param:
          phptype:               mysql     # fabricante de la base de datos
          hostspec:              localhost
          database:              blog
          username:              login
          password:              passwd
          port:                  80


www.librosweb.es                                                                                     165
Symfony, la guía definitiva                                                  Capítulo 8. El modelo

          encoding:             utf8         # Codificación utilizada para crear la tabla
          persistent:           true         # Utilizar conexiones persistentes

Las opciones de la conexión se establecen para cada entorno. Se pueden definir diferen-
tes opciones para los entornos prod, dev y test, o para cualquier otro entorno definido en
la aplicación. También es posible redefinir esta configuración en cada aplicación, estable-
ciendo diferentes valores para las opciones en un archivo específico de la aplicación, co-
mo por ejemplo apps/miaplicacion/config/databases.yml. De esta forma es posible por
ejemplo disponer de políticas de seguridad diferentes para las aplicaciones públicas y las
aplicaciones de administración del proyecto, y definir distintos usuarios de bases de datos
con privilegios diferentes.

Cada entorno puede definir varias conexiones. Cada conexión hace referencia a un esq-
uema con el mismo nombre. En el ejemplo del listado 8-17, la conexión propel se refiere
al esquema propel del listado 8-3.

Los valores permitidos para el parámetro phptype corresponden a los tipos de bases de
datos soportados por Creole:

      ▪ mysql

      ▪ sqlserver

      ▪ pgsql

      ▪ sqlite

      ▪ oracle

Los parámetros hostspec, database, username y password son las opciones típicas para co-
nectar con una base de datos. Estas opciones se pueden escribir de forma abreviada me-
diante un nombre de origen de datos o DSN (data source name). El listado 8-18 es equi-
valente a la sección all: del listado 8-17.

Listado 8-18 - Opciones abreviadas de conexión con la base de datos
   all:
     propel:
        class:            sfPropelDatabase
        param:
          dsn:            mysql://login:passwd@localhost/blog

Si se utiliza una base de datos de tipo SQLite, el parámetro hostspec debe indicar la ruta
al archivo de base de datos. Si por ejemplo se guarda la base de datos del blog en el ar-
chivo data/blog.db, las opciones del archivo databases.yml serán las del listado 8-19.

Listado 8-19 - Opciones de conexión con una base de datos SQLite utilizando la
ruta al archivo como host
        all:
          propel:
             class:          sfPropelDatabase
             param:
               phptype: sqlite
               database: %SF_DATA_DIR%/blog.db


www.librosweb.es                                                                             166
Symfony, la guía definitiva                                           Capítulo 8. El modelo


8.6. Extender el modelo
Los métodos del modelo que se generan automáticamente están muy bien, pero no siem-
pre son suficientes. Si se incluye lógica de negocio propia, es necesario extender el mo-
delo añadiendo nuevos métodos o redefiniendo algunos de los existentes.

8.6.1. Añadir nuevos métodos
Los nuevos métodos se pueden añadir en las clases vacías del modelo que se generan en
el directorio lib/model/. Se emplea $this para invocar a los métodos del objeto actual y
self:: para invocar a los métodos estáticos de la clase actual. No se debe olvidar que las
clases personalizadas heredan los métodos de las clases Base del directorio lib/model/
om/.

Por ejemplo, en el objeto Article generado en el listado 8-3, se puede añadir un método
mágico de PHP llamado __toString() de forma que al mostrar un objeto de la clase Arti-
cle se muestre su título, tal y como se indica en el listado 8-20.

Listado 8-20 - Personalizar el modelo, en lib/model/Article.php
   <?php

   class Article extends BaseArticle
   {
     public function __toString()
     {
       return $this->getTitle(); // getTitle() se hereda de BaseArticle
     }
   }

También se pueden extender las clases peer, como por ejemplo para obtener todos los
artículos ordenados por fecha de creación, tal y como muestra el listado 8-21.

Listado 8-21 - Personalizando el modelo, en lib/model/ArticlePeer.php
   <?php

   class ArticlePeer extends BaseArticlePeer
   {
     public static function getTodosOrdenadosPorFecha()
     {
       $c = new Criteria();
       $c->addAscendingOrderByColumn(self::CREATED_AT);
       return self::doSelect($c);

       }
   }

Los nuevos métodos están disponibles de la misma forma que los métodos generados au-
tomáticamente, tal y como muestra el listado 8-22.

Listado 8-22 - El uso de métodos personalizados del modelo es idéntico al de los
métodos generados automáticamente



www.librosweb.es                                                                      167
Symfony, la guía definitiva                                            Capítulo 8. El modelo

   foreach (ArticlePeer::getAllOrderedByDate() as $articulo)
   {
     echo $articulo;   // Se llama al método mágico __toString()
   }


8.6.2. Redefinir métodos existentes
Si alguno de los métodos generados automáticamente en las clases Base no satisfacen
las necesidades de la aplicación, se pueden redefinir en las clases personalizadas. Sola-
mente es necesario mantener el mismo número de argumentos para cada método.

Por ejemplo, el método $articulo->getComments() devuelve un array de objetos Comment,
sin ningún tipo de ordenamiento. Si se necesitan los resultados ordenados por fecha de
creación siendo el primero el comentario más reciente, se puede redefinir el método
getComments(), como muestra el listado 8-23. Como el método getComments() original (g-
uardado en lib/model/om/BaseArticle.php) requiere como argumentos un objeto de tipo
Criteria y una conexión, la nueva función debe contener esos mismos parámetros.

Listado 8-23 - Redefiniendo los métodos existentes en el modelo, en lib/model/
Article.php
   public function getComments($criteria = null, $con = null)
   {
     if (is_null($criteria))
     {
       $criteria = new Criteria();
     }
     else
     {
       // Los objetos se pasan por referencia en PHP5, por lo que se debe clonar
       // el objeto original para no modificarlo
       $criteria = clone $criteria;
     }
     $criteria->addDescendingOrderByColumn(CommentPeer::CREATED_AT);

       return parent::getComments($criteria, $con);
   }

El método personalizado acaba llamando a su método padre en la clase Base, lo que se
considera una buena práctica. No obstante, es posible saltarse completamente la clase
Base y devolver el resultado directamente.


8.6.3. Uso de comportamientos en el modelo
Algunas de las modificaciones que se realizan en el modelo son genéricas y por tanto se
pueden reutilizar. Por ejemplo, los métodos que hacen que un objeto del modelo sea re-
ordenable o un bloqueo de tipo optimistic en la base de datos para evitar conflictos cuan-
do se guardan de forma concurrente los objetos se pueden considerar extensiones gené-
ricas que se pueden añadir a muchas clases.

Symfony encapsula estas extensiones en “comportamientos” (del inglés behaviors). Los
comportamientos son clases externas que proporcionan métodos extras a las clases del


www.librosweb.es                                                                       168
Symfony, la guía definitiva                                             Capítulo 8. El modelo


modelo. Las clases del modelo están definidas de forma que se puedan enganchar estas
clases externas y Symfony extiende las clases del modelo mediante sfMixer (el Capítulo
17 contiene los detalles).

Para habilitar los comportamientos en las clases del modelo, se debe modificar una op-
ción del archivo config/propel.ini:
   propel.builder.AddBehaviors = true    // El valor por defecto es false

Symfony no incluye por defecto ningún comportamiento, pero se pueden instalar med-
iante plugins. Una vez que el plugin se ha instalado, se puede asignar un comportamien-
to a una clase mediante una sola línea de código. Si por ejemplo se ha instalado el plugin
sfPropelParanoidBehaviorPlugin en la aplicación, se puede extender la clase Article con
este comportamiento añadiendo la siguiente línea de código al final del archivo Article.-
class.php:
   sfPropelBehavior::add('Article', array(
     'paranoid' => array('column' => 'deleted_at')
   ));

Después de volver a generar el modelo, los objetos de tipo Article que se borren perma-
necerán en la base de datos, aunque será invisibles a las consultas que hacen uso de los
métodos del ORM, a no ser que se deshabilite temporalmente el comportamiento med-
iante sfPropelParanoidBehavior::disable().

La lista de plugins de Symfony disponible en el wiki incluye numerosos comportamientos
http://trac.symfony-project.com/wiki/SymfonyPlugins#Propelbehaviorplugins. Cada comporta-
miento tiene su propia documentación y su propia guía de instalación.


8.7. Sintaxis extendida del esquema
Un archivo schema.yml puede ser tan sencillo como el mostrado en el listado 8-3. Sin em-
bargo, los modelos relacionales suelen ser complejos. Este es el motivo por el que existe
una sintaxis extendida del esquema para que se pueda utilizar en cualquier caso.

8.7.1. Atributos
Se pueden definir atributos específicos para las conexiones y las tablas, tal y como se
muestra en el listado 8-24. Estas opciones se establecen bajo la clave _attributes.

Listado 8-24 - Atributos de las conexiones y las tablas
   propel:
     _attributes:   { noXsd: false, defaultIdMethod: none, package: lib.model }
     blog_article:
       _attributes: { phpName: Article }

Si se quiere validar el esquema antes de que se genere el código asociado, se debe de-
sactivar en la conexión el atributo noXSD. La conexión también permite que se le indique
el atributo defaultIdMethod. Si no se indica, se utilizará el método nativo de generación
de IDs –por ejemplo, autoincrement en MySQL o sequences en PostgreSQL. El otro valor
permitido es none.


www.librosweb.es                                                                        169
Symfony, la guía definitiva                                                     Capítulo 8. El modelo


El atributo package es como un namespace; indica la ruta donde se guardan las clases
generadas automáticamente. Su valor por defecto es lib/model/, pero se puede modifi-
car para organizar el modelo en una estructura de subpaquetes. Si por ejemplo no se qu-
ieren mezclar en el mismo directorio las clases del núcleo de la aplicación con las clases
de un sistema de estadísticas, se pueden definir dos esquemas diferentes con los paque-
tes lib.model.business y lib.model.stats.

Ya se ha visto el atributo de tabla phpName, que se utiliza para establecer el nombre de la
clase generada automáticamente para manejar cada tabla de la base de datos.

Las tablas que guardan contenidos adaptados para diferentes idiomas (es decir, varias
versiones del mismo contenido en una tabla relacionada, para conseguir la internacionali-
zación) también pueden definir dos atributos adicionales (explicados detalladamente en
el Capítulo 13), tal y como se muestra en el listado 8-25.

Listado 8-25 - Atributos para las tablas de internacionalización
   propel:
     blog_article:
       _attributes: { isI18N: true, i18nTable: db_group_i18n }

  Trabajando con varios esquemas

  Cada aplicación puede tener más de un esquema. Symfony tiene en cuenta todos los archivos que
  acaben en schema.yml o schema.xml que están en el directorio config/. Se trata de una estrateg-
  ia muy útil cuando la aplicación tiene muchas tablas o si algunas de las tablas no comparten la mis-
  ma conexión.

  Si se consideran los 2 siguientes esquemas:
      // En config/business-schema.yml
      propel:
        blog_article:
          _attributes: { phpName: Article }
        id:
        title: varchar(50)
      // En config/stats-schema.yml
      propel:
        stats_hit:
          _attributes: { phpName: Hit }
        id:
        resource: varchar(100)
        created_at:

  Los dos esquemas comparten la misma conexión (propel), y las clases Article y Hit se gene-
  rarán en el mismo directorio lib/model/. El resultado es equivalente a si se hubiera escrito sola-
  mente un esquema.

  También es posible definir esquemas que utilicen diferentes conexiones (por ejemplo propel y
  propel_bis definidas en databases.yml) y cuyas clases generadas se guarden en subdirectorios
  diferentes:
      # En config/business-schema.yml
      propel:
        blog_article:


www.librosweb.es                                                                                 170
Symfony, la guía definitiva                                                  Capítulo 8. El modelo

          _attributes: { phpName: Article, package: lib.model.business }
        id:
        title: varchar(50)
      # En config/stats-schema.yml
      propel_bis:
        stats_hit:
          _attributes: { phpName: Hit, package.lib.model.stat }
        id:
        resource: varchar(100)
        created_at:

  Muchas aplicaciones utilizan más de un esquema. Sobre todo los plugins, muchos de los cuales de-
  finen su propio esquema y paquete para evitar errores y duplicidades con las clases propias de la
  aplicación (más detalles en el Capítulo 17).


8.7.2. Detalles de las columnas
La sintaxis básica ofrece dos posibilidades: dejar que Symfony deduzca las características
de una columna a partir de su nombre (indicando un valor vacío para esa columna) o de-
finir el tipo de columna con uno de los tipos predefinidos. El listado 8-26 muestra estas 2
opciones.

Listado 8-26 - Atributos básicos de columna
   propel:
     blog_article:
       id:                    # Symfony se encarga de esta columna
       title: varchar(50)     # Definir el tipo explícitamente

Se pueden definir muchos más aspectos de cada columna. Si se definen, se utiliza un
array asociativo para indicar las opciones de la columna, tal y como muestra el listado
8-27.

Listado 8-27 - Atributos avanzados de columna
   propel:
     blog_article:
       id:       { type: integer, required: true, primaryKey: true, autoIncrement: true }
       name:     { type: varchar(50), default: foobar, index: true }
       group_id: { type: integer, foreignTable: db_group, foreignReference: id, onDelete:
   cascade }

Los parámetros de las columnas son los siguientes:

      ▪ type: Tipo de columna. Se puede elegir entre boolean, tinyint, smallint, inte-
        ger, bigint, double, float, real, decimal, char, varchar(tamano), longvarchar,
        date, time, timestamp, bu_date, bu_timestamp, blob y clob.

      ▪ required: valor booleano. Si vale true la columna debe tener obligatoriamente un
        valor.

      ▪ default: el valor por defecto.

      ▪ primaryKey: valor booleano. Si vale true indica que es una clave primaria.




www.librosweb.es                                                                              171
Symfony, la guía definitiva                                             Capítulo 8. El modelo


      ▪ autoIncrement: valor booleano. Si se indica true para las columnas de tipo inte-
        ger, su valor se auto-incrementará.

      ▪ sequence: el nombre de la secuencia para las bases de datos que utilizan secuen-
        cias para las columnas autoIncrement (por ejemplo PostgreSQL y Oracle).

      ▪ index: valor booleano. Si vale true, se construye un índice simple; si vale unique
        se construye un índice único para la columna.

      ▪ foreignTable: el nombre de una tabla, se utiliza para crear una clave externa a
        otra tabla.

      ▪ foreignReference: el nombre de la columna relacionada si las claves externas se
        definen mediante foreignTable.

      ▪ onDelete: determina la acción que se ejecuta cuando se borra un registro en una
        tabla relacionada. Si su valor es setnull, la columna de la clave externa se esta-
        blece a null. Si su valor es cascade, se borra el registro relacionado. Si el siste-
        ma gestor de bases de datos no soporta este comportamiento, el ORM lo emula.
        Esta opción solo tiene sentido para las columnas que definen una foreignTable y
        una foreignReference.

      ▪ isCulture: valor booleano. Su valor es true para las columnas de tipo culture en
        las tablas de contenidos adaptados a otros idiomas (más detalles en el Capítulo
        13).

8.7.3. Claves externas
Además de los atributos de columna foreignTable y foreignReference, es posible añadir
claves externas bajo la clave _foreignKeys: de cada tabla. El esquema del listado 8-28
crea una clave externa en la columna user_id, que hace referencia a la columna id de la
tabla blog_user.

Listado 8-28 - Sintaxis alternativa para las claves externas
   propel:
     blog_article:
       id:
       title:    varchar(50)
       user_id: { type: integer }
       _foreignKeys:
         -
           foreignTable: blog_user
           onDelete:      cascade
           references:
              - { local: user_id, foreign: id }

La sintaxis alternativa es muy útil para las claves externas múltiples y para indicar un
nombre a cada clave externa, tal y como muestra el listado 8-29.

Listado 8-29 - La sintaxis alternativa de las claves externas aplicada a una clave
externa múltiple


www.librosweb.es                                                                        172
Symfony, la guía definitiva                                               Capítulo 8. El modelo

   _foreignKeys:
     my_foreign_key:
       foreignTable: db_user
       onDelete:      cascade
       references:
         - { local: user_id, foreign: id }
         - { local: post_id, foreign: id }


8.7.4. Índices
Además del atributo de columna index, es posible añadir claves índices bajo la clave _in-
dexes: de cada tabla. Si se quieren crean índices únicos, se debe utilizar la clave _uniq-
ues:. El listado 8-30 muestra la sintaxis alternativa para los índices.

Listado 8-30 - Sintaxis alternativa para los índices y los índices únicos
   propel:
     blog_article:
       id:
       title:            varchar(50)
       created_at:
       _indexes:
         mi_indice:      [title, user_id]
       _uniques:
         mi_otro_indice: [created_at]

La sintaxis alternativa solo es útil para los índices que se construyen con más de una
columna.

8.7.5. Columnas vacías
Cuando Symfony se encuentra con una columna sin ningún valor, utiliza algo de magia
para determinar su valor. El listado 8-31 muestra los detalles del tratamiento de las co-
lumnas vacías.

Listado 8-31 - Los detalles deducidos para las columnas vacías en función de su
nombre
   // Las columnas vacías llamadas "id" se consideran claves primarias
   id:         { type: integer, required: true, primaryKey: true, autoIncrement: true }

   // Las columnas vacías llamadas "XXX_id" se consideran claves externas
   foobar_id: { type: integer, foreignTable: db_foobar, foreignReference: id }

   // Las columnas vacías llamadas created_at, updated at, created_on y updated_on
   // se consideran fechas y automáticamente se les asigna el tipo "timestamp"
   created_at: { type: timestamp }
   updated_at: { type: timestamp }

Para las claves externas, Symfony busca una tabla cuyo phpName sea igual al principio del
nombre de la columna; si se encuentra, se utiliza ese nombre de tabla como
foreignTable.




www.librosweb.es                                                                          173
Symfony, la guía definitiva                                            Capítulo 8. El modelo


8.7.6. Tablas i18n
Symfony permite internacionalizar los contenidos mediante tablas relacionadas. De esta
forma, cuando se dispone de contenido que debe ser internacionalizado, se guarda en 2
tablas distintas: una contiene las columnas invariantes y otra las columnas que permiten
la internacionalización.

Todo lo anterior se considera de forma implícita cuando en el archivo schema.yml se dis-
pone de una tabla con el nombre cualquiernombre_i18n. Por ejemplo, el esquema que
muestra el listado 8-32 se completa automáticamente con los atributos de columna y de
tabla necesarios para que funcione el mecanismo de internacionalización. De forma inter-
na, Symfony entiende ese listado como si se hubiera escrito tal y como se muestra en el
listado 8-33. El Capítulo 13 explica en detalle la internacionalización.

Listado 8-32 - Mecanismo i18n implícito
   propel:
     db_group:
       id:
       created_at:

      db_group_i18n:
        name:        varchar(50)

Listado 8-33 - Mecanismo i18n explícito
   propel:
     db_group:
       _attributes: { isI18N: true, i18nTable: db_group_i18n }
       id:
       created_at:

     db_group_i18n:
       id:       { type: integer, required: true, primaryKey: true,foreignTable: db_group,
   foreignReference: id, onDelete: cascade }
       culture: { isCulture: true, type: varchar(7), required: true,primaryKey: true }
       name:     varchar(50)


8.7.7. Más allá del schema.yml: schema.xml
En realidad, el formato de schema.yml es propio de Symfony. Cuando se ejecuta un co-
mando que empieza por propel-, Symfony transforma ese archivo en otro archivo llama-
do generated-schema.xml, que es el tipo de archivo que necesita Propel para realizar sus
tareas sobre el modelo.

El archivo schema.xml contiene la misma información que su equivalente en formato
YAML. Por ejemplo, el listado 8-3 se convierte en el archivo XML del listado 8-34.

Listado 8-34 - Ejemplo de schema.xml, que se corresponde con el listado 8-3
   <?xml version="1.0" encoding="UTF-8"?>
    <database name="propel" defaultIdMethod="native" noXsd="true" package="lib.model">
       <table name="blog_article" phpName="Article">
         <column name="id" type="integer" required="true"
   primaryKey="true"autoIncrement="true" />

www.librosweb.es                                                                         174
Symfony, la guía definitiva                                                      Capítulo 8. El modelo

         <column name="title" type="varchar" size="255" />
         <column name="content" type="longvarchar" />
         <column name="created_at" type="timestamp" />
       </table>
       <table name="blog_comment" phpName="Comment">
         <column name="id" type="integer" required="true"
   primaryKey="true"autoIncrement="true" />
         <column name="article_id" type="integer" />
         <foreign-key foreignTable="blog_article">
           <reference local="article_id" foreign="id"/>
         </foreign-key>
         <column name="author" type="varchar" size="255" />
         <column name="content" type="longvarchar" />
         <column name="created_at" type="timestamp" />
       </table>
    </database>

La descripción del formato schema.xml se puede consultar en la documentación y la sec-
ción “Getting started” del sitio web del proyecto Propel (http://propel.phpdb.org/docs/
user_guide/chapters/appendices/AppendixB-SchemaReference.html ).

El formato del esquema en YAML se diseñó para que los esquemas fueran fáciles de leer
y escribir, pero su inconveniente es que los esquemas más complejos no se pueden des-
cribir solamente con un archivo schema.yml. Por otra parte, el formato XML permite des-
cribir completamente el esquema, independientemente de su complejidad e incluye la
posibilidad de incluir opciones propias de algunas bases de datos, herencia de tablas, etc.

Symfony también puede trabajar con esquemas escritos en formato XML. Así que no es
necesario utilizar el formato YAML propio de Symfony si el esquema es demasiado com-
plejo, si ya dispones de un esquema en formato XML o si estás acostumbrado a trabajar
con la sintaxis XML de Propel. Solamente es necesario crear el archivo schema.xml en el
directorio config/ del proyecto y construir el modelo.

  Propel en Symfony

  Todos los detalles incluidos en este capítulo no son específicos de Symfony sino de Propel. Propel
  es la capa de abstracción de objetos/relacional preferida por Symfony, pero se puede utilizar cualq-
  uier otra. No obstante, Symfony se integra mucho mejor con Propel por las siguientes razones:

  Todas las clases del modelo de objetos de datos y las clases Criteria se cargan de forma au-
  tomática. La primera vez que se utilizan, Symfony incluye los archivos adecuados y no es necesario
  preocuparse por añadir las instrucciones que incluyen esos archivos. En Symfony no es necesario
  arrancar o inicializar Propel. Cuando un objeto utiliza Propel, la librería se inicia automáticamente.
  Algunos de los helpers de Symfony utilizan objetos Propel como parámetros para realizar tareas
  complejas, como la paginación y el filtrado. Los objetos Propel permiten crear prototipos rápidamen-
  te y generar de forma automática la parte de gestión de la aplicación (el Capítulo 14 incluye más
  detalles). El esquema es mucho más fácil de escribir mediante el archivo schema.yml.

  Y, como Propel es independiente de la base de datos utilizada, también lo es Symfony.


8.8. No crees el modelo dos veces


www.librosweb.es                                                                                   175
Symfony, la guía definitiva                                                  Capítulo 8. El modelo


La desventaja de utilizar un ORM es que se debe definir la estructura de datos 2 veces:
una para la base de datos y otra para el modelo de objetos. Por suerte, Symfony dispone
de utilidades de línea de comandos para generar uno en función del otro, de modo que se
evita duplicar el trabajo.

8.8.1. Construir la estructura SQL de la base de datos en función de un
esquema existente
Si se crea la aplicación escribiendo el archivo schema.yml, Symfony puede generar las
instrucciones SQL que crean las tablas directamente a partir del modelo de datos en
YAML. Para generarlas, se ejecuta el siguiente comando desde el directorio raíz del
proyecto:
   > symfony propel-build-sql

El anterior comando crea un archivo lib.model.schema.sql en el directorio miproyecto/
data/sql/. El código SQL generado se optimiza para el sistema gestor de bases de datos
definido en el parámetro phptype del archivo propel.ini.

Se puede utilizar directamente el archivo schema.sql para construir la base de datos. Por
ejemplo, en MySQL se puede ejecutar lo siguiente:
   > mysqladmin -u root -p create blog
   > mysql -u root -p blog < data/sql/lib.model.schema.sql

El código SQL generado también es útil para reconstruir la base de datos en otro entorno
o para cambiar de sistema gestor de bases de datos. Si el archivo propel.ini define las
opciones de conexión correctas con la base de datos, el comando symfony propel-
insert-sql se encarga de crear automáticamente las tablas.

  SUGERENCIA
  La línea de comandos también incluye una tarea para volcar los contenidos de un archivo de texto
  a la base de datos. El Capítulo 16 incluye más información sobre la tarea propel-load-data y so-
  bre los archivos en formato YAML llamados “fixtures”.


8.8.2. Construir un modelo de datos en formato YAML a partir de una
base de datos existente
Symfony puede utilizar la capa de acceso a bases de datos proporcionada por Creole para
generar un archivo schema.yml a partir de una base de datos existente, gracias a la in-
trospección (que es la capacidad de las bases de datos para determinar la estructura de
las tablas que la forman). Se trata de una opción muy útil cuando se hace ingeniería in-
versa o si se prefiere trabajar primero en la base de datos antes de trabajar con el mode-
lo de objetos.

Para construir el modelo, el archivo propel.ini del proyecto debe apuntar a la base de
datos correcta y debe tener todas las opciones de conexión. Después, se ejecuta el co-
mando propel-build-schema:
   > symfony propel-build-schema


www.librosweb.es                                                                             176
Symfony, la guía definitiva                                                    Capítulo 8. El modelo


Se genera un nuevo archivo schema.yml a partir de la estructura de la base de datos y se
almacena en el directorio config/. Ahora se puede construir el modelo a partir de este
esquema.

El comando para generar el esquema es bastante potente y es capaz de añadir diversa
información relativa a la base de datos en el esquema. Como el formato YAML no soporta
este tipo de información sobre la base de datos, se debe generar un esquema en formato
XML para poder incluirla. Para ello, solo es necesario añadir el argumento xml a la tarea
build-schema:
   > symfony propel-build-schema xml

En vez de generar un archivo schema.yml, se crea un archivo schema.xml que es total-
mente compatible con Propel y que contiene toda la información adicional. No obstante,
los esquemas XML generados suelen ser bastante profusos y difíciles de leer.

  La configuración de propel.ini

  Los comandos propel-build-sql y propel-build-schema no emplean las opciones de conexión
  definidas en el archivo databases.yml. En su lugar, estos comandos utilizan las opciones de cone-
  xión de otro archivo llamado propel.ini que se encuentra en el directorio config/ del proyecto:
      propel.database.createUrl = mysql://login:passwd@localhost
      propel.database.url       = mysql://login:passwd@localhost/blog

  Este archivo también contiene otras opciones que se utilizan para configurar el generador de Propel
  de forma que las clases del modelo generadas sean compatibles con Symfony. La mayoría de opc-
  iones son de uso interno y por tanto no interesan al usuario, salvo algunas de ellas:
      // Base classes are autoloaded in symfony
      // Set this to true to use include_once statements instead
      // (Small negative impact on performance)
      propel.builder.addIncludes = false

      // Generated classes are not commented by default
      // Set this to true to add comments to Base classes
      // (Small negative impact on performance)
      propel.builder.addComments = false

      // Behaviors are not handled by default
      // Set this to true to be able to handle them
      propel.builder.AddBehaviors = false

  Después de modificar las opciones del archivo propel.ini, se debe reconstruir el modelo para que
  los cambios surjan efecto.


8.9. Resumen
Symfony utiliza Propel como ORM y Creole como la capa de abstracción de bases de da-
tos. De esta forma, en primer lugar se debe describir el esquema relacional de la base de
datos en formato YAML antes de generar las clases del modelo de objetos. Después, du-
rante la ejecución de la aplicación, se utilizan los métodos de las clases objeto y clases
peer para acceder a la información de un registro o conjunto de registros. Se puede re-
definir y ampliar el modelo fácilmente añadiendo métodos a las clases personalizadas.

www.librosweb.es                                                                                177
Symfony, la guía definitiva                                         Capítulo 8. El modelo


Las opciones de conexión se definen en el archivo databases.yml, que puede definir más
de una conexión. La línea de comandos contiene tareas especiales que evitan tener que
definir la estructura de la base de datos más de una vez.

La capa del modelo es la más compleja del framework Symfony. Una de las razones de
esta complejidad es que la manipulación de datos es una tarea bastante intrincada. Las
consideraciones de seguridad relacionadas con el modelo son cruciales para un sitio web
y no deberían ignorarse. Otra de las razones es que Symfony se ajusta mejor a las apli-
caciones medianas y grandes en un entorno empresarial. En ese tipo de aplicaciones, las
tareas automáticas proporcionadas por el modelo de Symfony suponen un gran ahorro de
tiempo, por lo que merece la pena el tiempo dedicado a aprender su funcionamiento
interno.

Así que no dudes en dedicar algo de tiempo a probar los objetos y métodos del modelo
para entenderlos completamente. La recompensa será la gran solidez y escalabilidad de
las aplicaciones desarrolladas.




www.librosweb.es                                                                    178
Symfony, la guía definitiva                      Capítulo 9. Enlaces y sistema de enrutamiento




Capítulo 9. Enlaces y sistema de enrutamiento
Los enlaces y las URL requieren de un tratamiento especial en cualquier framework para
aplicaciones web. El motivo es que la definición de un único punto de entrada a la aplica-
ción (mediante el controlador frontal) y el uso de helpers en las plantillas, permiten sepa-
rar completamente el funcionamiento y el aspecto de las URL. Este mecanismo se conoce
como “enrutamiento” (del inglés “routing”). El enrutamiento no es solo una utilidad curio-
sa, sino que es una herramienta muy útil para hacer las aplicaciones web más fáciles de
usar y más seguras. En este capítulo se detalla la forma de manejar las URL en las apli-
caciones de Symfony:

      ▪ Qué es y como funciona el sistema de enrutamiento

      ▪ Cómo utilizar helpers de enlaces en las plantillas para enlazar URL salientes

      ▪ Cómo configurar las reglas de enrutamiento para modificar el aspecto de las URL

Además, se incluyen una serie de trucos para mejorar el rendimiento del sistema de en-
rutamiento y para añadirle algunos toques finales.


9.1. ¿Qué es el enrutamiento?
El enrutamiento es un mecanismo que reescribe las URL para simplificar su aspecto. An-
tes de poder comprender su importancia, es necesario dedicar unos minutos al estudio
de las URL de las aplicaciones

9.1.1. URL como instrucciones de servidor
Cuando el usuario realiza una acción, las URL se encargan de enviar la información desde
el navegador hasta el servidor. Las URL tradicionales incluyen la ruta hasta el script del
servidor y algunos parámetros necesarios para completar la petición, como se muestra
en el siguiente ejemplo:
   http://www.ejemplo.com/web/controlador/articulo.php?id=123456&codigo_formato=6532

La URL anterior incluye información sobre la arquitectura de la aplicación y sobre su base
de datos. Normalmente, los programadores evitan mostrar la estructura interna de la
aplicación en la interfaz (las páginas por ejemplo se titulan “Perfil personal” y no
“QZ7.65”). Desvelar detalles internos de la aplicación en la URL no solo contradice esta
norma, sino que tiene otras desventajas:

      ▪ Los datos técnicos que se muestran en las URL son una fuente potencial de agu-
        jeros de seguridad. En el ejemplo anterior, ¿qué sucede si un usuario malicioso
        modifica el valor del parámetro id? ¿Supone este caso que la aplicación ofrece
        una interfaz directa a la base de datos? ¿Qué sucedería si otro usuario probara
        otros nombres de script, como por ejemplo admin.php? En resumen, las URL di-
        rectas permiten jugar de forma directa y sencilla con una aplicación y es casi im-
        posible manejar su seguridad.

      ▪ Las URL complejas son muy difíciles de leer y hoy en día las URL no solo apare-
        cen en la barra de direcciones. También suelen aparecer cuando un usuario pasa

www.librosweb.es                                                                         179
Symfony, la guía definitiva                      Capítulo 9. Enlaces y sistema de enrutamiento


        el ratón por encima de un enlace y también en los resultados de búsqueda.
        Cuando los usuarios buscan información, es más útil proporcionarles URL senci-
        llas y fáciles de entender y no URL complejas como las que se muestran en la fi-
        gura 9.1




  Figura 9.1. Las URL aparecen en muchos lugares, como por ejemplo los resultados de
                                      búsqueda


      ▪ Si se modifica una URL (porque cambia el nombre del script o el de alguno de sus
        parámetros), se deben modificar todos los enlaces a esa URL. De esta forma, las
        modificaciones en la estructura del controlador son muy pesadas y costosas, lo
        que contradice la filosofía del desarrollo ágil de aplicaciones.

La situación podría ser incluso mucho peor si Symfony no utilizara un controlador frontal;
es decir, si la aplicación contiene varios scripts accesibles desde el exterior, como por
ejemplo:
   http://www.ejemplo.com/web/galeria/album.php?nombre=mis%20vacaciones
   http://www.ejemplo.com/web/weblog/publico/post/listado.php
   http://www.ejemplo.com/web/general/contenido/pagina.php?nombre=sobre%20nosotros

En este caso, los programadores deben hacer coincidir la estructura de las URL y la es-
tructura del sistema de archivos, por lo que su mantenimiento se convierte en una pesa-
dilla cuando cualquiera de las dos estructuras se modifica.

9.1.2. URL como parte de la interfaz
Una de las ideas del sistema de enrutamiento es utilizar las URL como parte de la inter-
faz. Las aplicaciones trasladan información al usuario mediante el formateo de las URL y
el usuario puede utilizar las URL para acceder a los recursos de la aplicación.

Lo anterior es posible en las aplicaciones Symfony porque la URL que se muestra al usua-
rio no tiene que guardar obligatoriamente relación con la instrucción del servidor necesa-
ria para completar la petición. En su lugar, la URL está relacionada con el recurso solicita-
do, y su aspecto puede configurarse libremente. En Symfony es posible por ejemplo utili-
zar la siguiente URL y obtener los mismos resultados que la primera URL mostrada en es-
te capítulo:
   http://www.ejemplo.com/articulos/economia/2006/sectores-actividad.html

Este tipo de URL tiene muchas ventajas:

      ▪ Las URL tienen significado y ayudan a los usuarios a decidir si la página que se
        cargará al pulsar sobre un enlace contiene lo que esperan. Un enlace puede con-
        tener detalles adicionales sobre el recurso que enlaza. Esto último es especial-
        mente útil para los resultados de los buscadores. Además, muchas veces las URL
        aparecen sin que se mencione el título de su página (por ejemplo cuando se

www.librosweb.es                                                                         180
Symfony, la guía definitiva                     Capítulo 9. Enlaces y sistema de enrutamiento


        copian las URL en un mensaje de email) por lo que en ese caso deberían conte-
        ner su propio significado. La figura 9-2 muestra una URL sencilla y fácil de
        entender.




  Figura 9.2. Las URL pueden incluir información adicional sobre una página, como por
                           ejemplo su fecha de publicación


      ▪ Las URL que aparecen en los documentos impresos son más fáciles de escribir y
        de recordar. Si la dirección del sitio web de una empresa se muestra en una tar-
        jeta de visita con un aspecto similar a http://www.ejemplo.com/controlador/web/in-
        dex.jsp?id=ERD4, probablemente no reciba muchas visitas.

      ▪ La URL se puede convertir en una especie de línea de comandos, que permita re-
        alizar acciones u obtener información de forma intuitiva. Este tipo de aplicaciones
        son las que más rápidamente utilizan los usuarios más avanzados.
   // Listado de resultados: se puede añadir una nueva etiqueta para restringir los
   resultados
   http://del.icio.us/tag/symfony+ajax
   // Página de perfil de usuario: se puede modificar el nombre para obtener otro perfil
   http://www.askeet.com/user/francois

      ▪ Se puede modificar el aspecto de la URL y el del nombre de la acción o de los
        parámetros de forma independiente y con una sola modificación. En otras pala-
        bras, es posible empezar a programar la aplicación y después modificar el aspec-
        to de las URL sin estropear completamente la aplicación.

      ▪ Aunque se modifique la estructura interna de la aplicación, las URL pueden man-
        tener su mismo aspecto hacia el exterior. De esta forma, las URL se convierten
        en persistentes y pueden ser añadidas a los marcadores o favoritos.

      ▪ Cuando los motores de búsqueda indexan un sitio web, suelen tratar de forma di-
        ferente (incluso saltándoselas) a las páginas dinámicas (las que acaban en .php,
        .asp, etc.) Así que si se formatean las URL de esta forma, los buscadores creen
        que están indexando contenidos estáticos, por lo que generalmente se obtiene
        una mejor indexación de las páginas de la aplicación.

      ▪ Son más seguras. Cualquier URL no reconocida se redirige a una página especifi-
        cada por el programador y los usuarios no pueden navegar por el directorio raíz
        del servidor mediante la prueba de diferentes URL. La razón es que no se visuali-
        za el nombre del script utilizado o el de sus parámetros.

La relación entre las URL mostradas al usuario y el nombre del script que se ejecuta y de
sus parámetros está gestionada por el sistema de enrutamiento, que utiliza patrones que
se pueden modificar mediante la configuración de la aplicación.



www.librosweb.es                                                                        181
Symfony, la guía definitiva                          Capítulo 9. Enlaces y sistema de enrutamiento


  NOTA
  ¿Qué sucede con los contenidos estáticos? Afortunadamente, las URL de los contenidos estáticos
  (imágenes, hojas de estilos y archivos de JavaScript) no suelen mostrarse durante la navegación,
  por lo que no es necesario utilizar el sistema de enrutamiento para este tipo de contenidos. Sym-
  fony almacena todos los contenidos estáticos en el directorio web/ y sus URL se corresponden con
  su localización en el sistema de archivos. No obstante, es posible gestionar dinámicamente los con-
  tenidos estáticos mediante URL generadas con un helper para contenidos estáticos. Por ejemplo,
  para mostrar una imagen generada dinámicamnete, se puede utilizar el helper image_tag(url_-
  for(’captcha/image?key=’.$key)).


9.1.3. Cómo funciona
Symfony desasocia las URL externas y las URI utilizadas internamente. La corresponden-
cia entre las dos es responsabilidad del sistema de enrutamiento. Symfony simplifica este
mecanismo utilizando una sintaxis para las URI internas muy similar a la de las URL habi-
tuales. El listado 9-1 muestra un ejemplo.

Listado 9-1 - URL externas y URI internas
   // Sintaxis de las URI internas
   <modulo>/<accion>[?parametro1=valor1][&parametro2=valor2][&parametro3=valor3]...

   // Ejemplo de URI interna que nunca se muestra al usuario
   articulo/permalink?ano=2006&tema=economia&titulo=sectores-actividad

   // Ejemplo de URL externa que se muestra al usuario
   http://www.ejemplo.com/articulos/economia/2006/sectores-actividad.html

El sistema de enrutamiento utiliza un archivo de configuración especial, llamado rou-
ting.yml, en el que se pueden definir las reglas de enrutamiento. Si se considera la regla
mostrada en el listado 9-2, se define un patrón cuyo aspecto es articulos/*/*/* y que
también define el nombre de cada pieza que forma parte de la URL.

Listado 9-2 - Ejemplo de regla de enrutamiento
   articulo_segun_titulo:
     url:    articulos/:tema/:ano/:titulo.html
     param: { module: articulo, action: permalink }

Todas las peticiones realizadas a una aplicación Symfony son analizadas en primer lugar
por el sistema de enrutamiento (que es muy sencillo porque todas las peticiones se gest-
ionan mediante un único controlador frontal). El sistema de enrutamiento busca coinci-
dencias entre la URL de la petición y los patrones definidos en las reglas de enrutamien-
to. Si se produce una coincidencia, las partes del patrón que tienen nombre se transfor-
man en parámetros de la petición y se juntan a los parámetros definidos en la clave pa-
ram:. El listado 9-3 muestra su funcionamiento.

Listado 9-3 - El sistema de enrutamiento interpreta las URL de las peticiones
entrantes
   // El usuario escribe (o pulsa) sobre esta URL externa
   http://www.ejemplo.com/articulos/economia/2006/sectores-actividad.html



www.librosweb.es                                                                                182
Symfony, la guía definitiva                           Capítulo 9. Enlaces y sistema de enrutamiento

   // El controlador frontal comprueba que coincide con la regla articulo_segun_titulo
   // El sistema de enrutamiento crea los siguientes parámetros de la petición
     'module' => 'articulo'
     'action' => 'permalink'
     'tema'    => 'economia'
     'ano'     => '2006'
     'titulo' => 'sectores-actividad'


  SUGERENCIA
  La extensión .html de las URL externas es solo un adorno y por ese motivo el sistema de enrutam-
  iento la ignora. Su única función es la de hacer que las páginas dinámicas parezcan páginas estáti-
  cas. La sección “Configuración del enrutamiento” al final de este capítulo explica cómo activar esta
  extensión.

Después, la petición se pasa a la acción permalink del módulo articulo, que dispone de
toda la información necesaria en los parámetros de la petición para obtener el artículo
solicitado.

El mecanismo de enrutamiento también funciona en la otra dirección. Para mostrar las
URL en los enlaces de una aplicación, se debe proporcionar al sistema de enrutamiento la
información necesaria para determinar la regla que se debe aplicar a cada enlace.
Además, no se deben escribir los enlaces directamente con etiquetas <a> (ya que de esta
forma no se estaría utilizando el sistema de enrutamiento) sino con un helper especial,
tal y como se muestra en el listado 9-4.

Listado 9-4 - El sistema de enrutamiento formatea las URL externas mostradas
en las plantillas
   // El helper url_for() transforma una URI interna en una URL externa
   <a href="<?php echo url_for('articulo/
   permalink?tema=economia&ano=2006&titulo=sectores-actividad') ?>">pincha aquí</a>

   // El helper reconoce que la URI cumple con la regla articulo_segun_titulo
   // El sistema de enrutamiento crea una URL externa a partir de el
    => <a href="http://www.ejemplo.com/articulos/economia/2006/
   sectores-actividad.html">pincha aquí</a>

   // El helper link_to() muestra directamente un enlace
   // y evita tener que mezclar PHP y HTML
   <?php echo link_to(
     'pincha aqui',
     'articulo/permalink?tema=economia&ano=2006&titulo=sectores-actividad'
   ) ?>

   // Internamente link_to() llama a url_for(), por lo que el resultado es el mismo
    => <a href="http://www.ejemplo.com/articulos/economia/2006/
   sectores-actividad.html">pincha aquí</a>

De forma que el enrutamiento es un mecanismo bidireccional y solo funciona cuando se
utiliza el helper link_to() para mostrar todos los enlaces.


9.2. Reescritura de URL


www.librosweb.es                                                                                 183
Symfony, la guía definitiva                      Capítulo 9. Enlaces y sistema de enrutamiento


Antes de adentrarse en el funcionamiento interno del sistema de enrutamiento, se debe
aclarar una cuestión importante. En los ejemplos mostrados en las secciones anteriores,
las URI internas no incluyen el controlador frontal (index.php o miapp_dev.php). Como se
sabe, es el controlador frontal y no otros elementos de la aplicación, el que decide el en-
torno de ejecución. Por este motivo, todos los enlaces deben ser independientes del en-
torno de ejecución y el nombre del controlador frontal nunca aparece en las URI internas.

Además, tampoco se muestra el nombre del script PHP en las URL generadas en los
ejemplos anteriores. La razón es que, por defecto, las URL no contienen el nombre de
ningún script de PHP en el entorno de producción. El parámetro no_script_name del archi-
vo settings.yml controla la aparición del nombre del controlador frontal en las URL gene-
radas. Si se establece su valor a off, como se muestra en el listado 9-5, las URL genera-
das por los helpers incluirán el nombre del script del controlador frontal en cada enlace.

Listado 9-5 - Mostrando el nombre del controlador frontal en las URL, en apps/
miapp/settings.yml
   prod:
     .settings
       no_script_name:        off

Ahora, las URL generadas tienen este aspecto:
   http://www.ejemplo.com/index.php/articulos/economia/2006/sectores-actividad.html

En todos los entornos salvo en el de producción, el parámetro no_script_name tiene un
valor igual a off por defecto. Si se prueba la aplicación en el entorno de desarrollo, el
nombre del controlador frontal siempre aparece en las URL.
   http://www.ejemplo.com/miapp_dev.php/articulos/economia/2006/sectores-actividad.html

En el entorno de producción, la opción no_script_name tiene el valor de on, por lo que las
URL solo muestran la información necesaria para el enrutamiento y son más sencillas pa-
ra los usuarios. No se muestra ningún tipo de información técnica.
   http://www.ejemplo.com/articulos/economia/2006/sectores-actividad.html

¿Cómo sabe la aplicación el nombre del script del controlador frontal que tiene que ejecu-
tar? En este punto es donde comienza la reescritura de URL. El servidor web se puede
configurar para que se llame siempre a un mismo script cuando la URL no indica el nom-
bre de ningún script.

En el servidor web Apache se debe tener activado previamente el módulo mode_rewrite.
Todos los proyectos de Symfony incluyen un archivo llamado .htaccess que añade las
opciones necesarias para el mod_rewrite de Apache en el directorio web/. El contenido por
defecto de este archivo se muestra en el listado 9-6.

Listado 9-6 - Reglas de reescritura de URL por defecto para Apache, en miproyec-
to/web/.htaccess
   <IfModule mod_rewrite.c>
     RewriteEngine On

      # we skip all files with .something


www.librosweb.es                                                                          184
Symfony, la guía definitiva                          Capítulo 9. Enlaces y sistema de enrutamiento

      RewriteCond %{REQUEST_URI} ..+$
      RewriteCond %{REQUEST_URI} !.html$
      RewriteRule .* - [L]

      # we check if the .html version is here (caching)
      RewriteRule ^$ index.html [QSA]
      RewriteRule ^([^.]+)$ $1.html [QSA]
      RewriteCond %{REQUEST_FILENAME} !-f

     # no, so we redirect to our front web controller
     RewriteRule ^(.*)$ index.php [QSA,L]
   </IfModule>

El servidor web analiza la estructura de las URL entrantes. Si la URL no contiene ningún
sufijo y no existe ninguna versión cacheada de la página disponible (el Capítulo 12 deta-
lla el sistema de cache), la petición se redirige al script index.php.

No obstante, el directorio web/ de un proyecto Symfony lo comparten todas las aplicacio-
nes y todos los entornos de ejecución del proyecto. Por este motivo, es habitual que exis-
ta más de un controlador frontal en el directorio web. Por ejemplo, si un proyecto tiene
dos aplicaciones llamadas frontend y backend y dos entornos de ejecución llamados dev y
prod, el directorio web/ contiene 4 controladores frontales:
   index.php           //     frontend en prod
   frontend_dev.php    //     frontend en dev
   backend.php         //     backend en prod
   backend_dev.php     //     backend en dev

Las opciones de mod_rewrite solo permiten especificar un script por defecto. Si se esta-
blece el valor on a la opción no_script_name de todas las aplicaciones y todos los entor-
nos, todas las URL se interpretan como si fueran peticiones al controlador frontal de la
aplicación frontend en el entorno de producción (prod). Esta es la razón por la que en un
mismo proyecto, solo se pueden aprovechar del sistema de enrutamiento una aplicación
y un entorno de ejecución concretos.

  SUGERENCIA
  Existe una forma de acceder a más de una aplicación sin indicar el nombre del script. Para ello, se
  crean subdirectorios en el directorio web/ y se mueven los controladores frontales a cada subdirec-
  torio. Después, se modifica el valor de las constantes SF_ROOT_DIR para cada uno de ellos y se
  crea el archivo .htaccess de configuración para cada aplicación.


9.3. Helpers de enlaces
Debido al sistema de enrutamiento, es conveniente utilizar los helpers de enlaces en las
plantillas en vez de etiquetas <a> normales y corrientes. Más que una molestia, el uso de
estos helpers debe verse como un método sencillo de mantener la aplicación limpia y
muy fácil de mantener. Además, los helpers de enlaces incluyen una serie de utilidades y
atajos que no es recomendable desaprovechar.




www.librosweb.es                                                                                185
Symfony, la guía definitiva                      Capítulo 9. Enlaces y sistema de enrutamiento


9.3.1. Hiperenlaces, botones y formularios
En secciones anteriores ya se ha mostrado el helper link_to(). Se utiliza para mostrar
enlaces válidos según XHTML y requiere de 2 parámetros: el elemento que va a mostrar
el enlace y la URI interna del recurso al que apunta el enlace. Si en vez de un enlace se
necesita un botón, simplemente se utiliza el helper button_to(). Los formularios también
disponen de un helper para controlar el valor del atributo action. El siguiente capítulo ex-
plica los formularios en detalle. El listado 9-7 muestra algunos ejemplos de helpers de
enlaces.

Listado 9-7 - Helpers de enlaces para las etiquetas <a>, <input> y <form>
   // Enlace simple de texto
   <?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia') ?>
    => <a href="/url/con/enrutamiento/a/Economia_en_Francia">Mi artículo</a>

   // Enlace en una imagen
   <?php echo link_to(image_tag('ver.gif'), 'articulo/ver?titulo=Economia_en_Francia') ?>
    => <a href="/url/con/enrutamiento/a/Economia_en_Francia"><img src="/images/ver.gif"
   /></a>

   // Boton
   <?php echo button_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia') ?>
    => <input value="Mi artículo" type="button" onclick="document.location.href='/url/con/
   enrutamiento/a/Economia_en_Francia';" />

   // Formulario
   <?php echo form_tag('articulo/ver?titulo=Economia_en_Francia') ?>
    => <form method="post" action="/url/con/enrutamiento/a/Economia_en_Francia" />

Los helpers de enlaces aceptan URI internas y también URL absolutas (las que empiezan
por http:// y para las que no se aplica el sistema de enrutamiento) y URL internas a una
página (también llamadas anclas). Las aplicaciones reales suelen construir sus URI inter-
nas en base a una serie de parámetros dinámicos. El listado 9-8 muestra ejemplos de to-
dos estos casos.

Listado 9-8 - URL que admiten los helpers de enlaces
   // URI interna
   <?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia') ?>
    => <a href="/url/con/enrutamiento/a/Economia_en_Francia">Mi artículo</a>

   // URI interna con parámetros dinámicos
   <?php echo link_to('Mi artículo', 'articulo/ver?titulo='.$articulo->getTitulo()) ?>

   // URI interna con anclas (enlaces a secciones internas de la página)
   <?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia#seccion1') ?>
    => <a href="/url/con/enrutamiento/a/Economia_en_Francia#seccion1">Mi artículo</a>

   // URL absolutas
   <?php echo link_to('Mi artículo', 'http://www.ejemplo.com/cualquierpagina.html') ?>
    => <a href="http://www.ejemplo.com/cualquierpagina.html">Mi artículo</a>




www.librosweb.es                                                                         186
Symfony, la guía definitiva                     Capítulo 9. Enlaces y sistema de enrutamiento


9.3.2. Opciones de los helpers de enlaces
Como se explicó en el Capítulo 7, los helpers aceptan como argumento opciones adicio-
nales, que se pueden indicar en forma de array asociativo o en forma de cadena de tex-
to. Los helpers de enlaces también aceptan este tipo de opciones, como muestra el lista-
do 9-9.

Listado 9-9 - Los helpers de enlaces aceptan opciones adicionales
   // Opciones adicionales como array asociativo
   <?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia', array(
     'class' => 'miclasecss',
     'target' => '_blank'
   )) ?>

   // Opciones adicionales como cadena de texto (producen el mismo resultado)
   <?php echo link_to('Mi artículo', 'articulo/
   ver?titulo=Economia_en_Francia','class=miclasecss target=_blank') ?>
    => <a href="/url/con/enrutamiento/a/Economia_en_Francia" class="miclasecss"
   target="_blank">Mi artículo</a>

También se pueden utilizar otras opciones específicas de Symfony llamadas confirm y po-
pup. La primera muestra una ventana JavaScript de confirmación al pinchar en el enlace
y la segunda opción abre el destino del enlace en una nueva ventana, como se muestra
en el listado 9-10.

Listado 9-10 - Opciones confirm y popup en los helpers de enlaces
   <?php echo link_to('Borrar elemento', 'item/borrar?id=123', 'confirm=¿Estás seguro?') ?>
    => <a onclick="return confirm('¿Estás seguro?');"
          href="/url/con/enrutamiento/a/borrar/123.html">Borrar elemento</a>

   <?php echo link_to('Añadir al carrito', 'carritoCompra/anadir?id=100', 'popup=true') ?>
    => <a onclick="window.open(this.href);return false;"
          href="/url/con/enrutamiento/a/carritoCompra/anadir/id/100.html">Añadir al
   carrito</a>

   <?php echo link_to('Añadir al carrito', 'carritoCompra/anadir?id=100', array(
     'popup' => array('Título de la ventana', 'width=310,height=400,left=320,top=0')
   )) ?>
    => <a onclick="window.open(this.href,'Título de la
   ventana','width=310,height=400,left=320,top=0');return false;"
          href="/url/con/enrutamiento/a/carritoCompra/anadir/id/100.html">Añadir al
   carrito</a>

Estas opciones también se pueden combinar entre si.

9.3.3. Opciones GET y POST falsas
En ocasiones, los programadores web utilizan peticiones GET para realizar acciones más
propias de una petición POST. Si se considera por ejemplo la siguiente URL:
   http://www.ejemplo.com/index.php/carritoCompra/anadir/id/100

Este tipo de petición modifica los datos de la aplicación, ya que añade un elemento al ob-
jeto que representa el carrito de la compra y que se almacena en la sesión del servidor o

www.librosweb.es                                                                         187
Symfony, la guía definitiva                      Capítulo 9. Enlaces y sistema de enrutamiento


en una base de datos. Si los usuarios añaden esta URL a los favoritos de sus navegado-
res o si la URL se cachea o es indexada por un buscador, se pueden producir problemas
en la base de datos y en las métricas del sitio web. En realidad, esta petición debería tra-
tarse como una petición de tipo POST, ya que los robots que utilizan los buscadores no
hacen peticiones POST para indexar las páginas.

Symfony permite transformar una llamada a los helpers link_to() o button_to() en una
petición POST. Solamente es necesario añadir la opción post=true, tal y como se muestra
en el listado 9-11.

Listado 9-11 - Convirtiendo un enlace en una petición POST
   <?php echo link_to('Ver carrito de la compra', 'carritoCompra/anadir?id=100',
   'post=true') ?>
    => <a onclick="f = document.createElement('form'); document.body.appendChild(f);
                   f.method = 'POST'; f.action = this.href; f.submit();return false;"
          href="/carritoCompra/anadir/id/100.html">Ver carrito de la compra</a>

La etiqueta <a> resultante conserva el atributo href, por lo que los navegadores sin so-
porte de JavaScript, como por ejemplo los robots que utilizan los buscadores, utilizan el
enlace normal con la petición GET. Asi que es posible que se deba restringir la acción pa-
ra que solamente responda a las peticiones de tipo POST, que se puede realizar añadien-
do por ejemplo la siguiente instrucción al principio de la acción:
   $this->forward404If($request->getMethod() != sfRequest::POST);

Esta opción no se debe utilizar en los enlaces que se encuentran dentro de los formular-
ios, ya que genera su propia etiqueta <form>.

Se trata de una buena práctica definir como peticiones POST los enlaces que realizan ac-
ciones que modifican los datos.

9.3.4. Forzando los parámetros de la petición como variables de tipo GET
Las variables que se pasan como parámetro a link_to() se transforman en patrones
según las reglas del sistema de enrutamiento. Si no existe en el archivo routing.yml nin-
guna regla que coincida con la URI interna, se aplica la regla por defecto que transforma
modulo/accion?clave=valor en /modulo/accion/clave/valor, como se muestra en el lista-
do 9-12.

Listado 9-12 - Regla de enrutamiento por defecto
   <?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia') ?>
   => <a href="/articulo/ver/titulo/Economia_en_Francia">Mi artículo</a>

Si es necesario utilizar la sintaxis de las peticiones GET (para pasar los parámetros de la
petición en la forma ?clave=valor) se deben indicar los parámetros en la opción query_s-
tring. Todos los helpers de enlaces admiten esta opción, como se muestra en el listado
9-13.

Listado 9-13 - Forzando el uso de variables tipo GET con la opción query_string
   <?php echo link_to('Mi artículo', 'articulo/ver', array(
     'query_string' => 'titulo=Economia_en_Francia'



www.librosweb.es                                                                         188
Symfony, la guía definitiva                            Capítulo 9. Enlaces y sistema de enrutamiento

   )) ?>
   => <a href="/articulo/ver?titulo=Economia_en_Francia">Mi artículo</a>

Las URL con los parámetros en forma de variables GET se pueden interpretar por los
scripts en el lado del cliente y por las variables $_GET y $_REQUEST en el lado del servidor.

  Helpers de contenidos estáticos

  El Capítulo 7 introdujo los helpers para contenidos estáticos image_tag(), stylesheet_tag() y
  javascript_include_ tag(), que permiten incluir imágenes, hojas de estilos y archivos JavaS-
  cript en la respuesta del servidor. Las rutas a los contenidos estáticos no se procesan en el sistema
  de enrutamiento, ya que se trata de enlaces a recursos que se guardan en el directorio web público.

  Además, no es necesario indicar la extensión para los contenidos estáticos. Symfony añade de for-
  ma automática las extensiones .png, .js o .css cuando se llama al helper de una imagen, un ar-
  chivo JavaScript o una hoja de estilos. Symfony también busca de forma automática estos conteni-
  dos estáticos en los directorios web/images/, web/js/ y web/css/. Evidentemente, es posible incl-
  uir otros tipos de archivos y archivos que se encuentren en otros directorios. Para ello, solo es ne-
  cesario indicar como argumento el nombre completo del archivo o la ruta completa al archivo. Tam-
  poco es necesario definir un valor para el atributo alt si el nombre del archivo enlazado es suficien-
  temente significativo, ya que Symfony utiliza por defecto el nombre como atributo alt.
      <?php echo image_tag('test') ?>
      <?php echo image_tag('test.gif') ?>
      <?php echo image_tag('/mis_imagenes/test.gif') ?>
       => <img href="/images/test.png" alt="Test" />
          <img href="/images/test.gif" alt="Test" />
          <img href="/mis_imagenes/test.gif" alt="Test" />

  Para indicar un tamaño personalizado a una imagen, se utiliza la opción size. Esta opción requiere
  una anchura y una altura en píxel separadas por un x.
      <?php echo image_tag('test', 'size=100x20')) ?>
       => <img href="/images/test.png" alt="Test" width="100" height="20"/>

  Si los contenidos estáticos se tienen que añadir en la sección <head> de la página (por ejemplo pa-
  ra los archivos JavaScript y las hojas de estilos), se deben utilizar los helpers use_stylesheet() y
  use_javascript() en las plantillas, en vez de las funciones acabadas en _tag() utilizadas en el
  layout. Estos helpers añaden los contenidos estáticos a la respuesta y los añaden antes de que se
  envíe la etiqueta </head> al navegador.


9.3.5. Utilizando rutas absolutas
Los helpers de enlaces y de contenidos estáticos generan rutas relativas por defecto. Pa-
ra forzar el uso de rutas absolutas, se debe asignar el valor true a la opción absolute,
como muestra el listado 9-14. Esta técnica es muy útil cuando se deben incluir enlaces
en mensajes de email, canales RSS o respuestas de una API.

Listado 9-14 - Utilizando URL absolutas en vez de relativas
   <?php echo url_for('articulo/ver?titulo=Economia_en_Francia') ?>
    => '/url/con/enrutamiento/a/Economia_en_Francia'
   <?php echo url_for('articulo/ver?titulo=Economia_en_Francia', true) ?>
    => 'http://www.ejemplo.com/url/con/enrutamiento/a/Economia_en_Francia'


www.librosweb.es                                                                                   189
Symfony, la guía definitiva                          Capítulo 9. Enlaces y sistema de enrutamiento



   <?php echo link_to('economía', 'articulo/ver?titulo=Economia_en_Francia') ?>
    => <a href="/url/con/enrutamiento/a/Economia_en_Francia">economía</a>
   <?php echo link_to('economía', 'articulo/
   ver?titulo=Economia_en_Francia','absolute=true') ?>
    => <a href=" http://www.ejemplo.com/url/con/enrutamiento/a/
   Economia_en_Francia">economía</a>

   // Lo mismo sucede con los helpers de contenidos estáticos
   <?php echo image_tag('prueba', 'absolute=true') ?>
   <?php echo javascript_include_tag('miscript', 'absolute=true') ?>

  El helper de correo electrónico

  Hoy en día, existen robots que rastrean todas las páginas web en busca de direcciones de correo
  electrónico que puedan ser utilizadas en los envíos masivos de spam. Por este motivo, no se pue-
  den incluir directamente las direcciones de correo electrónico en las páginas web sin acabar siendo
  una víctima del spam en poco tiempo. Afortunadamente, Symfony proporciona un helper llamado
  mail_to().

  El helper mail_to() requiere 2 parámetros: la dirección de correo electrónico real y la cadena de
  texto que se muestra al usuario. Como opción adicional se puede utilizar el parámetro encode, que
  produce un código HTML bastante difícil de leer, que los navegadores muestran correctamente, pe-
  ro que los robots de spam no son capaces de entender.
      <?php echo mail_to('midireccion@midominio.com', 'contacto') ?>
       => <a href="mailto:midireccion@midominio.com">contacto</a>
      <?php echo mail_to('midireccion@midominio.com', 'contacto', 'encode=true') ?>
       => <a href="&#109;&#x61;... &#111;&#x6d;">&#x63;&#x74;... e&#115;&#x73;</a>

  Las direcciones de email resultantes están compuestas por caracteres transformados por un codifi-
  cador aleatorio que los transforma en entidades decimales y hexadecimales aleatoriamente. Aunq-
  ue este truco funciona para la mayoría de robots de spam, las técnicas que emplean este tipo de
  empresas evolucionan rápidamente y podrían dejar obsoleta esta técnica en poco tiempo.


9.4. Configuración del sistema de enrutamiento
El sistema de enrutamiento se encarga de 2 tareas:

      ▪ Interpreta las URL externas de las peticiones entrantes y las transforma en URI
        internas para determinar el módulo, la acción y los parámetros de la petición.

      ▪ Transforma las URI internas utilizadas en los enlaces en URL externas (siempre
        que se utilicen los helpers de enlaces).

La transformación se realiza en base a una serie de reglas de enrutamiento. Todas estas
reglas se almacenan en un archivo de configuración llamado routing.yml y que se enc-
uentra en el directorio config/. El listado 9-15 muestra las reglas que incluyen por defec-
to todos los proyectos de Symfony.

Listado 9-15 - Las reglas de enrutamiento por defecto, en miapp/config/
routing.yml
   # default rules
   homepage:

www.librosweb.es                                                                                190
Symfony, la guía definitiva                       Capítulo 9. Enlaces y sistema de enrutamiento

      url:   /
      param: { module: default, action: index }

   default_symfony:
     url:   /symfony/:action/*
     param: { module: default }

   default_index:
     url:   /:module
     param: { action: index }

   default:
     url:   /:module/:action/*


9.4.1. Reglas y patrones
Las reglas de enrutamiento son asociaciones biyectivas entre las URL externas y las URI
internas. Una regla típica está formada por:

      ▪ Un identificador único en forma de texto, que se define por legibilidad y por rapi-
        dez, y que se puede utilizar en los helpers de enlaces

      ▪ El patrón que debe cumplirse (en la clave url)

      ▪ Un array de valores para los parámetros de la petición (en la clave param)

Los patrones pueden contener comodines (que se representan con un asterisco, *) y co-
modines con nombre (que empiezan por 2 puntos, :). Si se produce una coincidencia con
un comodín con nombre, ese valor que coincide se transforma en un parámetro de la pe-
tición. Por ejemplo, la regla anterior llamada default produce coincidencias con cualquier
URL del tipo /valor1/valor2, en cuyo caso se ejecutará el módulo llamado valor1 y la ac-
ción llamada valor2. Y en la regla llamada default_symfony, el valor symfony es una pala-
bra clave y action es un comodín con nombre que se transforma en parámetro de la
petición.

El sistema de enrutamiento procesa el archivo routing.yml desde la primera línea hasta
la última y se detiene en la primera regla que produzca una coincidencia. Por este motivo
se deben añadir las reglas personalizadas antes que las reglas por defecto. Si se conside-
ran las reglas del listado 9-16, la URL /valor/123 produce coincidencias con las 2 reglas,
pero como Symfony prueba primero la regla mi_regla:, y esa regla produce una coinci-
dencia, ni siquiera se llega a probar la regla default:. De esta forma, la petición se pro-
cesa en la acción mimodulo/miaccion con el parámetro id inicializado con el valor 123 (no
se procesa por tanto en la acción valor/123).

Listado 9-16 - Las reglas se procesan de principio a fin
   mi_regla:
     url:    /valor/:id
     param: { module: mimodulo, action: miaccion }

   # default rules
   default:
     url:   /:module/:action/*

www.librosweb.es                                                                          191
Symfony, la guía definitiva                           Capítulo 9. Enlaces y sistema de enrutamiento


  NOTA
  No siempre que se crea una nueva acción es necesario añadir una nueva regla al sistema de enru-
  tamiento. Si el patrón modulo/accion es útil para la nueva acción, no es necesario añadir más re-
  glas al archivo routing.yml. Sin embargo, si se quieren personalizar las URL externas de la ac-
  ción, es necesario añadir una nueva regla por encima de las reglas por defecto.

El listado 9-17 muestra el proceso de modificación del formato de la URL externa de la
acción articulo/ver.

Listado 9-17 - Modificación del formato de las URL externas de la acción articu-
lo/ver
   <?php echo url_for('Mi artículo', 'articulo/ver?id=123') ?>
    => /articulo/ver/id/123       // Formato por defecto

   // Para cambiarlo por /articulo/123, se añade una nueva regla al
   // principio del archivo routing.yml
   articulo_segun_id:
     url:   /articulo/:id
     param: { module: articulo, action: ver }

El problema es que la regla articulo_segun_id del listado 9-17 rompe con el enrutamien-
to normal de todas las otras acciones del módulo articulo. De hecho, ahora una URL co-
mo articulo/borrar produce una coincidencia en esta regla, por lo que no se ejecuta la
regla default, sino que se ejecuta la regla articulo_segun_id. Por tanto, esta URL no lla-
ma a la acción borrar, sino que llama a la acción ver con el atributo id inicializado con el
valor borrar. Para evitar estos problemas, se deben definir restricciones en el patrón, de
forma que la regla articulo_segun_id solo produzca coincidencias con las URL cuyo co-
modín id sea un número entero.


9.4.2. Restricciones en los patrones
Cuando una URL puede producir coincidencias con varias reglas diferentes, se deben refi-
nar las reglas añadiendo restricciones o requisitos a sus patrones. Un requisito es una se-
rie de expresiones regulares que deben cumplir los comodines para que la regla produzca
una coincidencia.

Para modificar por ejemplo la regla articulo_segun_id anterior de forma que solo se apli-
que a las URL cuyo atributo id sea un número entero, se debe añadir una nueva línea a
la regla, como muestra el listado 9-18.

Listado 9-18 - Añadiendo requisitos a las reglas de enrutamiento
   articulo_segun_id:
     url:   /articulo/:id
     param: { module: articulo, action: ver }
     requirements: { id: d+ }

Ahora, una URL como articulo/borrar nunca producirá una coincidencia con la regla ar-
ticulo_segun_id, porque la cadena de texto borrar no cumple con los requisitos de la




www.librosweb.es                                                                              192
Symfony, la guía definitiva                           Capítulo 9. Enlaces y sistema de enrutamiento


regla. Por consiguiente, el sistema de enrutamiento continua buscando posibles coinci-
dencias con otras reglas hasta que al final la encuentra en la regla llamada default.

  Enlaces permanentes (permalinks)

  Una buena recomendación sobre seguridad es la de no utilizar claves primarias en las URL y sustit-
  uirlas por cadenas de texto siempre que sea posible. ¿Cómo sería posible acceder a los artículos a
  través de su título en lugar de su ID? Las URL externas resultantes serían de esta forma:
      http://www.ejemplo.com/articulo/Economia_en_Francia

  Para utilizar estas URL, se crea una nueva acción llamada permalink y que utiliza un parámetro
  llamado slug en vez del parámetro id habitual. (Nota del traductor: “slug” es un término adaptado
  del periodismo anglosajón y que hace referencia al título de una noticia o artículo en el que se han
  sustituido los espacios en blanco por guiones y se han eliminado todos los caracteres que no sean
  letras o números, lo que los hace ideales para utilizarse como parte de las URL) La nueva regla
  queda de la siguiente forma:
      articulo_segun_id:
        url:   /articulo/:id
        param: { module: articulo, action: ver }
        requirements: { id: d+ }

      articulo_segun_slug:
        url:   /articulo/:slug
        param: { module: articulo, action: permalink }

  La acción permalink debe buscar el artículo solicitado a partir de su título, por lo que el modelo de
  la aplicación debe proporcionar el método adecuado.
      public function executePermalink()
      {
        $articulo = ArticlePeer::obtieneSegunSlug($this->getRequestParameter('slug');
        $this->forward404Unless($articulo); // Muestra un error 404 si no se encuentra el
      artículo
        $this->articulo = $articulo;         // Pasar el objeto a la plantilla
      }

  También es necesario modificar los enlaces que apuntan a la acción ver en las plantillas por nue-
  vos enlaces que apunten a la acción permalink, para que se aplique correctamente el nuevo for-
  mato de las URI internas.
      // Se debe sustituir esta línea...
      <?php echo link_to('Mi artículo', 'articulo/ver?id='.$articulo->getId()) ?>

      // ...por esta otra
      <?php echo link_to('Mi artículo', 'articulo/permalink?slug='.$articulo->getSlug()) ?>

  Gracias a la definición de requirements en las reglas, las URL externas como /articulo/Econo-
  mia_en_Francia se procesan en la regla articulo_segun_slug aunque la regla articulo_se-
  gun_id aparezca antes.

  Por último, como ahora los artículos se buscan a partir del campo slug, se debería añadir un índice
  a esa columna del modelo para optimizar el rendimiento de la base de datos.




www.librosweb.es                                                                                  193
Symfony, la guía definitiva                         Capítulo 9. Enlaces y sistema de enrutamiento


9.4.3. Asignando valores por defecto
Para completar las reglas, se pueden asignar valores por defecto a los comodines con
nombre, incluso aunque el parámetro no esté definido. Los valores por defecto se esta-
blecen en el array param:.

Por ejemplo, la regla articulo_segun_id no se ejecuta si no se pasa el parámetro id. El
listado 9-19 muestra como forzar la presencia de ese parámetro.

Listado 9-19 - Asignar un valor por defecto a un comodín
   articulo_segun_id:
     url:          /articulo/:id
     param:        { module: articulo, action: ver, id: 1 }

Los parámetros por defecto no necesariamente tienen que ser comodines que se encuen-
tran en el patrón de la regla de enrutamiento. En el listado 9-20, al parámetro display se
le asigna el valor true, aunque ni siquiera forma parte de la URL.

Listado 9-20 - Asignar un valor por defecto a un parámetro de la petición
   articulo_segun_id:
     url:          /articulo/:id
     param:        { module: articulo, action: ver, id: 1, display: true }

Si se mira con un poco de detenimiento, se puede observar que articulo y ver son tam-
bién valores por defecto asignados a las variables module y action que no se encuentran
en el patrón de la URL.

  SUGERENCIA
  Se puede definir un parámetro por defecto para todas las reglas de enrutamiento creando un pará-
  metro de configuración llamado sf_routing_default. Si por ejemplo se necesita que todas las re-
  glas tengan un parámetro llamado tema con un valor por defecto igual a default, se debe añadir la
  siguiente línea al archivo config.php de la aplicación: sfConfig::set(’sf_routing_defaults’,
  array(’tema’ => ‘default’));.


9.4.4. Acelerando el sistema de enrutamiento mediante el uso de los
nombres de las reglas
Los helpers de enlaces aceptan como argumento el nombre o etiqueta de la regla en vez
del par modulo/acción, siempre que la etiqueta vaya precedida del signo @, como mues-
tra el listado 9-21.

Listado 9-21 - Uso de la etiqueta de las reglas en vez de Modulo/Acción
   <?php echo link_to('Mi artículo', 'articulo/ver?id='.$articulo->getId()) ?>

   // también se puede escribir como...
   <?php echo link_to('Mi artículo', '@articulo_segun_id?id='.$articulo->getId()) ?>

Esta técnica tiene sus ventajas e inconvenientes. En cuanto a las ventajas:

      ▪ El formateo de las URI internas es mucho más rápido, ya que Symfony no debe
        recorrer todas las reglas hasta encontrar la que se corresponde con el enlace. Si

www.librosweb.es                                                                              194
Symfony, la guía definitiva                          Capítulo 9. Enlaces y sistema de enrutamiento


        la página contiene un gran número de enlaces, el ahorro de tiempo de las reglas
        con nombre será apreciable respecto a los pares módulo/acción.

      ▪ El uso de los nombres de las reglas permite abstraer aun más la lógica de la ac-
        ción. Si se modifica el nombre de la acción pero se mantiene la URL, solo será
        necesario realizar un cambio en el archivo routing.yml. Todas las llamadas al
        helper link_to() funcionarán sin tener que realizar ningún cambio.

      ▪ La lógica que se ejecuta es más comprensible si se utiliza el nombre de la regla.
        Aunque los módulos y las acciones tengan nombres explícitos, normalmente es
        más comprensible llamar a la regla @ver_articulo_segun_slug que simplemente
        llamar a articulo/ver.

Por otra parte, la desventaja principal es que es más complicado añadir los enlaces, ya
que siempre se debe consultar el archivo routing.yml para saber el nombre de la regla
que se utiliza en la acción.

La mejor técnica de las 2 depende del proyecto en el que se trate, por lo que es el pro-
gramador el que tendrá que tomar la decisión.

  SUGERENCIA
  Mientras se prueba la aplicación (en el entorno dev), se puede comprobar la regla que se está apli-
  cando para cada petición del navegador. Para ello, se debe desplegar la sección “logs and msgs”
  de la barra de depuración y se debe buscar la línea que dice “matched route XXX”. El Capítulo 16
  contiene más información sobre el modo de depuración web.


9.4.5. Añadiendo la extensión .html
Si se comparan estas dos URL:
   http://miapp.ejemplo.com/articulo/Economia_en_Francia
   http://miapp.ejemplo.com/articulo/Economia_en_Francia.html

Aunque se trata de la misma página, los usuarios (y los robots que utilizan los buscado-
res) las consideran como si fueran diferentes debido a sus URL. La segunda URL parece
que pertenece a un directorio web de páginas estáticas correctamente organizadas, que
es exactamente el tipo de sitio web que mejor saben indexar los buscadores.

Para añadir un sufijo a todas las URL externas generadas en el sistema de enrutamiento,
se debe modificar el valor de la opción suffix en el archivo de configuración set-
tings.yml, como se muestra en el listado 9-22.

Listado 9-22 - Establecer un sufijo a todas las URL, en miapp/config/settings.yml
   prod:
     .settings
       suffix:            .html

El sufijo por defecto es un punto (.), lo que significa que el sistema de enrutamiento no
añade ningún sufijo a menos que se especifique uno.

En ocasiones es necesario indicar un sufijo específico para una única regla de enrutam-
iento. En ese caso, se indica el sufijo directamente como parte del patrón definido


www.librosweb.es                                                                                195
Symfony, la guía definitiva                          Capítulo 9. Enlaces y sistema de enrutamiento


mediante url: en la regla del archivo routing.yml, como se muestra en el listado 9-23. El
sufijo global se ignora en este caso.

Listado 9-23 - Estableciendo un sufijo en una única URL, en miapp/config/
routing.yml
   articulo_listado:
     url:          /ultimos_articulos
     param:        { module: articulo, action: listado }

   articulo_listado_rss:
     url:          /ultimos_articulos.rss
     param:        { module: articulo, action: listado, type: feed }


9.4.6. Creando reglas sin el archivo routing.yml
Como sucede con la mayoría de archivos de configuración, el archivo routing.yml es una
buena solución para definir las reglas del sistema de enrutamiento, pero no es la única
solución. Se pueden definir reglas en PHP, en el archivo config.php de la aplicación o en
el script del controlador frontal, pero antes de llamar a la función dispatch(), ya que este
método determina la acción que se ejecuta en función de las reglas de enrutamiento dis-
ponibles en ese momento. Definir reglas mediante PHP permite crear reglas dinámicas
que dependan de la configuración o de otros parámetros.

El objeto que gestiona las reglas de enrutamiento es un singleton llamado sfRouting. Se
encuentra disponible en cualquier parte del código mediante la llamada sfRouting::-
getInstance(). Su método prependRoute() añade una nueva regla por encima de las re-
glas definidas en el archivo routing.yml. El método espera 4 parámetros, que son los
mismos que se utilizan para definir una regla: la etiqueta de la ruta, el patrón de la URL,
el array asociativo con los valores por defecto y otro array asociativo con los requisitos.
La regla definida en el archivo routing.yml del listado 9-18 es equivalente por ejemplo al
código PHP mostrado en el listado 9-24.

Listado 9-24 - Definiendo una regla en PHP
   sfRouting::getInstance()->prependRoute(
      'articulo_segun_id',                              //      Nombre ruta
      '/articulo/:id',                                  //      Patrón de la ruta
      array('module' => 'articulo', 'action' => 'ver'), //      Valores por defecto
      array('id' => 'd+'),                             //      Requisitos
   );

El singleton sfRouting define otros métodos muy útiles para la gestión manual de las ru-
tas:   clearRoutes(),     hasRoutes(),     getRoutesByName(),      etc.   La   API    de   Symfony
(http://www.symfony-project.org/api/symfony.html ) dispone de mucha más información.

  SUGERENCIA
  A medida que se profundiza en los conceptos presentados en este libro, se pueden ampliar los co-
  nocimientos visitando la documentación de la API disponible online o incluso, investigando el códi-
  go fuente de Symfony. En este libro no se describen todas las opciones y parámetros de Symfony,
  pero la documentación online contiene todos los detalles posibles.

www.librosweb.es                                                                                196
Symfony, la guía definitiva                       Capítulo 9. Enlaces y sistema de enrutamiento


9.5. Trabajando con rutas en las acciones
En ocasiones es necesario obtener información sobre la ruta actual, por ejemplo para
preparar un enlace típico de “Volver a la página XXX”. En estos casos, se deben utilizar
los métodos disponibles en el objeto sfRouting. Las URI devueltas por el método getCu-
rrentInternalUri() se pueden utilizar directamente en las llamadas al helper link_to(),
como se muestra en el listado 9-25.

Listado 9-25 - Uso de sfRouting para obtener información sobre la ruta actual
   // Si se necesita una URL como la siguiente
   http://miapp.ejemplo.com/articulo/21

   // Se utiliza lo siguiente en la acción articulo/ver
   $uri = sfRouting::getInstance()->getCurrentInternalUri();
    => articulo/ver?id=21

   $uri = sfRouting::getInstance()->getCurrentInternalUri(true);
    => @articulo_segun_id?id=21

   $regla = sfRouting::getInstance()->getCurrentRouteName();
    => articulo_segun_id

   // Si se necesitan los nombres del módulo y de la acción,
   // se pueden utilizar los parámetros de la petición
   $modulo = $this->getRequestParameter('module');
   $accion = $this->getRequestParameter('action');

Si se necesita transformar dentro de la acción una URI interna en una URL externa, como
se hace en las plantillas con el helper url_for(), se utiliza el método genUrl() del objeto
sfController, como se muestra en el listado 9-26.

Listado 9-26 - Uso de sfController para transformar una URI interna
   $uri = 'articulo/ver?id=21';

   $url = $this->getController()->genUrl($uri);
    => /articulo/21

   $url = $this->getController()->genUrl($uri, true);
    => http://miapp.ejemplo.com/articulo/21


9.6. Resumen
El sistema de enrutamiento es un mecanismo bidireccional diseñado para formatear las
URL externas de forma que sean más fáciles para los usuarios. La reescritura de URL es
necesaria para omitir el nombre del controlador frontal de las aplicaciones de cada pro-
yecto. Para que el sistema de enrutamiento funcione en ambas direcciones, es necesario
utilizar los helpers de enlaces cada vez que se incluye un enlace en las plantillas. El ar-
chivo routing.yml configura las reglas del sistema de enrutamiento, su prioridad y sus
requisitos. El archivo settings.yml controla otras opciones adicionales como la presencia
del nombre del controlador frontal en las URL y el uso de sufijos en las URL generadas.



www.librosweb.es                                                                          197
Symfony, la guía definitiva                                         Capítulo 10. Formularios




Capítulo 10. Formularios
Cuando se crean las plantillas, la mayor parte del tiempo se dedica a los formularios. No
obstante, los formularios normalmente se diseñan bastante mal. Como se debe prestar
atención a los valores por defecto, al formato de los datos, a la validación, a la recarga
de los datos introducidos y al manejo en general de los formularios, algunos programa-
dores tienden a olvidar otros aspectos importantes. Por este motivo, Symfony presta es-
pecial atención a este tema. En este capítulo se describen las herramientas que automa-
tizan partes de este proceso y que aceleran el desarrollo de los formularios:

      ▪ Los helpers de formulario proporcionan una manera más rápida de crear contro-
        les de formulario en las plantillas, sobre todo para los elementos más complejos
        como fechas, listas desplegables y áreas de texto con formato.

      ▪ Si un formulario se encarga de modificar las propiedades de un objeto, el uso de
        los helpers de objetos aceleran el desarrollo de las plantillas.

      ▪ Los archivos YAML de validación facilitan la validación de los formularios y la re-
        carga de los datos introducidos.

      ▪ Los validadores encapsulan todo el código necesario para validar los datos intro-
        ducidos por el usuario. Symfony incluye validadores para la mayoría de casos ha-
        bituales y permite añadir validadores propios de forma sencilla.


10.1. Helpers de formularios
En las plantillas, es común mezclar las etiquetas HTML con código PHP. Los helpers de
formularios que incluye Symfony intentan simplificar esta tarea para evitar tener que in-
cluir continuamente etiquetas <?php echo en medio de las etiquetas <input>.


10.1.1. Etiqueta principal de los formularios
Como se explicó en el capítulo anterior, para crear un formulario se emplea el helper
form_tag(), ya que se encarga de transformar la acción que se pasa como parámetro a
una URL válida para el sistema de enrutamiento. El segundo argumento se emplea para
indicar opciones adicionales, como por ejemplo, cambiar el valor del method por defecto,
establecer el valor de enctype o especificar otros atributos. El listado 10-1 muestra algu-
nos ejemplos.

Listado 10-1 - El helper form_tag()
   <?php echo form_tag('prueba/guardar') ?>
    => <form method="post" action="/ruta/a/guardar">

   <?php echo form_tag('prueba/guardar', 'method=get multipart=true
   class=formularioSimple') ?>
    => <form method="get" enctype="multipart/form-data" class="formularioSimple"
   action="/ruta/a/guardar">

Como no se utiliza un helper para cerrar el formulario, siempre debe incluirse la etiqueta
HTML </form>, aunque no quede bien en el código fuente de la plantilla.

www.librosweb.es                                                                       198
Symfony, la guía definitiva                                         Capítulo 10. Formularios


10.1.2. Elementos comunes de formulario
Los helpers de formulario asignan por defecto a cada elemento un atributo id cuyo valor
coincide con su atributo name, aunque esta no es la única convención útil. El listado 10-2
muestra una lista completa de los helpers disponibles para los elementos comunes de
formularios y sus opciones.

Listado 10-2 - Sintaxis de los helpers para los elementos comunes de formulario
   // Cuadro de texto (input)
   <?php echo input_tag('nombre', 'valor inicial') ?>
    => <input type="text" name="nombre" id="nombre" value="valor inicial" />

   // Todos los helpers de formularios aceptan un parámetro con opciones adicionales
   // De esta forma es posible añadir atributos propios a la etiqueta que se genera
   <?php echo input_tag('nombre', 'valor inicial', 'maxlength=20') ?>
    => <input type="text" name="nombre" id="nombre" value="valor inicial" maxlength="20" />

   // Cuadro de texto grande (área de texto)
   <?php echo textarea_tag('nombre', 'valor inicial', 'size=10x20') ?>
    => <textarea name="nombre" id="nombre" cols="10" rows="20">
         valor inicial
       </textarea>

   // Checkbox
   <?php echo checkbox_tag('soltero', 1, true) ?>
   <?php echo checkbox_tag('carnet_conducir', 'B', false) ?>
    => <input type="checkbox" name="soltero" id="soltero" value="1" checked="checked" />
       <input type="checkbox" name="carnet_conducir" id="carnet_conducir" value="B" />

   // Radio button
   <?php echo radiobutton_tag('estado[]', 'valor1', true) ?>
   <?php echo radiobutton_tag('estado[]', 'valor2', false) ?>
    => <input type="radio" name="estado[]" id="estado_valor1" value="valor1"
   checked="checked" />
       <input type="radio" name="estado[]" id="estado_valor2" value="valor2" />

   // Lista desplegable (select)
   <?php echo select_tag('pago',
      '<option selected="selected">Visa</option>
       <option>Eurocard</option>
       <option>Mastercard</option>')
   ?>
    => <select name="pago" id="pago">
          <option selected="selected">Visa</option>
          <option>Eurocard</option>
          <option>Mastercard</option>
        </select>

   // Lista de opciones para una etiqueta select
   <?php echo options_for_select(array('Visa', 'Eurocard', 'Mastercard'), 0) ?>
    => <option value="0" selected="selected">Visa</option>
       <option value="1">Eurocard</option>
       <option value="2">Mastercard</option>



www.librosweb.es                                                                       199
Symfony, la guía definitiva                                         Capítulo 10. Formularios

   // Helper de lista desplegable con una lista de opciones
   <?php echo select_tag('pago', options_for_select(array(
     'Visa',
     'Eurocard',
     'Mastercard'
   ), 0)) ?>
    => <select name="pago" id="pago">
         <option value="0" selected="selected">Visa</option>
         <option value="1">Eurocard</option>
         <option value="2">Mastercard</option>
       </select>

   // Para indicar el nombre de las opciones, se utiliza un array asociativo
   <?php echo select_tag('nombre', options_for_select(array(
     'Steve' => 'Steve',
     'Bob'    => 'Bob',
     'Albert' => 'Albert',
     'Ian'    => 'Ian',
     'Buck'   => 'Buck'
   ), 'Ian')) ?>
    => <select name="nombre" id="nombre">
         <option value="Steve">Steve</option>
         <option value="Bob">Bob</option>
         <option value="Albert">Albert</option>
         <option value="Ian" selected="selected">Ian</option>
         <option value="Buck">Buck</option>
       </select>

   // Lista desplegable que permite una selección múltiple
   // (los valores seleccionados se pueden indicar en forma de array)
   <?php echo select_tag('pago', options_for_select(
     array('Visa' => 'Visa', 'Eurocard' => 'Eurocard', 'Mastercard' => 'Mastercard'),
     array('Visa', 'Mastercard'),
   ), array('multiple' => true))) ?>
    => <select name="pago[]" id="pago" multiple="multiple">
         <option value="Visa" selected="selected">Visa</option>
         <option value="Eurocard">Eurocard</option>
         <option value="Mastercard">Mastercard</option>
       </select>

   // Lista desplegable que permite una selección múltiple
   // (los valores seleccionados se pueden indicar en forma de array)
   <?php echo select_tag('pago', options_for_select(
     array('Visa' => 'Visa', 'Eurocard' => 'Eurocard', 'Mastercard' => 'Mastercard'),
     array('Visa', 'Mastercard')
   ), 'multiple=multiple') ?>
    => <select name="pago" id="pago" multiple="multiple">
         <option value="Visa" selected="selected">
         <option value="Eurocard">Eurocard</option>
         <option value="Mastercard" selected="selected">Mastercard</option>
       </select>

   // Campo para adjuntar archivos
   <?php echo input_file_tag('nombre') ?>
    => <input type="file" name="nombre" id="nombre" value="" />


www.librosweb.es                                                                        200
Symfony, la guía definitiva                                                  Capítulo 10. Formularios



   // Cuadro de texto de contraseña
   <?php echo input_password_tag('nombre', 'valor') ?>
    => <input type="password" name="nombre" id="nombre" value="valor" />

   // Campo oculto
   <?php echo input_hidden_tag('nombre', 'valor') ?>
    => <input type="hidden" name="nombre" id="nombre" value="valor" />

   // Botón de envío de formulario (botón normal de texto)
   <?php echo submit_tag('Guardar') ?>
    => <input type="submit" name="submit" value="Guardar" />

   // Botón de envío de formulario (botón creado con la imagen indicada)
   <?php echo submit_image_tag('imagen_envio') ?>
    => <input type="image" name="submit" src="/images/imagen_envio.png" />

El helper submit_image_tag() utiliza la misma sintaxis y tiene las mismas características
que image_tag().

  NOTA
  En los radio button, el valor del atributo id no se copia directamente del atributo de name, sino que
  se construye mediante una combinación del nombre y de cada valor. El motivo es que el atributo
  name debe tener el mismo valor para todos los radio button que se quieren definir como mutuamen-
  te excluyentes, al mismo tiempo que en una página HTML dos o más elementos no pueden dispo-
  ner del mismo valor para su atributo id.

  Procesando los formularios

  ¿Cómo se obtienen los datos enviados por los usuarios a través de los formularios? Los datos se
  encuentran disponibles en los parámetros de la petición, por lo que en una acción se debe llamar a
  $this->getRequestParameter($nombreElemento) para obtener el valor.

  Una buena práctica consiste en utilizar la misma acción para mostrar y para procesar el formulario.
  En función del método de la solicitud (GET o POST) se muestra la plantilla del formulario o se pro-
  cesan los datos enviados para redirigir a otra acción.
      // En mimodulo/actions/actions.class.php
      public function executeModificarAutor()
      {
        if ($this->getRequest()->getMethod() != sfRequest::POST)
        {
          // Mostrar el formulario
          return sfView::SUCCESS;
        }
        else
        {
          // Procesar los datos del formulario
          $nombre = $this->getRequestParameter('nombre');
          ...
          $this->redirect('mimodulo/otraaccion');
        }
      }



www.librosweb.es                                                                                  201
Symfony, la guía definitiva                                                Capítulo 10. Formularios


  Para que esta técnica funcione, el destino del formulario tiene que ser la misma acción que la ac-
  ción que muestra el formulario.
      // En mimodulo/templates/modificarAutorSuccess.php
      <?php echo form_tag('mimodulo/modificarAutor') ?>

      ...

Symfony también incluye helpers de formularios para realizar peticiones asíncronas en
segundo plano. El siguiente capítulo se centra en Ajax y proporciona todos los detalles.

10.1.3. Campos para introducir fechas
Muchos formularios permiten al usuario introducir fechas. Uno de los principales fallos en
los datos de los formularios suele ser el formato incorrecto de las fechas. El helper in-
put_date_tag() simplifica la introducción de fechas mostrando un calendario interactivo
creado con JavaScript, tal y como muestra la figura 10-1. Para ello, se indica la opción
rich con un valor de true.




            Figura 10.1. Etiqueta para introducir la fecha mediante un calendario


Si no se utiliza la opción rich, el helper muestra 3 listas desplegables (<select>) carga-
das con una serie de meses, días y años. También es posible mostrar por separado cada
una de estas listas utilizando sus propios helpers (select_day_tag(), select_month_tag()
y select_year_tag()). Los valores iniciales de estos elementos son el día, mes y año act-
uales. El listado 10-3 muestra los helpers disponibles para introducir fechas.

Listado 10-3 - Helpers para introducir datos
   <?php echo input_date_tag('fechanacimiento', '2005-05-03', 'rich=true') ?>
    => Muestra un cuadro de texto y un calendario dinámico

   // Los siguientes helpers requieren incluir el grupo de helpers llamado DateForm
   <?php use_helper('DateForm') ?>

   <?php echo select_day_tag('dia', 1, 'include_custom=Seleccione un día') ?>
   => <select name="dia" id="dia">
         <option value="">Seleccione un día</option>
         <option value="1" selected="selected">01</option>
         <option value="2">02</option>


www.librosweb.es                                                                               202
Symfony, la guía definitiva                                             Capítulo 10. Formularios

          ...
          <option value="31">31</option>
        </select>

   <?php echo select_month_tag('mes', 1, 'include_custom=Seleccione un mes
   use_short_month=true') ?>
   => <select name="mes" id="mes">
         <option value="">Seleccione un mes</option>
         <option value="1" selected="selected">Jan</option>
         <option value="2">Feb</option>
         ...
         <option value="12">Dec</option>
       </select>

   <?php echo select_year_tag('ano', 2007, 'include_custom=Seleccione un año
   year_end=2010') ?>
    => <select name="ano" id="ano">
         <option value="">Seleccione un año</option>
         <option value="2006">2006</option>
         <option value="2007" selected="selected">2007</option>
         ...
       </select>

Los valores permitidos por el helper input_date_tag() son los mismos que admite la fun-
ción strtotime() de PHP. El listado 10-4 muestra algunos de los listados que se pueden
utilizar y el listado 10-5 muestra los que no se pueden emplear.

Listado 10-4 - Formatos de fecha válidos para los helpers de fecha
   // Funcionan bien
   <?php echo input_date_tag('prueba',     '2006-04-01', 'rich=true') ?>
   <?php echo input_date_tag('prueba',     1143884373, 'rich=true') ?>
   <?php echo input_date_tag('prueba',     'now', 'rich=true') ?>
   <?php echo input_date_tag('prueba',     '23 October 2005', 'rich=true') ?>
   <?php echo input_date_tag('prueba',     'next tuesday', 'rich=true') ?>
   <?php echo input_date_tag('prueba',     '1 week 2 days 4 hours 2 seconds', 'rich=true') ?>

   // Devuelven un valor null
   <?php echo input_date_tag('prueba', null, 'rich=true') ?>
   <?php echo input_date_tag('prueba', '', 'rich=true') ?>

Listado 10-5 - Formatos de fecha incorrectos para los helpers de fecha
   // Fecha de referencia = 01/01/1970
   <?php echo input_date_tag('prueba', 0, 'rich=true') ?>

   // Los formatos que no son válidos en inglés no funcionan
   <?php echo input_date_tag('prueba', '01/04/2006', 'rich=true') ?>


10.1.4. Editor de textos avanzado
Las áreas de texto definidas mediante <textarea> se pueden utilizar como editor de tex-
tos avanzado gracias a la integración con las herramientas TinyMCE y FCKEditor. Estos
editores muestran una interfaz similar a la de un procesador de textos, incluyendo




www.librosweb.es                                                                           203
Symfony, la guía definitiva                                            Capítulo 10. Formularios


botones para formatear el texto en negrita, cursiva y otros estilos, tal y como muestra la
figura 10-2.




                              Figura 10.2. Editor de textos avanzado


Los dos editores se tienen que instalar manualmente. Como el proceso es el mismo para
los dos, sólo se explica cómo instalar el editor TinyMCE. En primer lugar, se descarga el
editor desde la página web del proyecto (http://tinymce.moxiecode.com/) y se descomprime
en una carpeta temporal. A continuación, se copia el directorio tinymce/jscripts/tiny_m-
ce/ en la carpeta web/js/ del proyecto y se define la ruta a la librería en el archivo set-
tings.yml, como se muestra en el listado 10-6.

Listado 10-6 - Definiendo la ruta de la librería TinyMCE
   all:
     .settings:
        rich_text_js_dir:     js/tiny_mce

Una vez instalado, se puede activar el editor avanzado mediante la opción rich=true.
También es posible definir opciones propias para el editor JavaScript mediante la opción
tinymce_options. El listado 10-7 muestra algunos ejemplos.

Listado 10-7 - Editores de texto avanzado
   <?php echo textarea_tag('nombre', 'valor inicial', 'rich=true size=10x20')) ?>
    => se muestra un editor de textos avanzado creado con TinyMCE
   <?php echo textarea_tag('nombre', 'valor inicial', 'rich=true size=10x20
   tinymce_options=language:"fr",theme_advanced_buttons2:"separator"')) ?>
   => se muestra un editor de textos avanzado creado con TinyMCE y personalizado con
   opciones propias


10.1.5. Selección de idioma y país
En ocasiones es necesario mostrar un campo de formulario para seleccionar un país. Co-
mo el nombre de los países varía en función del idioma en el que se muestran, las opcio-
nes de una lista desplegable de países deberían cambiar en función de la cultura del us-
uario (el Capítulo 13 incluye más información sobre el concepto de culturas). Como se
muestra en el listado 10-8, el helper select_country_tag() automatiza este proceso: tra-
duce el nombre de todos los países y utiliza como valor los códigos estándar definidos
por el ISO.

Listado 10-8 - Helper para seleccionar un país
   <?php echo select_country_tag('pais', 'AL') ?>
    => <select name="pais" id="pais">


www.librosweb.es                                                                          204
Symfony, la guía definitiva                                              Capítulo 10. Formularios

            <option   value="AF">Afghanistan</option>
            <option   value="AL" selected="selected">Albania</option>
            <option   value="DZ">Algeria</option>
            <option   value="AS">American Samoa</option>
      ...

De forma similar a select_country_tag(), el helper select_language_tag() muestra una
lista de idiomas, tal y como indica el listado 10-9.

Listado 10-9 - Helper para seleccionar un idioma
   <?php echo select_language_tag('idioma', 'en') ?>
    => <select name="idioma" id="idioma">
         ...
         <option value="elx">Elamite</option>
         <option value="en" selected="selected">English</option>
         <option value="enm">English, Middle (1100-1500)</option>
         <option value="ang">English, Old (ca.450-1100)</option>
         <option value="myv">Erzya</option>
         <option value="eo">Esperanto</option>
         ...


10.2. Helpers de formularios para objetos
Cuando se utilizan los elementos de formulario para modificar las propiedades de un ob-
jeto, resulta tedioso utilizar los helpers normales. Por ejemplo, para editar el atributo te-
lefono de un objeto Cliente, se podría escribir lo siguiente:
   <?php echo input_tag('telefono', $cliente->getTelefono()) ?>
   => <input type="text" name="telefono" id="telefono" value="0123456789" />

Para no tener que repetir continuamente el nombre del atributo, Symfony define un hel-
per de formulario para objetos en cada uno de los helpers de formularios. Los helpers de
formularios para objetos deducen el nombre y el valor inicial del elemento a partir de un
objeto y del nombre de un método. El anterior input_tag() es equivalente a:
   <?php echo object_input_tag($cliente, 'getTelefono') ?>
   => <input type="text" name="telefono" id="telefono" value="0123456789" />

El ahorro de código no es muy significativo para el helper object_input_tag(). No obs-
tante, todos los helpers estándar de formulario dispone del correspondiente helper para
objetos y todos comparten la misma sintaxis. Utilizando estos helpers, es muy sencillo
crear los formularios. Esta es la razón por la que los helpers de formulario para objetos
se utilizan en el scaffolding y en los sistemas de gestión creados de forma automática (en
el Capítulo 14 se definen los detalles). El listado 10-10 muestra una lista de todos los
helpers de formularios para objetos.

Listado 10-10 - Sintaxis de los helpers de formularios para objetos
   <?php    echo   object_input_tag($objeto, $metodo, $opciones) ?>
   <?php    echo   object_input_date_tag($objeto, $metodo, $opciones) ?>
   <?php    echo   object_input_hidden_tag($objeto, $metodo, $opciones) ?>
   <?php    echo   object_textarea_tag($objeto, $metodo, $opciones) ?>
   <?php    echo   object_checkbox_tag($objeto, $metodo, $opciones) ?>
   <?php    echo   object_select_tag($objeto, $metodo, $opciones) ?>



www.librosweb.es                                                                            205
Symfony, la guía definitiva                                                  Capítulo 10. Formularios

   <?php echo object_select_country_tag($objeto, $metodo, $opciones) ?>
   <?php echo object_select_language_tag($objeto, $metodo, $opciones) ?>

No existe un helper llamado object_password_tag(), ya que no es recomendable proporc-
ionar un valor por defecto en un campo de texto de contraseña basado en lo que escribió
antes el usuario.

  ATENCIÓN
  Al contrario de lo que sucede con los helpers de formularios, los helpers de formularios para objetos
  solamente están disponibles si se incluye de forma explícita el grupo de helpers llamado Object en
  la plantilla, mediante use_helper(’Object’).

De todos los helpers de formularios para objetos, los más interesantes son objects_for_-
select() y object_select_tag(), que se emplean para construir listas desplegables.


10.2.1. Llenando listas desplegables con objetos
El helper options_for_select(), descrito anteriormente junto con el resto de helpers
estándar, transforma un array asociativo de PHP en una lista de opciones, como se
muestra en el listado 10-11.

Listado 10-11 - Creando una lista de opciones a partir de un array con
options_for_select()
   <?php echo options_for_select(array(
     '1' => 'Steve',
     '2' => 'Bob',
     '3' => 'Albert',
     '4' => 'Ian',
     '5' => 'Buck'
   ), 4) ?>
    => <option value="1">Steve</option>
       <option value="2">Bob</option>
       <option value="3">Albert</option>
       <option value="4" selected="selected">Ian</option>
       <option value="5">Buck</option>

Imagina que se dispone de un array de objetos de tipo Autor que ha sido obtenido med-
iante una consulta realizada con Propel. Si se quiere mostrar una lista desplegable cuyas
opciones se obtienen de ese array, es necesario recorrer el array para obtener el valor
del id y nombre de cada objeto, tal y como muestra el listado 10-12.

Listado 10-12 - Creando una lista de opciones a partir de un array de objetos
con options_for_select()
   // En la acción
   $opciones = array();
   foreach ($autores as $autor)
   {
     $opciones[$autor->getId()] = $autor->getNombre();
   }
   $this->opciones = $opciones;




www.librosweb.es                                                                                  206
Symfony, la guía definitiva                                         Capítulo 10. Formularios

   // En la plantilla
   <?php echo options_for_select($opciones, 4) ?>

Como esta técnica es muy habitual, Symfony incluye un helper que automatiza todo el
proceso llamado objects_for_select() y que crea una lista de opciones directamente a
partir de un array de objetos. El helper requiere 2 parámetros adicionales: los nombres
de los métodos empleados para obtener el value y el texto de las etiquetas <option> que
se generan. De esta forma, el listado 10-12 es equivalente a la siguiente línea de código:
   <?php echo objects_for_select($autores, 'getId', 'getNombre', 4) ?>

Aunque esta instrucción es muy rápida e inteligente, Symfony va más allá cuando se em-
plean claves externas.

10.2.2. Creando una lista desplegable a partir de una columna que es
clave externa
Los valores que puede tomar una columna que es clave externa de otra son los valores
de una clave primaria que corresponden a una tabla externa. Si por ejemplo se dispone
de una tabla llamada articulo con una columna autor_id que es una clave externa de la
tabla autor, los posibles valores de esta columna son los de la columna id de la tabla au-
tor. Básicamente, una lista desplegable para editar el autor de un artículo debería tener
el aspecto del listado 10-13.

Listado 10-13 - Creando una lista de opciones a partir de una clave externa con
objects_for_select()
   <?php echo select_tag('autor_id', objects_for_select(
     AutorPeer::doSelect(new Criteria()),
     'getId',
     '__toString',
     $articulo->getAutorId()
   )) ?>
   => <select name="autor_id" id="autor_id">
         <option value="1">Steve</option>
         <option value="2">Bob</option>
         <option value="3">Albert</option>
         <option value="4" selected="selected">Ian</option>
         <option value="5">Buck</option>
      </select>

El helper object_select_tag() automatiza todo el proceso. En el ejemplo anterior se
muestra una lista desplegable con el nombre extraído de las filas de la tabla externa. El
helper puede adivinar el nombre de la tabla y de la columna externa a partir del esquema
de base de datos, por lo que su sintaxis es muy concisa. El listado 10-13 es equivalente a
la siguiente línea de código:
   <?php echo object_select_tag($articulo, 'getAutorId') ?>

El helper object_select_tag() adivina el nombre de la clase peer relacionada (AutorPeer
en este caso) a partir del nombre del método que se pasa como parámetro. No obstante,
también es posible indicar una clase propia mediante la opción related_class pasada co-
mo tercer argumento. El texto que se muestra en cada etiqueta <option> es el nombre

www.librosweb.es                                                                       207
Symfony, la guía definitiva                                         Capítulo 10. Formularios


del registro de base de datos, que es el resultado de aplicar el método toString() a la
clase del objeto (si no está definido el método $autor->toString(), se utiliza el va-
lor de la clave primaria). Además, la lista de opciones se obtiene mediante un método
doSelect() al que se pasa un objeto Criteria vacío, por lo que el método devuelve todas
las filas de la tabla ordenadas por fecha de creación. Si se necesita mostrar solamente un
subconjunto de filas o se quiere realizar un ordenamiento diferente, se crea un método
en la clase peer que devuelve esa selección en forma de array de objetos y se indica co-
mo opción peer_method en el helper. Por último, es posible añadir una opción vacía o una
opción propia como primera opción de la lista desplegable gracias a las opciones inclu-
de_blank y include_custom. El listado 10-14 muestra todas estas opciones del helper
object_select_tag().

Listado 10-14 - Opciones del helper object_select_tag()
   // Sintaxis básica
   <?php echo object_select_tag($articulo, 'getAutorId') ?>
   // Construye la lista mediante AutorPeer::doSelect(new Criteria())

   // Utiliza otra clase peer para obtener los valores
   <?php echo object_select_tag($articulo, 'getAutorId', 'related_class=Otraclase') ?>
   // Construye la lista mediante OtraclasePeer::doSelect(new Criteria())

   // Utiliza otro método de la clase peer para obtener los valores
   <?php echo object_select_tag($articulo,
   'getAutorId','peer_method=getAutoresMasFamosos') ?>
   // Construye la lista mediante AutorPeer::getAutoresMasFamosos(new Criteria())

   // Añade una opción <option value="">&nbsp;</option> al principio de la lista
   <?php echo object_select_tag($articulo, 'getAutorId', 'include_blank=true') ?>

   // Añade una opción <option value="">Seleccione un autor</option> al principio de la
   lista
   <?php echo object_select_tag($articulo, 'getAutorId',
     'include_custom=Seleccione un autor') ?>


10.2.3. Modificando objetos
Las acciones pueden procesar de forma sencilla los formularios que permiten modificar
los datos de los objetos utilizando los helpers de formularios para objetos. El listado 10-
15 muestra un ejemplo de un objeto de tipo Autor con los atributos nombre, edad y
dirección.

Listado 10-15 - Un formulario construido con los helpers de formularios para
objetos
   <?php echo form_tag('autor/modificar') ?>
     <?php echo object_input_hidden_tag($autor, 'getId') ?>
     Nombre: <?php echo object_input_tag($autor, 'getNombre') ?><br />
     Edad: <?php echo object_input_tag($autor, 'getEdad') ?><br />
     Dirección: <br />
            <?php echo object_textarea_tag($autor, 'getDireccion') ?>
   </form>


www.librosweb.es                                                                          208
Symfony, la guía definitiva                                         Capítulo 10. Formularios


La acción modificar del módulo autor se ejecuta cuando se envía el formulario. Esta ac-
ción puede modificar los datos del objeto utilizando el modificador fromArray() generado
por Propel, tal y como muestra el listado 10-16.

Listado 10-16 - Procesando un formulario realizado con helpers de formularios
para objetos
   public function executeModificar ()
   {
     $autor = AutorPeer::retrieveByPk($this->getRequestParameter('id'));
     $this->forward404Unless($autor);

     $autor->fromArray($this->getRequest()->getParameterHolder()->getAll(),
   AutorPeer::TYPE_FIELDNAME);
     $autor->save();

       return $this->redirect('/autor/ver?id='.$autor->getId());
   }


10.3. Validación de formularios
En el Capítulo 6 se explica cómo utilizar los métodos validateXXX() en las acciones para
validar los parámetros de la petición. Sin embaro, si se utiliza este método para validar
los datos enviados en un formulario, se acaba escribiendo una y otra vez los mismos o
parecidos trozos de código. Symfony incluye un mecanismo específico de validación de
formularios realizado mediante archivos YAML, en vez de utilizar código PHP en la acción.

Para mostrar el funcionamiento de la validación de formularios, se va a utilizar el formu-
lario del listado 10-17. Se trata del típico formulario de contacto que incluye los campos
nombre, email, edad y mensaje.

Listado 10-17 - Ejemplo de formulario de contacto, en modules/contacto/templa-
tes/indexSuccess.php
   <?php echo form_tag('contacto/enviar') ?>
     Nombre: <?php echo input_tag('nombre') ?><br />
     Email:   <?php echo input_tag('email') ?><br />
     Edad:    <?php echo input_tag('edad') ?><br />
     Mensaje: <?php echo textarea_tag('mensaje') ?><br />
     <?php echo submit_tag() ?>
   </form>

El funcionamiento básico de la validación en un formulario es que si el usuario introduce
datos no válidos y envía el formulario, la próxima página que se muestra debería conte-
ner los mensajes de error. La siguiente lista explica con palabras sencillas lo que se con-
sideran datos válidos en el formulario de prueba:

       ▪ El campo nombre es obligatorio. Debe ser una cadena de texto de entre 2 y 100
         caracteres.

       ▪ El campo email es obligatorio. Debe ser una cadena de texto de entre 2 y 100 ca-
         racteres y debe contener una dirección de email válida.

       ▪ El campo edad es obligatorio. Debe ser un número entero entre 0 y 120.

www.librosweb.es                                                                       209
Symfony, la guía definitiva                                                  Capítulo 10. Formularios


      ▪ El campo mensaje es obligatorio.

Se podrían definir reglas de validación más complejas para el formulario de contacto, pe-
ro de momento solo es un ejemplo para mostrar las posibilidades de la validación de
formularios.

  NOTA
  La validación de formularios se puede realizar en el lado del servidor y/o en el lado del cliente. La
  validación en el servidor es obligatoria para no corromper la base de datos con datos incorrectos.
  La validación en el lado del cliente es opcional, pero mejora enormemente la experiencia de usuar-
  io. La validación en el lado del cliente debe realizarse de forma manual con JavaScript.


10.3.1. Validadores
Los campos nombre y email del formulario de ejemplo comparten las mismas reglas de
validación. Como algunas de las reglas de validación son tan comunes que aparecen en
todos los formularios, Symfony ha creado unos validadores que encapsulan todo el códi-
go PHP necesario para realizarlos. Un validador es una clase que proporciona un método
llamado execute(). El método requiere de un parámetro que es el valor del campo de
formulario y devuelve true si el valor es válido y false en otro caso.

Symfony incluye varios validadores ya construidos (que se describen más adelante en la
sección “Validadores estándar de Symfony”) aunque ahora solo se va a estudiar el valida-
dor sfStringValidator. Este validador comprueba que el valor introducido es una cadena
de texto y que su longitud se encuentra entre 2 límites indicados (definidos cuando se
llama al método initialize()). Este validador es justo lo que se necesita para validar el
campo nombre. El listado 10-18 muestra cómo utilizar este validador en un método de
validación.

Listado 10-18 - Validando parámetros de la petición con validadores reutiliza-
bles, en modules/contacto/action/actions.class.php
   public function validateEnviar()
   {
     $nombre = $this->getRequestParameter('nombre');

      // El campo 'nombre' es obligatorio
      if (!$nombre)
      {
        $this->getRequest()->setError('nombre', 'El campo nombre no se puede dejar vacío');

          return false;
      }

      // El campo nombre debe ser una cadena de texto de entre 2 y 100 caracteres
      $miValidador = new sfStringValidator();
      $miValidador->initialize($this->getContext(), array(
        'min'       => 2,
        'min_error' => 'El nombre es muy corto (mínimo 2 caracteres)',
        'max'       => 100,
        'max_error' => 'El nombre es muy largo (máximo 100 caracteres)',
      ));


www.librosweb.es                                                                                  210
Symfony, la guía definitiva                                                  Capítulo 10. Formularios

       if (!$miValidador->execute($nombre, $error))
       {
         return false;
       }

       return true;
   }

Si un usuario envía el formulario del listado 10-17 con el valor a en el campo nombre, el
método execute() de sfStringValidator devuelve un valor false (porque la longitud de
la cadena de texto es menor que el mínimo de 2 caracteres). El método validateSend()
devolverá false y se ejecutará el método handleErrorEnviar() en vez del método
executeEnviar().

  SUGERENCIA
  El método setError() del objeto sfRequest proporciona información a la plantilla para que se
  puedan mostrar los mensajes de error, como se explica más adelante en la sección “Mostrando
  mensajes de error en el formulario”. Los validadores establecen los errores de forma interna, por lo
  que se pueden definir diferentes errores para los diferentes casos de error en la validación. Este es
  precisamente el objetivo de los parámetros min_error y max_error de inicialización de
  sfStringValidator.

Las reglas de validación definidas anteriormente se pueden traducir en validadores:

       ▪ nombre: sfStringValidator (min=2, max=100)

       ▪ email: sfStringValidator (min=2, max=100) y sfEmailValidator

       ▪ edad: sfNumberValidator (min=0, max=120)

El hecho de que un campo sea requerido no es algo que se controle mediante un
validador.

10.3.2. Archivo de validación
Aunque se podría realizar de forma sencilla la validación del formulario de contacto med-
iante los validadores en el método validateEnviar(), esta forma de trabajo supondría re-
petir mucho código PHP. Symfony ofrece una alternativa mucho mejor para definir las re-
glas de validación de un formulario, mediante el uso de archivos YAML. El listado 10-19
muestra por ejemplo como realizar la misma validación que el listado 10-18 pero med-
iante un archivo de validación.

Listado 10-19 - Archivo de validación, en modules/contacto/validate/enviar.yml
   fields:
     name:
       required:
         msg:       El campo nombre no se puede dejar vacío
       sfStringValidator:
         min:       2
         min_error: El nombre es muy corto (mínimo 2 caracteres)
         max:       100
         max_error: El nombre es m uy largo (máximo 100 caracteres)

www.librosweb.es                                                                                  211
Symfony, la guía definitiva                                                 Capítulo 10. Formularios


En el archivo de validación, la clave fields define la lista de campos que tienen que ser
validados, si son requeridos o no y los validadores que deben utilizarse para comprobar
su validez. Los parámetros de cada validador son los mismos que se utilizan para iniciali-
zar manualmente los validadores. Se pueden utilizar tantos validadores como sean nece-
sarios sobre un mismo campo de formulario.

  NOTA
  El proceso de validación no termina cuando el validador falla. Symfony ejecuta todos los validado-
  res y determina que la validación ha fallado si al menos uno de ellos falla. Incluso cuando algunas
  de las reglas de validación fallan, Symfony busca el método validateXXX() y lo ejecuta. De esta
  forma, las 2 técnicas de validación son complementarias. La gran ventaja es que si un formulario
  tiene muchos errores, se muestran todos los mensajes de error.

Los archivos de validación se encuentran en el directorio validate/ del módulo y su nom-
bre se corresponde con el nombre de la acción que validan. El listado 10-19 por ejemplo
se debe guardar en un archivo llamado validate/enviar.yml.


10.3.3. Mostrando el formulario de nuevo
Cuando la validación falla, Symfony por defecto busca un método handleErrorEnviar()
en la clase de la acción o muestra la plantilla enviarError.php si el método no existe.

El procedimiento habitual para informar al usuario de que la validación ha fallado es el de
volver a mostrar el formulario con los mensajes de error. Para ello, se debe redefinir el
método handleErrorSend() para finalizar con una redirección a la acción que muestra el
formulario (en este caso module/index) tal y como muestra el listado 10-20.

Listado 10-20 - Volviendo a mostrar el formulario, en modules/contacto/actions/
actions.class.php
   class ContactoActions extends sfActions
   {
     public function executeIndex()
     {
       // Mostrar el formulario
     }

       public function handleErrorEnviar()
       {
         $this->forward('contacto', 'index');
       }

       public function executeEnviar()
       {
         // Procesar el envío del formulario
       }
   }

Si se utiliza la misma acción para mostrar el formulario y para procesarlo, el método
handleErrorEnviar() puede devolver el valor sfView::SUCCESS para volver a mostrar el
formulario, como se indica en el listado 10-21.


www.librosweb.es                                                                                212
Symfony, la guía definitiva                                        Capítulo 10. Formularios


Listado 10-21 - Una sola acción para mostrar y procesar el formulario, en modu-
les/contacto/actions/actions.class.php
   class ContactoActions extends sfActions
   {
     public function executeEnviar()
     {
       if ($this->getRequest()->getMethod() != sfRequest::POST)
       {
         // Preparar los datos para la plantilla

             // Mostrar el formulario
             return sfView::SUCCESS;
           }
           else
           {
             // Procesar el formulario
             ...
             $this->redirect('mimodulo/otraaccion');
           }
       }
       public function handleErrorEnviar()
       {
         // Preparar los datos para la plantilla

           // Mostrar el formulario
           return sfView::SUCCESS;
       }
   }

La lógica que se emplea para preparar los datos del formulario se puede refactorizar en
un método de tipo protected de la clase de la acción, para evitar su repetición en los mé-
todos executeSend() y handleErrorSend().

Con esta nueva configuración, cuando el usuario introduce un nombre inválido, se vuelve
a mostrar el formulario pero los datos introducidos se pierden y no se muestran los men-
sajes de error. Para arreglar este último problema, se debe modificar la plantilla que
muestra el formulario para insertar los mensajes de error cerca del campo que ha provo-
cado el error.

10.3.4. Mostrando los mensajes de error en el formulario
Cuando un campo del formulario no supera con éxito su validación, los mensajes de error
definidos como parámetros del validador se añaden a la petición (de la misma forma que
se añadían manualmente mediante el método setError() en el listado 10-18). El objeto
sfRequest proporciona un par de métodos útiles para obtener el mensaje de error: hasE-
rror() y getError(), cada uno de los cuales espera como argumento el nombre de un
campo de formulario. Además, se puede mostrar un mensaje de aviso al principio del for-
mulario para llamar la atención del usuario e indicarle que el formulario contiene errores
mediante el método hasErrors(). Los listados 10-22 y 10-23 muestran cómo utilizar es-
tos métodos.



www.librosweb.es                                                                      213
Symfony, la guía definitiva                                           Capítulo 10. Formularios


Listado 10-22 - Mostrando mensajes de error al principio del formulario, en tem-
plates/indexSuccess.php
   <?php if ($sf_request->hasErrors()): ?>
     <p>Los datos introducidos no son correctos.
     Por favor, corrija los siguientes errores y vuelva a enviar el formulario:</p>
     <ul>
     <?php foreach($sf_request->getErrors() as $nombre => $error): ?>
       <li><?php echo $nombre ?>: <?php echo $error ?></li>
     <?php endforeach; ?>
     </ul>
   <?php endif; ?>

Listado 10-23 - Mostrando mensajes de error dentro del formulario, en templa-
tes/indexSuccess.php
   <?php echo form_tag('contacto/enviar') ?>
     <?php if ($sf_request->hasError('nombre')): ?>
       <?php echo $sf_request->getError('nombre') ?> <br />
     <?php endif; ?>
     Nombre:    <?php echo input_tag('nombre') ?><br />
     ...
     <?php echo submit_tag() ?>
   </form>

La condición utilizada antes del método getError() en el listado 10-23 es un poco larga
de escribir. Por este motivo, Symfony incluye un helper llamado form_error() y que pue-
de sustituirlo. Para poder utilizarlo, es necesario declarar de forma explícita el uso de es-
te grupo de helpers llamado Validation. El listado 10-24 modifica al listado 10-23 para
utilizar este helper.

Listado 10-24 - Mostrando mensajes de error dentro del formulario, forma
abreviada
   <?php use_helper('Validation') ?>
   <?php echo form_tag('contacto/enviar') ?>

              <?php echo form_error('nombre') ?><br />
     Nombre: <?php echo input_tag('nombre') ?><br />
     ...
     <?php echo submit_tag() ?>
   </form>

El helper form_error() añade por defecto un carácter antes y después del mensaje de
error para hacerlos más visibles. Por defecto, el carácter es una flecha que apunta hacia
abajo (correspondiente a la entidad &darr;), pero se puede definir otro carácter en el ar-
chivo settings.yml:
   all:
     .settings:
        validation_error_prefix:    ' &darr;&nbsp;'
        validation_error_suffix:    ' &nbsp;&darr;'




www.librosweb.es                                                                         214
Symfony, la guía definitiva                                         Capítulo 10. Formularios


Si ahora falla la validación, el formulario muestra correctamente los mensajes de error,
pero los datos introducidos por el usuario se pierden. Para mejorar el formulario es nece-
sario volver a mostrar los datos que introdujo anteriormente el usuario.

10.3.5. Mostrando de nuevo los datos introducidos
Como los errores se manejan mediante el método forward() (como se muestra en el lis-
tado 10-20), la petición original sigue siendo accesible y por tanto los datos introducidos
por el usuario se encuentran en forma de parámetros de la petición. De esta forma, es
posible mostrar los datos introducidos en el formulario utilizando los valores por defecto,
tal y como se muestra en el listado 10-25.

Listado 10-25 - Indicando valores por defecto para mostrar los datos introduci-
dos por el usuario anteriormente después de un fallo en la validación, en templa-
tes/indexSuccess.php
   <?php use_helper('Validation') ?>
   <?php echo form_tag('contacto/enviar') ?>
               <?php echo form_error('nombre') ?><br />
     Nombre:   <?php echo input_tag('nombre', $sf_params->get('nombre')) ?><br />
               <?php echo form_error('email') ?><br />
     Email:    <?php echo input_tag('email', $sf_params->get('email')) ?><br />
               <?php echo form_error('edad') ?><br />
     Edad:     <?php echo input_tag('edad', $sf_params->get('edad')) ?><br />
               <?php echo form_error('mensaje') ?><br />
     Mensaje: <?php echo textarea_tag('mensaje', $sf_params->get('mensaje')) ?><br />
     <?php echo submit_tag() ?>
   </form>

Una vez más, se trata de un mecanismo bastante tedioso de escribir. Symfony ofrece
una alternativa para volver a mostrar los datos de todos los campos de un formulario.
Esta alternativa se realiza mediante el archivo YAML de validación y no mediante la modi-
ficación de los valores por defecto de los elementos. Solamente es necesario activar la
opción fillin: del formulario, con la sintaxis descrita en el listado 10-26.

Listado 10-26 - Activando la opción fillin para volver a mostrar los datos del
formulario cuando la validación falla, en validate/enviar.yml
   fillin:
     enabled: true # Habilita volver a mostrar los datos
     param:
       name: prueba   # Nombre del formulario (no es necesario indicarlo si solo hay 1
   formulario en la página)
       skip_fields:   [email] # No mostrar los datos introducidos en estos campos
       exclude_types: [hidden, password] # No mostrar los campos de estos tipos
       check_types:   [text, checkbox, radio, password, hidden] # Muestra los datos de
   estos tipos de campos

Por defecto, se vuelven a mostrar los datos de los campos de tipo cuadro de texto,
checkbox, radio button, áreas de texto y listas desplegables (sencillas y múltiples). No se
vuelven a mostrar los datos en los campos de tipo contraseña y en los campos ocultos.
Además, la opción fillin no funciona para los campos utilizados para adjuntar archivos.



www.librosweb.es                                                                         215
Symfony, la guía definitiva                                               Capítulo 10. Formularios


  NOTA
  La opción fillin funciona procesando el contenido XML de la respuesta antes de enviarla al usua-
  rio. Si la respuesta no es un documento XHTML válido, la opción fillin puede no funcionar.

Antes de volver a mostrar los datos introducidos por el usuario, puede ser necesario mo-
dificar sus valores. A los campos del formulario se les pueden aplicar mecanismos de es-
cape, reescritura de URL, transformación de caracteres especiales en entidades y cualqu-
ier otra transformación que se pueda llevar a cabo llamando a una función. Las convers-
iones se definen bajo la clave converters:, como muestra el listado 10-27.

Listado 10-27 - Convirtiendo los datos del usuario antes del fillin, en validate/
enviar.yml
   fillin:
     enabled: true
     param:
       name: prueba
       converters:         # Conversiones aplicadas
        htmlentities:     [nombre, comentarios]
        htmlspecialchars: [comentarios]


10.3.6. Validadores estándar de Symfony
Symfony contiene varios validadores ya definidos y que se pueden utilizar directamente
en los formularios:

      ▪ sfStringValidator

      ▪ sfNumberValidator

      ▪ sfEmailValidator

      ▪ sfUrlValidator

      ▪ sfRegexValidator

      ▪ sfCompareValidator

      ▪ sfPropelUniqueValidator

      ▪ sfFileValidator

      ▪ sfCallbackValidator

Cada uno dispone de una serie de parámetros y de mensajes de error, pero se pueden
redefinir fácilmente mediante el método initialize() del validador o mediante el archivo
YAML. Las siguientes secciones describen los validadores y muestran ejemplos de su uso.

10.3.6.1. Validador de cadenas de texo
sfStringValidator permite establecer una serie de restricciones relacionadas con las ca-
denas de texto.
   sfStringValidator:
     values:       [valor1, valor2]


www.librosweb.es                                                                               216
Symfony, la guía definitiva                                         Capítulo 10. Formularios

     values_error: Los únicos valores aceptados son valor1 y valor2
     insensitive: false # Si vale true, la comparación con los valores no tiene en
   cuenta mayúsculas y minúsculas
     min:          2
     min_error:    Por favor, introduce por lo menos 2 caracteres
     max:          100
     max_error:    Por favor, introduce menos de 100 caracteres


10.3.6.2. Validador de números
sfNumberValidator verifica si un parámetro es un número y permite establecer una serie
de restricciones sobre su valor.
   sfNumberValidator:
     nan_error:    Por favor, introduce un número entero
     min:          0
     min_error:    El valor debe ser como mínimo 0
     max:          100
     max_error:    El valor debe ser inferior o igual a 100


10.3.6.3. Validador de email
sfEmailValidator verifica si el valor de un parámetro es una dirección válida de email.
   sfEmailValidator:
     strict:       true
     email_error: Esta dirección de email no es válida

La recomendación RFC822 define el formato de las direcciones de correo electrónico. No
obstante, el formato válido es mucho más permisivo que el de las direcciones habituales
de email. Según la recomendación, un email como yo@localhost es una dirección válida,
aunque es una dirección que seguramente será poco útil. Si se establece la opción strict
a true (que es su valor por defecto) solo se consideran válidas las direcciones de correo
electrónico con el formato nombre@dominio.extension. Si la opción strict vale false, se
utilizan las normas de la recomendación RFC822.

10.3.6.4. Validador de URL
sfUrlValidator comprueba si el valor de un campo es una URL válido.
   sfUrlValidator:
     url_error:    La URL no es válida


10.3.6.5. Validador de expresiones regulares
sfRegexValidator permite comprar el valor de un campo con una expresión regular com-
patible con Perl.
   sfRegexValidator:
     match:        No
     match_error: Los comentarios con más de una URL se consideran spam
     pattern:      /http.*http/si

El parámetro match determina si el parámetro debe cumplir el patrón establecido (cuando
vale Yes) o no debe cumplirlo para considerarse válido (cuando vale No).

www.librosweb.es                                                                       217
Symfony, la guía definitiva                                         Capítulo 10. Formularios


10.3.6.6. Validador para comparaciones
sfCompareValidator compara dos parámetros de petición. Su mayor utilidad es para com-
parar dos contraseñas.
   fields:
     password1:
       required:
         msg:      Por favor, introduce una contraseña
     password2:
       required:
         msg:      Por favor, vuelve a introducir la contraseña
       sfCompareValidator:
         check:    password1
         compare_error: Las 2 contraseñas son diferentes

El parámetro check contiene el nombre del campo cuyo valor debe coincidir con el valor
del campo actual para considerarse válido.

Por defecto el validador comprueba que los dos parámetros sean iguales. Se puede utili-
zar otra comparación indicándola en el parámetro operator. Los operadores disponibles
son >, >=, <, <=, == y !=.


10.3.6.7. Validador Propel para valores únicos
sfPropelUniqueValidator comprueba que el valor de un parámetro de la petición no exis-
te en la base de datos. Se trata de un validador realmente útil para las columnas que de-
ben ser índices únicos.
   fields:
     nombre:
       sfPropelUniqueValidator:
         class:        Usuario
         column:       login
         unique_error: Ese login ya existe. Por favor, seleccione otro login.

En este ejemplo, el validador busca en la base de datos los registros correspondientes a
la clase Usuario y comprueba si alguna fila tiene en su columna login el mismo valor que
el parámetro que se pasa al validador.

10.3.6.8. Validador de archivos
sfFileValidator permite restringir el tipo (mediante un array de mime-types) y el ta-
maño de los archivos subidos por el usuario.
   fields:
     image:
       required:
         msg:       Por favor, sube un archivo de imagen
       file:        True
       sfFileValidator:
         mime_types:
            - 'image/jpeg'
            - 'image/png'
            - 'image/x-png'

www.librosweb.es                                                                       218
Symfony, la guía definitiva                                              Capítulo 10. Formularios

            - 'image/pjpeg'
          mime_types_error: Solo se permiten los formatos PNG y JPEG
          max_size:         512000
          max_size_error:   El tamaño máximo es de 512Kb

El atributo file debe valer True para ese campo y el formulario de la plantilla debe decla-
rarse de tipo multipart.


10.3.6.9. Validador de callback
sfCallbackValidator delega la validación en un método o función externa. El método que
se invoca debe devolver true o false como resultado de la validación.
   fields:
     numero_cuenta:
       sfCallbackValidator:
         callback:      is_numeric
         invalid_error: Por favor, introduce un número.
     numero_tarjeta_credito:
       sfCallbackValidator:
         callback:      [misUtilidades, validarTarjetaCredito]
         invalid_error: Por favor, introduce un número correcto de tarjeta de crédito.

El método o función que se llama recibe como primer argumento el valor que se debe
comprobar. Se trata de un método muy útil cuando se quieren reutilizar los métodos o
funciones existentes en vez de tener que volver a crear un código similar para la
validación.

  SUGERENCIA
  También es posible crear validadores propios, como se describe más adelante en la sección “Cre-
  ando validadores propios”.


10.3.7. Validadores con nombre
Si se utilizan de forma constante las mismas opciones para un validador, se pueden
agrupar bajo un validador con nombre. En el ejemplo del formulario de contacto, el cam-
po email requiere las mismas opciones en sfStringValidator que el campo name. De esta
forma, es posible crear un validador con nombre miStringValidator para evitar tener que
repetir las mismas opciones. Para ello, se añade una etiqueta miStringValidator bajo la
clave validators:, y se indica la class y los param del validador que se quiere utilizar.
Después, este validador ya se puede utilizar como cualquier otro validador indicando su
nombre en la sección fields, como se muestra en el listado 10-28.

Listado 10-28 - Reutilizando validadores con nombre en un archivo de valida-
ción, en validate/enviar.yml
   validators:
     miStringValidator:
       class: sfStringValidator
       param:
         min:       2
         min_error: Este campo es demasiado corto (mínimo 2 caracteres)


www.librosweb.es                                                                            219
Symfony, la guía definitiva                                          Capítulo 10. Formularios

          max:       100
          max_error: Este campo es demasiado largo (mínimo 100 caracteres)

   fields:
     nombre:
       required:
         msg:       El nombre no se puede dejar vacío
       miStringValidator:
     email:
       required:
         msg:       El email no se puede dejar vacío
       miStringValidator:
       sfEmailValidator:
         email_error: La dirección de email no es válida


10.3.8. Restringiendo la validación a un método
Por defecto, los validadores indicados en el archivo de validación se ejecutan cuando la
acción se llama mediante un método POST. Se puede redefinir esta opción de forma glo-
bal o campo a campo especificando otro valor en la clave methods, de forma que se pue-
da utilizar una validación diferente para métodos diferentes, como muestra el listado
10-29.

Listado 10-29 - Definiendo cuando se valida un campo, en validate/enviar.yml
   methods:           [post]      # Opción por defecto

   fields:
     nombre:
       required:
         msg:       El nombre no se puede dejar vacío
       miStringValidator:
     email:
       methods:     [post, get] # Redefine la opción global
       required:
         msg:       El email no se puede dejar vacío
       miStringValidator:
       sfEmailValidator:
         email_error: La dirección de email no es válida


10.3.9. ¿Cuál es el aspecto de un archivo de validación?
Hasta ahora solamente se han mostrado partes del archivo de validación. Cuando se jun-
tan todas las partes, las reglas de validación se pueden definir de forma sencilla en el ar-
chivo YAML. El listado 10-30 muestra el archivo de validación completo para el formulario
de contacto, incluyendo todas las reglas definidas anteriormente.

Listado 10-30 - Ejemplo de archivo de validación completo
   fillin:
     enabled:        true

   validators:
     miStringValidator:
       class: sfStringValidator

www.librosweb.es                                                                        220
Symfony, la guía definitiva                                            Capítulo 10. Formularios

        param:
          min:         2
          min_error:   Este campo es demasiado corto (mínimo 2 caracteres)
          max:         100
          max_error:   Este campo es demasiado largo (máximo 100 caracteres)

   fields:
     nombre:
       required:
         msg:       El nombre no se puede dejar vacío
       miStringValidator:
     email:
       required:
         msg:       El email no se puede dejar vacío
       myStringValidator:
       sfEmailValidator:
         email_error: La dirección de email no es válida
     edad:
       sfNumberValidator:
         nan_error:    Por favor, introduce un número
         min:          0
         min_error:    "Aun no has nacido, ¿cómo vas a enviar un mensaje?"
         max:          120
         max_error:    "Abuela, ¿no es usted un poco mayor para navegar por Internet?"
     mensaje:
       required:
         msg:          El mensaje no se puede dejar vacío


10.4. Validaciones complejas
El archivo de validación es útil en la mayoría de los casos, aunque puede no ser suficien-
te cuando la validación es muy compleja. En este caso, se puede utilizar el método vali-
dateXXX() en la acción o se puede utilizar alguna de las soluciones que se presentan a
continuación.

10.4.1. Creando un validador propio
Los validadores son clases que heredan de la clase sfValidator. Si las clases de valida-
ción que incluye Symfony no son suficientes, se puede crear otra clase fácilmente y si se
guarda en cualquier directorio lib/ del proyecto, se cargará automáticamente. La sinta-
xis es muy sencilla: cuando el validador se ejecuta, se llama al método execute(). El mé-
todo initialize() se puede emplear para definir opciones por defecto.

El método execute() recibe como primer argumento el valor que se debe comprobar y
como segundo argumento, el mensaje de error que se debe mostrar cuando falla la vali-
dación. Los dos parámetros se pasan por referencia, por lo que se pueden modificar los
mensajes de error directamente en el propio método de validación.

El método initialize() recibe el singleton del contexto y el array de parámetros del ar-
chivo YAML. En primer lugar debe invocar el método initialize() de su clase padre
sfValidator y después, debe establecer los valores por defecto.


www.librosweb.es                                                                          221
Symfony, la guía definitiva                                         Capítulo 10. Formularios


Todos los validadores disponen de un contenedor de parámetros accesible mediante
$this->getParameterHolder().

Si por ejemplo se quiere definir un validador llamado sfSpamValidator para comprobar si
una cadena de texto no es spam, se puede utilizar el código del listado 10-31 en un ar-
chivo llamado sfSpamValidator.class.php. El validador comprueba si $valor contiene
más de max_url veces la cadena de texto http.

Listado 10-31 - Creando un validador propio, en lib/sfSpamValidator.class.php
   class sfSpamValidator extends sfValidator
   {
     public function execute (&$valor, &$error)
     {
       // Para max_url=2, la expresión regular es /http.*http/is
       $re = '/'.implode('.*', array_fill(0, $this->getParameter('max_url') + 1,
   'http')).'/is';

           if (preg_match($re, $valor))
           {
             $error = $this->getParameter('spam_error');

               return false;
           }

           return true;
       }

       public function initialize ($contexto, $parametros = null)
       {
         // Inicializar la clase padre
         parent::initialize($contexto);

           // Valores por defecto de los parámetros
           $this->setParameter('max_url', 2);
           $this->setParameter('spam_error', 'Esto es spam');

           // Establecer los parámetros
           $this->getParameterHolder()->add($parametros);

           return true;
       }
   }

Después de incluir el validador en cualquier directorio con carga automática de clases (y
después de borrar la cache de Symfony) se puede utilizar en los archivos de validación
de la forma que muestra el listado 10-32.

Listado 10-32 - Utilizando un validador propio, en validate/enviar.yml
   fields:
     mensaje:
       required:
         msg:           El mensaje no se puede dejar vacío
       sfSpamValidator:


www.librosweb.es                                                                       222
Symfony, la guía definitiva                                         Capítulo 10. Formularios

          max_url:        3
          spam_error:     En este sitio web no nos gusta el spam


10.4.2. Utilizando la sintaxis de los arrays para los campos de formulario
PHP permite utilizar la sintaxis de los arrays para los campos de formulario. Cuando se
diseñan manualmente los formularios o cuando se utilizan los que genera automática-
mente Propel (ver Capítulo 14) el código HTML resultante puede ser similar al del listado
10-33.

Listado 10-33 - Formulario con sintaxis de array
   <label for="articulo_titulo">Titulo:</label>
   <input type="text" name="articulo[titulo]" id="articulo_titulo" value="Valor inicial"
          size="45" />

Si en un archivo de validación se utiliza el nombre del campo de formulario tal y como
aparece en el formulario (con los corchetes) se producirá un error al procesar el archivo
YAML. La solución consiste en reemplazar los corchetes [] por llaves {} en la sección
fields, como muestra el listado 10-34. Symfony se encarga de la conversión de los nom-
bres que se envían después a los validadores.

Listado 10-34 - Archivo de validación para un formulario que utiliza la sintaxis
de los arrays
   fields:
     articulo{titulo}:
       required:     Yes


10.4.3. Ejecutando un validador en un campo vacío
En ocasiones es necesario ejecutar un validador a un campo que no es obligatorio, es de-
cir, en un campo que puede estar vacío. El caso más habitual es el de un formulario en el
que el usuario puede (pero no es obligatorio) cambiar su contraseña. Si decide cambiar-
la, debe escribir la nueva contraseña dos veces. El ejemplo se muestra en el listado
10-35.

Listado 10-35 - Archivo de validación para un formulario con 2 campos de
contraseña
   fields:
     password1:
     password2:
       sfCompareValidator:
         check:         password1
         compare_error: Las 2 contraseñas no coinciden

La validación que se ejecuta es la siguiente:

      ▪ Si password1 == null y password2 == null:

               ▪ La comprobación required se cumple.

               ▪ Los validadores no se ejecutan.

               ▪ El formulario es válido.


www.librosweb.es                                                                       223
Symfony, la guía definitiva                                           Capítulo 10. Formularios


      ▪ Si password2 == null y password1 no es null:

               ▪ La comprobación required se cumple.

               ▪ Los validadores no se ejecutan.

               ▪ El formulario es válido.

El validador para password2 debería ejecutarse si password1 es not null. Afortunadamen-
te, los validadores de Symfony permiten controlar este caso gracias al parámetro group.
Cuando un campo de formulario pertenece a un grupo, su validador se ejecuta si el cam-
po no está vacío y si alguno de los campos que pertenecen al grupo no está vacío.

Así que si se modifica la configuración del proceso de validación por lo que se muestra en
el listado 10-36, la validación se ejecuta correctamente.

Listado 10-36 - Archivo de validación para un formulario con 2 campos de con-
traseña y un grupo
   fields:
     password1:
       group:           grupo_password
     password2:
       group:           grupo_password
       sfCompareValidator:
         check:         password1
         compare_error: Las 2 contraseñas no coinciden

El proceso de validación ahora se ejecuta de la siguiente manera:

      ▪ Si password1 == null y password2 == null:

               ▪ La comprobación required se cumple.

               ▪ Los validadores no se ejecutan.

               ▪ El formulario es válido.

      ▪ Si password1 == null and password2 == lo_que_sea:

               ▪ La comprobación required se cumple.

               ▪ password2 es not null, por lo que se ejecuta su validador y falla.

               ▪ Se muestra un mensaje de error para password2.

      ▪ If password1 == lo_que_sea and password2 == null:

               ▪ La comprobación required se cumple.

               ▪ password1 es not null, por lo que se ejecuta también el validador para
                 password2 por pertenecer al mismo grupo y la validación falla.

               ▪ Se muestra un mensaje de error para password2.

      ▪ If password1 == lo_que_sea and password2 == lo_que_sea:

               ▪ La comprobación required se cumple.



www.librosweb.es                                                                         224
Symfony, la guía definitiva                                        Capítulo 10. Formularios


               ▪ password2 es not null, por lo que se ejecuta su validador y no se produ-
                 cen errores.

               ▪ El formulario es válido.


10.5. Resumen
Incluir formularios en las plantillas es muy sencillo gracias a los helpers de formularios
que incluye Symfony y a sus opciones avanzadas. Si se definen formularios para modifi-
car las propiedades de un objeto, los helpers de formularios para objetos simplifican
enormemente su desarrollo. Los archivos de validación, los helpers de validación y la op-
ción de volver a mostrar los datos en un formulario, permiten reducir el esfuerzo necesa-
rio para crear un control estricto de los formularios que sea robusto y a la vez fácil de
utilizar por parte de los usuarios. Además, cualquier validación por muy compleja que
sea se puede realizar escribiendo un validador propio o utilizando un método valida-
teXXX() en la clase de la acción.




www.librosweb.es                                                                      225
Symfony, la guía definitiva                                 Capítulo 11. Integración con Ajax




Capítulo 11. Integración con Ajax
Las aplicaciones de la denominada Web 2.0 incluyen numerosas interacciones en el lado
del cliente, efectos visuales complejos y comunicaciones asíncronas con los servidores.
Todo lo anterior se realiza con JavaScript, pero programarlo manualmente es una tarea
tediosa y que requiere de mucho tiempo para corregir los posibles errores. Afortunada-
mente, Symfony incluye una serie de helpers que automatizan muchos de los usos comu-
nes de JavaScript en las plantillas. La mayoría de comportamientos en el lado del cliente
se pueden programar sin necesidad de escribir ni una sola línea de JavaScript. Los pro-
gramadores solo tienen que ocuparse del efecto que quieren incluir y Symfony se encar-
ga de lidiar con la sintaxis necesaria y con las posibles incompatibilidades entre
navegadores.

En este capítulo se describen las herramientas proporcionadas por Symfony para facilitar
la programación en el lado del cliente:

      ▪ Los helpers básicos de JavaScript producen etiquetas <script> válidas según los
        estándares XHTML, para actualizar elementos DOM (Document Object Model) o
        para ejecutar un script mediante un enlace.

      ▪ Prototype es una librería de JavaScript completamente integrada en Symfony y
        que simplifica el desarrollo de scripts mediante la definición de nuevas funciones
        y métodos de JavaScript.

      ▪ Los helpers de Ajax permiten al usuario actualizar partes de la página web pin-
        chando sobre un enlace, enviando un formulario o modificando un elemento de
        formulario.

      ▪ Todos estos helpers disponen de múltiples opciones que proporcionan una mayor
        flexibilidad, sobre todo mediante el uso de las funciones de tipo callback.

      ▪ Script.aculo.us es otra librería de JavaScript que también está integrada en Sym-
        fony y que añade efectos visuales dinámicos que permiten mejorar la interfaz y
        la experiencia de usuario.

      ▪ JSON (JavaScript Object Notation) es un estándar utilizado para que un script de
        cliente se comunique con un servidor.

      ▪ Las aplicaciones Symfony también permiten definir interacciones complejas en el
        lado del cliente, combinando todos los elementos anteriores. Mediante una sola
        línea de código PHP (la llamada al helper de Symfony) es posible incluir las opcio-
        nes de autocompletado, arrastrar y soltar, listas ordenables dinámicamente y
        texto editable.


11.1. Helpers básicos de JavaScript
JavaScript siempre se había considerado como poco útil en el desarrollo de aplicaciones
web profesionales debido a sus problemas de incompatibilidad entre distintos navegado-
res. Hoy en día, se han resuelto la mayoría de incompatibilidades y se han creado li-
brerías muy completas que permiten programar interacciones complejas de JavaScript


www.librosweb.es                                                                        226
Symfony, la guía definitiva                                  Capítulo 11. Integración con Ajax


sin necesidad de programar cientos de líneas de código y sin perder cientos de horas co-
rrigiendo problemas. El avance más popular se llama Ajax, como se explica más adelante
en la sección “Helpers de Ajax”.

Sorprendentemente, en este capítulo casi no se incluye código JavaScript. La razón es
que Symfony permite la programación de scripts del lado del cliente de forma diferente:
encapsula y abstrae toda la lógica JavaScript en helpers, por lo que las plantillas no inclu-
yen código JavaScript. Para el programador, añadir cierta lógica a un elemento de la pá-
gina solo requiere de una línea de código PHP, pero la llamada a este helper produce có-
digo JavaScript, cuya complejidad se puede comprobar al ver el código fuente de la pági-
na generada como respuesta. Los helpers se encargan de resolver los problemas de in-
compatibilidades entre navegadores por lo que la cantidad de código JavaScript que ge-
neran puede ser muy importante. Por tanto, en este capítulo se muestra como realizar
los efectos que normalmente se programan manualmente con JavaScript sin necesidad
de utilizar JavaScript.

Todos los helpers descritos se encuentran disponibles en las plantillas siempre que se de-
clare de forma explícita el uso del helper llamado Javascript.
   <?php use_helper('Javascript') ?>

Algunos de estos helpers generan código HTML y otros generan directamente código
JavaScript.

11.1.1. JavaScript en las plantillas
En XHTML, los bloques de código JavaScript deben encerrarse en secciones CDATA. Por
eso es tedioso crear páginas que tienen muchos bloques de código JavaScript. Symfony
incluye un helper llamado javascript_tag() y que transforma una cadena de texto en
una etiqueta <script> válida según los estándares XHTML. El listado 11-1 muestra el uso
de este helper.

Listado 11-1 - Incluyendo JavaScript con el helper javascript_tag()
   <?php echo javascript_tag("
     function mifuncion()
     {
     ...
     }
   ") ?>
    => <script type="text/javascript">
       //<![CDATA[
         function mifuncion()
         {
           ...
         }
       //]]>
       </script>

El uso habitual de JavaScript, más que sus bloques de código, es la definición de enlaces
que ejecutan un determinado script cuando se pincha en ellos. El helper link_to_funct-
ion() se encarga exactamente de eso, como muestra el listado 11-2.


www.librosweb.es                                                                         227
Symfony, la guía definitiva                                    Capítulo 11. Integración con Ajax


Listado 11-2 - Ejecutando JavaScript mediante un enlace con el helper
link_to_function()
   <?php echo link_to_function('¡Pínchame!', "alert('Me has pinchado')") ?>
    => <a href="#" onClick="alert('Me has pinchado'); return none;">¡Pínchame!</a>

Como sucede con el helper link_to(), se pueden añadir opciones a la etiqueta <a> gene-
rada mediante un tercer argumento de la función.

  NOTA
  De la misma forma que el helper link_to() tiene una función relacionada llamada button_to(),
  también es posible ejecutar un script al pulsar un botón (<input type=”button”>) utilizando el
  helper button_to_function(). Si se necesita una imagen pinchable, se puede llamar a link_-
  to_function(image_tag(’mi_imagen’), “alert(’Me has pinchado’)”).


11.1.2. Actualizando un elemento DOM
Una de las tareas habituales de las interfaces dinámicas es la actualización de algunos
elementos de la página. Normalmente se realiza como se muestra en el listado 11-3.

Listado 11-3 - Actualizando un elemento con JavaScript
   <div id="indicador">Comienza el procesamiento de datos</div>
   <?php echo javascript_tag("
     document.getElementById("indicador").innerHTML =
       "<strong>El procesamiento de datos ha concluido</strong>";
   ") ?>

Symfony incluye un helper que realiza esta tarea y que genera código JavaScript (no
HTML). El helper se denomina update_element_function() y el listado 11-4 muestra su
uso.

Listado 11-4 - Actualizar un elemento mediante JavaScript con el helper
update_element_function()
   <div id="indicador">Comienza el procesamiento de datos</div>
   <?php echo javascript_tag(
     update_element_function('indicador', array(
        'content' => "<strong>El procesamiento de datos ha concluido</strong>",
     ))
   ) ?>

A primera vista parece que este helper no es muy útil, ya que el código necesario es tan
largo como el código JavaScript original. En realidad su ventaja es la facilidad de lectura
del código. Si lo que se necesita es insertar el contenido antes o después de un elemen-
to, eliminarlo en vez de actualizarlo o no hacer nada si no se cumple una condición, el
código JavaScript resultante es muy complicado. Sin embargo, el helper update_ele-
ment_function() permite mantener la facilidad de lectura del código de la plantilla, tal y
como se muestra en el listado 11-5.

Listado 11-5 - Opciones del helper update_element_function()
   // Insertar el contenido después del elemento 'indicador'
   update_element_function('indicador', array(


www.librosweb.es                                                                           228
Symfony, la guía definitiva                                  Capítulo 11. Integración con Ajax

     'position' => 'after',
     'content' => "<strong>El procesamiento de datos ha concluido</strong>",
   ));

   // Eliminar el elemento anterior a 'indicador', solo si $condicion vale true
   update_element_function('indicador', array(
      'action'   => $condicion ? 'remove' : 'empty',
      'position' => 'before',
   ))

El helper permite que el código de las plantillas sea más fácil de entender que el código
JavaScript, además de proporcionar una sintaxis unificada para efectos similares. Tam-
bién esa es la razón por la que el nombre del helper es tan largo: su nombre es tan
explícito que no hace falta añadir comentarios que lo expliquen.

11.1.3. Aplicaciones que se degradan correctamente
La etiqueta <noscript> permite especificar cierto código HTML que muestran los navega-
dores que no tienen soporte de JavaScript. Symfony complementa esta etiqueta con un
helper que funciona de forma inversa: asegura que cierto código solo se ejecuta en los
navegadores que soportan JavaScript. Los helpers if_javascript() y end_if_javas-
cript() permiten crear aplicaciones que se degradan correctamente en los navegadores
que no soportan JavaScript, como muestra el listado 11-6.

Listado 11-6 - Uso del helper if_javascript() para que la aplicación se degrade
correctamente
   <?php if_javascript(); ?>
     <p>Tienes activado JavaScript.</p>
   <?php end_if_javascript(); ?>

   <noscript>
     <p>No tienes activado JavaScript.</p>
   </noscript>


  NOTA
  No es necesario incluir instrucciones echo cuando se llama a los helpers if_javascript() y
  end_if_javascript().


11.2. Prototype
Prototype es una librería de JavaScript muy completa que amplía las posibilidades del
lenguaje de programación, añade todas esas funciones que faltaban y con las que los
programadores soñaban y ofrece nuevos mecanismos para la manipulación de los ele-
mentos DOM. El sitio web del proyecto es http://prototypejs.org/.

Los archivos de Prototype se incluyen con el framework Symfony y son accesibles en
cualquier nuevo proyecto, en la carpeta web/sf/prototype/. Por tanto, se puede utilizar
Prototype añadiendo el siguiente código a la acción:
   $directorioPrototype = sfConfig::get('sf_prototype_web_dir');
   $this->getResponse()->addJavascript($directorioPrototype.'/js/prototype');


www.librosweb.es                                                                         229
Symfony, la guía definitiva                                          Capítulo 11. Integración con Ajax


También se puede añadir con el siguiente cambio en el archivo view.yml:
   all:
     javascripts: [%SF_PROTOTYPE_WEB_DIR%/js/prototype]
  NOTA
  Como los helpers de Ajax de Symfony, que se describen en la siguiente sección, dependen de Pro-
  totype, la librería Prototype se incluye automáticamente cuando se utiliza cualquiera de ellos. Por
  tanto, no es necesario añadir los archivos JavaScript de Prototype a la respuesta si la plantilla hace
  uso de cualquier helper cuyo nombre acaba en _remote.

Una vez que la librería Prototype se ha cargado, se pueden utilizar todas las funciones
nuevas que añade al lenguaje JavaScript. El objetivo de este libro no es describir esas
nuevas funciones, pero es fácil encontrar buena documentación de Prototype en la web,
como por ejemplo:

      ▪ Particletree (http://particletree.com/features/quick-guide-to-prototype/ )

      ▪ Sergio Pereira (http://www.sergiopereira.com/articles/prototype.js.html )

      ▪ Script.aculo.us (http://wiki.script.aculo.us/scriptaculous/show/Prototype )

Una de las funciones que Prototype añade a JavaScript es la función dólar, $(). Básica-
mente se trata de un atajo de la función document.getElementById(), pero tiene más po-
sibilidades. El listado 7-11 muestra un ejemplo de su uso.

Listado 11-7 - Uso de la función $() para obtener un elemento a partir de su ID
con JavaScript
   nodo = $('elementoID');

   // Es equivalente a...
   nodo = document.getElementById('elementoID');

   // Puede obtener más de un elemento a la vez
   // En este caso, el resultado es un array de elementos DOM
   nodos = $('primerDiv', 'segundoDiv');

Prototype también incluye una función que no dispone JavaScript y que devuelve un arr-
ay de todos los elementos DOM que tienen un valor del atributo class igual al indicado
como argumento:
   nodos = document.getElementByClassName('miclass');

No obstante, no se suele utilizar la función anterior, ya que Prototype incluye una función
mucho más poderosa llamada doble dólar, $$(). Esta función devuelve un array con to-
dos los elementos DOM seleccionados mediante un selector de CSS. La función anterior
es equivalente por tanto a la siguiente:
   nodos = $$('.miclass');

Gracias al poder de los selectores CSS, se pueden procesar los nodos DOM mediante su
class, su id y mediante selectores avanzados como el descendiente (padre-hijo) y el re-
lacional (anterior-siguiente), mucho más fácilmente que como se haría mediante Xpath.
Incluso es posible combinar todos los selectores CSS para seleccionar los elementos DOM
mediante esta función:

www.librosweb.es                                                                                   230
Symfony, la guía definitiva                                      Capítulo 11. Integración con Ajax

   nodos = $$('body div#principal ul li.ultimo img > span.leyenda');

Un último ejemplo de las mejoras en la sintaxis de JavaScript proporcionadas por Pro-
totype es el iterador de arrays llamado each. Permite un código tan conciso como PHP y
con la posibilidad añadida de definir funciones anónimas y closures de JavaScript. Se tra-
ta de un truco muy útil si se programa JavaScript manualmente.
   var verduras = ['Zanahorias', 'Lechuga', 'Ajo'];
   verduras.each(function(comida) { alert('Me encanta ' + comida); });

Como programar JavaScript con Prototype es mucho más divertido que hacerlo sin su
ayuda y como Prototype es parte de Symfony, es conveniente dedicar el tiempo necesar-
io para leer su documentación antes de continuar.


11.3. Helpers de Ajax
¿Qué sucede si se quiere actualizar un elemento de la página no con JavaScript como en
el listado 11-5, sino mediante un script de PHP que se encuentra en el servidor? De esta
forma, sería posible modificar parte de la página en función de una respuesta del servi-
dor. El helper remote_function() realiza exactamente esa tarea, como se demuestra en
el listado 11-8.

Listado 11-8 - Uso del helper remote_function()
   <div id="mizona"></div>
   <?php echo javascript_tag(
     remote_function(array(
        'update' => 'mizona',
        'url'    => 'mimodulo/miaccion',
     ))
   ) ?>
  NOTA
  El parámetro url puede contener una URI interna (modulo/accion?clave1=valor1&...) o el
  nombre de una regla del sistema de enrutamiento, al igual que sucede con el helper url_for().

Cuando se ejecuta, el script anterior actualiza el contenido del elemento cuyo id es igual
a mizona con la respuesta de la acción mimodulo/miaccion. Este tipo de interacción se lla-
ma Ajax, y es el núcleo de las aplicaciones web más interactivas. La versión en inglés de
la Wikipedia (http://en.wikipedia.org/wiki/AJAX) lo describe de la siguiente manera:

Ajax permite que las páginas web respondan de forma más rápida mediante el intercam-
bio en segundo plano de pequeñas cantidades de datos con el servidor, por lo que no es
necesario recargar la página entera cada vez que el usuario realiza un cambio. El objetivo
es aumentar la interactividad, la rapidez y la usabilidad de la página.

Ajax depende de XMLHttpRequest, un objeto JavaScript cuyo comportamiento es similar a
un frame oculto, cuyo contenido se puede actualizar realizando una petición al servidor y
se puede utilizar para manipular el resto de la página web. Se trata de un objeto a muy
bajo nivel, por lo que los navegadores lo tratan de forma diferente y el resultado es que
se necesitan muchas líneas de código para realizar peticiones Ajax a mano. Afortunada-
mente, Prototype encapsula todo el código necesario para trabajar con Ajax y proporcio-
na un objeto Ajax mucho más simple y que también utiliza Symfony. Este es el motivo


www.librosweb.es                                                                              231
Symfony, la guía definitiva                                           Capítulo 11. Integración con Ajax


por el que la librería Prototype se carga automáticamente cuando se utiliza un helper de
Ajax en la plantilla.

  SUGERENCIA
  Los helpers de Ajax no funcionan si la URL de la acción remota no pertenece al mismo dominio que
  la página web que la llama. Se trata de una restricción por motivos de seguridad que imponen los
  navegadores y que no puede saltarse.

Las interacciones de Ajax están formadas por 3 partes: el elemento que la invoca (un en-
lace, un formulario, un botón, un contador de tiempo o cualquier otro elemento que el
usuario manipula e invoca la acción), la acción del servidor y una zona de la página en la
que mostrar la respuesta de la acción. Se pueden crear interacciones más complejas si
por ejemplo la acción remota devuelve datos que se procesan en una función JavaScript
en el navegador del cliente. Symfony incluye numerosos helpers para insertar interaccio-
nes Ajax en las plantillas y todos contienen la palabra remote en su nombre. Además, to-
dos comparten la misma sintaxis, un array asociativo con todos los parámetros de Ajax.
Debe tenerse en cuenta que los helpers de Ajax generan código HTML, no código
JavaScript.

  ¿Qué sucede con las acciones para Ajax?

  Las acciones que se invocan de forma remota no dejan de ser acciones normales y corrientes. Se
  les aplica el sistema de enrutamiento, determinan la vista que deben generar en función del valor
  que devuelven, pasan variables a sus plantillas y pueden modificar el modelo como cualquier otra
  acción.

  Sin embargo, cuando se invocan mediante Ajax, las acciones devuelven el valor true a la siguiente
  función:
      $esAjax = $this->getRequest()->isXmlHttpRequest();

  Symfony es capaz de darse cuenta de que una acción se está ejecutando en un contexto Ajax y
  puede adaptar la respuesta de forma adecuada. Por tanto, y por defecto, las acciones Ajax no inclu-
  yen la barra de depuración de aplicaciones ni siquiera en el entorno de desarrollo. Además, no apli-
  can el proceso de decoración (es decir, sus plantillas no se insertan por defecto en el layout corres-
  pondiente). Si se necesita decorar la vista de una acción Ajax, se debe indicar explícitamente la op-
  ción has_layout: true para su vista en el archivo view.yml.

  Como el tiempo de respuesta es crucial en las interacciones Ajax, si la respuesta es sencilla, es
  una buena idea no crear la vista completa y devolver la respuesta directamente en forma de texto.
  Se puede utilizar por tanto el método renderText() en la acción para no utilizar la plantilla y mejo-
  rar el tiempo de respuesta de las peticiones Ajax.

  Novedad introducida en la versión en desarrollo: la mayoría de acciones Ajax finalizan con una
  plantilla que simplemente incluye un elemento parcial, porque el código de la respuesta Ajax ya se
  ha utilizado para mostrar la página inicial. Para evitar tener que crear una plantilla solo para una lí-
  nea de código, la acción puede utilizar el método renderPartial(). Este método se aprovecha de
  las ventajas de la reutilización de los elementos parciales, sus posibilidades de cache y la velocidad
  de ejecución del método renderText().
      public function executeMiAccion()
      {


www.librosweb.es                                                                                     232
Symfony, la guía definitiva                                    Capítulo 11. Integración con Ajax

          // Código PHP de la acción
          return $this->renderPartial('mimodulo/miparcial');
      }


11.3.1. Enlaces Ajax
Los enlaces Ajax constituyen una de las partes más importantes de las interacciones Ajax
realizadas en las aplicaciones de la Web 2.0. El helper link_to_remote() muestra un en-
lace que llama a una función remota. La sintaxis es muy similar a link_to(), excepto que
el segundo parámetro es el array asociativo con las opciones Ajax, como muestra el lista-
do 11-9.

Listado 11-9 - Enlace Ajax realizado con el helper link_to_remote()
   <div id="respuesta"></div>
   <?php echo link_to_remote('Borrar este post', array(
       'update' => 'respuesta',
       'url'    => 'post/borrar?id='.$post->getId(),
   )) ?>

En el ejemplo anterior, al pulsar sobre el enlace “Borrar este post” se realiza una llama-
da en segundo plano a la acción post/borrar. La respuesta devuelta por el servidor se
muestra automáticamente en el elemento de la página cuyo atributo id sea igual a resp-
uesta. La figura 11-1 ilustra el proceso completo.




            Figura 11.1. Ejecutando una actualización remota mediante un enlace


También es posible utilizar una imagen en vez de texto para mostrar el enlace, utilizar el
nombre de una regla de enrutamiento en vez de modulo/accion y añadir opciones a la eti-
queta <a> como tercer argumento, tal y como muestra el listado 11-10.

Listado 11-10 - Opciones del helper link_to_remote()
   <div id="emails"></div>
   <?php echo link_to_remote(image_tag('refresh'), array(
       'update' => 'emails',
       'url'    => '@listado_emails',
   ), array(
       'class' => 'enlace_ajax',
   )) ?>


11.3.2. Formularios Ajax
Los formularios web normalmente realizan una llamada a una acción que provoca que se
deba recargar la página completa. El helper equivalente a link_to_function() para un
formulario sería un helper que enviara los datos del formulario al servidor y que



www.librosweb.es                                                                           233
Symfony, la guía definitiva                                        Capítulo 11. Integración con Ajax


actualizara un elemento de la página con la respuesta del servidor. Eso es precisamente
lo que hace el helper form_remote_tag(), y su sintaxis se muestra en el listado 11-11.

Listado 11-11 - Formulario Ajax con el helper form_remote_tag()
   <div id="lista_elementos"></div>
   <?php echo form_remote_tag(array(
       'update'   => 'lista_elementos',
       'url'      => 'elemento/anadir',
   )) ?>
     <label for="elemento">Elemento:</label>
     <?php echo input_tag('elemento') ?>
     <?php echo submit_tag('Añadir') ?>
   </form>

El helper form_remote_tag() crea una etiqueta <form> de apertura, como sucede con el
helper form_tag(). El envío del formulario consiste en el envío en segundo plano de una
petición de tipo POST a la acción elemento/anadir y con la variable elemento como pará-
metro de la petición. La respuesta del servidor reemplaza los contenidos del elemento
cuyo atributo id sea igual a lista_elementos, como se muestra en la figura 11-2. Los for-
mularios Ajax se cierran con una etiqueta </form> de cierre de formularios.




         Figura 11.2. Ejecutando una actualización remota mediante un formulario



  ATENCIÓN
  Los formularios Ajax no pueden ser multipart, debido a una limitación del objeto XMLHttpReq-
  uest. En otras palabras, no es posible enviar archivos mediante formularios Ajax. Existen algunas
  técnicas para saltarse esta limitación, como por ejemplo utilizar un iframe oculto en vez del objeto
  XMLHttpRequest (se puede ver un ejemplo en http://www.air4web.com/files/upload/).

Si es necesario incluir un formulario que sea normal y Ajax a la vez, lo mejor es definirlo
como formulario normal y añadir, además del botón de envío tradicional, un segundo
botón (<input type=”button” />) para enviar el formulario mediante Ajax. Symfony defi-
ne este botón mediante el helper submit_to_remote(). De esta forma, es posible definir
interacciones Ajax que se degradan correctamente en los navegadores que no las sopor-
tan. El listado 11-12 muestra un ejemplo.

Listado 11-12 - Formulario con envío de datos tradicional y Ajax
   <div id="lista_elementos"></div>
   <?php echo form_tag('@elemento_anadir_normal') ?>
     <label for="elemento">Elemento:</label>
     <?php echo input_tag('elemento') ?>
     <?php if_javascript(); ?>


www.librosweb.es                                                                                 234
Symfony, la guía definitiva                                           Capítulo 11. Integración con Ajax

       <?php echo submit_to_remote('envio_ajax', 'Anadir con Ajax', array(
           'update'   => 'lista_elementos',
           'url'      => '@elemento_anadir',
       )) ?>
     <?php end_if_javascript(); ?>
     <noscript>
       <?php echo submit_tag('Anadir') ?>
     </noscript>
   </form>

Otro ejemplo en el que se podría utilizar la combinación de botones normales y botones
Ajax es el de un formulario que edita un artículo o noticia. Podría incluir un botón realiza-
do con Ajax para previsualizar los contenidos y un botón normal para publicar los conte-
nidos directamente.

  NOTA
  Si el usuario envía el formulario pulsando la tecla Enter, el formulario se envía utilizando la acción
  definida en la etiqueta <form> principal, es decir, la acción normal y no la acción Ajax.

Los formularios más modernos no solo se encargan de enviar sus datos cuando el usuario
pulsa sobre el botón de envío, sino que también pueden reaccionar a los cambios produ-
cidos por el usuario sobre alguno de sus campos. Symfony proporciona el helper obser-
ve_field() para realizar esa tarea. El listado 11-13 muestra un ejemplo de uso de este
helper para crear un sistema que sugiere valores a medida que el usuario escribe sobre
un campo: cada carácter escrito en el campo elemento lanza una petición Ajax que actua-
liza el valor del elemento sugerencias_elemento de la página.

Listado 11-13 - Ejecutando una función remota cada vez que cambia el valor de
un campo de formulario mediante observe_field()
   <?php echo form_tag('@elemento_anadir_normal') ?>
     <label for="elemento">Elemento:</label>
     <?php echo input_tag('elemento') ?>
     <div id="sugerencias_elemento"></div>
     <?php echo observe_field('elemento', array(
         'update'   => 'sugerencias_elemento',
         'url'      => '@elemento_escrito',
     )) ?>
     <?php echo submit_tag('Anadir') ?>
   </form>

El par modulo/accion correspondiente a la regla @elemento_escrito se ejecuta cada vez
que el usuario modifica el valor del campo de formulario que se está observando (en este
caso, elemento) sin necesidad de enviar el formulario. La acción puede acceder a los ca-
racteres escritos en cada momento por el usuario mediante el parámetro elemento de la
petición. Si se necesita enviar otro valor en vez del contenido del campo de formulario
que se está observando, se puede especificar en forma de expresión JavaScript en el
parámetro with. Si por ejemplo es necesario que la acción disponga de un parámetro lla-
mado param, se puede utilizar el helper observe_field() como muestra el listado 11-14.

Listado 11-14 - Pasando parámetros personalizados a la acción remota con la
opción with

www.librosweb.es                                                                                   235
Symfony, la guía definitiva                                Capítulo 11. Integración con Ajax

   <?php echo observe_field('elemento', array(
       'update'   => 'sugerencias_elemento',
       'url'      => '@elemento_escrito',
       'with'     => "'param=' + value",
   )) ?>

Este helper no genera un elemento HTML, sino que añade un comportamiento (del inglés,
“behavior”) al elemento que se pasa como parámetro. Más adelante en este capítulo se
describen más ejemplos de helpers de JavaScript que añaden comportamientos.

Si se quieren observar todos los campos de un formulario, se puede utilizar el helper ob-
serve_form(), que llama a una función remota cada vez que se modifica uno de los cam-
pos del formulario.

11.3.3. Ejecución periódica de funciones remotas
Por último, el helper periodically_call_remote() permite crear una interacción de Ajax
que se repite cada pocos segundos. No está asociado con ningún elemento HTML de la
página, sino que se ejecuta de forma transparente en segundo plano como una especie
de comportamiento de la página entera. Se puede utilizar para seguir la posición del pun-
tero del ratón, autoguardar el contenido de un área de texto grande, etc. El listado 11-15
muestra un ejemplo de uso de este helper.

Listado 11-15 - Ejecutando periódicamente una función remota mediante
periodically_call_remote()
   <div id="notificacion"></div>
   <?php echo periodically_call_remote(array(
       'frequency' => 60,
       'update'    => 'notificacion',
       'url'       => '@observa',
       'with'      => "'param=' + $F('micontenido')",
   )) ?>

Si no se especifica el número de segundos (mediante el parámetro frequency) que se es-
peran después de cada repetición, se tiene en cuenta el valor por defecto que son 10 se-
gundos. El parámetro with se evalúa con JavaScript, así que se puede utilizar cualquier
función de Prototype, como por ejemplo la función $F().


11.4. Parámetros para la ejecución remota
Todos los helpers de Ajax descritos anteriormente pueden utilizar otros parámetros,
además de los parámetros update y url. El array asociativo con los parámetros de Ajax
puede modificar el comportamiento de la ejecución remota y del procesamiento de las
respuestas.

11.4.1. Actualizar elementos diferentes en función del estado de la
respuesta
Si la ejecución remota no devuelve un resultado, los helpers pueden actualizar otro ele-
mento distinto al elemento que se actualizaría en caso de una respuesta satisfactoria.


www.librosweb.es                                                                       236
Symfony, la guía definitiva                                       Capítulo 11. Integración con Ajax


Para conseguirlo, solo es necesario indicar como valor del parámetro update un array
asociativo que establezca los diferentes elementos que se actualizan en caso de respues-
ta correcta (success) y respuesta incorrecta (failure). Se trata de una técnica eficaz
cuando una página contiene muchas interacciones de Ajax y una única zona de notifica-
ción de errores. El listado 11-16 muestra el uso de esta técnica.

Listado 11-16 - Actualización condicional en función de la respuesta
   <div id="error"></div>
   <div id="respuesta"></div>
   <p>¡Hola Mundo!</p>
   <?php echo link_to_remote('Borrar este artículo', array(
       'update'   => array('success' => 'respuesta', 'failure' => 'error'),
       'url'      => 'articulo/borrar?id='.$articulo->getId(),
   )) ?>


  SUGERENCIA
  Solo las respuestas de servidor cuyo código de estado HTTP sea de tipo error (500, 404 y todos los
  códigos diferentes de 2XX) provocan la actualización del elemento preparado para las respuestas
  erroneas. Las acciones que devuelven el valor sfView::ERROR no se consideran como erróneas.
  De esta forma, si se requiere que una acción de tipo Ajax devuelva una respuesta errónea, se debe
  ejecutar $this->getResponse()->setStatusCode(404) con cualquier código HTTP de error.


11.4.2. Actualizar un elemento según su posición
Al igual que sucede con el helper update_element_function(), se puede especificar el ele-
mento a actualizar de forma relativa respecto de otro elemento mediante el parámetro
position. El listado 11-17 muestra un ejemplo.

Listado 11-17 - Uso del parámetro position para modificar el lugar donde se
muestra la respuesta
   <div id="respuesta"></div>
   <p>¡Hola Mundo!</p>
   <?php echo link_to_remote('Borrar este artículo', array(
       'update'   => 'respuesta',
       'url'      => 'articulo/borrar?id='.$articulo->getId(),
       'position' => 'after',
   )) ?>

En esta ocasión, la respuesta de la petición Ajax se muestra después (after) del elemen-
to cuyo atributo id es igual a respuesta; es decir, se muestra después del <div> y antes
del <p>. De esta forma, se pueden realizar varias peticiones Ajax y ver como se acumulan
todas las respuestas después del elemento que se actualiza.

El parámetro position puede tomar uno de los siguientes valores:

      ▪ before: antes del elemento

      ▪ after: después del elemento

      ▪ top: antes que cualquier otro contenido del elemento



www.librosweb.es                                                                               237
Symfony, la guía definitiva                                 Capítulo 11. Integración con Ajax


      ▪ bottom: después de todos los contenidos del elemento


11.4.3. Actualizar un elemento en función de una condición
Las peticiones Ajax pueden tomar un parámetro adicional que permite que el usuario de
su consentimiento antes de ejecutar la petición con el objeto XMLHttpRequest, como
muestra el listado 11-18.

Listado 11-18 - Uso del parámetro confirm para solicitar el consentimiento del
usuario antes de realizar la petición remota
   <div id="respuesta"></div>
   <?php echo link_to_remote('Borrar este artículo', array(
       'update'   => 'respuesta',
       'url'      => 'articulo/borrar?id='.$articulo->getId(),
       'confirm' => '¿Estás seguro?',
   )) ?>

En este caso, se muestra al usuario un cuadro de diálogo de JavaScript con el mensaje
“¿Estás seguro?” cuando pincha sobre el enlace. La acción articulo/borrar solo se ejecu-
ta si el usuario da su consentimiento a esta petición pulsando sobre el botón de
“Aceptar”.

La ejecución de la petición remota también se puede condicionar a que se cumpla una
condición JavaScript evaluada en el navegador del usuario, mediante el parámetro condi-
tion, tal y como se muestra en el listado 11-19.

Listado 11-19 - Ejecución de petición remota condicionada a que se cumpla una
condición probada en el lado del cliente
   <div id="respuesta"></div>
   <?php echo link_to_remote('Borrar este artículo', array(
       'update'    => 'respuesta',
       'url'       => 'articulo/borrar?id='.$articulo->getId(),
       'condition' => "$('IDelemento') == true",
   )) ?>


11.4.4. Determinando el método de una petición Ajax
Las peticiones Ajax se realizan por defecto mediante un método POST. Si se quiere reali-
zar una petición Ajax que no modifica los datos o si se quiere mostrar un formulario que
incluye validación como resultado de una petición Ajax, se puede utilizar el método GET.
La opción method modifica el método de la petición Ajax, como muestra el listado 11-20.

Listado 11-20 - Modificando el método de una petición Ajax
   <div id="respuesta"></div>
   <?php echo link_to_remote('Borrar este artículo', array(
       'update'    => 'respuesta',
       'url'       => 'articulo/borrar?id='.$articulo->getId(),
       'method'    => 'get',
   )) ?>




www.librosweb.es                                                                        238
Symfony, la guía definitiva                                       Capítulo 11. Integración con Ajax


11.4.5. Permitiendo la ejecución de un script
Si la respuesta de una petición Ajax incluye código JavaScript (el código es la respuesta
del servidor y se incluye en el elemento indicado por el parámetro update) por defecto no
se ejecuta ese código. El motivo es el de reducir la posibilidad de ataques remotos y para
permitir al programador autorizar la ejecución del código de la respuesta después de
comprobar el contenido del código.

Para permitir la ejecución de los scripts de la respuesta del servidor, se debe utilizar la
opción script. El listado 11-21 muestra un ejemplo de una petición Ajax remota que au-
toriza la ejecución del código JavaScript que forme parte de la respuesta.

Listado 11-21 - Permitiendo la ejecución de un script en una respuesta Ajax
   <div id="respuesta"></div>
   // Si la respuesta de la acción articulo/borrar contiene código
   // JavaScript, se ejecuta en el navegador del usuario
   <?php echo link_to_remote('Borrar este artículo', array(
       'update'    => 'respuesta',
       'url'       => 'articulo/borrar?id='.$articulo->getId(),
       'script'    => 'true',
   )) ?>

Si la plantilla remota contiene helpers de Ajax (como por ejemplo remote_function()),
estas funciones PHP generan código JavaScript, que no se ejecuta a menos que se indiq-
ue la opción script => true.

  NOTA
  Cuando se permite la ejecución de los scripts de la respuesta remota, el código fuente del código
  remoto no se puede ver ni siquiera con una herramienta para visualizar el código generado. Los
  scripts se ejecutan pero su código no se muestra. Se trata de un comportamiento poco habitual, pe-
  ro completamente normal.


11.4.6. Creando callbacks
Una desventaja importante de las interacciones creadas con Ajax es que son invisibles al
usuario hasta que se actualiza la zona preparada para las notificaciones. Por tanto, si se
produce un error de servidor o la red está congestionada, los usuarios pueden pensar
que su acción se ha realizado correctamente cuando en realidad aun no ha sido procesa-
da. Este es el motivo por el que es muy importante notificar al usuario sobre los eventos
que se producen a lo largo de una interacción creada con Ajax.

Por defecto, cada petición remota es un proceso asíncrono durante el que se pueden eje-
cutar varias funciones JavaScript de tipo callback (por ejemplo para indicar el progreso
de la petición). Todas las funciones de callback tienen acceso directo al objeto request,
que contiene a su vez el objeto XMLHttpRequest. Los callback que se pueden definir se co-
rresponden con los eventos que se producen durante una interacción de Ajax:

      ▪ before: antes de que se inicie la petición

      ▪ after: justo después de que se inicie la petición y antes de que se cargue



www.librosweb.es                                                                               239
Symfony, la guía definitiva                                  Capítulo 11. Integración con Ajax


      ▪ loading: cuando se está cargando la respuesta remota en el navegador

      ▪ loaded: cuando el navegador ha terminado de cargar la respuesta remota

      ▪ interactive: cuando el usuario puede interaccionar con la respuesta remota, in-
        cluso si no se ha terminado de cargar

      ▪ success: cuando XMLHttpRequest está completo y el código HTTP de estado co-
        rresponde al rango 2XX

      ▪ failure: cuando XMLHttpRequest está completo y el código HTTP de estado no co-
        rresponde al rango 2XX

      ▪ 404: cuando la petición devuelve un error de tipo 404

      ▪ complete: cuando XMLHttpRequest está completo (se ejecuta después de success
        o failure, si alguno de los 2 está definido)

El ejemplo más habitual es el de mostrar un indicador de tipo Cargando... mientras la
petición remota se está ejecutando y ocultarlo cuando se recibe la respuesta. Para incluir
este comportamiento, solo es necesario añadir los parámetros loading y complete a la
petición Ajax, tal y como muestra el listado 11-22.

Listado 11-22 - Uso de callbacks en Ajax para mostrar y ocultar un indicador de
actividad
   <div id="respuesta"></div>
   <div id="indicador">Cargando...</div>
   <?php echo link_to_remote('Borrar este artículo', array(
       'update'    => 'respuesta',
       'url'       => 'articulo/borrar?id='.$articulo->getId(),
       'loading' => "Element.show('indicador')",
       'complete' => "Element.hide('indicador')",
   )) ?>

Los métodos show(), hide() y el objeto Element son otras de las utilidades proporciona-
das por la librería Prototype.


11.5. Creando efectos visuales
Symfony integra los efectos visuales de la librería script.aculo.us, para poder incluir efec-
tos más avanzados que simplemente mostrar y ocultar elementos <div> en las páginas.
La mejor documentación sobre la sintaxis que se puede emplear en los efectos se enc-
uentra en el wiki de la librería en http://script.aculo.us/. Básicamente, la librería se en-
carga de proporcionar objetos y funciones JavaScript que manipulan el DOM de la página
para conseguir efectos visuales complejos. El listado 11-23 incluye algunos ejemplos. Co-
mo el resultado es una animación o efecto visual de ciertas partes de la página, es reco-
mendable que pruebes los efectos para entender bien en qué consiste cada efecto. El sit-
io web de script.aculo.us incluye una galería donde se pueden ver sus efectos visuales.

Listado 11-23 - Efectos visuales en JavaScript con Script.aculo.us
   // Resalta el elemento 'mi_elemento'
   Effect.Highlight('mi_elemento', { startcolor:'#ff99ff', endcolor:'#999999' })


www.librosweb.es                                                                         240
Symfony, la guía definitiva                                 Capítulo 11. Integración con Ajax



   // Oculta un elemento
   Effect.BlindDown('mi_elemento');

   // Hace desaparecer un elemento
   Effect.Fade('mi_elemento', { transition: Effect.Transitions.wobble })

Symfony encapsula el objeto Effect de JavaScript en un helper llamado visual_effect(),
que forma parte del helper Javascript. El código generado es JavaScript y puede utilizar-
se en un enlace normal, como muestra el listado 11-24.

Listado 11-24 - Efectos visuales en las plantillas con el helper visual_effect()
   <div id="div_oculto" style="display:none">¡Aquí estaba!</div>
   <?php echo link_to_function(
     'Mostrar el DIV oculto',
     visual_effect('appear', 'div_oculto')
   ) ?>
   // Equivalente a llamar a Effect.Appear('div_oculto')

El helper visual_effects() también se puede utilizar en los callbacks de Ajax, como en el
listado 11-22, que muestra un indicador de actividad de forma más elegante que en el
listado 11-22. El elemento indicador aparece de forma progresiva cuando comienza la
petición Ajax y se desaparece también progresivamente cuando se recibe la respuesta
del servidor. Además, el elemento respuesta se resalta después de ser actualizado por la
petición remota, de forma que esa parte de la página capte la atención del usuario.

Listado 11-25 - Efectos visuales en los callbacks de Ajax
   <div id="respuesta"></div>
   <div id="indicador" style="display: none">Cargando...</div>
   <?php echo link_to_remote('Borrar este artículo', array(
       'update'    => 'respuesta',
       'url'       => 'articulo/borrar?id='.$articulo->getId(),
       'loading' => visual_effect('appear', 'indicator'),
       'complete' => visual_effect('fade', 'indicator').
                      visual_effect('highlight', 'feedback'),
   )) ?>

Los efectos visuales se pueden combinar de forma muy sencilla concatenando sus llama-
das (mediante el .) dentro de un callback.


11.6. JSON
JSON (JavaScript Object Notation) es un formato sencillo para intercambiar datos. Con-
siste básicamente en un array asociativo de JavaScript (ver ejemplo en el listado 11-26)
que se utilizar para incluir información del objeto. JSON ofrece 2 grandes ventajas para
las interacciones Ajax: es muy fácil de leer en JavaScript y puede reducir el tamaño en
bytes de la respuesta del servidor.

Listado 11-26 - Ejemplo de objeto JSON en JavaScript
   var misDatosJson = {"menu": {
     "id": "archivo",
     "valor": "Archivo",


www.librosweb.es                                                                        241
Symfony, la guía definitiva                                      Capítulo 11. Integración con Ajax

        "popup": {
          "menuitem":   [
            {"value":   "Nuevo", "onclick": "CrearNuevoDocumento()"},
            {"value":   "Abrir", "onclick": "AbrirDocumento()"},
            {"value":   "Cerrar", "onclick": "CerrarDocumento()"}
          ]
        }
   }}

El formato JSON es el más adecuado para la respuesta del servidor cuando la acción Ajax
debe devolver una estructura de datos a la página que realizó la llamada de forma que se
pueda procesar con JavaScript. Este mecanismo es útil por ejemplo cuando una sola peti-
ción Ajax debe actualizar varios elementos en la página.

En el listado 11-27 se muestra un ejemplo de página que contiene 2 elementos que de-
ben ser actualizados. Un helper remoto solo puede actualizar uno de los elementos de la
página (o titulo o nombre) pero no los 2 a la vez.

Listado 11-27 - Plantilla de ejemplo para actualizaciones Ajax múltiples
   <h1 id="titulo">Carta normal</h1>
   <p>Estimado <span id="nombre">el_nombre</span>,</p>
   <p>Hemos recibido su email y le contestaremos en el menor plazo de tiempo.</p>
   <p>Reciba un saludo cordial,</p>

Para actualizar los 2 elementos, la respuesta Ajax podría consistir en una única cabecera
JSON cuyo contenido fuera el siguiente array:
     [["titulo", "Mi carta normal"], ["nombre", "Sr. Pérez"]]

Mediante algunas pocas instrucciones de JavaScript se puede interpretar la respuesta del
servidor y actualizar varios elementos de la página de forma seguida. El listado 11-28
muestra el código que se podría añadir a la plantilla del listado 11-27 para conseguir este
efecto.

Listado 11-28 - Actualizando más de un elemento mediante una respuesta
remota
   <?php echo link_to_remote('Actualizar la carta', array(
     'url'      => 'publicaciones/actualizar',
     'complete' => 'actualizaJSON(request, json)'
   )) ?>

   <?php echo javascript_tag("
   function actualizaJSON(request, json)
   {
     var numeroElementos = json.length;
     for (var i = 0; i < numeroElementos; i++)
     {
         Element.update(json[i][0], json[i][1]);
     }
   }
   ") ?>

El callback complete tiene acceso directo a la cabecera json de la respuesta y por tanto
puede enviarlo a una función externa. La función actualizaJSON() recorre la cabecera


www.librosweb.es                                                                             242
Symfony, la guía definitiva                                         Capítulo 11. Integración con Ajax


JSON y para cada elemento del array actualiza el elemento cuyo atributo id coincide con
el primer parámetro del array y muestra el contenido incluido en el segundo parámetro
del array.

El listado 11-29 muestra como devuelve la acción publicaciones/actualizar una resp-
uesta de tipo JSON.

Listado 11-29 - Acción de ejemplo devolviendo una cabecera JSON
   class publicacionesActions extends sfActions
   {
     public function executeActualizar()
     {
       $salida = '[["titulo", "Mi carta normal"], ["nombre", "Sr. Pérez"]]';
       $this->getResponse()->setHttpHeader("X-JSON", '('.$salida.')');

          return sfView::HEADER_ONLY;
      }

El protocolo HTTP permite que la respuesta JSON se pueda enviar como una cabecera de
la respuesta. Como la respuesta no tiene ningún contenido, la acción envía solo la cabe-
cera de forma inmediata. De esta forma, se evita completamente la capa de la vista y es
tan rápido como ->renderText() pero además con una respuesta más pequeña.

  ATENCIÓN
  Existe una limitación muy importante a la técnica mostrada en el listado 11-29: el tamaño máximo
  de las cabeceras HTTP. Aunque no existe un límite oficial, las cabeceras grandes pueden no trans-
  mitirse correctamente o no interpretarse bien en el navegador. De esta forma, si el array JSON es
  grande, la acción remota debería devolver una respuesta normal con los datos JSON incluídos co-
  mo un array de JavaScript.

JSON se ha convertido en un estandar en el desarrollo de aplicaciones web. Los servicios
web proponen la utilización de JSON en vez de XML para permitir la integración de servi-
cios en el navegador del usuario en vez de en el servidor. El formato JSON es segura-
mente la mejor opción para el intercambio de información entre el servidor y las funcio-
nes JavaScript.

  SUGERENCIA
  Desde la versión 5.2 de PHP existen 2 funciones, json_encode() y json_decode(), que permiten
  convertir un array PHP en un array JSON y viceversa (http://www.php.net/manual/en/ref.j-
  son.php). Estas funciones facilitan la integración de los arrays JSON y de Ajax en general.


11.7. Interacciones complejas con Ajax
Entre los helpers de Ajax de Symfony, también existen utilidades que permiten construir
interacciones complejas con una sola llamada a una función. Estas utilidades permiten
mejorar la experiencia de usuario añadiendo características propias de las aplicaciones de
escritorio (arrastrar y soltar, autocompletar, edición directa de contenidos, etc.) sin nece-
sidad de escribir código JavaScript. En las siguientes secciones se describen los helpers
de las interacciones complejas mediante ejemplos sencillos. Los parámetros adicionales y
otras configuraciones se pueden consultar en la documentación de script.aculo.us.



www.librosweb.es                                                                                243
Symfony, la guía definitiva                                        Capítulo 11. Integración con Ajax


  ATENCIÓN
  Aunque es sencillo incluir interacciones complejas, lo más complicado es configurarlas de forma
  que el usuario las perciba como algo natural en la página. Por tanto, solo se deben utilizar cuando
  se está seguro de que va a mejorar la experiencia de usuario. No deberían incluirse cuando su
  efecto es el de confundir al usuario.


11.7.1. Autocompletar
La interacción denominada “autocompletar” consiste en un cuadro de texto que muestra
una lista de valores relacionados con los caracteres que teclea el usuario. Este efecto se
puede conseguir con una única llamada al helper input_auto_complete_tag(), siempre
que la acción remota devuelva una respuesta formateada como una lista de elementos
HTML (<ul> y <li>) similar a la mostrada en el ejemplo 11-30.

Listado 11-30        -   Ejemplo      de   respuesta      compatible       con    el   helper    de
autocompletar
   <ul>
     <li>sugerencia 1</li>
     <li>sugerencia 2</li>
     ...
   </ul>

El helper se puede incluir en cualquier plantilla de la misma forma que se incluiría cualqu-
ier cuadro de texto, como se muestra en el ejemplo 11-31.

Listado 11-31 - Uso del helper de autocompletar en una plantilla
   <?php echo form_tag('mimodulo/miaccion') ?>
     Buscar un autor en función de su nombre:
     <?php echo input_auto_complete_tag('autor', 'nombre por defecto',
       'autor/autocompletar',
       array('autocomplete' => 'off'),
       array('use_style'    => true)
     ) ?>
     <?php echo submit_tag('Buscar') ?>
   </form>

Cada vez que el usuario teclee un carácter en el cuadro de texto autor, se realiza una lla-
mada a la acción remota autor/autocompletar. El código de esa acción depende de cada
caso y aplicación, pero debe obtener una lista de valores sugeridos en forma de lista de
elementos HTML como la mostrada en el listado 11-30. El helper muestra la lista devuel-
ta debajo del cuadro de texto autor y si el usuario pincha sobre un valor o lo selecciona
mediante el teclado, el cuadro de texto se completa con ese valor, tal y como muestra la
figura 11-3.




                              Figura 11.3. Ejemplo de autocompletar

www.librosweb.es                                                                                244
Symfony, la guía definitiva                                      Capítulo 11. Integración con Ajax




El tercer argumento del helper input_auto_complete_tag() puede tomar uno de los sigu-
ientes parámetros:

      ▪ use_style: aplica estilos CSS de forma automática a la lista que se muestra.

      ▪ frequency: frecuencia con la que se realizan peticiones remotas (por defecto son
         0.4 segundos).

      ▪ tokens: permite autocompletar por partes. Si el valor de este parámetro es , y el
         usuario introduce pedro, juan a la acción solo se le pasa el valor juan (siempre
         se le pasa el último valor después de trocear el cuadro de texto según el carácter
         definido por tokens).

  NOTA
  El helper input_auto_complete_tag(), al igual que los que se muestran a continuación, también
  acepta las opciones habituales de los helpers remotos que se han descrito anteriormente en este
  capítulo. Una buena recomendación es la de utilizar los efectos visuales loading y complete para
  mejorar la experiencia de usuario.


11.7.2. Arrastrar y soltar
En las aplicaciones de escritorio suele ser normal coger un elemento con el ratón, mover-
lo y soltarlo en otro lugar. Sin embargo, en las aplicaciones web es mucho más raro de
ver esta técnica, ya que es bastante difícil de programarla a mano con JavaScript. Afortu-
nadamente, en Symfony se puede incluir esta técnica solo con una línea de código.

El framework incluye 2 helpers, draggable_element() y drop_receiving_element(), que se
encargan de modificar el comportamiento de los elementos; estos helpers “observan” a
los elementos y les añaden nuevas habilidades. Se utilizan para declarar a los elementos
como “arrastrable” o como “elemento en el que se pueden soltar los elementos arrastra-
bles”. Un elemento arrastrable se activa cuando se pulsa con el ratón sobre el. Mientras
no se suelte el ratón, el elemento se mueve siguiendo la posición del ratón. Los elemen-
tos en los que se pueden soltar los elementos arrastrables llaman a una función remota
cuando el elemento arrastrable se suelta sobre esa zona. El listado 11-32 muestra un
ejemplo de esta interacción mediante un elemento que hace de carrito de la compra.

Listado 11-32 - Elementos de arrastrar y soltar en un carrito de la compra
   <ul id="elementos">
     <li id="elemento1" class="comida">Zanahoria</li>
     <?php echo draggable_element('elemento1', array('revert' => true)) ?>
     <li id="elemento2" class="comida">Manzana</li>
     <?php echo draggable_element('elemento2', array('revert' => true)) ?>
     <li id="elemento3" class="comida">Naranja</li>
     <?php echo draggable_element('elemento3', array('revert' => true)) ?>
   </ul>
   <div id="carrito">
     <p>El carrito está vacío</p>
     <p>Arrastra y suelta elementos aquí para añadirlos al carrito</p>
   </div>


www.librosweb.es                                                                             245
Symfony, la guía definitiva                                      Capítulo 11. Integración con Ajax

   <?php echo drop_receiving_element('carrito', array(
     'url'        => 'carrito/anadir',
     'accept'     => 'comida',
     'update'     => 'carrito',
   )) ?>

Cada uno de los elementos de la lista se pueden coger con el ratón y moverlos por la
ventana del navegador. Cuando se suelta el ratón, el elemento vuelve a su posición origi-
nal. Si el elemento se suelta sobre el elemento cuyo atributo id es carrito, se realiza
una llamada a la acción remota carrito/anadir. La acción puede determinar el elemento
que se ha añadido mediante el parámetro de petición id. De esta forma, el listado 11-32
es una aproximación muy realista al proceso físico de compra de productos: se cogen los
productos, se sueltan en el carrito y después se realiza el pago.

  SUGERENCIA
  En el lsitado 11-32, los helpers aparecen justo después del elemento que modifican, aunque no es
  obligatorio. Si se quiere, se pueden agrupar todos los helpers draggable_element() y drop_rec-
  eiving_element() al final de la plantilla. Lo único importante es el primer argumento que se pasa
  al helper y que indica el elemento al que se aplica.

El helper draggable_element() acepta los siguientes parámetros:

      ▪ revert: si vale true, el elemento vuelve a su posición original cuando se suelta el
         ratón. También se puede indicar el nombre de una función que se ejecuta cuando
         finaliza el arrastre del elemento.

      ▪ ghosting: realiza una copia del elemento original y el usuario mueve la copia,
         quedando inmóvil el elemento original.

      ▪ snap: si vale false, el movimiento del elemento es libre. En otro caso, el elemen-
         to solo se puede desplazar de forma escalonada como si estuviera una gran reji-
         lla a la que se ajusta el elemento. El valor del desplazamiento horizontal (x) y
         vertical (y) del elemento se puede definir como xy, [x,y] o function(x,y){ re-
         turn [x,y] }.

El helper drop_receiving_element() acepta los siguientes parámetros:

      ▪ accept: una cadena de texto o un array de cadenas de texto que representan a
         valores de clases CSS. Este elemento solo permitirá que se suelten sobre el los
         elementos cuyas clases CSS contengan al menos uno de los valores indicado.

      ▪ hoverclass: clase CSS que se añade al elemento cuando el usuario arrastra (sin
         soltarlo) un elemento sobre esta zona.

11.7.3. Listas ordenables
Otra posibilidad que brindan los elementos arrastrables es la de ordenar una lista mov-
iendo sus elementos con el ratón. El helper sortable_element() añade este comportam-
iento a los elementos de la lista, como se muestra en el ejemplo del listado 11-33.

Listado 11-33 - Ejemplo de lista ordenable


www.librosweb.es                                                                              246
Symfony, la guía definitiva                                  Capítulo 11. Integración con Ajax

   <p>What do you like most?</p>
   <ul id="ordenar">
     <li id="elemento_1" class="ordenable">Zanahorias</li>
     <li id="elemento_2" class="ordenable">Manzanas</li>
     <li id="elemento_3" class="ordenable">Naranjas</li>
     // A nadie le gustan las coles de Bruselas
     <li id="elemento_4">Coles de Bruselas</li>
   </ul>
   <div id="respuesta"></div>
   <?php echo sortable_element('ordenar', array(
     'url'    => 'elemento/ordenar',
     'update' => 'respuesta',
     'only'   => 'ordenable',
   )) ?>

Gracias a la magia del helper sortable_element(), la lista <ul> se transforma en una lista
ordenable dinámicamente, de forma que sus elementos se pueden reordenar mediante la
técnica de arrastras y soltar. Cada vez que el usuario mueve un elemento y lo suelta pa-
ra reordenar la lista, se realiza una petición Ajax con los siguientes parámetros:
   POST /sf_sandbox/web/frontend_dev.php/elemento/ordenar HTTP/1.1
     ordenar[]=1&ordenar[]=3&ordenar[]=2&_=

La lista completa se pasa como un array con el formato ordenar[$rank]=$id, el $rank em-
pieza en 0 y el $id es el valor que se indica después del guión bajo (_) en el valor del
atributo id de cada elemento de la lista. El atributo id de la lista completa (ordenar en
este caso) se utiliza para el nombre del array de parámetros que se pasan al servidor.

El helper sortable_element() acepta los siguientes parámetros:

      ▪ only: una cadena de texto o un array de cadenas de texto que representan a va-
        lores de clases CSS. Solamente se podrán mover los elementos de la lista que
        tengan este valor en su atributo class.

      ▪ hoverclass: clase CSS que se añade a la lista cuando el usuario posiciona el pun-
        tero del ratón encima de ella.

      ▪ overlap: su valor debería ser horizontal si los elementos de la lista se muestran
        de forma horizontal y su valor debería ser vertical (que es el valor por defecto)
        cuando los elementos se muestran cada uno en una línea (como se muestran por
        defecto las listas en HTML).

      ▪ tag: si la lista reordenable no contiene elemento <li>, se debe indicar la etiqueta
        que define los elementos que se van a hacer reordenables (por ejemplo div o
        dl).


11.7.4. Edición directa de contenidos
Cada vez más aplicaciones web permiten editar los contenidos de sus páginas sin necesi-
dad de utilizar formularios que incluyen el contenido de la página. El funcionamiento de
esta interacción es muy sencillo. Cuando el usuario pasa el ratón por encima de un bloq-
ue de texto, este se resalta. Si el usuario pincha sobre el bloque, el texto se convierte en
un control de formulario llamado área de texto (textarea) que muestra el texto original.

www.librosweb.es                                                                         247
Symfony, la guía definitiva                                  Capítulo 11. Integración con Ajax


Además, se muestra un botón para guardar los cambios. El usuario realiza los cambios
en el texto original y pulsa sobre el botón de guardar los cambios. Una vez guardado, el
área de texto desaparece y el texto modificado se vuelve a mostrar de forma normal.
Con Symfony, toda esta interacción se puede realizar aplicando el helper input_in_pla-
ce_editor_tag() al elemento. El listado 11-34 muestra el uso de este helper.

Listado 11-34 - Ejemplo de texto editable
   <div id="modificame">Puedes modificar este texto</div>
   <?php echo input_in_place_editor_tag('modificame', 'mimodulo/miaccion', array(
     'cols'        => 40,
     'rows'        => 10,
   )) ?>

Cuando el usuario pincha sobre el texto editable, se reemplaza por un cuadro de texto
que contiene el texto original y que se puede modificar. Al guardar los cambios, se llama
mediante Ajax a la acción mimodulo/miaccion con el contenido modificado como valor del
parámetro value. El resultado de la acción actualiza el elemento editable. Se trata de una
interacción muy rápida de incluir y muy poderosa.

El helper input_in_place_editor_tag() acepta los siguientes parámetros:

      ▪ cols y rows: el tamaño (en filas y columnas) del área de texto que se muestra
        para editar el contenido original (si el valor de rows es mayor que 1, se muestra
        un <textarea>; en otro caso, se muestra un <input type=”text”>).

      ▪ loadTextURL: la URI de la acción que se llama para obtener el texto que se debe
        editar. Se trata de una opción útil cuando el contenido del elemento tiene un for-
        mato especial y se quiere que el usuario edite el texto sin ese formato aplicado.

      ▪ save_text y cancel_text: el texto del enlace para guardar los cambios (el valor
        por defecto es “ok”) y el del enlace para cancelar los cambios (el valor por defec-
        to es “cancel”).


11.8. Resumen
Si estás cansado de escribir código JavaScript en las plantillas para incluir efectos en el
navegador del usuario, los helpers de JavaScript de Symfony son una alternativa más
sencilla. No solo automatizan los enlaces JavaScript tradicionales y la actualización de los
elementos, sino que también permiten incluir interacciones Ajax de forma muy sencilla.
Gracias a las mejoras que Prototype proporciona a la sintaxis de JavaScript y gracias a
los efectos visuales de la librería script.aculo.us, hasta las interacciones más complejas
se pueden realizar con unas pocas líneas de código.

Y como en Symfony es igual de fácil hacer una página estática que una página completa-
mente interactiva y dinámica, las aplicaciones web pueden incluir todas las interacciones
tradicionales de las aplicaciones de escritorio.




www.librosweb.es                                                                         248
Symfony, la guía definitiva                                      Capítulo 12. Uso de la cache




Capítulo 12. Uso de la cache
Una de las técnicas disponibles para mejorar el rendimiento de una aplicación consiste en
almacenar trozos de código HTML o incluso páginas enteras para poder servirlas en futu-
ras peticiones. Esta técnica se denomina “utilizar caches” y se pueden definir tanto en el
lado del servidor como en el del cliente.

Symfony incluye un sistema de cache en el servidor muy flexible. Con este sistema es
muy sencillo guardar en un archivo una página entera, el resultado de una acción, un
elemento parcial o un trozo de plantilla. La configuración del sistema de cache se realiza
de forma intuitiva mediante archivos de tipo YAML. Cuando los datos se modifican, se
pueden borrar partes de la cache de forma selectiva mediante la línea de comandos o
mediante algunos métodos especiales en las acciones. Symfony también permite contro-
lar la cache en el lado del cliente mediante las cabeceras de HTTP 1.1. En este capítulo se
presentan todas estas técnicas y se dan pistas para determinar las mejoras que las ca-
ches confieren a las aplicaciones.


12.1. Guardando la respuesta en la cache
El principio básico de las caches de HTML es muy sencillo: parte o todo el código HTML
que se envía al usuario como respuesta a su petición se puede reutilizar en peticiones si-
milares. El código HTML se almacena en un directorio especial (el directorio cache/) don-
de el controlador frontal lo busca antes de ejecutar la acción. Si se encuentra el código
en la cache, se envía sin ejecutar la acción, por lo que se consigue un gran ahorro de
tiempo de ejecución. Si no se encuentra el código, se ejecuta la acción y su respuesta (la
vista) se guarda en el directorio de la cache para las futuras peticiones.

Como todas las páginas pueden contener información dinámica, la cache HTML está
deshabilitada por defecto. El administrador del sitio web debe activarla para mejorar el
rendimiento de la aplicación.

Symfony permite gestionar 3 tipos diferentes de cache HTML:

      ▪ Cache de una acción (con o sin layout)

      ▪ Cache de un elemento parcial, de un componente o de un slot de componentes

      ▪ Cache de un trozo de plantilla

Los dos primeros tipos de cache se controlan mediante archivos YAML de configuración.
La cache de trozos de plantillas se controla mediante llamadas a helpers dentro de las
propias plantillas.

12.1.1. Opciones de la cache global
La cache HTML se puede habilitar y deshabilitar (su valor por defecto) para cada aplica-
ción de un proyecto y para cada entorno mediante la opción cache del archivo set-
tings.yml. El listado 12-1 muestra como habilitar la cache.

Listado 12-1 - Activando la cache, en miapp/config/settings.yml


www.librosweb.es                                                                        249
Symfony, la guía definitiva                                      Capítulo 12. Uso de la cache

   dev:
     .settings:
        cache:                    on


12.1.2. Guardando una acción en la cache
Las acciones que muestran información estática (que no depende de bases de datos ni de
información guardada en la sesión) y las acciones que leen información de una base de
datos pero no la modifican (acciones típicas del método GET) son el tipo de acción ideal
para almacenar su resultado en la cache. La figura 12-1 muestra los elementos de la pá-
gina que se guardan en la cache en este caso: o el resultado de la acción (su plantilla) o
el resultado de la acción junto con el layout.




                        Figura 12.1. Guardando una acción en la cache


Si se dispone por ejemplo de una acción usuario/listado que devuelve un listado de to-
dos los usuarios de un sitio web, a no ser que se modifique, añada o elimine un usuario
(que se verá más adelante en la sección “Eliminar elementos de la cache”) la lista contie-
ne siempre la misma información, por lo que esta acción es ideal para guardarla en la
cache.

La activación de la cache y las opciones para cada acción se definen en el archivo ca-
che.yml del directorio config/ del módulo. El listado 12-2 muestra un ejemplo de este
archivo.

Listado 12-2 - Activando la cache de una acción, en miapp/modules/usuario/config/
cache.yml
   listado:
     enabled:     on
     with_layout: false       # Valor por defecto
     lifetime:    86400       # Valor por defecto

La anterior configuración activa la cache para la acción listado y el layout no se guarda
junto con el resultado de la acción (que además, es el comportamiento por defecto). Por
tanto, aunque exista en la cache el resultado de la acción, el layout completo (junto con
sus elementos parciales y componentes) se sigue ejecutando. Si la opción with_layout
vale true, en la cache se guarda el resultado de la acción junto con el layout, por lo que
este último no se vuelve a ejecutar.

Para probar las opciones de la cache, se accede con el navegador a la acción en el entor-
no de desarrollo.


www.librosweb.es                                                                        250
Symfony, la guía definitiva                                                Capítulo 12. Uso de la cache

   http://miapp.ejemplo.com/miapp_dev.php/usuario/listado

Ahora se puede apreciar un borde que encierra la zona del área en la página. La primera
vez, el área tiene una cabecera azul, lo que indica que no se ha obtenido de la cache. Si
se recarga la página, el área de la acción muestra una cabecera amarilla, indicando que
esta vez sí se ha obtenido directamente de la cache (resultando en una gran reducción
en el tiempo de respuesta de la acción). Más adelante en este capítulo se detallan las for-
mas de probar y monitorizar el funcionamiento de la cache.

  NOTA
  Los slots son parte de la plantilla, por lo que si se guarda el resultado de una acción en la cache,
  también se guarda el valor de los slots definidos en la plantilla de la acción. De esta forma, la cache
  funciona de forma nativa para los slots.

El sistema de cache también funciona para las páginas que utilizan parámetros. El módu-
lo usuario anterior podría disponer de una acción llamada ver y a la que se pasa como
parámetro una variable llamada id para poder mostrar los detalles de un usuario. El lis-
tado 12-3 muestra como modificar los cambios necesarios en el archivo cache.yml para
habilitar la cache también en esta acción.

Se puede organizar de forma más clara el archivo cache.yml reagrupando las opciones
comunes a todas las acciones del módulo bajo la clave all:, como también muestra el
listado 12-3.

Listado 12-3 - Ejemplo de cache.yml completo, en miapp/modules/usuario/config/
cache.yml
   listado:
     enabled:      on
   ver:
     enabled:      on

   all:
     with_layout: false       # Valor por defecto
     lifetime:    86400       # Valor por defecto

Ahora, cada llamada a la acción usuario/ver que tenga un valor del parámetro id dife-
rente, crea un nuevo archivo en la cache. De esta forma, la cache para la petición:
   http://miapp.ejemplo.com/usuario/ver/id/12

es completamente diferente de la cache de la petición:
   http://miapp.ejemplo.com/usuario/ver/id/25


  ATENCIÓN
  Las acciones que se ejecutan mediante el método POST o que tienen parámetros GET no se guar-
  dan en la cache.

La opción with_layout merece una explicación más detallada. Esta opción determina el ti-
po de información que se guarda en la cache. Si vale true, solo se almacenan en la cache
el resultado de la ejecución de la plantilla y las variables de la acción. Si la opción vale



www.librosweb.es                                                                                    251
Symfony, la guía definitiva                                      Capítulo 12. Uso de la cache


false, se guarda el objeto response entero. Por tanto, la cache en la que se guarda el la-
yout (valor true) es mucho más rápido que la cache sin el layout.

Si es posible, es decir, si el layout no depende por ejemplo de datos de sesión, es conve-
niente optar por la opción que guarda el layout en la cache. Desgraciadamente, el layout
normalmente contiene elementos dinámicos (como por ejemplo el nombre del usuario
que está conectado), por lo que la opción habitual es la de no almacenar el layout en la
cache. No obstante, las páginas que no depende de cookies, los canales RSS, las venta-
nas emergentes, etc. se pueden guardar en la cache incluyendo su layout.

12.1.3. Guardando un elemento parcial, un componente o un slot de
componentes en la cache
En el Capítulo 7 se explicó la forma de reutilizar trozos de código en varias plantillas me-
diante el helper include_partial(). Guardar un elemento parcial en la cache es tan sen-
cillo como hacerlo en una acción y se activa de la misma forma, tal y como muestra la fi-
gura 12-2.




 Figura 12.2. Guardando un elemento parcial, un componente o un slot de componentes
                                    en la cache


El listado 12-4 por ejemplo muestra los cambios necesarios en el archivo cache.yml para
activar la cache en el elemento parcial _mi_parcial.php que pertenece al módulo usuario.
La opción with_layout no tiene sentido en este caso.

Listado 12-4 - Guardando un elemento parcial en la cache, en miapp/modules/us-
uario/config/cache.yml
   _mi_parcial:
     enabled:      on
   listado:
     enabled:      on
   ...

Ahora todas las plantillas que incluyen este elemento parcial no ejecutan su código PHP,
sino que utilizan la versión almacenada en la cache.
   <?php include_partial('usuario/mi_parcial') ?>

Al igual que sucede en las acciones, la información que se guarda en la cache depende de
los parámetros que se pasan al elemento parcial. El sistema de cache almacena tantas
versiones diferentes como valores diferentes de parámetros se pasen al elemento parcial.


www.librosweb.es                                                                        252
Symfony, la guía definitiva                                                 Capítulo 12. Uso de la cache

   <?php include_partial('usuario/mi_otro_parcial', array('parametro' => 'valor')) ?>


  SUGERENCIA
  Guardar la acción en la cache es más avanzado que guardar elementos parciales, ya que cuando
  una acción se encuentra en la cache, la plantilla ni siquiera se ejecuta; si la plantilla incluye elemen-
  tos parciales, no se realizan las llamadas a esos elementos parciales. Por tanto, guardar elementos
  parciales en la cache solo es útil cuando no se está guardando en la cache la acción que se ejecuta
  o para los elementos parciales incluidos en el layout.

Recordando lo que se explicó en el Capítulo 7: un componente es una pequeña acción
que utiliza como vista un elemento parcial y un slot de componentes es un componente
para el que la acción varía en función de las acciones que se ejecuten. Estos dos elemen-
tos son similares a los elementos parciales, por lo que el funcionamiento de su cache es
muy parecido. Si el layout global incluye un componente llamado dia mediante include_-
component(’general/dia’) para mostrar la fecha, el archivo cache.yml del módulo gene-
ral debería activar la cache de ese componente de la siguiente forma:
   _dia:
     enabled: on

Cuando se guarda un componente o un elemento parcial en la cache, se debe decidir si
se almacena solo una versión para todas las plantillas o una versión para cada plantilla.
Por defecto, los componentes se guardan independientemente de la plantilla que lo inclu-
ye. No obstante, los componentes contextuales, como por ejemplo los componentes que
muestran una zona lateral diferente en cada acción, deben almacenarse tantas veces co-
mo el número de plantillas diferentes que los incluyan. El sistema de cache se encarga
automáticamente de este último caso, siempre que se establezca el valor true a la opción
contextual:
   _dia:
     contextual: true
     enabled:   on


  NOTA
  Los componentes globales (los que se guardan en el directorio templates/ de la aplicación) tam-
  bién se pueden guardar en la cache, siempre que se configuren sus opciones de cache en el archi-
  vo cache.yml de la aplicación.


12.1.4. Guardando un fragmento de plantilla en la cache
Guardar en la cache el resultado completo de una acción solamente es posible para algu-
nas acciones. Para el resto de acciones, las que actualizan información y las que mues-
tran en la plantilla información que depende de la sesión, todavía es posible mejorar su
rendimiento mediante la cache, pero de forma muy diferente. Symfony incluye un tercer
tipo de cache, que se utiliza para los fragmentos de las plantillas y que se activa directa-
mente en la propia plantilla, como se muestra en la figura 12-3.




www.librosweb.es                                                                                      253
Symfony, la guía definitiva                                                Capítulo 12. Uso de la cache




                Figura 12.3. Guardando un fragmento de plantilla en la cache


Si por ejemplo se dispone de un listado de usuarios que muestra un enlace al último us-
uario que se ha accedido, esta última información es dinámica. El helper cache() define
las partes de la plantilla que se pueden guardar en la cache. El listado 12-5 muestra los
detalles sobre su sintaxis.

Listado 12-5 - Uso del helper cache(), en miapp/modules/usuario/templates/
listadoSuccess.php
   <!-- Código que se ejecuta cada vez -->
   <?php echo link_to('Último usuario accedido', 'usuario/
   ver?id='.$id_ultimo_usuario_accedido) ?>

   <!-- Código guardado en la cache -->
   <?php if (!cache('usuarios')): ?>
     <?php foreach ($usuarios as $usuario): ?>
       <?php echo $usuario->getNombre() ?>
     <?php endforeach; ?>
     <?php cache_save() ?>
   <?php endif; ?>

Así es como funciona esta cache:

      ▪ Si se encuentra en la cache una versión del fragmento llamado ‘usuarios’, se uti-
        liza para reemplazar todo el código existente entre <?php if (!cache(’usuar-
         ios’)): ?> y <?php endif; ?>.

      ▪ Si no se encuentra, se ejecuta el código definido entre esas 2 líneas y el resulta-
        do se guarda en la cache identificado con el nombre indicando en la llamada al
        helper cache().

Todo el código que no se incluye entre esas dos líneas, se ejecuta siempre y por tanto
nunca se guarda en la cache.

  ATENCIÓN
  La acción (listado en este ejemplo) no puede tener activada la cache, ya que en ese caso, no se
  ejecutaría la plantilla y se ignoraría por completo la declaración de la cache de los fragmentos.

La mejora en la velocidad de la aplicación cuando se utiliza esta cache no es tan signifi-
cativa como cuando se guarda en la cache la acción entera, ya que en este caso siempre
se ejecuta la acción, la plantilla se procesa al menos de forma parcial y siempre se utiliza
el layout para decorar la plantilla.

www.librosweb.es                                                                                      254
Symfony, la guía definitiva                                             Capítulo 12. Uso de la cache


Se pueden guardar otros fragmentos de la misma plantilla en la cache; sin embargo, en
este caso se debe indicar un nombre único a cada fragmento, de forma que el sistema de
cache de Symfony pueda encontrarlos cuando sea necesario.

Como sucede con las acciones y los componentes, los fragmentos que se guardan en la
cache pueden tener definido un tiempo de vida en segundos como segundo argumento
de la llamada al helper cache().
   <?php if (!cache('usuarios', 43200)): ?>

Si no se indica explícitamente en el helper, se utiliza el valor por defecto para el tiempo
de vida de la cache (que son 86400 segundos, equivalentes a 1 día).

  SUGERENCIA
  Otra forma de hacer que una acción se pueda guardar en la cache es pasar las variables que modi-
  fican su comportamiento en el patrón del sistema de enrutamiento de la acción. Si la página princi-
  pal muestra el nombre del usuario que está conectado, no se puede cachear la página a menos
  que la URL contenga el nombre del usuario. Otro caso es el de las aplicaciones multi-idioma: si se
  quiete activar la cache para una página que tiene varias traducciones, el código del idioma debería
  incluirse dentro del patrón de la URL. Aunque este truco aumenta el número de páginas que se
  guardan en la cache, puede ser muy útil para acelerar las aplicaciones que son muy interactivas.


12.1.5. Configuración dinámica de la cache
El archivo cache.yml es uno de los métodos disponibles para definir las opciones de la ca-
che, pero tiene el inconveniente de que no se puede modificar de forma dinámica. No
obstante, como sucede habitualmente en Symfony, se puede utilizar código PHP en vez
de archivos YAML, por lo que se puede configurar de forma dinámica la cache.

¿Para qué puede ser útil modificar dinámicamente las opciones de la cache? Un ejemplo
práctico puede ser el de una página que es diferente para los usuarios autenticados y pa-
ra los usuarios anónimos, aunque la URL sea la misma. Si se dispone de una página crea-
da por la acción articulo/ver y que contiene un sistema de puntuación para los artículos,
el sistema de puntuación podría estar deshabilitado para los usuarios anónimos. Para es-
te tipo de usuarios, se muestra el formulario para registrarse cuando pinchan en el siste-
ma de puntuación. Esta versión de la página se puede guardar tranquilamente en la ca-
che. Por otra parte, los usuarios autenticados que pinchan sobre el sistema de puntua-
ción, generan una petición POST que se emplea para calcular la nueva puntuación del
artículo. En esta ocasión, la cache se debería deshabilitar para que Symfony cree la pági-
na de forma dinámica.

El sitio adecuado para definir las opciones dinámicas de la cache es en un filtro que se
ejecute antes de sfCacheFilter. De hecho, todo el sistema de cache es un filtro de Sym-
fony, como también lo son la barra de depuración de aplicaciones y las opciones de segu-
ridad. Para habilitar la cache en la acción articulo/ver solo cuando el usuario no está au-
tenticado, se crea el archivo conditionalCacheFilter en el directorio lib/ de la aplica-
ción, tal y como se muestra en el listado 12-6.

Listado    12-6     -   Configurando        la   cache     mediante      PHP,     en    miapp/lib/
conditionalCacheFilter.class.php

www.librosweb.es                                                                                255
Symfony, la guía definitiva                                          Capítulo 12. Uso de la cache

   class conditionalCacheFilter extends sfFilter
   {
     public function execute($filterChain)
     {
       $contexto = $this->getContext();
       if (!$contexto->getUser()->isAuthenticated())
       {
         foreach ($this->getParameter('pages') as $page)
         {
           $contexto->getViewCacheManager()->addCache($page['module'],
   $page['action'],array('lifeTime' => 86400));
         }
       }

           // Ejecutar el siguiente filtro
           $filterChain->execute();
       }
   }

Este filtro se debe registrar en el archivo filters.yml antes de sfCacheFilter, como se
muestra en el listado 12-7.

Listado 12-7 - Registrando un filtro propio, en miapp/config/filters.yml
   ...
   security: ~

   conditionalCache:
     class: conditionalCacheFilter
     param:
       pages:
         - { module: articulo, action: ver }

   cache: ~
   ...

Para que la cache condicional pueda utilizarse, solo es necesario borrar la cache de Sym-
fony para que se autocargue la clase del nuevo filtro. La cache solo se habilitará para las
páginas definidas en el parámetro pages y solo para los usuarios que no están
autenticados.

El método addCache() del objeto sfViewCacheManager requiere como parámetros el nom-
bre de un módulo, el nombre de una acción y un array asociativo con las mismas opcio-
nes que se definen en el archivo cache.yml. Si por ejemplo se necesita guardar en la ca-
che la acción articulo/ver con el layout y con un tiempo de vida de 300 segundos, se
puede utilizar el siguiente código:
   $contexto->getViewCacheManager()->addCache('articulo', 'ver', array(
     'withLayout' => true,
     'lifeTime'   => 3600,
   ));

  Almacenamiento alternativo para la cache

  Por defecto, la cache de Symfony guarda sus datos en archivos almacenados en el disco duro del
  servidor. No obstante, existen métodos alternativos como almacenar los contenidos en la memoria

www.librosweb.es                                                                            256
Symfony, la guía definitiva                                              Capítulo 12. Uso de la cache


  (utilizando memcache por ejemplo) o en una base de datos (útil si se quiere compartir la cache en-
  tre varios servidores o si se quiere poder borrar rápidamente la cache). En cualquier caso, es muy
  sencillo modificar el modo de almacenamiento de la cache de Symfony porque la clase PHP que
  utiliza el gestor de la cache está definida en el archivo factories.yml.

  La clase sfFileCache es la factoría que emplea por defecto la cache:
      view_cache:
        class: sfFileCache
        param:
          automaticCleaningFactor: 0
          cacheDir:                %SF_TEMPLATE_CACHE_DIR%

  Se puede reemplazar el valor de la opción class con una clase propia de almacenamiento de la
  cache o con una de las alternativas disponibles en Symfony (por ejemplo sfSQLiteCache). Los
  parámetros definidos en la clave param se pasan al método initialize() de la clase utilizada en
  forma de array asociativo. Cualquier clase definida para controlar el almacenamiento de la cache
  debe implementar todos los métodos de la clase abstracta sfCache. La documentación de la API
  (http://www.symfony-project.com/api/symfony.html ) tiene más información sobre este tema.


12.1.6. Uso de la cache super rápida
Todas las páginas guardadas en la cache que se han explicado anteriormente implican la
ejecución de algo de código PHP. En este tipo de páginas, Symfony carga toda la configu-
ración, crea la respuesta, etc. Si se está completamente seguro de que una página no va
a cambiar durante un periodo de tiempo, se puede saltar completamente Symfony si se
guarda en la carpeta web/ el código HTML completo de la página. Este funcionamiento es
posible gracias a las opciones del módulo mod_rewrite de Apache, siempre que la regla
de enrutamiento defina un patrón que no termine en ningún sufijo o en .html.

Para guardar las páginas completas en la cache, se puede acceder manualmente a todas
las páginas mediante la siguiente instrucción ejecutada en la línea de comandos:
   > curl http://miapp.ejemplo.com/usuario/listado.html > web/usuario/listado.html

Una vez ejecutado el anterior comando, cada vez que se realice una petición a la acción
usuario/listado, Apache encuentra la página listado.html y la sirve directamente sin
llegar a ejecutar Symfony. Aunque la desventaja es que no se puede controlar mediante
Symfony las opciones de esa cache (tiempo de vida, borrado automático, etc.) la gran
ventaja es el increíble aumento del rendimiento de la aplicación.

Una forma más cómoda de generar estas páginas estáticas es la de utilizar el plugin
sfSuperCache, que automatiza todo el proceso, permite definir el tiempo de vida de la ca-
che e incluso permite el borrado de las páginas guardadas en la cache. El Capítulo 17 in-
cluye más información sobre los plugins.

  Otras técnicas para mejorar el rendimiento

  Además de la cache de HTML, Symfony dispone de otros dos mecanismos de cache, que son com-
  pletamente automáticos y transparentes para el programador. En el entorno de producción, la confi-
  guración y las traducciones de las plantillas se guardan automáticamente en la cache en los direc-
  torios miproyecto/cache/config/ y miproyecto/cache/i18n/.


www.librosweb.es                                                                                257
Symfony, la guía definitiva                                             Capítulo 12. Uso de la cache


  Los aceleradores PHP (eAccelerator, APC, XCache, etc.), también llamados módulos que guardan
  los opcodes en la cache, mejoran el rendimiento de los scripts PHP al guardar en una cache la ver-
  sión compilada de los scripts, por lo que se elimina el procesamiento y compilación de los scripts
  cada vez que se ejecutan. Las clases de Propel contienen muchísimo código PHP, por lo que son
  las que más se benefician de esta técnica. Todos estos aceleradores son compatibles con Symfony
  y pueden fácilmente triplicar el rendimiento de cualquier aplicación. Se recomienda su uso en los
  servidores de producción de las aplicaciones utilizadas por muchos usuarios.

  Con un acelerador PHP, se pueden almacenar datos en la memoria mediante la clase sfPro-
  cessCache, para no tener que realizar el mismo procesamiento en cada petición. Además, si se qu-
  iere almacenar el resultado de una función que consume una gran cantidad de CPU para su reutili-
  zación posterior, es posible utilizar el objeto sfFunctionCache. El Capítulo 18 muestra los detalles
  sobre estos dos mecanismos.


12.2. Eliminando elementos de la cache
Si se modifican los scripts o los datos de la aplicación, la información de la cache estará
desfasada. Para evitar incoherencias y posibles errores, se pueden eliminar partes de la
cache de varias formas en función de las necesidades de cada caso.

12.2.1. Borrando toda la cache
La tarea clear-cache del comando symfony se emplea para borrar la cache (la cache de
HTML, de configuración y de internacionalización). Para borrar solo una parte de la cache,
se pueden pasar parámetros, tal y como se muestra en el listado 12-8. Este comando so-
lo se puede ejecutar desde el directorio raíz del proyecto.

Listado 12-8 - Borrando la cache
   // Borrar toda la cache
   > symfony clear-cache

   // Atajo para borrar toda la cache
   > symfony cc

   // Borrar solo la cache de la aplicación miapp
   > symfony clear-cache miapp

   // Borrar solo la cache HTML de la aplicación miapp
   > symfony clear-cache miapp template

   // Borrar solo la cache de configuración de la aplicación miapp
   > symfony clear-cache miapp config


12.2.2. Borrando partes de la cache
Cuando se modifican los datos de la base de datos, debería borrarse la cache de las acc-
iones que tienen relación con los datos modificados. Aunque se podría borrar la cache en-
tera, en este caso se borraría también la cache de todas las acciones que no tienen rela-
ción con los datos modificados. Por este motivo, Symfony proporciona el método remo-
ve() del objeto sfViewCacheManager. El argumento que se le pasa es una URI interna (tal


www.librosweb.es                                                                                 258
Symfony, la guía definitiva                                        Capítulo 12. Uso de la cache


y como se utilizan por ejemplo en la función link_to()) y se elimina la cache de la acción
relacionada con esa URI.

Si se dispone de una acción llamada modificar en el módulo usuario, esta acción modifi-
ca el valor de los datos de los objetos Usuario. Las páginas de las acciones listado y ver
de este módulo que se guardan en la cache deberían borrarse, ya que en otro caso, se
mostrarían datos desfasados. Para borrar estas páginas de la cache, se utiliza el método
remove() tal y como muestra el listado 12-9.

Listado 12-9 - Borrando la cache de una acción, en modules/usuario/actions/
actions.class.php
   public function executeModificar()
   {
     // Modificar un usuario
     $id_usuario = $this->getRequestParameter('id');
     $usuario = UsuarioPeer::retrieveByPk($id_usuario);
     $this->foward404Unless($usuario);
     $usuario->setNombre($this->getRequestParameter('nombre'));
     ...
     $usuario->save();

       // Borrar la cache de las acciones relacionadas con este usuario
       $cacheManager = $this->getContext()->getViewCacheManager();
       $cacheManager->remove('usuario/listado');
       $cacheManager->remove('usuario/ver?id='.$id_usuario);
       ...
   }

Eliminar de la cache los elementos parciales, los componentes y los slots de componentes
es un poco más complicado. Como se les puede pasar cualquier tipo de parámetro (inclu-
so objetos), es casi imposible identificar la versión guardada en la cache en cada caso.
Como la explicación es idéntica para los 3 tipos de elementos, solo se va a explicar el
proceso para los elementos parciales. Symfony identifica los elementos parciales almace-
nados en la cache mediante un prefijo especial (sf_cache_partial), el nombre del módu-
lo, el nombre del elemento parcial y una clave única o hash generada a partir de todos
los parámetros utilizados en la llamada a la función:
   // Un elemento parcial que se llama así
   <?php include_partial('usuario/mi_parcial', array('user' => $user) ?>

   // Se identifica en la cache de la siguiente manera
   /sf_cache_partial/usuario/_mi_parcial/sf_cache_key/bf41dd9c84d59f3574a5da244626dcc8

En teoría, es posible eliminar un elemento parcial guardado en la cache mediante el mé-
todo remove() siempre que se conozca el valor de todos loa parámetros utilizados en ese
elemento, aunque en la práctica es casi imposible conseguirlo. Afortunadamente, si se
añade un parámetro denominado sf_cache_key en la llamada del helper include_part-
ial(), se puede definir un identificador fácil de recordar para ese elemento parcial. De
esta forma, y como muestra el listado 12-10, es fácil borrar un elemento parcial:

Listado 12-10 - Borrando elementos parciales de la cache


www.librosweb.es                                                                          259
Symfony, la guía definitiva                                             Capítulo 12. Uso de la cache

   <?php include_partial('usuario/mi_parcial', array(
     'user'         => $user,
     'sf_cache_key' => $user->getId()
   ) ?>

   // Se identifica en la cache de la siguiente forma
   /sf_cache_partial/usuario/_mi_parcial/sf_cache_key/12

   // Se puede borrar _mi_parcial para un usuario específico
   $cacheManager->remove('@sf_cache_partial?module=usuario&action=_mi_parcial&sf_cache_key='.$user->get

Este método no se puede utilizar para borrar todas las versiones de un elemento parcial
guardadas en la cache. Más adelante, en la sección “Borrando la cache a mano” se deta-
lla como conseguirlo.

El método remove() también se emplea para borrar fragmentos de plantillas. El nombre
que identifica a cada fragmento en la cache se compone del perfijo sf_cache_partial, el
nombre del módulo, el nombre de la acción y el valor de sf_cache_key (el identificador ú-
nico utilizado en la llamada al helper cache()). El listado 12-11 muestra un ejemplo.

Listado 12-11 - Borrando fragmentos de plantilla en la cache
   <!-- Código guardado en la cache -->
   <?php if (!cache('usuarios')): ?>
     ... // Lo que sea...
     <?php cache_save() ?>
   <?php endif; ?>

   // Se identifica en la cache de la siguiente forma
   /sf_cache_partial/usuario/listado/sf_cache_key/usuarios

   // Se puede borrar con el siguiente método
   $cacheManager->remove('@sf_cache_partial?module=usuario&action=listado&sf_cache_key=usuarios');

  El borrado selectivo de la cache es realmente complicado

  La parte más complicada del borrado de la cache es la de determinar que acciones se ven afecta-
  das por la modificación de los datos.

  Imagina que dispones de una aplicación con un módulo llamado publicacion y las acciones lis-
  tado y ver, además de estar relacionada con un autor (representado por la clase Usuario). Si se
  modifican los datos de un Usuario, se verán afectadas todas las publicaciones de ese autor y el lis-
  tado de las publicaciones. Por tanto, en la acción modificar del módulo usuario se debería añadir
  lo siguiente:
      $c = new Criteria();
      $c->add(PublicacionPeer::AUTOR_ID, $this->getRequestParameter('id'));
      $publicaciones = PublicacionPeer::doSelect($c);

      $cacheManager = sfContext::getInstance()->getViewCacheManager();
      foreach ($publicaciones as $publicacion)
      {
        $cacheManager->remove('publicacion/ver?id='.$publicacion->getId());
      }
      $cacheManager->remove('publicacion/listado');


www.librosweb.es                                                                                 260
Symfony, la guía definitiva                                             Capítulo 12. Uso de la cache


  Si se utiliza la cache HTML, es necesario disponer de una visión clara de las dependencias y relac-
  iones entre el modelo y las acciones, de forma que no se produzcan errores por no comprender
  completamente esas relaciones. Debe tenerse en cuenta que todas las acciones que modifican el
  modelo seguramente deben incluir una serie de llamadas al método remove() si se utiliza la cache
  HTML.

  Cuando la situación sea realmente complicada, siempre se puede borrar la cache entera cada vez
  que se actualiza la base de datos.


12.2.3. Estructura del directorio de la cache
El directorio cache/ de cada aplicación tiene la siguiente estructura:
   cache/                   # sf_root_cache_dir
     [nombre_aplicacion]/ # sf_base_cache_dir
       [nombre_entorno]/    # sf_cache_dir
          config/           # sf_config_cache_dir
          i18n/             # sf_i18n_cache_dir
          modules/          # sf_module_cache_dir
          template/         # sf_template_cache_dir
            [nombre_servidor]/
              all/

Las plantillas se guardan en la cache bajo el directorio [nombre_servidor] (sustituyendo
los puntos por guiones bajos para mantener la compatibilidad con algunos sistemas de fi-
cheros) y siguiendo una estructura relacionada con la URL. Por ejemplo, la plantilla de la
siguiente página:
   http://www.miapp.com/usuario/ver/id/12

se guarda en el siguiente directorio de la cache:
   cache/miapp/prod/template/www_miapp_com/all/usuario/ver/id/12.cache

El código no debería incluir las rutas de los archivos escritas manualmente. En su lugar,
se deben utilizar las constantes definidas para las rutas. Para obtener por ejemplo la ruta
absoluta del directorio template/ para la aplicación y entorno actuales, se emplea
sfConfig::get(’sf_template_cache_dir’).

Conocer la estructura de directorios es muy útil cuando se tienen que borrar manualmen-
te partes de la cache.

12.2.4. Borrado manual de la cache
El borrado de la cache entre diferentes aplicaciones suele ser problemático. Si por ejem-
plo un administrador modifica los datos de la tabla usuario en la aplicación backend (la
aplicación de gestión), se deberían borrar de la cache todas las acciones que dependen
de ese usuario en la aplicación frontend (la aplicación pública). Como el método remove()
utiliza URI internas, no se puede utilizar para borrar la cache de otras aplicaciones, ya
que cada aplicación siempre se encuentra aislada de las demás y no tiene acceso a las
reglas de enrutamiento del resto de aplicaciones.




www.librosweb.es                                                                                261
Symfony, la guía definitiva                                         Capítulo 12. Uso de la cache


La solución consiste en borrar manualmente los archivos del directorio cache/. Si la apli-
cación backend quiere borrar la cache de la acción usuario/ver de la aplicación frontend
para el usuario cuyo id vale 12, se podría utilizar el siguiente código:
   $sf_root_cache_dir = sfConfig::get('sf_root_cache_dir');
   $cache_dir = $sf_root_cache_dir.'/frontend/prod/template/www_miapp_com/all';
   unlink($cache_dir.'/usuario/ver/id/12.cache');

Este método de borrado no es muy convincente, ya que el comando anterior solo borra la
cache del entorno actual y obliga a escribir el nombre del entorno y el nombre del servi-
dor en la ruta del archivo. Para evitar estas molestias, se puede utilizar el método sfTo-
olkit::clearGlob(). Este método acepta como parámetro un patrón de nombre de fiche-
ro en el que se pueden incluir comodines. El siguiente ejemplo borra de la cache los mis-
mos archivos que el ejemplo anterior sin necesidad de especificar ni el entorno ni el nom-
bre del servidor:
   $cache_dir = $sf_root_cache_dir.'/frontend/*/template/*/all';
   sfToolkit::clearGlob($cache_dir.'/usuario/ver/id/12.cache');

Este método también es muy práctico cuando se quieren borrar todas las páginas de una
acción independientemente de los parámetros. Si la aplicación dispone de varios idiomas,
es posible que el código del idioma aparezca en la URL. El enlace al perfil de un usuario
podría tener el siguiente aspecto:
   http://www.miapp.com/en/usuario/ver/id/12

Para eliminar de la cache el perfil de un usuario cuyo id vale 12 independientemente del
idioma, se debe ejecutar la siguiente instrucción:
   sfToolkit::clearGlob($cache_dir.'/*/usuario/ver/id/12.cache');


12.3. Probando y monitorizando la cache
La cache de HTML puede provocar incoherencias en los datos mostrados si no se gestiona
correctamente. Cada vez que se activa la cache para un elemento, se debe probar y mo-
nitorizar la mejora obtenida en el rendimiento de su ejecución.

12.3.1. Creando un entorno de ejecución intermedio
El sistema de cache es propenso a crear errores en el entorno de producción que no se
pueden detectar en el entorno de desarrollo, ya que en este último entorno la cache
HTML está deshabilitada por defecto. Si se habilita la cache de HTML para algunas accio-
nes, se debería crear un nuevo entorno de ejecución llamado staging en este capítulo y
con las mismas opciones que el entorno prod (por lo tanto con la cache activada) pero
con la opción web_debug activada (valor on).

Para crear el nuevo entorno, se deben añadir las líneas mostradas en el listado 12-12 al
archivo settings.yml de la aplicación.

Listado 12-12 - Opciones del entorno staging, en miapp/config/settings.yml
   staging:
     .settings:


www.librosweb.es                                                                           262
Symfony, la guía definitiva                                              Capítulo 12. Uso de la cache

        web_debug:   on
        cache:       on

Además, se debe crear un nuevo controlador frontal copiando el de producción (que se-
guramente se llamará miproyecto/web/index.php) en un archivo llamado miapp_sta-
ging.php. En este archivo copiado es necesario modificar el valor de SF_ENVIRONMENT y
SF_DEBUG, tal y como se muestra a continuación:
   define('SF_ENVIRONMENT', 'staging');
   define('SF_DEBUG',        true);

Y solo con esos cambios ya se dispone de un nuevo entorno de ejecución. Para probarlo,
se añade el nombre del controlador frontal a la URL después del nombre de dominio:
   http://miapp.ejemplo.com/miapp_staging.php/usuario/listado


  SUGERENCIA
  En vez de copiar un controlador frontal existente, es posible crear un nuevo controlador frontal med-
  iante la línea de comandos de Symfony. Para crear un entorno llamado staging en la aplicación
  miapp llamado miapp_staging.php y con la opción SF_DEBUG igual a true, se puede ejecutar el si-
  guiente comando: symfony init-controller miapp staging miapp_staging true.


12.3.2. Monitorizando el rendimiento
El Capítulo 16 describe en detalle la barra de depuración de aplicaciones y sus conteni-
dos. No obstante, como esa barra también contiene información relacionada con los ele-
mentos guardados en la cache, se incluye ahora una breve descripción de sus caracterís-
ticas relacionadas con la cache.

Cuando se accede a una página que contiene elementos susceptibles de estar en la cache
(acciones, elementos parciales, fragmentos, etc.) la barra de depuración web (que apare-
ce en la esquina superior izquierda) muestra un botón para ignorar la cache (una flecha
curvada verde), como se puede ver en la figura 12-4. Este botón se emplea para recar-
gar la página y forzar a que se procesen todos los elementos que estaban en la cache. Se
debe tener en cuenta que este botón no borra la cache.

El último número que se muestra en la derecha de la barra es el tiempo que ha durado la
ejecución de la petición. Si se habilita la cache en una página, este número debería ser
muy inferior al recargar la página, ya que Symfony utilizará los datos de la cache en vez
de volver a ejecutar por completo los scripts. Este indicador se puede utilizar para moni-
torizar fácilmente las mejoras introducidas por la cache.



         Figura 12.4. Barra de depuración web en las páginas que utilizan la cache


La barra de depuración también muestra el número de consultas de base de datos que se
han ejecutado para la petición, el detalle del tiempo de ejecución de cada categoría (se
muestra al pulsar sobre el tiempo de ejecución total). Monitorizando esta información es
sencillo medir las mejoras en el rendimiento que son debidas a la cache.



www.librosweb.es                                                                                  263
Symfony, la guía definitiva                                       Capítulo 12. Uso de la cache


12.3.3. Pruebas de rendimiento (benchmarking)
La depuración de las aplicaciones reduce notablemente la velocidad de ejecución de la
aplicación, ya que se genera mucha información para que esté disponible en la barra de
depuración web. De esta forma, el tiempo total de ejecución que se muestra cuando se
accede a la aplicación en el entorno staging no es representativo del tiempo que se em-
pleará en producción, donde la depuración está deshabilitada.

Para obtener información sobre el tiempo de ejecución de cada petición, deberían utili-
zarse herramientas para realizar pruebas de rendimiento, como Apache Bench o JMeter.
Estas herramientas permiten realizar pruebas de carga y calculan dos parámetros muy
importantes: el tiempo de carga medio de una página y la capacidad máxima del servi-
dor. El tiempo medio de carga es esencial para monitorizar las mejoras de rendimiento
introducidas por la activación de la cache.

12.3.4. Identificando elementos de la cache
Cuando la barra de depuración web está activada, los elementos de la página que se enc-
uentran en la cache se identifican mediante un recuadro rojo, además de que cada uno
dispone de una caja de información sobre la cache en la esquina superior izquierda del
elemento, como muestra la figura 12-5. La caja muestra un fondo azul si el elemento se
ha ejecutado y un fondo de color amarillo si se ha obtenido directamente de la cache. Al
pulsar sobre el enlace de información de la cache se muestra el identificador del elemen-
to en la cache, su tiempo de vida y el tiempo que ha transcurrido dede su última modifi-
cación. Esta información es útil para resolver problemas con elementos fuera de contex-
to, para ver cuando se crearon los elementos y para visualizar las partes de la plantilla
que se pueden guardar en la cache.




  Figura 12.5. Identificación de los elementos de la página que se guardan en la cache



12.4. HTTP 1.1 y la cache del lado del cliente
El protocolo HTTP 1.1 define una serie de cabeceras que se pueden utilizar para acelerar
una aplicación controlando la cache del navegador del usuario.

La especificación del protocolo HTTP 1.1 publicada por el W3C (World Wide Web Consort-
ium) define todas las cabeceras con gran detalle (http://www.w3.org/Protocols/rfc2616/
rfc2616-sec14.html). Si una acción tiene habilitada la cache y utiliza la opción with_layout,
entonces puede hacer uso de los mecanismos que se describen en las siguientes
secciones.

Aunque algunos de los navegadores de los usuarios no soporten HTTP 1.1, no existe
ningún riesgo en utilizar las opciones de cache de HTTP 1.1. Los navegadores que reciben
cabeceras que no entienden, simplemente las ignoran, por lo que se aconseja utilizar los
mecanismos de cache de HTTP 1.1.


www.librosweb.es                                                                         264
Symfony, la guía definitiva                                     Capítulo 12. Uso de la cache


Además, las cabeceras de HTTP 1.1 también las interpretan los servidores proxy y servi-
dores cache. De esta forma, aunque el navegador del usuario no soporte HTTP 1.1, pue-
de haber un proxy en la ruta de la petición que pueda aprovechar esas características.

12.4.1. Uso de la cabecera ETag para evitar el envío de contenidos no
modificados
Cuando se habilita la característica de ETag, el servidor web añade a la respuesta una ca-
becera especial que contiene una firma de la respuesta enviada.
   ETag: "1A2Z3E4R5T6Y7U"

El navegador del usuario almacena esta firma y la envía junto con la petición la próxima
vez que el usuario acceda a la misma página. Si la firma demuestra que la página no se
ha modificado desde la primera petición, el servidor no envía de nuevo la página de resp-
uesta. En su lugar, envía una cabecera de tipo 304: Not modified. Esta técnica ahorra
tiempo de CPU (si se está utilizando la compresión de contenidos) y ancho de banda (ya
que la página no se vuelve a enviar) en el servidor, y tiempo de carga (porque la página
no se envía de nuevo) en el cliente. En resumen, las páginas que se guardan en la cache
con la cabecera ETag son todavía más rápidas de cargar que las páginas que están en la
cache y no tienen ETag.

Symfony permite activar la característica ETag para toda la aplicación en el archivo set-
tings.yml. El valor por defecto de la opción ETag se muestra a continuación:
   all:
     .settings:
        etag: on

En las acciones que se guardan en la cache junto con el layout, la respuesta se obtiene
directamente del directorio cache/, por lo que el proceso es todavía más rápido.


12.4.2. Añadiendo la cabecera Last-Modified para evitar el envío de
contenidos todavía válidos
Cuando el servidor envía la respuesta al navegador, puede añadir una cabecera especial
que indica cuando se modificaron por última vez los datos contenidos en la página:
   Last-Modified: Sat, 23 Nov 2006 13:27:31 GMT

Los navegadores interpretan esta cabecera y la próxima vez que solicitan la misma pági-
na, añaden una cabecera If-Modified apropiada:
   If-Modified-Since: Sat, 23 Nov 2006 13:27:31 GMT

El servidor entonces puede comparar el valor enviado por el cliente y el valor devuelto
por la aplicación. Si coinciden, el servidor devuelve una cabecera304: Not modified, aho-
rrando ancho de banda y tiempo de CPU, al igual que sucedía con la cabecera ETag.

Symfony permite establecer la cabecera Last-Modified de la misma forma que se esta-
blece cualquier otra cabecera. En una acción se puede añadir de la siguiente manera:
   $this->getResponse()->setHttpHeader('Last-Modified',
   $this->getResponse()->getDate($timestamp));

www.librosweb.es                                                                       265
Symfony, la guía definitiva                                         Capítulo 12. Uso de la cache


La fecha puede ser la fecha actual o la fecha de la última actualización de los datos de la
página, obtenida a partir de la base de datos o del sistema de archivos. El método getDa-
te() del objeto sfResponse convierte un timestamp en una fecha formateada según el
estándar requerido por la cabecera Last-Modified (RFC1123).


12.4.3. Añadiendo cabeceras Vary para permitir varias versiones de la
página en la cache
Otra de las cabeceras de HTTP 1.1 es Vary, que define los parámetros de los que depen-
de una página y que utilizan los navegadores y los servidores proxy para organizar la ca-
che de las páginas. Si por ejemplo el contenido de una página depende de las cookies, se
puede utilizar la siguiente cabecera Vary:
   Vary: Cookie

En la mayoría de ocasiones, es difícil habilitar la cache para las acciones porque la página
puede variar en función de las cookies, el idioma del usuario o cualquier otro parámetro.
Si no es un inconveniente aumentar el tamaño de la cache, se puede utilizar en este caso
la cabecera Vary. Además, se puede emplear esta cabecera para toda la aplicación o solo
para algunas acciones, definiéndolo en el archivo de configuración cache.yml o mediante
el método disponible en sfResponse, como se muestra a continuación:
   $this->getResponse()->addVaryHttpHeader('Cookie');
   $this->getResponse()->addVaryHttpHeader('User-Agent');
   $this->getResponse()->addVaryHttpHeader('Accept-Language');

Symfony guarda en la cache versiones diferentes de la página en función de cada uno de
estos parámetros. Aunque el tamaño de la cache aumenta, la ventaja es que cuando el
servidor recibe una petición que coincide con estas cabeceras, la respuesta se obtiene di-
rectamente de la cache en vez de tener que procesarla. Se trata de un mecanismo muy
útil para mejorar el rendimiento de las páginas que solo varían en función de las cabece-
ras de la petición.

12.4.4. Añadiendo la cabecera Cache-Control para permitir la cache en el
lado del cliente
Hasta ahora, aunque se hayan añadido las cabeceras, el navegador sigue enviando petic-
iones al servidor a pesar de disponer de una versión de la página en su cache. Para evitar
estas peticiones, se pueden añadir las cabeceras Cache-Control y Expires a la respuesta.
PHP deshabilita por defecto estas cabeceras, pero Symfony puede saltarse este compor-
tamiento para evitar las peticiones innecesarias al servidor.

Como es habitual, esta opción se activa mediante un método del objeto sfResponse. En
una acción se puede definir el tiempo máximo que una página debería permanecer en la
cache (en segundos):
   $this->getResponse()->addCacheControlHttpHeader('max_age=60');

Además, se pueden especificar las condiciones bajo las cuales se guarda la página en la
cache, de forma que la cache del proveedor no almacene por ejemplo datos privados (co-
mo números de cuenta y contraseñas):

www.librosweb.es                                                                           266
Symfony, la guía definitiva                                             Capítulo 12. Uso de la cache

   $this->getResponse()->addCacheControlHttpHeader('private=True');

Mediante el uso de las directivas HTTP de Cache-Control es posible controlar los diversos
mecanismos de cache existentes entre el servidor y el navegador del cliente. La especifi-
cación del W3C de Cache-Control contiene la explicación detallada de todas estas
directivas.

Symfony permite añadir otra cabecera llamada Expires:
   $this->getResponse()->setHttpHeader('Expires',
   $this->getResponse()->getDate($timestamp));


  SUGERENCIA
  La consecuencia más importante de activar el mecanismo Cache-Control es que los logs del ser-
  vidor no muestran todas las peticiones realizadas por los usuarios, sino solamente las que recibe
  realmente el servidor. De esta forma, si mejora el rendimiento de un sitio web, su popularidad des-
  cenderá de forma aparente en las estadísticas de acceso al sitio.


12.5. Resumen
El sistema de cache permite mejorar el rendimiento de la aplicación de forma variable en
función del tipo de cache utilizado. La siguiente lista muestra los tipos de cache disponi-
bles en Symfony ordenados de mayor a menor mejora en el rendimiento de la aplicación:

      ▪ Super cache

      ▪ Cache de una acción con layout

      ▪ Cache de una acción sin layout

      ▪ Cache de fragmentos de plantillas

Además, tambien se pueden guardar en la cache los elementos parciales y los
componentes.

Si la modificación de los datos del modelo o de la sesión obliga a borrar la cache para
mantener la coherencia de la información, se puede realizar un borrado muy selectivo
para no penalizar el rendimiento, ya que es posible borrar solamente los elementos mo-
dificados manteniendo todos los demás.

Una recomendación muy importante es la de probar cuidadosamente todas las páginas
para las que se ha habilitado la cache, ya que suele ser habitual que se produzcan erro-
res por haber guardado en la cache elementos inadecuados o por no haber borrado de la
cache los elementos modificados. Una buena técnica es la de crear un entorno intermedio
llamado staging dedicado a probar la cache y las mejoras en el rendimiento de la
aplicación.

Por último, es posible exprimir al máximo algunas características del protocolo HTTP 1.1
gracias a las opciones que proporciona Symfony para controlar la cache y que permite
aprovechar las ventajas de la cache en el navegador de los clientes, de forma que se au-
mente aun más el rendimiento de la aplicación.




www.librosweb.es                                                                                267
Symfony, la guía definitiva                     Capítulo 13. Internacionalización y localización




Capítulo 13. Internacionalización y localización
Cuando se desarrollan aplicaciones con soporte para varios idiomas, es fácil que la tra-
ducción de todos los contenidos, el soporte de los estándares de cada país y la traducción
de la interfaz se conviertan en una pesadilla. Afortunadamente, Symfony automatiza de
forma nativa todos los aspectos del proceso de internacionalización.

Como la palabra “internacionalización” es demasiado larga, los programadores normal-
mente se refieren a ella como i18n (18 es el número de letras que existen entre la letra
“i” y la letra “n” de la palabra “internacionalización”). La “localización” normalmente se
abrevia como l10n. Estas 2 palabras se refieren a 2 aspectos diferentes de las aplicacio-
nes web multiidioma.

Una aplicación internacionalizada dispone de varias versiones de un mismo contenido en
diferentes idiomas o formatos. La interfaz de una aplicación web de correo electrónico,
puede ofrecer por ejemplo el mismo servicio en diferentes idiomas, cambiando solamente
la interfaz.

Una aplicación localizada dispone de información diferente en función del país desde el
que se accede. El caso más sencillo es el de los contenidos de un portal de noticias: si el
usuario accede desde Estados Unidos, se muestran las últimas noticias de Estados Uni-
dos, pero si el usuario accede desde Francia, se mostrarán las noticias de Francia. Por
tanto, una aplicación con l10n no solo proporciona los contenidos traducidos, sino que to-
do el contenido puede cambiar de una versión a otra.

En cualquier caso, el soporte de i18n y l10n en una aplicación comprende los siguientes
aspectos:

      ▪ Traducción de texto (interfaz, contenidos estáticos y contenido)

      ▪ Estándares y formatos (fechas, cantidades, números, etc.)

      ▪ Contenido localizado (varias versiones de un mismo objeto en función del país del
        usuario)

En este capítulo se muestra la forma en la que Symfony trata cada uno de estos elemen-
tos y la forma en la que se pueden desarrollar aplicaciones con i18n y l10n.


13.1. Cultura del usuario
Todas las opciones relacionadas con i18n en Symfony se basan en un parámetro de la
sesión de usuario llamado culture (cultura). La cultura está formada por la combinación
del país e idioma del usuario y determina la forma en la que se muestra el texto y la in-
formación que depende de la cultura. Como su valor se serializa en la sesión de usuario,
la cultura se almacena de forma persistente entre páginas diferentes.

13.1.1. Indicando la cultura por defecto
Por defecto, la cultura de los nuevos usuarios toma el valor de la opción default_culture.
Se puede modificar su valor en el archivo de configuración i18n.yml, como se muestra en
el listado 13-1.

www.librosweb.es                                                                           268
Symfony, la guía definitiva                           Capítulo 13. Internacionalización y localización


Listado 13-1 - Indicando la cultura por defecto, en miapp/config/i18n.yml
   all:
     default_culture:         fr_FR


  NOTA
  Durante el desarrollo de la aplicación, es posible que los cambios en el archivo i18n.yml no se re-
  flejen en la aplicación accedida mediante el navegador. La razón es que la sesión guarda la infor-
  mación de la cultura de las páginas anteriores. Para acceder a la aplicación con el nuevo valor de la
  cultura, se deben borrar las cookies del dominio de la aplicación o se debe reiniciar el navegador.

La cultura debe indicar el país y el idioma ya que, por ejemplo, se puede disponer de una
traducción al francés diferente para los usuarios de Francia, Bélgica y Canadá, como tam-
bién se pueden ofrecer traducciones diferentes al español para los usuarios de España y
México. El idioma se codifica mediante 2 caracteres en minúscula siguiendo el estándar
ISO 639-1 (en para inglés, por ejemplo). El país se codifica en forma de 2 caracteres en
mayúscula siguiendo el estándar ISO 3166-1 (GB para Reino Unido, por ejemplo).


13.1.2. Modificando la cultura de un usuario
La cultura de un usuario se puede modificar mientras accede a la aplicación (por ejemplo
cuando un usuario decide cambiar la versión en inglés por la versión en francés) o cuan-
do el usuario accede a la aplicación y se utiliza el idioma que ha seleccionado en sus pre-
ferencias. Por este motivo la clase sfUser ofrece métodos getter y setter para la cultura
del usuario. El listado 13-2 muestra cómo utilizar estos métodos en la acción.

Listado 13-2 - Modificando y obteniendo la cultura en una acción
   // Modificando la cultura
   $this->getUser()->setCulture('en_US');

   // Obteniendo la cultura
   $cultura = $this->getUser()->getCulture();
    => en_US

  La cultura en la URL

  Cuando se utilizan las opciones de localización e internacionalización de Symfony, parece que exis-
  ten varias versiones diferentes de una página para una misma URL, ya que la versión que se mues-
  tra depende de la sesión de usuario. Este comportamiento hace difícil guardar las páginas en la ca-
  che o que las páginas se indexen correctamente en los buscadores.

  Una solución es hacer que la cultura se muestre en todas las URL, de forma que las páginas tradu-
  cidas se muestran como si fueran URL diferentes. Para conseguirlo, se añade la opción :sf_cul-
  ture en todas las reglas del archivo routing.yml de la aplicación:
      pagina:
        url: /:sf_culture/:pagina
        requirements: { sf_culture: (?:fr|en|de) }
        params: ...

      articulo:
        url: /:sf_culture/:ano/:mes/:dia/:slug


www.librosweb.es                                                                                  269
Symfony, la guía definitiva                           Capítulo 13. Internacionalización y localización

        requirements: { sf_culture: (?:fr|en|de) }
        params: ...

  Para no tener que añadir manualmente el parámetro de petición sf_culture en todas las llamadas
  a link_to(), Symfony añade automáticamente la cultura del usuario a los parámetros de enrutam-
  iento por defecto. También funciona de forma inversa, ya que Symfony modifica automáticamente
  la cultura del usuario si encuentra el parámetro sf_culture en la URL.


13.1.3. Determinando la cultura de forma automática
En muchas aplicaciones, la cultura del usuario se define durante la primera petición, en
función de las preferencias de su navegador. Los usuarios pueden definir en el navegador
una serie de idiomas que están dispuestos a aceptar. Esta información se envía al servi-
dor en cada petición, mediante la cabecera HTTP Accept-Language. En Symfony esta ca-
becera se puede acceder a través del objeto sfRequest. Si por ejemplo se quiere obtener
la lista de idiomas preferidos del usuario en una acción, se utiliza la siguiente instrucción:
   $idiomas = $this->getRequest()->getLanguages();

Aunque la cabecera HTTP es una cadena de texto, Symfony la procesa y la convierte au-
tomáticamente en un array. Por tanto, el idioma preferido del usuario se puede obtener
en el ejemplo anterior mediante $idiomas[0].

En la página principal de un sitio web y en un filtro utilizado en varias páginas, puede ser
útil establecer automáticamente la cultura del usuario al idioma preferido del navegador
del usuario.

  ATENCIÓN
  La cabecera HTTP Accept-Language no es una información muy fiable, ya que casi ningún usuario
  sabe cómo modificar su valor en el navegador. En la mayoría de los casos, el idioma preferido del
  navegador es el idioma de la propia interfaz del navegador y los usuarios no están disponibles en
  todos los idiomas. Si se decide establecer de forma automática el valor de la cultura según el idio-
  ma preferido del navegador, es una buena idea proporcionar una forma sencilla de seleccionar otro
  idioma.


13.2. Estándares y formatos
Las partes internas de una aplicación web no deben preocuparse por las diferencias cul-
turales entre países. Las bases de datos por ejemplo almacenan las fechas y cantidades
siguiendo estándares internacionales. Pero cuando los datos se envían o se reciben del
usuario, es necesario realizar una conversión. Los usuarios normales no entienden lo que
es un timestamp y prefieren llamar a su idioma en su propio idioma (por ejemplo “Franç-
ais” en vez de “French”). Así que se debe aprovechar la posibilidad de realizar estas con-
versiones de forma automática en función de la cultura del usuario.

13.2.1. Mostrando datos según la cultura del usuario
Una vez que se define la cultura del usuario, los helpers que dependen de la cultura
muestran automáticamente los datos de forma correcta. El helper format_number() por



www.librosweb.es                                                                                 270
Symfony, la guía definitiva                     Capítulo 13. Internacionalización y localización


ejemplo, muestra un número en un formato familiar para el usuario, en función de su
cultura, tal y como muestra el listado 13-3.

Listado 13-3 - Mostrando un número según la cultura del usuario
   <?php use_helper('Number') ?>

   <?php $sf_user->setCulture('en_US') ?>
   <?php echo format_number(12000.10) ?>
    => '12,000.10'

   <?php $sf_user->setCulture('fr_FR') ?>
   <?php echo format_number(12000.10) ?>
    => '12 000,10'

No es necesario indicar a los helpers la cultura de forma explícita. Los helpers la buscan
automáticamente en el objeto sesión. El listado 13-4 muestra todos los helpers que tie-
nen en cuenta la cultura para mostrar sus datos.

Listado 13-4 - Helpers dependientes de la cultura
   <?php use_helper('Date') ?>

   <?php echo format_date(time()) ?>
    => '9/14/06'

   <?php echo format_datetime(time()) ?>
    => 'September 14, 2006 6:11:07 PM CEST'

   <?php use_helper('Number') ?>

   <?php echo format_number(12000.10) ?>
    => '12,000.10'

   <?php echo format_currency(1350, 'USD') ?>
    => '$1,350.00'

   <?php use_helper('I18N') ?>

   <?php echo format_country('US') ?>
    => 'United States'

   <?php format_language('en') ?>
    => 'English'

   <?php use_helper('Form') ?>

   <?php echo input_date_tag('fecha_nacimiento', mktime(0, 0, 0, 9, 14, 2006)) ?>
    => input type="text" name="fecha_nacimiento" id="fecha_nacimiento" value="9/14/06"
   size="11" />

   <?php echo select_country_tag('pais', 'US') ?>
    => <select name="pais" id="pais"><option value="AF">Afghanistan</option>
         ...
         <option value="GB">United Kingdom</option>
         <option value="US" selected="selected">United States</option>


www.librosweb.es                                                                           271
Symfony, la guía definitiva                      Capítulo 13. Internacionalización y localización

          <option value="UM">United States Minor Outlying Islands</option>
          <option value="UY">Uruguay</option>
          ...
        </select>

Los helpers de fechas aceptan un parámetro opcional para indicar su formato, de modo
que se pueda mostrar una fecha independiente de la cultura del usuario, pero no debería
utilizarse en las aplicaciones con soporte de i18n.

13.2.2. Obteniendo información en una aplicación localizada
Si es necesario obtener información del usuario, se debería obligar al usuario, si es posi-
ble, a introducir datos que ya estén internacionalizados. Esta técnica evita tener que adi-
vinar el formato en el que ha introducido el usuario sus datos. Por ejemplo, es complica-
do que un usuario introduzca una cantidad monetaria con la separación de los miles.

Se pueden restringir las posibilidades del usuario ocultando los datos realmente enviados
al servidor (como por ejemplo mediante select_country_tag()) o separando las partes
de un dato complejo en varias partes individuales sencillas.

No obstante, para datos como fechas esta técnica no siempre es posible. Los usuarios
están acostumbrados a introducir las fechas en el formato propio de su país, por lo que
se deben convertir a un formato internacional. Para ello se puede utilizar la clase sfI18N.
El listado 13-5 muestra cómo utilizar esta clase.

Listado 13-5 - Obteniendo una fecha a partir de un formato propio del usuario
en una acción
   $fecha= $this->getRequestParameter('fecha_nacimiento');
   $cultura_usuario = $this->getUser()->getCulture();

   // Obtener un timestamp
   $timestamp = sfI18N::getTimestampForCulture($fecha, $cultura_usuario);

   // Obtener las partes de una fecha
   list($dia, $mes, $ano) = sfI18N::getDateForCulture($fecha, $cultura_usuario);


13.3. Información textual en la base de datos
Una aplicación que soporta la localización ofrece diferentes contenidos en función de la
cultura del usuario. Una tienda online podría ofrecer los mismos productos al mismo pre-
cio en todo el mundo, pero con una descripción personalizada para cada país. De esta
forma, la base de datos tiene que ser capaz de almacenar diferentes versiones de una
misma información y el esquema de la base de datos debe diseñarse de una forma espe-
cial, además de indicar la cultura cada vez que se manipulan los objetos del modelo.

13.3.1. Creando un esquema para una aplicación localizada
Cada una de las tablas que contiene información localizada, se debe dividir en 2 partes:
una tabla que no contiene ninguna información relativa a la i18n y otra tabla con todas
las columnas relacionadas con la i18n. Las dos tablas tienen una relación de tipo “uno a



www.librosweb.es                                                                            272
Symfony, la guía definitiva                       Capítulo 13. Internacionalización y localización


muchos”. De esta forma, es posible añadir más idiomas sin tener que modificar el mode-
lo. Como ejemplo se va a considerar una tabla llamada Producto.

En primer lugar, se crean las tablas en el archivo schema.yml, tal y como muestra el lista-
do 13-6.

Listado 13-6 - Esquema de ejemplo para datos i18n, en config/schema.yml
   mi_conexion:
     mi_producto:
       _attributes: { phpName: Producto, isI18N: true, i18nTable: mi_producto_i18n }
       id:          { type: integer, required: true, primaryKey: true, autoincrement: true
   }
       precio:      { type: float }

     mi_producto_i18n:
       _attributes: { phpName: ProductoI18n }
       id:          { type: integer, required: true, primaryKey: true, foreignTable:
   mi_producto, foreignReference: id }
       culture:     { isCulture: true, type: varchar, size: 7, required: true, primaryKey:
   true }
       nombre:      { type: varchar, size: 50 }

Lo más importante del listado anterior son los atributos isI18N y i18nTable de la primera
tabla y la columna especial culture en la segunda. Todos estos atributos son mejoras de
Propel creadas por Symfony.

Symfony puede automatizar aun más este proceso. Si la tabla que contiene los datos in-
ternacionalizados tiene el mismo nombre que la tabla principal seguido de un sufijo _i18n
y ambas están relacionadas con una columna llamada id, se pueden omitir las columnas
id y culture en la tabla _i18n y los atributos específicos para i18n en la tabla principal. Si
se siguen estas convenciones, Symfony es capaz de inferir toda esta información. Así,
para Symfony es equivalente el esquema del listado 13-7 al listado 13-6 mostrado
anteriormente.

Listado 13-7 - Versión abreviada del esquema de ejemplo para datos i18n, en
config/schema.yml
   mi_conexion:
     mi_producto:
       _attributes: { phpName: Producto }
       id:
       precio:      float
     mi_producto_i18n:
       _attributes: { phpName: ProductoI18n }
       nombre:      varchar(50)


13.3.2. Usando los objetos i18n generados
Una vez construido el modelo de objetos (mediante la llamada a symfony propel-build-
model y el borrado de la cache mediante symfony cc después de cada modificación del ar-
chivo schema.yml), se puede utilizar la clase Producto con soporte de i18n como si fuera
una sola tabla, tal y como muestra el listado 13-8.


www.librosweb.es                                                                             273
Symfony, la guía definitiva                     Capítulo 13. Internacionalización y localización


Listado 13-8 - Trabajando con objetos i18n
   $producto = ProductoPeer::retrieveByPk(1);
   $producto->setCulture('fr');
   $producto->setNombre('Nom du produit');
   $producto->save();

   $producto->setCulture('en');
   $producto->setNombre('Product name');
   $producto->save();

   echo $producto->getNombre();
    => 'Product name'

   $producto->setCulture('fr');
   echo $producto->getNombre();
    => 'Nom du produit'

Si no se quiere modificar la cultura cada vez que se utiliza un objeto i18n, es posible mo-
dificar el método hydrate() en la clase del objeto. El listado 13-9 muestra un ejemplo.

Listado 13-9 - Redefiniendo el método hydrate() para establecer la cultura, en
miproyecto/lib/model/Producto.php
   public function hydrate(ResultSet $rs, $startcol = 1)
   {
     parent::hydrate($rs, $startcol);
     $this->setCulture(sfContext::getInstance()->getUser()->getCulture());
   }

Las consultas realizadas mediante los objetos peer se pueden restringir para que solo ob-
tengan los objetos que disponen de una traducción para la cultura actual, mediante el
método doSelectWithI18n en lugar del habitual doSelect, como muestra el listado 13-10.
Además, crea los objetos i18n relacionados a la vez que los objetos normales, de forma
que se reduce el número de consultas necesarias para obtener el contenido completo (el
Capítulo 18 incluye más información sobre las ventajas de este método sobre el rendim-
iento de la aplicación).

Listado 13-10 - Obteniendo objetos con un Criteria de tipo i18n
   $c = new Criteria();
   $c->add(ProductoPeer::PRECIO, 100, Criteria::LESS_THAN);
   $productos = ProductoPeer::doSelectWithI18n($c, $cultura);
   // El argumento $cultura es opcional
   // Si no se indica, se utiliza la cultura actual

Así que no es necesario trabajar directamente con los objetos i18n, sino que se pasa la
cultura al modelo (o se deja que el modelo la obtenga automáticamente) cada vez que se
quiere realizar una consulta con un objeto normal.


13.4. Traducción de la interfaz
La interfaz de usuario es otro de los elementos que se deben adaptar en las aplicaciones
i18n. Las plantillas tienen que poder mostrar las etiquetas, los mensajes y la navegación
en diferentes idiomas pero manteniendo la misma presentación. Symfony recomienda

www.librosweb.es                                                                           274
Symfony, la guía definitiva                          Capítulo 13. Internacionalización y localización


que las plantillas se construyan con el lenguaje por defecto de la aplicación y que se defi-
na la traducción de las frases en un archivo de diccionario. De esta forma, no es necesar-
io modificar las plantillas cada vez que se añade, modifica o elimina una traducción.

13.4.1. Configurando la traducción
Las plantillas no se traducen automáticamente, lo que significa que antes que nada, se
debe activar la opción de traducción de las plantillas en el archivo settings.yml, como se
muestra en el listado 13-11.

Listado 13-11 - Activando la traducción de la interfaz, en miapp/config/
settings.yml
   all:
     .settings:
        i18n: on


13.4.2. Usando el helper de traducción
En esta sección se va a considerar que se quiere construir un sitio web en inglés y en
francés, siendo el inglés el idioma por defecto. Antes de empezar con la traducción del si-
tio web, una de las plantillas del sitio podría ser similar a la del listado 13-12.

Listado 13-12 - Plantilla con un único idioma
   Welcome to our website. Today's date is <?php echo format_date(date()) ?>

Para que Symfony pueda traducir las frases de una plantilla, estas deben identificarse co-
mo “texto traducible”. Para ello se ha definido el helper __() (2 guiones bajos seguidos),
que es parte del grupo de helpers llamado I18N. De esa forma, todas las plantillas deben
encerrar las frases que se van a traducir en llamadas a ese helper. El listado 13-12 por
ejemplo se puede modificar para que tenga el aspecto del listado 13-13 (como se verá
más adelante en la sección “Cómo realizar traducciones complejas”, existe una forma
mejor para llamar al helper de traducción).

Listado 13-13 - Plantilla preparada para múltiples idiomas
   <?php use_helper('I18N') ?>

   <?php echo __('Welcome to our website.') ?>
   <?php echo __("Today's date is ") ?>
   <?php echo format_date(date()) ?>


  SUGERENCIA
  Si la aplicación hace uso del grupo de helpers I18N en todas sus páginas, puede ser una buena
  idea incluirlo en la opción standard_helpers del archivo settings.yml, de forma que no sea ne-
  cesario incluir use_helper(’I18N’) en cada plantilla.


13.4.3. Utilizando un archivo de diccionario
Cuando se invoca la función __(), Symfony busca la traducción del argumento que se le
pasa en el diccionario correspondiente a la cultura del usuario. Si se encuentra una frase


www.librosweb.es                                                                                275
Symfony, la guía definitiva                         Capítulo 13. Internacionalización y localización


equivalente, la función devuelve la traducción y se muestra en la respuesta. De esta for-
ma, la traducción de la interfaz se basa en los archivos de diccionario.

Los archivos de diccionario se crean siguiendo el formato XLIFF (XML Localization Inter-
change File Format), sus nombres siguen el patrón messages.[codigo de idioma].xml y
se guardan en el directorio i18n/ de la aplicación.

XLIFF es un formato estándar basado en XML. Como se trata de un formato muy utiliza-
do, se pueden emplear herramientas externas que facilitan la traducción del sitio web en-
tero. Las empresas que se encargan de realizar traducciones manejan este tipo de archi-
vos y saben cómo traducir un sitio web entero añadiendo un nuevo archivo XLIFF.

  SUGERENCIA
  Además del estándar XLIFF, Symfony también permite utilizar otros sistemas para guardar los dicc-
  ionarios: gettext, MySQL, SQLite y Creole. La documentación de la API contiene toda la informa-
  ción sobre la configuración de estos métodos alternativos.

El listado 13-14 muestra un ejemplo de la sintaxis XLIFF necesaria para crear el archivo
messages.fr.xml que traduce al francés los contenidos del listado 13-13.

Listado 13-14 - Diccionario en formato XLIFF, en miapp/i18n/messages.fr.xml
   <?xml version="1.0" ?>
   <xliff version="1.0">
     <file orginal="global" source-language="en_US" datatype="plaintext">
       <body>
         <trans-unit id="1">
            <source>Welcome to our website.</source>
            <target>Bienvenue sur notre site web.</target>
         </trans-unit>
         <trans-unit id="2">
            <source>Today's date is </source>
            <target>La date d'aujourd'hui est </target>
         </trans-unit>
       </body>
     </file>
   </xliff>

El atributo source-language siempre contiene el código ISO completo correspondiente a
la cultura por defecto. Cada frase o elemento que se traduce, se indica en una etiqueta
trans-unit con un atributo id único.

Si en la aplicación se utiliza la cultura por defecto (en este ejemplo en_US), las frases no
se traducen y por tanto se muestran directamente los argumentos indicados en las lla-
madas a __(). El resultado del listado 13-13 es similar al listado 13-12. Sin embargo, si
se modifica la cultura a fr_FR o fr_BE, se muestran las traducciones del archivo messa-
ges.fr.xml, y el resultado es el que se muestra en el listado 13-15.

Listado 13-15 - Una plantilla traducida
   Bienvenue sur notre site web. La date d'aujourd'hui est
   <?php echo format_date(date()) ?>




www.librosweb.es                                                                               276
Symfony, la guía definitiva                       Capítulo 13. Internacionalización y localización


Si se necesita añadir una nueva traducción, solamente es preciso crear un nuevo archivo
messages.XX.xml de traducción en el mismo directorio que el resto de traducciones.


13.4.4. Trabajando con diccionarios
Si el archivo messages.XX.xml aumenta tanto de tamaño como para hacerlo difícil de ma-
nejar, se pueden dividir sus contenidos en varios archivos de diccionarios ordenados por
temas. De esta forma, es posible por ejemplo dividir el archivo messages.fr.xml en los si-
guientes tres archivos dentro del directorio i18n/:

      ▪ navegacion.fr.xml

      ▪ terminos_de_servicio.fr.xml

      ▪ busqueda.fr.xml

Siempre que una traducción no se encuentre en el archivo messages.XX.xml por defecto,
se debe indicar como tercer argumento en la llamada al helper __() el archivo de diccio-
nario que debe utilizarse. Para traducir una cadena de texto que se encuentra en el ar-
chivo navegacion.fr.xml, se utilizaría la siguiente instrucción:
   <?php echo __('Welcome to our website', null, 'navegacion') ?>

Otra forma de organizar los diccionarios es mediante su división en módulos. En vez de
crear un solo archivo messages.XX.xml para toda la aplicación, se crea un archivo en cada
directorio modules/[nombre_modulo]/i18n/. Así se consigue que los módulos sean más in-
dependientes de la aplicación, lo que es necesario para reutilizarlos, como por ejemplo en
los plugins (ver Capítulo 17).

13.4.5. Trabajando con otros elementos que requieren traducción
Otros elementos también pueden requerir ser traducidos:

      ▪ Las imágenes, documentos y cualquier otro tipo de contenido estático pueden va-
        riar en función de la cultura del usuario. Un ejemplo típico es el de las imágenes
        que se utilizan para mostrar un contenido de texto con una tipografía muy espec-
        ial. En este caso, se pueden crear subdirectorios para cada una de las culturas
        disponibles (utilizando el valor culture para el nombre de cada subdirectorio):
   <?php echo image_tag($sf_user->getCulture().'/miTexto.gif') ?>

      ▪ Los mensajes de error de los archivos de validación se muestran automáticamen-
        te mediante __(), por lo que para traducirlos, solo es necesario añadirlos a los
        archivos de diccionario.

      ▪ Las páginas por defecto de Symfony (página no encontrada, error interno de ser-
        vidor, acceso restringido, etc.) están escritas en inglés y tienen que reescribirse
        para las aplicaciones i18n. Probablemente, la solución consiste en crear un mó-
        dulo default propio en la aplicación y utilizar __() en las plantillas. El Capítulo 19
        explica cómo personalizar estas páginas.




www.librosweb.es                                                                             277
Symfony, la guía definitiva                     Capítulo 13. Internacionalización y localización


13.4.6. Cómo realizar traducciones complejas
La traducción mediante __() requiere que se se le pase como argumento una frase com-
pleta. Sin embargo, es muy común tener variables mezcladas con el texto en una frase.
Aunque puede ser tentador intentar cortar las frases en varios trozos, el resultado es que
las llamadas al helper pierden su significado. Afortunadamente, el helper __() dispone de
una opción para reemplazar el valor de las variables y que permite crear diccionarios que
conservan su significado y simplifican la traducción. Las etiquetas HTML también se pue-
den incluir en la llamada al helper. El listado 13-16 muestra un ejemplo.

Listado 13-16 - Traduciendo frases con etiquetas HTML y código PHP
   // Frases originales
   Welcome to all the <strong>new</strong> users.<br />
   There are <?php echo count_logged() ?> persons logged.

   // Ejemplo malo de como traducir las frases anteriores
   <?php echo __('Welcome to all the') ?>
   <strong><?php echo __('new') ?></strong>
   <?php echo __('users') ?>.<br />
   <?php echo __('There are') ?>
   <?php echo count_logged() ?>
   <?php echo __('persons logged') ?>

   // Ejemplo correcto para traducir las frases anteriores
   <?php echo __('Welcome to all the <strong>new</strong> users') ?> <br />
   <?php echo __('There are %1% persons logged', array('%1%' => count_logged())) ?>

En este ejemplo, el nombre que se utiliza para la sustitución es %1%, pero puede utilizarse
cualquier nombre, ya que el reemplazo se realiza en el helper mediante la función
strtr() de PHP.

Otro de los problemas habituales de las traducciones es el uso del plural. En función del
número de resultados, el texto cambia, pero no lo hace de la misma forma en todos los
idiomas. La última frase del listado 13-16 por ejemplo no es correcta si count_logged()
devuelve 0 o 1. Aunque es posible comprobar el valor devuelto por la función y seleccio-
nar la frase adecuada mediante código PHP, esta forma de trabajar es bastante tediosa.
Además, cada idioma tiene sus propias reglas gramaticales, por lo que intentar inferir el
plural de las palabras puede ser muy complicado. Como se trata de un problema muy ha-
bitual, Symfony incluye un helper llamado format_number_choice(). El listado 13-17
muestra cómo utilizar este helper.

Listado 13-17 - Traduciendo las frases en función del valor de los parámetros
   <?php echo format_number_choice(
         '[0]Nobody is logged|[1]There is 1 person logged|(1,+Inf]There are%1% persons
   logged', array('%1%' => count_logged()), count_logged()) ?>

El primer argumento está formado por las diferentes posibilidades de frases. El segundo
parámetro es el patrón utilizado para reemplazar variables (como con el helper __()) y es
opcional. El tercer argumento es el número utilizado para determinar la frase que se
utiliza.



www.librosweb.es                                                                           278
Symfony, la guía definitiva                           Capítulo 13. Internacionalización y localización


Las frases de las diferentes posibilidades se separan mediante el carácter | seguido de un
array de valores, utilizando la siguiente sintaxis:

      ▪ [1,2]: Acepta valores entre 1 y 2, ambos incluidos.

      ▪ (1,2): Acepta valores entre 1 y 2, ambos excluidos.

      ▪ {1,2,3,4}: Solo se aceptan los valores definidos en este conjunto.

      ▪ [-Inf,0): Acepta valores mayores o iguales que -infinito y que son estrictamente
        menores que 0.

Se puede utilizar cualquier combinación no vacía de paréntesis y corchetes.

Para que la traducción funcione correctamente, el archivo XLIFF debe contener el mensa-
je tal y como aparece en la llamada al helper format_number_choice(). El listado 13-18
muestra un ejemplo.

Listado 13-18 - diccionario XLIFF para un argumento de format_number_choice()
   ...
   <trans-unit id="3">
     <source>[0]Nobody is logged|[1]There is 1 person logged|(1,+Inf]There are%1% persons
   logged</source>
     <target>[0]Personne n'est connecté|[1]Une personne est connectée|(1,+Inf]Ily a %1%
   personnes en ligne</target>
   </trans-unit>
   ...

  Comentarios sobre los charset

  Si se trabaja con contenidos internacionalizados en las plantillas, es habitual encontrarse con pro-
  blemas de charsets. Si se emplea un charset propio de un idioma, se debe modificar cada vez que
  el usuario cambia su cultura. Además, las plantillas escritas en un determinado charset no mues-
  tran correctamente los caracteres pertenecientes a otro charset.

  Por este motivo, si se utiliza más de una cultura, es muy recomendable crear todas las plantillas
  con el charset UTF-8 y que el layout declare que su contenido es UTF-8. Si se utiliza siempre UTF-
  8, es poco probable que se produzcan sorpresas desagradables.

  Las aplicaciones construidas con Symfony definen el charset utilizado de forma centralizada en el
  archivo settings.yml. Si se modifica su valor, se modifican todas las cabeceras content-type de
  todas las páginas de respuesta.
      all:
        .settings:
           charset: utf-8


13.4.7. Utilizando el helper de traducción fuera de una plantilla
No todo el texto que se muestra en las páginas viene de las plantillas. Por este motivo,
es habitual tener que utilizar el helper __() en otras partes de la aplicación: acciones, fil-
tros, clases del modelo, etc. El listado 13-19 muestra cómo utilizar el helper en una ac-
ción mediante la instancia del objeto I18N obtenida a través del singleton de contexto.

Listado 13-19 - Utilizando __() en una acción

www.librosweb.es                                                                                 279
Symfony, la guía definitiva                      Capítulo 13. Internacionalización y localización

   $this->getContext()->getI18N()->__($texto, $argumentos, 'mensajes');


13.5. Resumen
La internacionalización y localización de las aplicaciones web es una tarea sencilla si se
trabaja con el concepto de la cultura del usuario. Los helpers utilizan la cultura para mos-
trar la información en el formato correcto y el contenido localizado que se guardan en la
base de datos se ve como si fuera parte de una única tabla. Para la traducción de las in-
terfaces, el helper __() y los diccionarios XLIFF permiten obtener la máxima flexibilidad
con el mínimo trabajo.




www.librosweb.es                                                                            280
Symfony, la guía definitiva                                          Capítulo 14. Generadores




Capítulo 14. Generadores
Muchas aplicaciones web se reducen a una mera interfaz de acceso a la información al-
macenada en una base de datos. Symfony automatiza la tarea repetitiva de crear módu-
los para manipular datos mediante el uso de objetos Propel. Si el modelo de objetos está
bien definido, es posible incluso generar de forma automática la parte de administración
completa de un sitio web. En este capítulo se explican los 2 tipos de generadores au-
tomáticos incluidos en Symfony: el scaffolding (literalmente se puede traducir como “an-
damiaje”) y el generador de la parte de administración. Este último generador se basa en
un archivo de configuración especial con su propia sintaxis, por lo que la mayor parte de
este capítulo hace referencia a las posibilidades que ofrece el generador de la
administración.


14.1. Generación de código en función del modelo
En las aplicaciones web, las operaciones de acceso a los datos se pueden clasificar en
una de las siguientes categorías:

      ▪ Insertar un registro (creation, en inglés)

      ▪ Obtener registros (retrieval, en inglés)

      ▪ Modificar un registro o alguna de sus columnas (modification, en inglés)

      ▪ Borrar un registro (deletion, en inglés)

Como estas operaciones son tan comunes, se ha creado un acrónimo para referirse a to-
das ellas: CRUD (por las iniciales de sus nombres en inglés). Muchas páginas se reducen
a alguna de esas operaciones. En un foro por ejemplo, el listado de los últimos mensajes
es una operación de obtener registros y responder a un mensaje se corresponde con la
opción de insertar un registro.

En muchas aplicaciones web se crean continuamente acciones y plantillas que realizan las
operaciones CRUD para una determinada tabla de datos. En Symfony, el modelo contiene
la información necesaria para poder generar de forma automática el código de las opera-
ciones CRUD, de forma que se simplifica el desarrollo inicial de la aplicación y las interfa-
ces de la parte de gestión de las aplicaciones.

Las tareas de generación de código a partir del modelo de datos crean un módulo entero,
y se pueden ejecutar mediante el siguiente comando de Symfony:
   > symfony <NOMBRE_TAREA> <NOMBRE_APLICACION> <NOMBRE_MODULO> <NOMBRE_CLASE>

Las tareas de generación de código son propel-init-crud, propel-generate-crud y
propel-init-admin.


14.1.1. Scaffolding y administración
Durante la fase de desarrollo de una aplicación, se puede utilizar la generación de código
para alguna de las siguientes tareas:




www.librosweb.es                                                                         281
Symfony, la guía definitiva                                           Capítulo 14. Generadores


      ▪ El “scaffolding” es una estructura básica de acciones y plantillas para poder reali-
        zar las operaciones CRUD en una tabla de la base de datos. El código generado
        es mínimo, ya que solo es una guía para seguir desarrollando. Se trata de la base
        inicial que debe adaptarse para seguir los requerimientos de lógica y presenta-
        ción de la aplicación. El “scaffolding” se utiliza durante la fase de desarrollo de la
        aplicación para crear una acceso vía web a la base de datos, para construir un
        prototipo rápido o para realizar automáticamente el código básico de un módulo
        basado en una tabla de la base de datos.

      ▪ La “administración” es una interfaz avanzada para manipular los datos y que se
        emplea en la parte de gestión o administración de las aplicaciones. La principal
        diferencia con el “scaffolding” es que el programador no modifica el código gene-
        rado para la parte de administración. Mediante archivos de configuración y he-
        rencia de clases se puede personalizar y extender la parte de administración ge-
        nerada. La presentación de la interfaz es importante y por eso incluyen opciones
        como el filtrado, la paginación y la ordenación de datos. La parte de administra-
        ción generada automáticamente con Symfony tiene calidad suficiente como para
        entregarla al cliente formando parte de la aplicación que se le ha desarrollado.

La línea de comandos de Symfony utiliza la palabra crud para referirse al “scaffolding” y
admin para referirse a la parte de administración de la aplicación.


14.1.2. Generando e iniciando el código
Symfony dispone de dos formas para generar el código: mediante herencia (init) o me-
diante la generación automática de código (generate).

Los módulos se pueden “iniciar”, esto es, crear una serie de clases vacías que heredan de
las del framework. Este método enmascara el código PHP de las acciones y de las planti-
llas para evitar que sean modificadas. Se trata de un método útil cuando la estructura de
datos puede variar o cuando se necesita crear rápidamente un interfaz para el acceso a
la base de datos. El código que se ejecuta durante la ejecución de la aplicación no se en-
cuentra en la aplicación, sino en la cache. Las tareas de la línea de comandos utilizadas
para este tipo de generación comienzan con propel-init-.

El código de la acción generada está vacío. Si por ejemplo se inicia un módulo llamado
articulo, el código de las acciones será el siguiente:
   class articuloActions extends autoarticuloActions
   {
   }

Por otra parte, también es posible generar el código completo de las acciones y plantillas
para que pueda ser modificado. El módulo resultante es por tanto, independiente de las
clases del framework y no es posible adaptarlo utilizando exclusivamente archivos de
configuración. Las tareas de la línea de comandos utilizadas para este tipo de generación
comienzan con propel-generate-.

Como el objetivo del “scaffolding” es generar la base para futuros desarrollos, es mejor
generar el “scaffolding” y no solo iniciarlo. Por otra parte, la parte de administración es
fácil de actualizar mediante un cambio en los archivos de configuración y sigue siendo

www.librosweb.es                                                                          282
Symfony, la guía definitiva                                                Capítulo 14. Generadores


usable aunque se modifique el modelo de datos. Esta es la razón por la que en las admi-
nistraciones el código de “inicia” y no sólo se “genera”.

14.1.3. Modelo de datos de ejemplo
A lo largo de este capítulo, los listados de código muestran las posibilidades de los gene-
radores de Symfony mediante un ejemplo sencillo, similar al utilizado en el Capítulo 8. Se
trata de la típica aplicación para crear un blog, que contiene las clases Article y Comment.
El listado 14-1 muestra el esquema de datos y la figura 14-1 lo ilustra.

Listado 14-1 - Archivo schema.yml de la aplicación de ejemplo
   propel:
     blog_article:
       _attributes:   { phpName: Article }
       id:
       title:         varchar(255)
       content:       longvarchar
       created_at:
     blog_comment:
       _attributes:   { phpName: Comment }
       id:
       article_id:
       author:        varchar(255)
       content:       longvarchar
       created_at:

Modelo de datos de ejemplo

La generación de código no impone ninguna regla o restricción a la creación del esque-
ma. Symfony utiliza el esquema tal y como se ha definido, interpreta sus atributos y ge-
nera el “scaffolding” o la parte de administración de la aplicación.

  SUGERENCIA
  Para aprovechar al máximo este capítulo, deberías hacer todos los ejemplos que se incluyen. Si se
  realizan todos los pasos descritos en los listados de código, se obtiene un mejor conocimiento de lo
  que genera Symfony y de lo que se puede llegar a hacer con el código generado. La recomenda-
  ción es que crees una estructura de datos como la descrita anteriormente para crear una base de
  datos con las tablas blog_article y blog_comment, rellenándolas con datos de prueba.


14.2. Scaffolding
El scaffolding es muy útil cuando se empieza a desarrollar una aplicación. Con un solo co-
mando, Symfony es capaz de crear un módulo entero basado en la descripción de una ta-
bla de la base de datos.

14.2.1. Generando el scaffolding
Para generar el scaffolding del módulo article basado en la clase Article del modelo, se
utiliza el siguiente comando:
     > symfony propel-generate-crud miaplicacion article Article



www.librosweb.es                                                                                 283
Symfony, la guía definitiva                                                Capítulo 14. Generadores


Symfony comprueba la definición de la clase Article en el archivo schema.yml y crea una
serie de acciones y plantillas que guarda en el directorio miaplicacion/modules/article/
y que están basadas en esa definición.

El módulo generado incluye 3 vistas. La vista list, que es la vista por defecto, muestra
las filas de datos de la tabla blog_article cuando se accede a la aplicación mediante
http://localhost/miaplicacion_dev.php/article , tal y como muestra la figura 14-2.




                          Figura 14.1. Vista "list" del módulo "article"


Si se pincha sobre el identificador de un artículo, se muestra la lista show. Todos los deta-
lles de una fila de datos se muestran en una única página, como se ve en la figura 14-3.




                        Figura 14.2. Vista "show" del módulo "article"


Si se modifica un artículo pinchando sobre el enlace edit o si se crea un nuevo artículo
mediante el enlace create en la vista list, se muestra la vista edit, que se puede ver en
la figura 14-4.

Mediante las opciones incluidas en este módulo, es posible crear nuevos artículos y bo-
rrar o modificar los artículos existentes. El código generado es una buena base a partir
de la cual empezar el desarrollo de la aplicación. El listado 14-2 muestra las acciones y
plantillas generadas para el nuevo módulo.




www.librosweb.es                                                                               284
Symfony, la guía definitiva                                           Capítulo 14. Generadores




                         Figura 14.3. Vista "edit" del módulo "article"


Listado 14-2 - Elementos generados para las operaciones CRUD, en miaplicacion/
modules/article/
   // En actions/actions.class.php
   index           // Redirige a la acción "list"
   list            // Muestra un listado de todas las filas de la tabla
   show            // Muestra todas las columnas de una fila
   edit            // Muestra un formulario para modificar la columnas de una fila
   update                 // Acción que se llama en el formulario de la acción "edit"
   delete                 // Borra una fila
   create                 // Crea una nueva fila

   // En templates/
   editSuccess.php // Formulario para modificar una fila (vista "edit")
   listSuccess.php // Listado de todas las filas (vista "list")
   showSuccess.php // Detalle de una fila (vista "show")

El código de todas estas acciones y plantillas es bastante sencillo y explícito, por lo que
en vez de mostrar todo el código para explicarlo, el listado 14-3 muestra un pequeño ex-
tracto de la clase de las acciones.

Listado 14-3 - Clase Action generada, en miaplicacion/modules/article/actions/
actions.class.php
   class articleActions extends sfActions
   {
     public function executeIndex()
     {
       return $this->forward('article', 'list');
     }

      public function executeList()
      {
        $this->articles = ArticlePeer::doSelect(new Criteria());
      }

      public function executeShow()
      {
        $this->article = ArticlePeer::retrieveByPk($this->getRequestParameter('id'));
        $this->forward404Unless($this->article);
      }
      ...



www.librosweb.es                                                                          285
Symfony, la guía definitiva                                          Capítulo 14. Generadores


Para obtener una aplicación básica, solamente es necesario modificar el código generado
para ajustarlo a las necesidades de la aplicación y repetir la generación del código de las
operaciones CRUD para el resto de tablas con las que se deba interactuar. Generar el
scaffolding de una aplicación permite dar un gran impulso inicial a su desarrollo, por lo
que es buena idea dejar que Symfony haga el trabajo sucio y el desarrollador se centre
en el diseño de la interfaz y de otros detalles específicos.

14.2.2. Iniciando el scaffolding
Además de generarlo, también es posible “iniciar” el scaffolding, que se utiliza sobre todo
para comprobar que se puede acceder a los datos de la base de datos. Un scaffolding que
solo ha sido iniciado es muy fácil de crear y muy fácil de borrar una vez que se ha com-
probado que todo funciona correctamente.

El siguiente comando inicia el scaffolding correspondiente al módulo article que maneja
las filas de datos correspondientes a la clase Article del modelo:
     > symfony propel-init-crud miaplicacion article Article

Una vez iniciado, se puede acceder a la vista list mediante la acción por defecto:
   http://localhost/miaplicacion_dev.php/article

Las páginas resultantes son exactamente iguales que las que tiene un scaffolding com-
pletamente generado. Estas páginas se pueden utilizar como una interfaz web sencilla
para la base de datos.

Si se accede al archivo actions.class.php creado para el módulo article, se comprueba
que está vacío, ya que todo hereda de una clase generada automáticamente. Con las
plantillas sucede lo mismo: no existe ningún archivo de plantilla en el directorio templa-
tes/. El código utilizado en las acciones y plantillas que solamente han sido iniciadas es
el mismo que para el scaffolding que se genera completamente, pero se guarda en la ca-
che de la aplicación (miproyecto/cache/miaplicacion/prod/module/autoArticle/).

Durante el desarrollo de la aplicación, los programadores inician los scaffolding para inte-
ractuar con los datos, sin importar el aspecto de la interfaz. El objetivo del código gene-
rado con este método no es el de ser modificado para ajustarse a los requisitos de la
aplicación; un scaffolding que solamente ha sido iniciado se puede considerar como una
alternativa sencilla a la aplicación PHPmyadmin para la gestión de la información de la
base de datos.


14.3. Creando la parte de administración de las aplicaciones
Symfony es capaz de generar módulos más avanzados para la parte de gestión o admi-
nistración de las aplicaciones, también basados en las definiciones de las clases del mo-
delo del archivo schema.yml. Se puede crear toda la parte de administración de la aplica-
ción mediante módulos generados automáticamente. Los ejemplos de esta sección des-
criben los módulos de administración creados para una aplicación llamada backend. El es-
queleto de la aplicación se puede crear mediante la tarea init-app de Symfony:
     > symfony init-app backend


www.librosweb.es                                                                         286
Symfony, la guía definitiva                                             Capítulo 14. Generadores


Los módulos de administración interpretan el modelo con la ayuda de un archivo de con-
figuración especial llamado generator.yml, que se puede modificar para extender los
componentes generados automáticamente y para controlar el aspecto visual de los mó-
dulos. Este tipo de módulos también disponen de los mecanismos habituales descritos en
los capítulos anteriores (layout, validación, enrutamiento, configuración propia, carga au-
tomática de clases, etc.). Incluso es posible redefinir las acciones y plantillas generadas
para incluir características propias, aunque el archivo generator.yml es suficiente para
realizar la mayoría de modificaciones, por lo que el código PHP solamente es necesario
para las tareas muy específicas.

14.3.1. Iniciando un módulo de administración
Symfony permite crear la parte de administración de una aplicación módulo a módulo.
Los módulos se generan en base a objetos Propel mediante la tarea propel-init-admin,
que utiliza una sintaxis similar a la que se utiliza para iniciar un scaffolding:
     > symfony propel-init-admin backend article Article

Este comando es suficiente para crear un módulo llamado article en la aplicación bac-
kend y basado en la definición de la clase Article, que además es accesible desde la
dirección:
     http://localhost/backend.php/article

El aspecto visual de los módulos generados automáticamente, que se muestra en las fi-
guras 14-5 y 14-6, es suficiente para incluirlo tal cual en una aplicación comercial.




             Figura 14.4. Vista "list" del módulo "article" en la aplicación "backend"




www.librosweb.es                                                                            287
Symfony, la guía definitiva                                            Capítulo 14. Generadores




          Figura 14.5. Vista "edit" del módulo "article" en la aplicación "backend"


Las diferencias entre la interfaz de un scaffolding y la de una administración generada
automáticamente pueden parecer insignificantes, pero las posibilidades de configuración
de la administración permiten mejorar el aspecto por defecto con muchas características
para las que no es necesario escribir ni una sola línea de código PHP.

  NOTA
  Los módulos de una administración solamente pueden ser iniciados y nunca generados.


14.3.2. Un vistazo al código generado
El código del módulo de administración module, que se encuentra en el directorio apps/
backend/modules/article/, está completamente vacío porque solo ha sido iniciado. La
mejor forma de comprobar el código generado para este módulo es acceder con el nave-
gador a sus páginas y después comprobar los contenidos de la carpeta cache/. El listado
14-4 muestra todas las acciones y plantillas generadas que se encuentran en la cache.

Listado 14-4 - Elementos de administración generados automáticamente, en ca-
che/backend/ENV/modules/article/
   // En actions/actions.class.php
   create           // Redirige a "edit"
   delete                  // Borra una fila
   edit             // Muestra un formulario para modificar la columnas de una fila
                    // y procesa el envío del formulario
   index            // Redirige a "list"
   list             // Muestra un listado de todas las filas de la tabla
   save             // Redirige a "edit"

   // En templates/
   _edit_actions.php
   _edit_footer.php
   _edit_form.php

www.librosweb.es                                                                           288
Symfony, la guía definitiva                                           Capítulo 14. Generadores

   _edit_header.php
   _edit_messages.php
   _filters.php
   _list.php
   _list_actions.php
   _list_footer.php
   _list_header.php
   _list_messages.php
   _list_td_actions.php
   _list_td_stacked.php
   _list_td_tabular.php
   _list_th_stacked.php
   _list_th_tabular.php
   editSuccess.php
   listSuccess.php

Los módulos de administración generados automáticamente se componen básicamente
de las vistas edit y list. Si se observa el código PHP, se encontrará un código muy mo-
dular, fácil de leer y extensible.

14.3.3. Conceptos básicos del archivo de configuración generator.yml
La principal diferencia entre el scaffolding y la parte de administración de la aplicación (a-
demás de que los módulos de una administración no disponen de la acción show) es que
la administración se basa en las opciones del archivo de configuración generator.yml. Las
opciones de configuración por defecto para un módulo de administración recién creado
llamado article se pueden ver en el archivo backend/modules/article/config/genera-
tor.yml, reproducido en el listado 14-5.

Listado 14-5 - Configuración por defecto para la generación de la administra-
ción, en backend/modules/article/config/generator.yml
   generator:
     class:                   sfPropelAdminGenerator
     param:
       model_class:           Article
       theme:                 default

Esta configuración es suficiente para generar una administración básica. Todas las opcio-
nes propias se añaden bajo la clave param, después de la línea theme (lo que significa que
todas las líneas que se añadan al final del archivo generator.yml tienen que empezar al
menos por 4 espacios en blanco, para que estén correctamente indentadas). El listado
14-6 muestra un archivo generator.yml típico.

Listado 14-6 - Configuración completa típica para el generador
   generator:
     class:                   sfPropelAdminGenerator
     param:
       model_class:           Article
       theme:                 default

        fields:
          author_id:          { name: Article author }


www.librosweb.es                                                                          289
Symfony, la guía definitiva                                               Capítulo 14. Generadores



        list:
          title:          List of all articles
          display:        [title, author_id, category_id]
          fields:
            published_on: { params: date_format='dd/MM/yy' }
          layout:         stacked
          params:         |
            %%is_published%%<strong>%%=title%%</strong><br /><em>by %%author%%
            in %%category%% (%%published_on%%)</em><p>%%content_summary%%</p>
          filters:        [title, category_id, author_id, is_published]
          max_per_page:   2

        edit:
          title:              Editing article "%%title%%"
          display:
            "Post":           [title, category_id, content]
            "Workflow":       [author_id, is_published, created_on]
          fields:
            category_id:      {   params: disabled=true }
            is_published:     {   type: plain}
            created_on:       {   type: plain, params: date_format='dd/MM/yy' }
            author_id:        {   params: size=5 include_custom=>> Choose an author << }
            published_on:     {   credentials: }
            content:          {   params: rich=true tinymce_options=height:150 }

Las siguientes secciones explican en detalle todas las opciones que se pueden utilizar en
este archivo de configuración.


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 ten-
ga 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 sinta-
xis: 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/sfAdminGene-
ratorRefCard.pdf. Puede ser de utilidad tener la chuleta a mano cuando se leen los ejem-
plos 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ódu-
lo comment, es necesario crearlo mediante la tarea propel-init-admin:
     > symfony propel-init-admin backend comment Comment




www.librosweb.es                                                                              290
Symfony, la guía definitiva                                        Capítulo 14. Generadores




                   Figura 14.6. 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 cam-
pos 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 sche-
ma.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 mensa-
je 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



www.librosweb.es                                                                       291
Symfony, la guía definitiva                                             Capítulo 14. Generadores

       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 cualq-
uier 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 em-
plea 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]


www.librosweb.es                                                                            292
Symfony, la guía definitiva                                       Capítulo 14. Generadores



        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.




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




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




www.librosweb.es                                                                      293
Symfony, la guía definitiva                                               Capítulo 14. Generadores


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 utili-
        za 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.

  SUGERENCIA
  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 corres-
ponderse con alguna de las columnas definidas en el esquema. Si la clase relacionada in-
cluye 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 pue-
de 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 co-
mentarios 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ódu-
los 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.




www.librosweb.es                                                                              294
Symfony, la guía definitiva                                          Capítulo 14. Generadores




              Figura 14.9. 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 getArti-
cleLink() 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 utili-
zando 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 pue-
den utilizar como columnas, en modules/comment/config/generator.yml
   generator:
     class:                   sfPropelAdminGenerator
     param:
       model_class:           Comment
       theme:                 default

        list:
          display:            [id, article_link, content]




www.librosweb.es                                                                         295
Symfony, la guía definitiva                                        Capítulo 14. Generadores




            Figura 14.10. 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 getArti-
cleLink() 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 genera-
tor.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 llama-
do _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/com-
ment/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ódu-
lo construido para la clase llamada GrupoUsuario, el elemento parcial tiene acceso al ob-
jeto actual mendiante la variable $grupo_usuario.




www.librosweb.es                                                                       296
Symfony, la guía definitiva                                                   Capítulo 14. Generadores


El resultado de este ejemplo es idéntico al mostrado en la figura 14-11, salvo que en es-
te caso se respeta la separación en capas. Si se acostumbra a separar el código en ca-
pas, 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 pre-
fijo ~
             ...
             list:
               display:           [id, ~article_link, created_at]

En la plantilla que se genera, la configuración anterior resulta en una llamada al compo-
nente 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ítu-
lo 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




www.librosweb.es                                                                                     297
Symfony, la guía definitiva                                             Capítulo 14. Generadores

            list:
              title:           List of Articles
              ...

            edit:
              title:           Body of article %%title%%
              display:         [content]




              Figura 14.11. 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 nece-
sario modificarlos (siempre que el modelo utilice nombres de clase explícitos).

  SUGERENCIA
  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/com-
ment/config/generator.yml
            edit:
              fields:
                ...
                article_id:    { help: The current comment relates to this article }




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

www.librosweb.es                                                                            298
Symfony, la guía definitiva                                                 Capítulo 14. Generadores




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.

  Las plantillas de administración están preparadas para la internacionalización

  Todo el texto incluido en las plantillas generadas automáticamente está internacionalizado, es decir,
  todos los textos se muestran mediante llamadas al helper __(). De esta forma, es muy fácil traducir
  una aplicación de administración generada automáticamente incluyendo la traducción de todas las
  frases en un archivo XLIFF, en el directorio apps/miaplicacion/i18n/, tal y como se explica 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 pue-
den 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 co-
lumna id de la lista de comentarios no solo muestra la clave primaria de cada comentar-
io, 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 con-
tent. 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




www.librosweb.es                                                                                  299
Symfony, la guía definitiva                                             Capítulo 14. Generadores

            list:
              display:        [article_link, =content]




 Figura 14.13. 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 posi-
ble mostrar de forma seguida todos los datos en una sola cadena de texto que ocupe to-
da la anchura de la tabla. El aspecto con el que se muestran los datos se denomina “lay-
out” 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]




www.librosweb.es                                                                             300
Symfony, la guía definitiva                                         Capítulo 14. Generadores




          Figura 14.14. 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 fil-
tros, 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ña-
dir un método __toString() a la clase Article (este método puede devolver, por ejem-
plo, 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]




www.librosweb.es                                                                        301
Symfony, la guía definitiva                                          Capítulo 14. Generadores




                Figura 14.15. 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 fil-
        tro 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 fil-
        tro es una lista desplegable con los datos de la columna correspondiente en la ta-
        bla 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 ca-
        lendario) de forma que se pueda indicar un intervalo temporal.

      ▪ Para las columnas booleanas, el filtro muestra una lista desplegable con los valo-
        res 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 valo-
res (open y closed), pero estos valores se almacenan directamente en cada fila de la ta-
bla (no se utiliza una relación con otra tabla). Un filtro de Symfony en este campo mos-
trará 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 desple-
gable. 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',


www.librosweb.es                                                                         302
Symfony, la guía definitiva                                          Capítulo 14. Generadores

          '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 so-
lució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]




           Figura 14.16. 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 cabe-
ceras 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 per-
mite reordenar los datos de forma adecuada.




www.librosweb.es                                                                         303
Symfony, la guía definitiva                                         Capítulo 14. Generadores




  Figura 14.17. 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 apun-
te 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 colum-
  nas 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 pagina-
ció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.




www.librosweb.es                                                                        304
Symfony, la guía definitiva                                         Capítulo 14. Generadores




          Figura 14.18. 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 op-
ció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úme-
ro 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. Afortunada-
mente, se puede indicar al paginador que utilice un método específico tipo doSelectJ-
oinXXX() 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 ti-
po de campo de formulario que se muestra. Después, genera un helper de tipo ob-
ject_*_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, ...]

www.librosweb.es                                                                        305
Symfony, la guía definitiva                                                  Capítulo 14. Generadores


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 directa-
mente 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 opc-
iones 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 opcio-
nes 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() genera-
do. La opción params del campo article_id en el ejemplo anterior produce el siguiente
código en la plantilla:


www.librosweb.es                                                                                 306
Symfony, la guía definitiva                                         Capítulo 14. Generadores

       <?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 manual-
mente la actualización de la columna en función del valor enviado por el elemento parc-
ial. Symfony puede procesar automáticamente los campos normales (los que se corres-
ponden con columnas reales) pero no puede adivinar la forma de tratar los datos que uti-
lizan 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 for-
mulario debería mostrar un cuadro de texto vacío para la contraseña y así el usuario pue-
de 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/act-
ions/actions.class.php
   class userActions extends sfActions
   {
     protected function updateUserFromRequest()
     {
       // Procesar los datos del campo parcial
       $password = $this->getRequestParameter('newpassword');



www.librosweb.es                                                                         307
Symfony, la guía definitiva                                                  Capítulo 14. Generadores

           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 me-
  diante un validador. Los módulos generados automáticamente pueden hacer uso de este mecanis-
  mo 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 cam-
pos, 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 adminis-
traciones para iniciar el módulo de la clase Comment, la acción comment/edit muestra au-
tomá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 el objeto 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 ar-
ticle (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 es-
tar 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.

www.librosweb.es                                                                                   308
Symfony, la guía definitiva                                         Capítulo 14. Generadores




      Figura 14.19. 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 (ele-
        mentos disponibles) a la segunda lista (elementos seleccionados).

      ▪ El tipo admin_select_list es una lista desplegable expandida que permite selecc-
        ionar más de 1 elemento cada vez.

      ▪ El tipo admin_check_list es una lista de elementos checkbox seleccionables.




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




www.librosweb.es                                                                        309
Symfony, la guía definitiva                                               Capítulo 14. Generadores


14.4.7. Añadiendo nuevas interacciones
Los módulos de administración permiten a los usuarios realizar las operaciones CRUD ha-
bituales, 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ági-
na 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 nue-
vos elementos.




                         Figura 14.21. 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 ac-
ció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

www.librosweb.es                                                                              310
Symfony, la guía definitiva                                           Capítulo 14. Generadores


elemento. Por tanto, la acción save_and_add es un atajo muy útil cuando se están añad-
iendo 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 pulsa-
do 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 inte-
racciones por defecto. El generador de la administración es capaz de reconocer las accio-
nes _edit, _delete, _create, _list, _save, _save_and_add y _create.




                         Figura 14.22. Interacciones de la vista "edit"


También es posible definir interacciones propias, para lo que se debe especificar un nom-
bre 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 addcom-
ment.png, tal y como se muestra en la figura 14-24. Al pinchar sobre ese botón, se ejecu-
ta la acción addComment del módulo actual. La clave primaria del objeto relacionado se pa-
sa automáticamente a los parámetros de la petición que se produce.




www.librosweb.es                                                                           311
Symfony, la guía definitiva                                            Capítulo 14. Generadores


                     Figura 14.23. 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 utili-
za 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 em-
bargo, como se explica en el Capítulo 10, complica un poco la configuración del valida-
dor, 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

www.librosweb.es                                                                           312
Symfony, la guía definitiva                                         Capítulo 14. Generadores


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 genera-
dos 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 res-
tringir 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 pue-
de 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 }


14.5. Modificando el aspecto de los módulos generados



www.librosweb.es                                                                          313
Symfony, la guía definitiva                                           Capítulo 14. Generadores


La presentación de los módulos generados se puede modificar completamente para inte-
grarlo con cualquier otro estilo gráfico. Los cambios no solo se pueden realizar mediante
una hoja de estilos, sino que es posible redefinir las plantillas por defecto.

14.5.1. Utilizando una hoja de estilos propia
Como el código HTML generado tiene un contenido bien estructurado, es posible modifi-
car fácilmente su aspecto.

Mediante la opción css de la configuración del generador es posible definir la hoja de es-
tilos alternativa que se utiliza en el módulo de administración, como se muestra en el lis-
tado 14-38.

Listado 14-38 - Utilizando una hoja de estilos propia en vez de la de por defecto
        generator:
          class:              sfPropelAdminGenerator
          param:
            model_class:      Comment
            theme:            default
            css:              mystylesheet

Además, también es posible utilizar las opciones habituales del archivo view.yml del mó-
dulo para redefinir los estulos utilizados en cada vista.

14.5.2. Creando una cabecera y un pie propios
Las vistas list y edit incluyen por defecto elementos parciales para la cabecera y el pie
de página. Como no existen por defecto elementos parciales en el directorio templates/
del módulo de administración, solamente es necesario crearlos con los siguientes nom-
bres para que se incluyan de forma automática:
        _list_header.php
        _list_footer.php
        _edit_header.php
        _edit_footer.php

Si se quiere añadir por ejemplo una cabecera propia en la vista article/edit, se crea un
archivo llamado _edit_header.php como el que muestra el listado 14-39. No es necesario
realizar más configuraciones para que se incluya automáticamente.

Listado 14-39 - Ejemplo de elemento parcial para la cabecera de la vista edit, en
modules/articles/templates/_edit_header.php
        <?php if ($article->getNbComments() > 0): ?>
          <h2>This article has <?php echo $article->getNbComments() ?> comments.</h2>
        <?php endif; ?>

Debe tenerse en cuenta que un elemento parcial de la vista edit siempre tiene acceso al
objeto al que hace referencia mediante una variable con el mismo nombre que la clase y
que un elemento parcial de la vista list tiene acceso al paginador actual mediante la va-
riable $pager.

  Utilizando parámetros propios en la llamada a las acciones de la administración


www.librosweb.es                                                                          314
Symfony, la guía definitiva                                                   Capítulo 14. Generadores


  Las acciones del módulo de administración pueden recibir parámetros propios mediante la opción
  query_string del helper link_to(). Por ejemplo, para extender el elemento parcial _edit_hea-
  der anterior con un enlace a los comentarios del artículo, se utiliza el siguiente código:
           <?php if ($article->getNbComments() > 0): ?>
             <h2>This article has <?php echo link_to($article->getNbComments().' comments',
      'comment/list', array('query_string' =>
      'filter=filter&filters%5Barticle_id%5D='.$article->getId())) ?></h2>
           <?php endif; ?>

  El valor que se pasa a la opción query_string es una versión codificada del siguiente valor más
  fácil de leer:
             'filter=filter&filters[article_id]='.$article->getId()

  Se filtran los comentarios que se muestran a solamente los que estén relacionados con $article.
  Si se utiliza la opción query_string, es posible especificar el orden en el que se ordenan los datos
  y los filtros utilizados para mostrar una vista de tipo list. Esta opción también es útil para las inte-
  racciones propias.


14.5.3. Modificando el tema
Existen otros elementos parciales en el directorio templates/ del módulo que heredan del
framework y que se pueden redefinir para adaptarse a las necesidades de cada proyecto.

Las plantillas del generador están divididas en pequeñas partes que se pueden redefinir
de forma independiente, al igual que se pueden modificar las acciones una a una.

No obstante, si se quieren redefinir todos los elementos parciales para varios módulos, lo
mejor es crear un tema que se pueda reutilizar. Un tema es un conjunto completo de
plantillas y acciones que se pueden utilizar en un módulo de administración si así se indi-
ca en el archivo generator.yml. En el tema por defecto, Symfony utiliza los archivos defi-
nidos en $sf_symfony_data_dir/generator/sfPropelAdmin/default/.

Los archivos de los temas tienen que guardarse en el directorio data/generator/sfPro-
pelAdmin/[nombre_tema]/template/ del proyecto, y la mejor forma de crear un nuevo te-
ma es copiando los archivos del tema por defecto (que se encuentran en el directorio
$sf_symfony_data_dir/generator/sfPropelAdmin/default/template/). De esta forma, es
fácil asegurarse de que el tema propio contiene todos los archivos requeridos:
         // Elementos parciales, en [nombre_tema]/template/templates/
         _edit_actions.php
         _edit_footer.php
         _edit_form.php
         _edit_header.php
         _edit_messages.php
         _filters.php
         _list.php
         _list_actions.php
         _list_footer.php
         _list_header.php
         _list_messages.php
         _list_td_actions.php
         _list_td_stacked.php

www.librosweb.es                                                                                     315
Symfony, la guía definitiva                                                   Capítulo 14. Generadores

        _list_td_tabular.php
        _list_th_stacked.php
        _list_th_tabular.php

        // Acciones, en [nombre_tema]/template/actions/actions.class.php
        processFilters()     // Procesa los filtros de la petición
        addFiltersCriteria() // Añade un filtro al objeto Criteria
        processSort()
        addSortCriteria()

Se debe tener en cuenta que los archivos de las plantillas son en realidad “plantillas de
plantillas”, es decir, archivos PHP que se procesan mediante una herramienta especial
para generar las plantillas en función de las opciones del generador (este proceso se co-
noce como la fase de compilación). Como las plantillas generadas deben contener código
PHP que se ejecuta cuando se accede a estas plantillas, los archivos que son “plantillas
de plantillas” tienen que utilizar una sintaxis alternativa para que el código PHP final no
se ejecute durante el proceso de compilación de las plantillas. El listado 14-40 muestra
un trozo de una de las “plantillas de plantillas” de Symfony.

Listado 14-40 - Sintaxis de las plantillas de plantillas
       <?php foreach ($this->getPrimaryKey() as $pk): ?>
       [?php echo object_input_hidden_tag($<?php echo $this->getSingularName()
   ?>,'get<?php echo $pk->getPhpName() ?>') ?]
       <?php endforeach; ?>

En el listado anterior, el código PHP indicado mediante <? se ejecuta durante la compila-
ción, mientras que el código indicado mediante [? se ejecuta solamente durante la ejecu-
ción final de la plantilla generada. El generador de plantillas reemplaza las etiquetas [?
en etiquetas <?, por lo que la plantilla resultante es la siguiente:
        <?php echo object_input_hidden_tag($article, 'getId') ?>

Trabajar con las “plantillas de plantillas” es bastante complicado, por lo que el mejor con-
sejo para crear un tema propio es comenzarlo a partir del tema default, modificarlo poco
a poco y probar los cambios continuamente.

  SUGERENCIA
  También es posible encapsultar un tema completo para el generador en un plugin, con lo que el te-
  ma es más fácil de reutilizar y más fácil de instalar en diferentes aplicaciones. El Capítulo 17 incluye
  más información.

  Contruyendo tu propio generador

  Tanto el scaffolding como la administración utilizan una serie de componentes internos de Symfony
  que automatizan la creación de acciones y plantillas en la cache, el uso d etemas y el procesamien-
  to de las “plantillas de plantillas”.

  De esta forma, Symfony proporciona todas las herramientas para crear tu propio generador, que
  puede ser similar a los existentes o ser completamente diferente. La generación automática de un
  módulo se gestiona mediante el método generate() de la clase sfGeneratorManager. Por ejem-
  plo, para generar una administración, Symfony realiza internamente la siguiente llamada a este
  método:


www.librosweb.es                                                                                     316
Symfony, la guía definitiva                                               Capítulo 14. Generadores

           $generator_manager = new sfGeneratorManager();
           $data = $generator_manager->generate('sfPropelAdminGenerator', $parameters);

  Si se quiere construir un generador propio, es conveniente mirar la documentación de la API de las
  clases sfGeneratorManager y sfGenerator, y utilizar las clases sfAdminGenerator y sfCRUDGe-
  nerator como ejemplo.


14.6. Resumen
Para iniciar o generar automáticamente los módulos de una aplicación de gestión, lo prin-
cipal es disponer de un esquema y un modelo de objetos bien definidos. El código PHP
del scaffolding está pensado para ser modificado, pero los módulos de una administración
generada automáticamente se modifican mediante los archivos de configuración.

El archivo generator.yml es la clave de los módulos generados automáticamente. Permite
modificar completamente el contenido, las opciones y el aspecto gráfico de las vistas list
y edit. Sin utilizar ni una sola línea de código PHP y solamente mediante opciones en un
archivo de configuración YAML es posible añadir títulos a los campos de formulario, men-
sajes de ayuda, filtros, configurar la ordenación de los datos, definir el tamaño de los lis-
tados, el tipo de campos empleados en los formularios, las relaciones con claves exter-
nas, las interacciones propias y el uso de credenciales.

Si el generador de las administraciones no permite incluir las características requeridas
por el proyecto, se pueden utilizar elementos parciales y se pueden redefinir las acciones
para conseguir la máxima flexibilidad. Además, se pueden reutilizar todas las adaptacio-
nes realizadas al generador de administraciones mediante el uso de los temas.




www.librosweb.es                                                                               317
Symfony, la guía definitiva                        Capítulo 15. Pruebas unitarias y funcionales




Capítulo 15. Pruebas unitarias y funcionales
La automatización de pruebas (automated tests) es uno de los mayores avances en la
programación desde la invención de la orientación a objetos. Concretamente en el desa-
rrollo de las aplicaciones web, las pruebas aseguran la calidad de la aplicación incluso
cuando el desarrollo de nuevas versiones es muy activo. En este capítulo se introducen
todas las herramientas y utilidades que proporciona Symfony para automatizar las
pruebas.


15.1. Automatización de pruebas
Cualquier programador con experiencia en el desarrollo de aplicaciones web conoce de
sobra el esfuerzo que supone probar correctamente la aplicación. Crear casos de prueba,
ejecutarlos y analizar sus resultados es una tarea tediosa. Además, es habitual que los
requisitos de la aplicación varíen constantemente, con el consiguiente aumento del nú-
mero de versiones de la aplicación y la refactorización continua del código. En este con-
texto, es muy probable que aparezcan nuevos errores.

Este es el motivo por el que la automatización de pruebas es una recomendación, aunque
no una obligación, útil para crear un entorno de desarrollo satisfactorio. Los conjuntos de
casos de prueba garantizan que la aplicación hace lo que se supone que debe hacer. In-
cluso cuando el código interno de la aplicación cambia constantemente, las pruebas auto-
matizadas permiten garantizar que los cambios no introducen incompatibilidades en el
funcionamiento de la aplicación. Además, este tipo de pruebas obligan a los programado-
res a crear pruebas en un formato estandarizado y muy rígido que pueda ser procesado
por un framework de pruebas.

En ocasiones, las pruebas automatizadas pueden reemplazar la documentación técnica
de la aplicación, ya que ilustran de forma clara el funcionamiento de la aplicación. Un
buen conjunto de pruebas muestra la salida que produce la aplicación para una serie de
entradas de prueba, por lo que es suficiente para entender el propósito de cada método.

Symfony aplica este principio a su propio código. El código interno del framework se vali-
da mediante la automatización de pruebas. Estas pruebas unitarias y funcionales no se
incluyen en la distribución estándar de Symfony, pero se pueden descargar directamente
desde el repositorio de Subversion y se pueden acceder online en http://trac.symfony-
project.com/browser/trunk/test.


15.1.1. Pruebas unitarias y funcionales
Las pruebas unitarias aseguran que un único componente de la aplicación produce una
salida correcta para una determinada entrada. Este tipo de pruebas validan la forma en
la que las funciones y métodos trabajan en cada caso particular. Las pruebas unitarias se
encargan de un único caso cada vez, lo que significa que un único método puede necesi-
tar varias pruebas unitarias si su funcionamiento varía en función del contexto.

Las pruebas funcionales no solo validan la transformación de una entrada en una salida,
sino que validan una característica completa. Un sistema de cache por ejemplo


www.librosweb.es                                                                          318
Symfony, la guía definitiva                              Capítulo 15. Pruebas unitarias y funcionales


solamente puede ser validado por una prueba funcional, ya que comprende más de 1 so-
lo paso: la primera vez que se solicita una página, se produce su código; la segunda vez,
se obtiene directamente de la cache. De modo que las pruebas funcionales validan proce-
sos y requieren de un escenario. En Symfony, se deberían crear pruebas funcionales para
todas las acciones.

Para las interacciones más complejas, estos 2 tipos de pruebas no son suficientes. Las in-
teracciones de Ajax, por ejemplo, requieren de un navegador web que ejecute código Ja-
vaScript, por lo que es necesaria una herramienta externa para la automatización de las
pruebas. Además, los efectos visuales solamente pueden ser validados por una persona.

Si las pruebas automatizadas van a validar una aplicación compleja, probablemente sea
necesario el uso combinado de estos 3 tipos de pruebas. Como recomendación final, es
aconsejable crear pruebas sencillas y fáciles de entender.

  NOTA
  Las pruebas automatizadas comparan un resultado con la salida esperada para ese método. En
  otras palabras, evalúan “asertos” (del inglés, “assertions”, que son expresiones del tipo $a == 2. El
  valor de salida de un aserto es true o false, lo que determina si la prueba tiene éxito o falla. La
  palabra “aserto” es de uso común en las técnicas de automatización de pruebas.


15.1.2. Desarrollo basado en pruebas
La metodología conocida como TDD o “desarrollo basado en pruebas” (“test-driven deve-
lopment”) establece que las pruebas se escriben antes que el código de la aplicación.
Crear las pruebas antes que el código, ayuda a pensar y centrarse en el funcionamiento
de un método antes de programarlo. Se trata de una buena práctica que también recom-
iendan otras metodologías como XP (“Extreme Programming”). Además, es un hecho in-
negable que si no se escriben las pruebas antes, se acaba sin escribirlas nunca.

En el siguiente ejemplo se supone que se quiere desarrollar una función elimine los ca-
racteres problemáticos de una cadena de texto. La función elimina todos los espacios en
blanco del principio y del final de la cadena; además, reemplaza todos los caracteres que
no son alfanuméricos por guiones bajos y transforma todas las mayúsculas en minúscu-
las. En el desarrollo basado en pruebas, en primer lugar se piensa en todos los posibles
casos de funcionamiento de este método y se elabora una serie de entradas de prueba
junto con el resultado esperado para cada una, como se muestra en la tabla 15-1.

Tabla 15-1 - Lista de casos de prueba para la función que elimina caracteres
problemáticos

Dato de entrada                                     Resultado esperado

” valor “                                           “valor”

“valor otrovalor”                                   “valor_otrovalor”

“-)valor:..=otrovalor?”                             “__valor____otrovalor_“

“OtroValor”                                         “otrovalor”



www.librosweb.es                                                                                  319
Symfony, la guía definitiva                            Capítulo 15. Pruebas unitarias y funcionales


“¡Un valor y otro valor!”                         “_un_valor_y_otro_valor_”

A continuación, se crearían las pruebas unitarias, se ejecutarían y todas fallarían. Des-
pués, se escribe el código necesario para realizar correctamente el primer caso y se vuel-
ven a pasar todas las pruebas. En esta ocasión, la primera prueba no fallaría. Así se seg-
uiría desarrollando el código del método completo hasta que todas las pruebas se pasen
correctamente.

Una aplicación desarrollada con la metodología basada en pruebas, acaba teniendo tanto
código para pruebas como código para aplicación. Por tanto, las pruebas deberían ser
sencillas para no perder el tiempo arreglando los problemas con el código de las pruebas.

  NOTA
  Refactorizar el código de un método puede crear errores que antes no existían. Esta es otra razón
  por la que es una buena idea pasar todas las pruebas creadas antes de instalar una nueva versión
  de la aplicación en el servidor de producción. Esta técnica se conoce como “regression testing”.


15.1.3. El framework de pruebas Lime
En el ámbito de PHP existen muchos frameworks para crear pruebas unitarias, siendo los
más conocidos PhpUnit y SimpleTest. Symfony incluye su propio frameowrk llamado Li-
me. Se basa en la librería Test::More de Perl y es compatible con TAP, lo que significa
que los resultados de las pruebas se muestran con el formato definido en el “Test
Anything Protocol”, creado para facilitar la lectura ed los resultados de las pruebas.

Lime proporciona el soporte para las pruebas unitarias, es más eficiente que otros frame-
works de pruebas de PHP y tiene las siguientes ventajas:

      ▪ Ejecuta los archivos de prueba en un entorno independiente para evitar interfe-
        rencias entre las diferentes pruebas. No todos los frameworks de pruebas garan-
        tizan un entorno de ejecución “limpio” para cada prueba.

      ▪ Las pruebas de Lime son fáciles de leer y sus resultados también lo son. En los
        sistemas operativos que lo soportan, los resultados de Lime utilizan diferentes
        colores para mostrar de forma clara la información más importante.

      ▪ Symfony utiliza Lime para sus propias pruebas y su “regression testing”, por lo
        que el código fuente de Symfony incluye muchos ejemplos reales de pruebas uni-
        tarias y funcionales.

      ▪ El núcleo de Lime se valida mediante pruebas unitarias.

      ▪ Está escrito con PHP, es muy rápido y está bien diseñado internamente. Consta
        úicamente de un archivo, llamado lime.php, y no tiene ninguna dependencia.

Las pruebas que se muestran en las secciones siguientes utilizan la sintaxis de Lime, por
lo que funcionan directamente en cualquier instalación de Symfony.




www.librosweb.es                                                                              320
Symfony, la guía definitiva                              Capítulo 15. Pruebas unitarias y funcionales


  NOTA
  Las pruebas unitarias y funcionales no están pensadas para lanzarlas en un servidor de produc-
  ción. Se trata de herramientas para el programador, por lo que solamente deberían ejecutarse en la
  máquina de desarrollo del programador y no en un servidor de producción.


15.2. Pruebas unitarias
Las pruebas unitarias de Symfony son archivos PHP normales cuyo nombre termina en
Test.php y que se encuentran en el directorio test/unit/ de la aplicación. Su sintaxis es
sencilla y fácil de leer.

15.2.1. ¿Qué aspecto tienen las pruebas unitarias?
El listado 15-1 muestra un conjunto típico de pruebas unitarias para la función strtolo-
wer(). En primer lugar, se instancia el objeto lime_test (todavía no hace falta que te pre-
ocupes de sus parámetros). Cada prueba unitaria consiste en una llamada a un método
de la instancia de lime_test. El último parámetro de estos método siempre es una cade-
na de texto opcional que se utiliza como resultado del método.

Listado    15-1    -   Archivo    de    ejemplo     de     prueba     unitaria,    en   test/unit/
strtolowerTest.php
   <?php

   include(dirname(__FILE__).'/../bootstrap/unit.php');
   require_once(dirname(__FILE__).'/../../lib/strtolower.php');

   $t = new lime_test(7, new lime_output_color());

   // strtolower()
   $t->diag('strtolower()');
   $t->isa_ok(strtolower('Foo'), 'string',
       'strtolower() returns a string');
   $t->is(strtolower('FOO'), 'foo',
       'strtolower() transforms the input to lowercase');
   $t->is(strtolower('foo'), 'foo',
       'strtolower() leaves lowercase characters unchanged');
   $t->is(strtolower('12#?@~'), '12#?@~',
       'strtolower() leaves non alphabetical characters unchanged');
   $t->is(strtolower('FOO BAR'), 'foo bar',
       'strtolower() leaves blanks alone');
   $t->is(strtolower('FoO bAr'), 'foo bar',
       'strtolower() deals with mixed case input');
   $t->is(strtolower(''), 'foo',
       'strtolower() transforms empty strings into foo');

Para ejecutar el conjunto de pruebas, se utiliza la tarea test-unit desde la línea de co-
mandos. El resultado de esta tarea en la línea de comandos es muy explícito, lo que per-
mite localizar fácilmente las pruebas que han fallado y las que se han ejecutado correcta-
mente. El listado 15-2 muestra el resultado del ejemplo anterior.

Listado 15-2 - Ejecutando una prueba unitaria desde la línea de comandos


www.librosweb.es                                                                                321
Symfony, la guía definitiva                               Capítulo 15. Pruebas unitarias y funcionales

   > symfony test-unit strtolower

   1..7
   # strtolower()
   ok 1 - strtolower() returns a string
   ok 2 - strtolower() transforms the input to lowercase
   ok 3 - strtolower() leaves lowercase characters unchanged
   ok 4 - strtolower() leaves non alphabetical characters unchanged
   ok 5 - strtolower() leaves blanks alone
   ok 6 - strtolower() deals with mixed case input
   not ok 7 - strtolower() transforms empty strings into foo
   #     Failed test (.batchtest.php at line 21)
   #            got: ''
   #       expected: 'foo'
   # Looks like you failed 1 tests of 7.


  SUGERENCIA
  La instrucción include al principio del listado 15-1 es opcional, pero hace que el archivo de la prue-
  ba sea un script de PHP independiente, es decir, que se puede ejecutar sin utilizar la línea de co-
  mandos de Symfony, mediante php test/unit/strtolowerTest.php.


15.2.2. Métodos para las pruebas unitarias
El objeto lime_test dispone de un gran número de métodos para las pruebas, como se
muestra en la figura 15-2.

Tabla 15-2 - Métodos del objeto lime_test para las pruebas unitarias

Método                                  Descripción

diag($mensaje)                          Muestra un comentario, pero no ejecuta ninguna prueba

ok($prueba, $mensaje)                   Si la condición que se indica es true, la prueba tiene éxito

is($valor1, $valor2, $mensaje)          Compara 2 valores y la prueba pasa si los 2 son iguales (==)

isnt($valor1, $valor2,
                                        Compara 2 valores y la prueba pasa si no son iguales
$mensaje)

like($cadena,                           Prueba que una cadena cumpla con el patrón de una
$expresionRegular, $mensaje)            expresión regular

unlike($cadena,                         Prueba que una cadena no cumpla con el patrón de una
$expresionRegular, $mensaje)            expresión regular

cmp_ok($valor1, $operador,
                                        Compara 2 valores mediante el operador que se indica
$valor2, $mensaje)

isa_ok($variable, $tipo,                Comprueba si la variable que se le pasa es del tipo que se
$mensaje)                               indica

isa_ok($objeto, $clase,                 Comprueba si el objeto que se le pasa es de la clase que se
$mensaje)                               indica


www.librosweb.es                                                                                   322
Symfony, la guía definitiva                         Capítulo 15. Pruebas unitarias y funcionales


can_ok($objeto, $metodo,           Comprueba si el objeto que se le pasa dispone del método
$mensaje)                          que se indica

is_deeply($array1, $array2,
                                   Comprueba que 2 arrays tengan los mismos valores
$mensaje)

                                   Valida que un archivo existe y que ha sido incluido
include_ok($archivo, $mensaje)
                                   correctamente

                                   Provoca que la prueba siempre falle (es útil para las
fail()
                                   excepciones)

                                   Provoca que la prueba siempre se pase (es útil para las
pass()
                                   excepciones)

                                   Cuenta como si fueran $numeroPruebas pruebas (es útil
skip($mensaje, $numeroPruebas)
                                   para las pruebas condicionales)

                                   Cuenta como si fuera 1 prueba (es útil para las pruebas que
todo()
                                   todavía no se han escrito)

La sintaxis es tan clara que prácticamente se explica por sí sola. Casi todos los métodos
permiten indicar un mensaje como último parámetro. Este mensaje es el que se muestra
como resultado de la prueba cuando esta tiene éxito. La mejor manera de aprender a uti-
lizar estos métodos es utilizarlos, así que es importante el código del listado 15-3, que
utiliza todos los métodos.

Listado 15-3 - Probando los métodos del objeto lime_test, en test/unit/
ejemploTest.php
   <?php

   include(dirname(__FILE__).'/../bootstrap/unit.php');

   // Funciones y objetos vacíos para las pruenas
   class miObjeto
   {
     public function miMetodo()
     {
     }
   }

   function lanza_una_excepcion()
   {
     throw new Exception('excepción lanzada');
   }

   // Inicializar el objeto de pruebas
   $t = new lime_test(16, new lime_output_color());

   $t->diag('hola mundo');
   $t->ok(1 == '1', 'el operador de igualdad ignora el tipo de la variable');
   $t->is(1, '1', 'las cadenas se convierten en números para realizar la comparación');
   $t->isnt(0, 1, '0 y 1 no son lo mismo');


www.librosweb.es                                                                             323
Symfony, la guía definitiva                            Capítulo 15. Pruebas unitarias y funcionales

   $t->like('prueba01', '/pruebad+/', 'prueba01 sigue el patrón para numerar las
   pruebas');
   $t->unlike('pruebas01', '/pruebad+/', 'pruebas01 no sigue el patrón');
   $t->cmp_ok(1, '<', 2, '1 es inferior a 2');
   $t->cmp_ok(1, '!==', true, '1 y true no son exactamente lo mismo');
   $t->isa_ok('valor', 'string', ''valor' es una cadena de texto');
   $t->isa_ok(new miObjeto(), 'miObjeto', 'new crea un objeto de la clase correcta');
   $t->can_ok(new miObjeto(), 'miMetodo', 'los objetos de la clase miObjeto tienen un
   método llamado miMetood');
   $array1 = array(1, 2, array(1 => 'valor', 'a' => '4'));
   $t->is_deeply($array1, array(1, 2, array(1 => 'valor', 'a' => '4')),
       'el primer array y el segundo array son iguales');
   $t->include_ok('./nombreArchivo.php', 'el archivo nombreArchivo.php ha sido incluido
   correctamente');

   try
   {
     lanza_una_excepcion();
     $t->fail('no debería ejecutarse ningún código después de lanzarse la excepción');
   }
   catch (Exception $e)
   {
     $t->pass('la excepción ha sido capturada correctamente');
   }

   if (!isset($variable))
   {
     $t->skip('saltamos una prueba para mantener el contador de pruebas correcto para la
   condición', 1);
   }
   else
   {
     $t->ok($variable, 'valor');
   }

   $t->todo('la última prueba que falta');

Las pruebas unitarias de Symfony incluyen muchos más ejemplos de uso de todos estos
métodos.

  SUGERENCIA
  Puede que sea confuso el uso de is() en vez de ok() en el ejemplo anterior. La razón es que el
  mensaje de error que muestra is() es mucho más explícito, ya que muestra los 2 argumentos de
  la prueba, mientras que ok() simplemente dice que la prueba ha fallado.


15.2.3. Parámetros para las pruebas
En la inicialización del objeto lime_test se indica como primer parámetro el número de
pruebas que se van a ejecutar. Si el número de pruebas realmente realizadas no coincide
con este valor, la salida producida por Lime muestra un aviso. El conjunto de pruebas del
listado 15-3 producen la salida del listado 15-4. Como en la inicialización se indica que se
deben ejecutar 16 pruebas y realmente solo se han realizado 15, en la salida se muestra
un mensaje de aviso.

www.librosweb.es                                                                              324
Symfony, la guía definitiva                       Capítulo 15. Pruebas unitarias y funcionales


Listado 15-4 - El contador de pruebas realizadas permite planificar las pruebas
   > symfony test-unit ejemplo

   1..16
   # hola mundo
   ok 1 - el operador de igualdad ignora el tipo de la variable
   ok 2 - las cadenas se convierten en números para realizar la comparación
   ok 3 - 0 y 1 no son lo mismo
   ok 4 - prueba01 sigue el patrón para numerar las pruebas
   ok 5 - pruebas01 no sigue el patrón
   ok 6 - 1 es inferior a 2
   ok 7 - 1 y true no son exactamente lo mismo
   ok 8 - 'valor' es una cadena de texto
   ok 9 - new crea un objeto de la clase correcta
   ok 10 - los objetos de la clase miObjeto tienen un método llamado miMetood
   ok 11 - el primer array y el segundo array son iguales
   not ok 12 - el archivo nombreArchivo.php ha sido incluido correctamente
   #     Failed test (.testunitejemploTest.php at line 35)
   #       Tried to include './nombreArchivo.php'
   ok 13 - la excepción ha sido capturada correctamente
   ok 14 # SKIP saltamos una prueba para mantener el contador de pruebas correcto para la
   condición
   ok 15 # TODO la última prueba que falta
   # Looks like you planned 16 tests but only ran 15.
   # Looks like you failed 1 tests of 16.

El método diag() no cuenta como una prueba. Se utiliza para mostrar mensajes, de for-
ma que la salida por pantalla esté más organizada y sea más fácil de leer. Por otra parte,
los métodos todo() y skip() cuentan como si fueran pruebas reales. La combinación
pass()/fail() dentro de un bloque try/catch cuenta como una sola prueba.

Una estrategia de pruebas bien planificada requiere que se indique el número esperado
de pruebas. Indicar este número es una buena forma de validar los propios archivos de
pruebas, sobre todo en los casos más complicados en los que algunas pruebas se ejecu-
tan dentro de condiciones y/o excepciones. Además, si la prueba falla en cualquier punto,
es muy fácil de verlo porque el número de pruebas realizadas no coincide con el número
de pruebas esperadas.

El segundo parámetro del constructor del objeto lime_test indica el objeto que se utiliza
para mostrar los resultado. Se trata de un objeto que extiende la clase lime_output. La
mayoría de las veces, como las pruebas se realizan en una interfaz de comandos, la sali-
da se construye mediante el objeto lime_output_color, que muestra la salida coloreada
en los sistemas que lo permiten.

15.2.4. La tarea test-unit
La tarea test-unit, que se utiliza para ejecutar las pruebas unitarias desde la línea de
comandos, admite como argumento una serie de nombres de pruebas o un patrón de
nombre de archivos. El listado 15-5 muestra los detalles.

Listado 15-5 - Ejecutando las pruebas unitarias



www.librosweb.es                                                                         325
Symfony, la guía definitiva                          Capítulo 15. Pruebas unitarias y funcionales

       // Estructura del directorio de pruebas
       test/
         unit/
           miFuncionalTest.php
           miSegundoFuncionalTest.php
           otro/
             nombreTest.php
   > symfony test-unit miFuncional                      ##   Ejecutar miFuncionalTest.php
   > symfony test-unit miFuncional miSegundoFuncional   ##   Ejecuta las 2 pruebas
   > symfony test-unit 'otro/*'                         ##   Ejecuta nombreTest.php
   > symfony test-unit '*'                              ##   Ejecuta todas las pruebas (de
   forma recursiva)


15.2.5. Stubs, Fixtures y carga automática de clases
La carga automática de clases no funciona por defecto en las pruebas unitarias. Por tan-
to, todas las clases que se utilizan en una prueba se deben definir en el propio archivo de
la prueba o se deben incluir como una dependencia externa. Este es el motivo por el que
muchos archivos de pruebas empiezan con un grupo de instrucciones include, como se
muestra en el listado 15-6.

Listado 15-6 - Incluyendo las clases de forma explícita en las pruebas unitarias
   <?php

   include(dirname(__FILE__).'/../bootstrap/unit.php');
   include(dirname(__FILE__).'/../../config/config.php');
   require_once($sf_symfony_lib_dir.'/util/sfToolkit.class.php');

   $t = new lime_test(7, new lime_output_color());

   // isPathAbsolute()
   $t->diag('isPathAbsolute()');
   $t->is(sfToolkit::isPathAbsolute('/test'), true,
       'isPathAbsolute() returns true if path is absolute');
   $t->is(sfToolkit::isPathAbsolute('test'), true,
       'isPathAbsolute() returns true if path is absolute');
   $t->is(sfToolkit::isPathAbsolute('C:test'), true,
       'isPathAbsolute() returns true if path is absolute');
   $t->is(sfToolkit::isPathAbsolute('d:/test'), true,
       'isPathAbsolute() returns true if path is absolute');
   $t->is(sfToolkit::isPathAbsolute('test'), false,
       'isPathAbsolute() returns false if path is relative');
   $t->is(sfToolkit::isPathAbsolute('../test'), false,
       'isPathAbsolute() returns false if path is relative');
   $t->is(sfToolkit::isPathAbsolute('..test'), false,
       'isPathAbsolute() returns false if path is relative');

En las pruebas unitarias, no solo se debe instanciar el objeto que se está probando, sino
también el objeto del que depende. Como las pruebas unitarias deben ser autosuficien-
tes, depender de otras clases puede provocar que más de una prueba falle si alguna cla-
se no funciona correctamente. Además, crear objetos reales es una tarea costosa, tanto
en número de líneas de código necesarias como en tiempo de ejecución. Debe tenerse en



www.librosweb.es                                                                             326
Symfony, la guía definitiva                               Capítulo 15. Pruebas unitarias y funcionales


cuenta que la velocidad de ejecución es esencial para las pruebas unitarias, ya que los
programadores en seguida se cansan de los procesos que son muy lentos.

Si se incluyen muchos scripts en una prueba unitaria, lo más útil es utilizar un sistema
sencillo de carga automática de clases. Para ello, la clase sfCore (que se debe incluir ma-
nualmente) dispone del método initSimpleAutoload(), que utiliza como parámetro una
ruta absoluta. Todas las clases que se encuentren bajo esa ruta, se cargarán automática-
mente. Si por ejemplo se quieren cargar automáticamente todas las clases del directorio
$sf_symfony_lib_dir/util/, se utilizan las siguientes instrucciones al principio del script
de la prueba unitaria:
   require_once($sf_symfony_lib_dir.'/util/sfCore.class.php');
   sfCore::initSimpleAutoload($sf_symfony_lib_dir.'/util');


  SUGERENCIA
  Los objetos Propel generados automáticamente dependen de muchísimas clases, por lo que en
  cuento se quiere probar un objeto Propel es necesario utilizar la carga automática de clases.
  Además, para que funcione Propel es necesario incluir los archivos del directorio vendor/propel/
  (por lo que la llamada a sfCore se transforma en sfCore::initSimpleAutoload(arr-
  ay(SF_ROOT_ DIR.’/lib/model’, $sf_symfony_lib_dir.’/vendor/propel’));) e incluir las
  clases internas de Propel en la ruta para incluir archivos, también llamada ‘include_path’ (se utiliza
  set_include_path($sf_symfony_lib_dir.’/
  vendor’.PATH_SEPARATOR.SF_ROOT_DIR.PATH_SEPARATOR.get_include_path().

Otra técnica muy utilizada para evitar los problemas de la carga automática de clases es
el uso de stubs o clases falsas. Un stub es una implementación alternativa de una clase
en la que los métodos reales se sustituyen por datos simples especialmente preparados.
De esta forma, se emula el comportamiento de la clase real y se reduce su tiempo de
ejecución. Los casos típicos para utilizar stubs son las conexiones con bases de datos y
las interfaces de los servicios web. En el listado 15-7, las pruebas unitarias para una API
de un servicio de mapas utilizan la clase WebService. En vez de ejecutar el método
fetch() real de la clase del servicio web, la prueba utiliza un stub que devuelve datos de
prueba.

Listado 15-7 - Utilizando stubs en las pruebas unitarias
   require_once(dirname(__FILE__).'/../../lib/WebService.class.php');
   require_once(dirname(__FILE__).'/../../lib/MapAPI.class.php');

   class testWebService extends WebService
   {
     public static function fetch()
     {
       return file_get_contents(dirname(__FILE__).'/fixtures/data/servicio_web_falso.xml');
     }
   }

   $miMapa = new MapAPI();

   $t = new lime_test(1, new lime_output_color());



www.librosweb.es                                                                                   327
Symfony, la guía definitiva                         Capítulo 15. Pruebas unitarias y funcionales



   $t->is($miMapa->getMapSize(testWebService::fetch(), 100));

Los datos de prueba pueden ser más complejos que una cadena de texto o la llamada a
un método. Los datos de prueba complejos se suelen denominar “fixtures”. Para mejorar
el código de las pruebas unitarias, es recomendable mantener los fixtures en archivos in-
dependientes, sobre todo si se utilizan en más de una prueba. Además, Symfony es ca-
paz de transformar un archivo YAML en un array mediante el método sfYAML::load(). De
esta forma, en vez de escribir arrays PHP muy grandes, los datos para las pruebas se
pueden guardar en archivos YAML, como en el listado 15-8.

Listado 15-8 - Usando archivos para los “fixtures” de las pruebas unitarias
   // En fixtures.yml:
   -
     input:   '/test'
     output: true
     comment: isPathAbsolute()   returns true if path is absolute
   -
     input:   'test'
     output: true
     comment: isPathAbsolute()   returns true if path is absolute
   -
     input:   'C:test'
     output: true
     comment: isPathAbsolute()   returns true if path is absolute
   -
     input:   'd:/test'
     output: true
     comment: isPathAbsolute()   returns true if path is absolute
   -
     input:   'test'
     output: false
     comment: isPathAbsolute()   returns false if path is relative
   -
     input:   '../test'
     output: false
     comment: isPathAbsolute()   returns false if path is relative
   -
     input:   '..test'
     output: false
     comment: isPathAbsolute()   returns false if path is relative
   // En testTest.php
   <?php

   include(dirname(__FILE__).'/../bootstrap/unit.php');
   include(dirname(__FILE__).'/../../config/config.php');
   require_once($sf_symfony_lib_dir.'/util/sfToolkit.class.php');
   require_once($sf_symfony_lib_dir.'/util/sfYaml.class.php');

   $testCases = sfYaml::load(dirname(__FILE__).'/fixtures.yml');

   $t = new lime_test(count($testCases), new lime_output_color());

   // isPathAbsolute()


www.librosweb.es                                                                           328
Symfony, la guía definitiva                       Capítulo 15. Pruebas unitarias y funcionales

   $t->diag('isPathAbsolute()');
   foreach ($testCases as $case)
   {
     $t->is(sfToolkit::isPathAbsolute($case['input']), $case['output'],$case['comment']);
   }


15.3. Pruebas funcionales
Las pruebas funcionales validan partes de las aplicaciones. Estas pruebas simulan la na-
vegación del usuario, realizan peticiones y comprueban los elementos de la respuesta, tal
y como lo haría manualmente un usuario para validar que una determinada acción hace
lo que se supone que tiene que hacer. En las pruebas funcionales, se ejecuta un escenar-
io correspondiente a lo que se denomina un “caso de uso”.

15.3.1. ¿Cómo son las pruebas funcionales?
Las pruebas funcionales se podrían realizar mediante un navegador en forma de texto y
un montón de asertos definidos con expresiones regulares complejas, pero sería una pér-
dida de tiempo muy grande. Symfony dispone de un objeto especial, llamado sfBrowser,
que actua como un navegador que está accediendo a una aplicación Symfony, pero sin
necesidad de utilizar un servidor web real (y sin la penalización de las conexiones HTTP).
Este objeto permite el acceso directo a los objetos que forman cada petición (el objeto
petición, el objeto sesión, el objeto contexto y el objeto respuesta). Symfony también
dispone de una extensión de esta clase llamada sfTestBrowser, que está especialmente
diseñada para las pruebas funcionales y que tiene todas las características de sfBrowser,
además de algunos métodos muy útiles para los asertos.

Una prueba funcional suele comenzar con la inicialización del objeto del navegador para
pruebas. Este objeto permite realizar una petición a una acción de la aplicación y permite
verificar que algunos elementos están presentes en la respuesta.

Por ejemplo, cada vez que se genera el esqueleto de un módulo mediante las tareas
init-module o propel-init-crud, Symfony crea una prueba funciona de prueba para este
módulo. La prueba realiza una petición a la acción por defecto del módulo y comprueba el
código de estado de la respuesta, el módulo y la acción calculados por el sistema de en-
rutamiento y la presencia de una frase específica en el contenido de la respuesta. Si el
módulo se llama foobar, el archivo foobarActionsTest.php generado es similar al del lis-
tado 15-9.

Listado 15-9 - Prueba funcional por defecto para un módulo nuevo, en tests/
functional/frontend/foobarActionsTest.php
   <?php

   include(dirname(__FILE__).'/../../bootstrap/functional.php');

   // Create a new test browser
   $browser = new sfTestBrowser();
   $browser->initialize();

   $browser->


www.librosweb.es                                                                         329
Symfony, la guía definitiva                                Capítulo 15. Pruebas unitarias y funcionales

       get('/foobar/index')->
       isStatusCode(200)->
       isRequestParameter('module', 'foobar')->
       isRequestParameter('action', 'index')->
       checkResponseElement('body', '!/This is a temporary page/')
   ;


  SUGERENCIA
  Todos los métodos del navegador de Symfony devuelven un objeto sfTestBrowser, por lo que se
  pueden encadenar las llamadas a los métodos para que los archivos de prueba sean más fáciles de
  leer. Esta estrategia se llama “interfaz fluida con el objeto”, ya que nada puede parar el flujo de lla-
  madas a los métodos del objeto.

Las pruebas funcionales pueden contener varias peticiones y asertos más complejos, co-
mo se mostrará en las próximas secciones.

Para ejecutar una prueba funcional, se utiliza la tarea test-functional de la línea de co-
mandos de Symfony, como se muestra en el listado 15-10. Los argumentos que se indi-
can a la tarea son el nombre de la aplicación y el nombre de la prueba (omitiendo el sufi-
jo Test.php).

Listado 15-10 - Ejecutando una prueba funcional mediante la línea de comandos
   > symfony test-functional frontend foobarActions

   # get /comment/index
   ok 1 - status code is 200
   ok 2 - request parameter module is foobar
   ok 3 - request parameter action is index
   not ok 4 - response selector body does not match regex /This is a temporary page/
   # Looks like you failed 1 tests of 4.
   1..4

Por defecto, las pruebas funcionales generadas automáticamente para un módulo nuevo
no pasan correctamente todas las pruebas. El motivo es que en los módulos nuevos, la
acción index redirige a una página de bienvenida (que pertenece al módulo default de
Symfony) que contiene la frase “This is a temporary page”. Mientras no se modifique la
acción index del módulo, las pruebas funcionales de este módulo no se pasarán correcta-
mente, lo que garantiza que no se ejecuten correctamente todas las pruebas para un
módulo que está sin terminar.

  NOTA
  En las pruebas funcionales, la carga automática de clases está activada, por lo que no se deben in-
  cluir los archivos manualmente.


15.3.2. Navegando con el objeto sfTestBrowser
El navegador para pruebas permite realizar peticiones GET y POST. En ambos casos se
utiliza una URI real como parámetro. El listado 15-11 muestra cómo crear peticiones con
el objeto sfTestBrowser para simular peticiones reales.

Listado 15-11 - Simulando peticiones con el objeto sfTestBrowser


www.librosweb.es                                                                                     330
Symfony, la guía definitiva                          Capítulo 15. Pruebas unitarias y funcionales

   include(dirname(__FILE__).'/../../bootstrap/functional.php');

   // Se crea un nuevo navegador de pruebas
   $b = new sfTestBrowser();
   $b->initialize();

   $b->get('/foobar/show/id/1');                      // Petición GET
   $b->post('/foobar/show', array('id' => 1));        // Petición POST

   // Los métodos get() y post() son atajos del método call()
   $b->call('/foobar/show/id/1', 'get');
   $b->call('/foobar/show', 'post', array('id' => 1));

   // El método call() puede simular peticiones de cualquier método
   $b->call('/foobar/show/id/1', 'head');
   $b->call('/foobar/add/id/1', 'put');
   $b->call('/foobar/delete/id/1', 'delete');

Una navegación típica no sólo está formada por peticiones a determinadas acciones, sino
que también incluye clicks sobre enlaces y botones. Como se muestra en el listado 15-
12, el objeto sfTestBrowser también es capaz de simular la acción de pinchar sobre estos
elementos.

Listado 15-12 - Simulando una navegación real con el objeto sfTestBrowser
   $b->get('/');                   // Petición a la página principal
   $b->get('/foobar/show/id/1');
   $b->back();                     //   Volver a la página anterior del historial
   $b->forward();                  //   Ir a la página siguiente del historial
   $b->reload();                   //   Recargar la página actual
   $b->click('go');                //   Buscar un enlace o botón llamado 'go' y pincharlo

El navegador para pruebas incluye un mecanismo para guardar todas las peticiones reali-
zadas, por lo que los métodos back() y forward() funcionan de la misma manera que en
un navegador real.

  SUGERENCIA
  El navegador de pruebas incluye sus propios mecanismos para gestionar las sesiones
  (sfTestStorage) y las cookies.

Entre las interacciones que más se deben probar, las de los formularios son probable-
mente las más necesarias. Symfony dispone de 3 formas de probar la introducción de da-
tos en los formularios y su envío. Se puede crear una petición POST con los parámetros
que se quieren enviar, se puede llamar al método click() con los parámetros del formu-
lario en un array o se pueden rellenar los campos del formulario de uno en uno y des-
pués pulsar sobre el botón de envío. En cualquiera de los 3 casos, la petición POST resul-
tante es la misma. El listado 15-13 muestra un ejemplo.

Listado 15-13 - Simulando el envío de un formulario con datos mediante el obje-
to sfTe stBrowser
   // Plantilla de ejemplo en modules/foobar/templates/editSuccess.php
   <?php echo form_tag('foobar/update') ?>
     <?php echo input_hidden_tag('id', $sf_params->get('id')) ?>


www.librosweb.es                                                                            331
Symfony, la guía definitiva                           Capítulo 15. Pruebas unitarias y funcionales

     <?php   echo   input_tag('name', 'foo') ?>
     <?php   echo   submit_tag('go') ?>
     <?php   echo   textarea('text1', 'foo') ?>
     <?php   echo   textarea('text2', 'bar') ?>
   </form>

   // Prueba funcional de ejemplo para este formulario
   $b = new sfTestBrowser();
   $b->initialize();
   $b->get('/foobar/edit/id/1');

   // Opción 1: petición POST
   $b->post('/foobar/update', array('id' => 1, 'name' => 'dummy', 'commit' => 'go'));

   // Opción 2: Pulsar sobre el botón de envío con parámetros
   $b->click('go', array('name' => 'dummy'));

   // Opción 3: Introducir los valores del formulario campo a campo y
   // presionar el botón de envío
   $b->setField('name', 'dummy')->
       click('go');


  NOTA
  En las opciones 2 y 3, los valores por defecto del formulario se incluyen automáticamente en su
  envío y no es necesario especificar el destino del formulario.

Si una acción finaliza con una redirección (redirect()), el navegador para pruebas no si-
gue automáticamente la redirección, sino que se debe seguir manualmente mediante fo-
llowRedirect(), como se muestra en el listado 15-14.

Listado 15-14 - El navegador para pruebas no sigue automáticamente las
redirecciones
   // Acción de ejemplo en modules/foobar/actions/actions.class.php
   public function executeUpdate()
   {
     ...
     $this->redirect('foobar/show?id='.$this->getRequestParameter('id'));
   }

   // Prueba funcional de ejemplo para esta acción
   $b = new sfTestBrowser();
   $b->initialize();
   $b->get('/foobar/edit?id=1')->
       click('go', array('name' => 'dummy'))->
       isRedirected()->   // Check that request is redirected
       followRedirect();     // Manually follow the redirection

Existe un último método muy útil para la navegación: restart(), que inicializa el historial
de navegación, la sesión y las cookies, es decir, como si se reiniciara el navegador.

Una vez realizada la primera petición, el objeto sfTestBrowser dispone de acceso directo
a los objetos de la petición, del contexto y de la respuesta. De esta forma, se pueden



www.librosweb.es                                                                             332
Symfony, la guía definitiva                             Capítulo 15. Pruebas unitarias y funcionales


probar muchas cosas diferentes, desde el contenido textual de las páginas a las cabece-
ras de la respuesta, pasando por los parámetros de la petición y la configuración:
   $peticion = $b->getRequest();
   $contexto = $b->getContext();
   $respuesta = $b->getResponse();

  El objeto sfBrowser

  Todos los métodos para realizar la navegación descritos en los listados 15-10 a 15-13, no solamen-
  te están disponibles para las pruebas, sino que se pueden acceder desde cualquier parte de la apli-
  cación mediante el objeto sfBrowser. La llamada que se debe realizar es la siguiente:
      // Crear un nuevo navegador
      $b = new sfBrowser();
      $b->initialize();
      $b->get('/foobar/show/id/1')->
          setField('name', 'dummy')->
          click('go');
      $content = $b->getResponse()->getContent();
      ...

  El objeto sfBrowser es muy útil para ejecutar scripts programados, como por ejemplo para navegar
  por una serie de páginas para generar la cache de cada página (el Capítulo 18 muestra un ejemplo
  detallado).


15.3.3. Utilizando asertos
Como el objeto sfTestBrowser dispone de acceso directo a la respuesta y a otros compo-
nentes de la petición, es posible realizar pruebas sobre estos componentes. Se podría
crear un nuevo objeto lime_test para estas pruebas, pero por suerte, sfTestBrowser dis-
pone de un método llamado test() que devuelve un objeto lime_test sobre el que se
pueden invocar los métodos para asertos descritos anteriormentes. El listado 15-15
muestra cómo realizar asertos mediante sfTestBrowser.

Listado 15-15 - El navegador para pruebas dispone del método test() para reali-
zar pruebas
   $b = new sfTestBrowser();
   $b->initialize();
   $b->get('/foobar/edit/id/1');
   $request = $b->getRequest();
   $context = $b->getContext();
   $response = $b->getResponse();

   // Acceder a los métodos de lime_test mediante el método test()
   $b->test()->is($request->getParameter('id'), 1);
   $b->test()->is($response->getStatuscode(), 200);
   $b->test()->is($response->getHttpHeader('content-type'), 'text/html;charset=utf-8');
   $b->test()->like($response->getContent(), '/edit/');




www.librosweb.es                                                                                333
Symfony, la guía definitiva                         Capítulo 15. Pruebas unitarias y funcionales


  NOTA
  Los métodos getResponse(), getContext(), getRequest() y test() no devuelven un objeto
  sfTestBrowser, por lo que no se pueden encadenar después de ellos otras llamadas a los méto-
  dos de sfTestBrowser.

Las cookies enviadas y recibidas se pueden probar fácilmente mediante los objetos de la
petición y de la respuesta, como se muestra en el listado 15-16.

Listado 15-16 - Probando las cookies con sfTestBrowser
   $b->test()->is($request->getCookie('foo'), 'bar');       // Cookie enviada
   $cookies = $response->getCookies();
   $b->test()->is($cookies['foo'], 'foo=bar');              // Cookie recibida

Si se utiliza el método test() para probar los elementos de la petición, se acaban escrib-
iendo unas líneas de código demasiado largas. Afortunadamente, sfTestbrowser contiene
una serie de métodos especiales que permiten mantener las pruebas funcionales cortas y
fáciles de leer, además de que devuelven objetos sfTestBrowser. El listado 15-15 se
podría reescribir por ejemplo de forma más sencilla como se muestra en el listado 15-17.

Listado 15-17 - Realizando pruebas directamente con sfTestBrowser
   $b = new sfTestBrowser();
   $b->initialize();
   $b->get('/foobar/edit/id/1')->
       isRequestParameter('id', 1)->
       isStatusCode()->
       isResponseHeader('content-type', 'text/html; charset=utf-8')->
       responseContains('edit');

El código de estado 200 es el valor por defecto que espera el método isStatusCode(), por
lo que, para comprobar si la respuesta es correcta, se puede realizar la llamada sin
argumentos.

Otra ventaja del uso de estos métodos especiales es que no es necesario especificar el
texto que se muestra en la salida, como sí que era necesario en los métodos del objeto
lime_test. Los mensajes se generan automáticamente en los métodos especiales, y la
salida producida es clara y muy sencilla de entender.
   # get /foobar/edit/id/1
   ok 1 - request parameter "id" is "1"
   ok 2 - status code is "200"
   ok 3 - response header "content-type" is "text/html"
   ok 4 - response contains "edit"
   1..4

En la práctica, los métodos especiales del listado 15-17 cubren la mayor parte de las pr-
uebas habituales, por lo que raramente es necesario utilizar el método test() del objeto
sfTestBrowser.

El listado 15-14 demuestra que sfTestBrowser no sigue directamente las redirecciones.
La ventaja de este comportamiento es que se pueden probar las propias redirecciones. El
listado 15-18 muestra cómo probar la respuesta del listado 15-14.



www.librosweb.es                                                                           334
Symfony, la guía definitiva                         Capítulo 15. Pruebas unitarias y funcionales


Listado 15-18 - Probando las redirecciones con sfTestBrowser
   $b = new sfTestBrowser();
   $b->initialize();
   $b->
        get('/foobar/edit/id/1')->
        click('go', array('name' => 'dummy'))->
        isStatusCode(200)->
        isRequestParameter('module', 'foobar')->
        isRequestParameter('action', 'update')->

        isRedirected()->      // Comprobar que la respuseta es una redirección
        followRedirect()->    // Obligar manualmente a seguir la redirección

        isStatusCode(200)->
        isRequestParameter('module', 'foobar')->
        isRequestParameter('action', 'show');


15.3.4. Utilizando los selectores CSS
Muchas pruebas funcionales validan que una página sea correcta comprobando que un
determinado texto se encuentre en el contenido de la respuesta. Utilizando el método
responseContains() y las expresiones regulares, es posible comprobar que existe un de-
terminado texto, los atributos de una etiqueta o sus valores. Pero si lo que se quiere pro-
bar se encuentra en lo más profundo del árbol DOM del contenido de la respuesta, la so-
lución de las expresiones regulares es demasiado compleja.

Este es el motivo por el que el objeto sfTestBrowser dispone de un método llamado
getResponseDom(). El método devuelve un objeto DOM de libXML2, que es mucho más fá-
cil de procesar que el texto simple. El listado 15-19 muestra un ejemplo de uso de este
método.

Listado 15-19 - El navegador para pruebas devuelve el contenido de la respues-
ta como un objeto DOM
   $b = new sfTestBrowser();
   $b->initialize();
   $b->get('/foobar/edit/id/1');
   $dom = $b->getResponseDom();
   $b->test()->is($dom->getElementsByTagName('input')->item(1)->getAttribute('type'),'text');

Sin embargo, procesar un documento HTML con los métodos DOM de PHP no es lo sufic-
ientemente rápido y sencillo. Por su parte, los selectores utilizados en las hojas de estilos
CSS son una forma aun más potente de obtener los elementos de un documento HTML.
Symfony incluye una herramienta llamada sfDomCssSelector, cuyo constructor espera un
documento DOM como argumento. Esta utilidad dispone de un método llamado
getTexts() que devuelve un array de las cadenas de texto seleccionadas mediante un se-
lector CSS, y otro método llamado getElements() que devuelve un array de elementos
DOM. El listado 15-20 muestra un ejemplo.

Listado 15-20 - El navegador para pruebas permite acceder al contenido de la
respuesta mediante el objeto sfDomCssSelector



www.librosweb.es                                                                           335
Symfony, la guía definitiva                       Capítulo 15. Pruebas unitarias y funcionales

   $b = new sfTestBrowser();
   $b->initialize();
   $b->get('/foobar/edit/id/1');
   $c = new sfDomCssSelector($b->getResponseDom())
   $b->test()->is($c->getTexts('form input[type="hidden"][value="1"]'), array('');
   $b->test()->is($c->getTexts('form textarea[name="text1"]'), array('foo'));
   $b->test()->is($c->getTexts('form input[type="submit"]'), array(''));

Como es habitual, Symfony busca siempre la máxima brevedad y claridad en el código,
por lo que se dispone de un método alternativo llamado checkResponseElement(). Utili-
zando este método, el listado 15-20 se puede transformar en el listado 15-21.

Listado 15-21 - El navegador para pruebas permite acceder a los elementos de
la respuesta utilizando selectores de CSS
   $b = new sfTestBrowser();
   $b->initialize();
   $b->get('/foobar/edit/id/1')->
       checkResponseElement('form input[type="hidden"][value="1"]', true)->
       checkResponseElement('form textarea[name="text1"]', 'foo')->
       checkResponseElement('form input[type="submit"]', 1);

El comportamiento del método checkResponseElement() depende del tipo de dato del se-
gundo argumento que se le pasa:

      ▪ Si es un valor booleano, comprueba si existe un elemento que cumpla con el se-
        lector CSS indicado.

      ▪ Si es un número entero, comprueba que el selector CSS indicado devuelva el nú-
        mero de elementos de este parámetro.

      ▪ Si es una expresión regular, comprueba que el primer elemento seleccionado me-
        diante el selector CSS cumpla el patrón de la expresión regular.

      ▪ Si es una expresión regular precedida de !, comprueba que el primer elemento
        seleccionado mediante el selector CSS no cumpla con el patrón de la expresión
        regular.

      ▪ En el resto de casos, compara el primer elemento seleccionado mediante el se-
        lector CSS y el valor del segundo argumento que se pasa en forma de cadena de
        texto.

El método acepta además un tercer parámetro opcional en forma de array asociativo. De
esta forma es posible no solo realizar la prueba sobre el primer elemento devuelto por el
selector CSS (si es que devuelve varios elementos) sino sobre otro elemento que se enc-
uentra en una posición determinada, tal y como muestra el listado 15-22.

Listado 15-22 - Utilizando la opción de posición para comprobar un elemento
que se encuentra en una posición determinada
   $b = new sfTestBrowser();
   $b->initialize();
   $b->get('/foobar/edit?id=1')->
       checkResponseElement('form textarea', 'foo')->
       checkResponseElement('form textarea', 'bar', array('position' => 1));



www.librosweb.es                                                                         336
Symfony, la guía definitiva                         Capítulo 15. Pruebas unitarias y funcionales


El array de opciones también se puede utilizar para realizar 2 pruebas a la vez. Se puede
comprobar que existe un elemento que cumple un selector y al mismo tiempo comprobar
cuantos elementos lo cumplen, como se muestra en el listado 15-23.

Listado 15-23 - Utilizando la opción para contar el número de elementos que
cumplen el selector CSS
   $b = new sfTestBrowser();
   $b->initialize();
   $b->get('/foobar/edit?id=1')->
       checkResponseElement('form input', true, array('count' => 3));

La herramienta del selector es bastante potente, ya que acepta la mayor parte de los se-
lectores de CSS 2.1. De esta forma, se pueden hacer selecciones tan complejas como las
que se muestran en el listado 15-24.

Listado 15-24 - Ejemplo             de    selectores    CSS     complejos       que    acepta
checkResponseElement()
   $b->checkResponseElement('ul#list li a[href]', 'click me');
   $b->checkResponseElement('ul > li', 'click me');
   $b->checkResponseElement('ul + li', 'click me');
   $b->checkResponseElement('h1, h2', 'click me');
   $b->checkResponseElement('a[class$="foo"][href*="bar.html"]', 'my link');


15.3.5. Trabajando en el entorno de pruebas
El objeto sfTestBrowser utiliza un controlador frontal especial, que trabaja en el entorno
test. El listado 15-25 muestra la configuración por defecto de este entorno.

Listado 15-25 - Configuración por defecto del entorno test, en myapp/config/
settings.yml
   test:
     .settings:
       # E_ALL | E_STRICT & ~E_NOTICE = 2047
       error_reporting:        2047
       cache:                  off
       web_debug:              off
       no_script_name:         off
       etag:                   off

En este entorno, la cache y la barra de depuración web están desactivadas. No obstante,
la ejecución del código genera logs en un archivo distinto a los logs de los entornos dev y
prod, por lo que se pueden observar de forma independiente (en miproyecto/log/miapli-
cacion_test.log). Además, en este entorno las excepciones no detienen la ejecución de
los scripts, de forma que se pueda ejecutar un conjunto completo de pruebas incluso
cuando falla alguna prueba. También es posible definir una conexión específica con la ba-
se de datos, por ejemplo para utilizar una base de datos que tenga datos de prueba.

Antes de utilizar el objeto sfTestBrowser, es necesario inicializarlo. Si se necesita, es po-
sible especificar el nombre del servidor para la aplicación y una dirección IP para el clien-
te, por si la aplicación controla estos dos parámetros. El listado 15-26 muestra cómo
configurar estos parámetros.

www.librosweb.es                                                                           337
Symfony, la guía definitiva                       Capítulo 15. Pruebas unitarias y funcionales


Listado 15-26 - Indicar el hostname y la IP en el navegador para pruebas
   $b = new sfTestBrowser();
   $b->initialize('miaplicacion.ejemplo.com', '123.456.789.123');


15.3.6. La tarea test-functional
La tarea test-functional puede ejecutar una o varias pruebas funcionales, dependiendo
del número de argumentos indicados. La sintaxis que se utiliza es muy similar a la de la
tarea test-unit, salvo que la tarea para pruebas funcionales requiere como primer argu-
mento el nombre de una aplicación, tal y como muestra el listado 15-27.

Listado 15-27 - Sintaxis de la tarea para pruebas funcionales
   // Estructura del directorio de pruebas
   test/
     functional/
       frontend/
         miModuloActionsTest.php
         miEscenarioTest.php
       backend/
         miOtroEscenarioTest.php
   ## Ejecutar todas las pruebas funcionales de una aplicacion recursivamente
   > symfony test-functional frontend

   ## Ejecutar la prueba funcional cuyo nombre se indica como parámetro
   > symfony test-functional frontend myScenario

   ## Ejecutar todas las pruebas funcionales cuyos nombres cumplan con el patrón indicado
   > symfony test-functional frontend my*


15.4. Recomendaciones sobre el nombre de las pruebas
En esta sección se presentan algunas de las buenas prácticas para mantener bien organi-
zadas las pruebas y facilitar su mantenimiento. Los consejos abarcan la organización de
los archivos, de las pruebas unitarias y de las pruebas funcionales.

En lo que respecta a la estructura de archivos, los archivos de las pruebas unitarias de-
berían nombrarse según la clase que se supone que están probando y las pruebas funcio-
nales deberían nombrarse en función del módulo o escenario que se supone que están
probando. El listado 15-28 muestra un ejemplo de estas recomendaciones. Como el nú-
mero de archivos en el directorio test/ puede crecer muy rápidamente, si no se siguen
estas recomendaciones, es posible que sea muy difícil encontrar el archivo de una prueba
determinada.

Listado 15-28 - Ejemplo de recomendaciones sobre el nombre de los archivos
        test/
          unit/
            miFuncionTest.php
            miSegundaFuncionTest.php
            foo/
              barTest.php
          functional/
            frontend/

www.librosweb.es                                                                         338
Symfony, la guía definitiva                       Capítulo 15. Pruebas unitarias y funcionales

              miModuloActionsTest.php
              miEscenarioTest.php
            backend/
              miOtroEscenarioTest.php

En las pruebas unitarias, una buena práctica consiste en agrupar las pruebas según la
función o método y comenzar cada grupo de pruebas con una llamada al método diag().
Los mensajes de cada prueba unitaria deberían mostrar el nombre de la función o méto-
do que se prueba, seguido de un verbo y una propiedad, de forma que el resultado que
se muestra parezca una frase que describe una propiedad de un objeto. El listado 15-29
muestra un ejemplo.

Listado 15-29 - Ejemplo de recomendaciones para las pruebas unitarias
   // srttolower()
   $t->diag('strtolower()');
   $t->isa_ok(strtolower('Foo'), 'string', 'strtolower() devuelve una cadena de texto');
   $t->is(strtolower('FOO'), 'foo', 'strtolower() transforma la entrada en minúsculas');
   # strtolower()
   ok 1 - strtolower() devuelve una cadena de texto
   ok 2 - strtolower() transforma la entrada en minúsculas

Las pruebas funcionales deberían agruparse por página y deberían comenzar con una pe-
tición. El listado 15-30 muestra un ejemplo de esta práctica.

Listado 15-30 - Ejemplo de recomendaciones para las pruebas funcionales
   $browser->
     get('/foobar/index')->
     isStatusCode(200)->
     isRequestParameter('module', 'foobar')->
     isRequestParameter('action', 'index')->
     checkResponseElement('body', '/foobar/')
   ;
   # get /comment/index
   ok 1 - status code is 200
   ok 2 - request parameter module is foobar
   ok 3 - request parameter action is index
   ok 4 - response selector body matches regex /foobar/

Si se sigue esta recomendación, el resultado de la prueba es lo suficientemente claro co-
mo para utilizarlo como documentación técnica del proyecto, ya que puede hacer innece-
saria la propia documentación de la aplicación.


15.5. Otras utilidades para pruebas
Las herramientas que incluye Symfony para realizar pruebas unitarias y funcionales son
suficientes para la mayoría de casos. No obstante, se muestran a continuación algunas
técnicas adicionales para resolver problemas comunes con las pruebas automatizadas:
ejecutar pruebas en un entorno independiente, acceder a la base de datos desde las pr-
uebas, probar la cache y realizar pruebas de las interacciones en el lado del cliente.




www.librosweb.es                                                                         339
Symfony, la guía definitiva                        Capítulo 15. Pruebas unitarias y funcionales


15.5.1. Ejecutando las pruebas en grupos
Las tareas test-unit y test-functional ejecutan una sola prueba o un conjunto de prue-
bas. Sin embargo, si se ejecutan las tareas sin indicar ningún parámetro, se lanzan todas
las pruebas unitarias y funcionales del directorio test/. Para evitar el riesgo de interefe-
rencias de unas pruebas a otras, cada prueba se ejecuta en un entorno de ejecución in-
dependiente. Además, cuando se ejecutan todas las pruebas, el resultado que se mues-
tra no es el mismo que el que genera cada prueba de forma independiente, ya que en es-
te caso la salida estaría formada por miles de líneas. Lo que se hace es generar una sali-
da resumida especialmente preparada. Por este motivo, la ejecución de un gran número
de pruebas utiliza un test harness, que es un framework de pruebas con algunas carac-
terísticas especiales. El test harness depende de un componente del framework Lime lla-
mado lime_harness. Como se muestra en el listado 15-31, la salida producida indica el
estado de las pruebas archivo por archivo y al final se muestra un resumen de todas las
pruebas que se han pasado y el número total de pruebas.

Listado 15-31 - Ejecutando todas las pruebas mediante el test harness
   > symfony test-unit

   unit/miFuncionTest.php.................ok
   unit/miSegundaFuncionTest.php..........ok
   unit/foo/barTest.php...................not ok

   Failed Test                     Stat Total    Fail List of Failed
   ------------------------------------------------------------------
   unit/foo/barTest.php               0      2      2 62 63
   Failed 1/3 test scripts, 66.66% okay. 2/53 subtests failed, 96.22% okay.

Las pruebas se ejecutan de la misma forma que si se lanzaran una a una, solamente es
la salida la que se resume para hacerla más útil. De hecho, la estadística final se centra
en las pruebas que no han tenido éxito y ayuda a localizarlas.

Incluso es posible lanzar todas las pruebas de cualquier tipo mediante la tarea test-all,
que también hace uso del test harness, como se muestra en el listado 15-32. Una buena
práctica consiste en ejecutar esta tarea antes de realizar el paso a producción del nuevo
código, ya que asegura que no se ha introducido ningún nuevo error desde la versión
anterior.

Listado 15-32 - Ejecutando todas las pruebas de un proyecto
   > symfony test-all


15.5.2. Acceso a la base de datos
Normalmente, las pruebas unitarias necesitan acceder a la base de datos. Cuando se lla-
ma al método sfTestBrowser::get() por primera vez, se inicializa una conexión con la
base de datos. No obstante, si se necesita acceder a la base de datos antes de utilizar
sfTestBrowser, se debe inicializar el objeto sfDabataseManager a mano, como muestra el
listado 15-33.

Listado 15-33 - Inicializando la base de datos en una prueba


www.librosweb.es                                                                          340
Symfony, la guía definitiva                       Capítulo 15. Pruebas unitarias y funcionales

   $databaseManager = new sfDatabaseManager();
   $databaseManager->initialize();

   // Opcionalmente, se puede obtener la conexión con la base de datos
   $con = Propel::getConnection();

Antes de comenzar las pruebas, se suele cargar la base de datos con datos de prueba,
también llamados fixtures. El objeto sfPropelData permite realizar esta carga. No sola-
mente es posible utilizar este objeto para cargar datos a partir de un archivo (como con
la tarea propel-load-data) sino que también es posible hacerlo desde un array, como
muestra el listado 15-34.

Listado 15-34 - Cargando datos en la base de datos desde una prueba
   $data = new sfPropelData();

   // Cargar datos desde un archivo
   $data->loadData(sfConfig::get('sf_data_dir').'/fixtures/test_data.yml');

   // Cargar datos desde un array
   $fixtures = array(
      'Article' => array(
         'article_1' => array(
            'title'      => 'foo title',
            'body'       => 'bar body',
            'created_at' => time(),
         ),
         'article_2'     => array(
            'title'      => 'foo foo title',
            'body'       => 'bar bar body',
            'created_at' => time(),
         ),
      ),
   );
   $data->loadDataFromArray($fixtures);

Una vez cargados los datos, se pueden utilizar los objetos Propel necesarios como en
cualquier aplicación normal. Las pruebas unitarias deben incluir los archivos correspond-
ientes a esos objetos (se puede utilizar el método sfCore::sfSimpleAutoloading() para
automatizar la carga, como se explicó en la sección anterior “Stubs, Fixtures y carga au-
tomática de clases”). Los objetos de Propel se cargan automáticamente en las pruebas
funcionales.

15.5.3. Probando la cache
Cuando se habilita la cache para una aplicación, las pruebas funcionales se encargan de
verificar que las acciones guardadas en la cache se comportan como deberían.

En primer lugar, se habilita la cache para el entorno de pruebas (en el archivo set-
tings.yml). Una vez habilitada, se puede utilizar el método isCached() del objeto
sfTestBrowser para comprobar si una página se ha obtenido directamente de la cache o
ha sido generada en ese momento. El listado 15-35 muestra cómo utilizar este método.

Listado 15-35 - Probando la cache con el método isCached()

www.librosweb.es                                                                         341
Symfony, la guía definitiva                             Capítulo 15. Pruebas unitarias y funcionales

   <?php

   include(dirname(__FILE__).'/../../bootstrap/functional.php');

   // Create a new test browser
   $b = new sfTestBrowser();
   $b->initialize();

   $b->get('/mymodule');
   $b->isCached(true);       // Comprueba si la respuesta viene de la cache
   $b->isCached(true, true); // Comprueba si la respuesta de la cache incluye el
   layoutlayout
   $b->isCached(false);      // Comprueba que la respuesta no venga de la cache


  NOTA
  No es necesario borrar la cache antes de realizar la prueba funcional, ya que el proceso de arranq-
  ue utilizado por la prueba se encarga de hacerlo automáticamente.


15.5.4. Probando las interacciones en el lado del cliente
El principal inconveniente de las técnicas descritas anteriormente es que no pueden si-
mular el comportamiento de JavaScript. Si se definen interacciones muy complejas, como
por ejemplo interacciones con Ajax, es necesario reproducir de forma exacta los movim-
ientos del ratón y las pulsaciones de teclado que realiza el usuario y ejecutar los scripts
de JavaScript. Normalmente, estas pruebas se hacen a mano, pero cuestan mucho tiem-
po y son propensas a cometer errores.

La solución a estos problemas se llama Selenium (http://www.openqa.org/selenium/), que
consiste en un framework de pruebas escrito completamente en JavaScript. Selenium
permite realizar una serie de acciones en la página de la misma forma que las haría un
usuario normal. La ventaja de Selenium sobre el objeto sfBrowser es que Selenium es
capaz de ejecutar todo el código JavaScript de la página, incluidas las interacciones crea-
das con Ajax.

Symfony no incluye Selenium por defecto. Para instalarlo, se crea un directorio llamado
selenium/ en el directorio web/ del proyecto y se descomprime el contenido del archivo
descargado desde http://www.openqa.org/selenium-core/download.action. Como Selenium se
basa en JavaScript y la mayoría de navegadores tienen unas restricciones de seguridad
muy estrictas, es importante ejecutar Selenium desde el mismo servidor y el mismo
puerto que el que utiliza la propia aplicación.

  SUGERENCIA
  Debe ponerse especial cuidado en no subir el directorio selenium/ al servidor de producción, ya
  que estaría disponible para cualquier usuario que acceda a la raíz del servidor desde un navegador
  web.

Las pruebas de Selenium se escriben en HTML y se guardan en el directorio web/selen-
ium/tests/. El listado 15-36 muestra un ejemplo de prueba funcional en la que se carga
la página principal, se pulsa el enlace “pinchame” y se busca el texto “Hola Mundo” en el



www.librosweb.es                                                                                342
Symfony, la guía definitiva                           Capítulo 15. Pruebas unitarias y funcionales


contenido de la respuesta. Para acceder a la aplicación en el entorno test, se debe utili-
zar el controlador frontal llamado miaplicacion_test.php.

Listado 15-36 - Un ejemplo de prueba de Selenium, en web/selenium/test/
testIndex.html
   <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
   <html>
   <head>
     <meta content="text/html; charset=UTF-8" http-equiv="content-type">
     <title>Index tests</title>
   </head>
   <body>
   <table cellspacing="0">
   <tbody>
     <tr><td colspan="3">First step</td></tr>
     <tr><td>open</td>              <td>/miaplicacion_test.php/</td> <td>&nbsp;</td></tr>
     <tr><td>clickAndWait</td>      <td>link=pinchame</td>    <td>&nbsp;</td></tr>
     <tr><td>assertTextPresent</td> <td>Hola Mundo</td>    <td>&nbsp;</td></tr>
   </tbody>
   </table>
   </body>
   </html>

Cada caso de prueba consiste en una página HTML con una tabla de 3 columnas: coman-
do, destino y valor. No obstante, no todos los comandos indican un valor. En caso de que
no se utilice un valor, es recomendable incluir el valor &nbsp; en esa columna (para que
la tabla se vea mejor). El sitio web de Selenium dispone de la lista completa de coman-
dos que se pueden utilizar.

También es necesario añadir esta prueba al conjunto completo de pruebas, insertando
una nueva línea en la tabla del archivo TestSuite.html del mismo directorio. El listado
15-37 muestra cómo hacerlo.

Listado 15-37 - Añadiendo un archivo de pruebas al conjunto de pruebas, en
web/selenium/test/TestSuite.html
   ...
   <tr><td><a href='./testIndex.html'>Mi primera prueba</a></td></tr>
   ...

Para ejecutar la prueba, solamente es necesario acceder a la página:
   http://miaplicacion.ejemplo.com/selenium/index.html

Si se selecciona la “Main Test Suite” y se pulsa sobre el botón de ejecutar todas las prue-
bas, el navegador reproducirá automáticamente todos los pasos que se han indicado.

  NOTA
  Como las pruebas de Selenium se ejecutan en el propio navegador, permiten descubrir las incon-
  sistencias entre navegadores. Si se construye la prueba para un solo navegador, se puede lanzar
  esa prueba sobre todos los navegadores y comprobar su funcionamiento.

Como las pruebas de Selenium se crean con HTML, acaba siendo muy aburrido escribir
todo ese código HTML. Afortunadamente, existe una extensión de Selenium para Firefox

www.librosweb.es                                                                             343
Symfony, la guía definitiva                             Capítulo 15. Pruebas unitarias y funcionales


(http://seleniumrecorder.mozdev.org/) que permite grabar todos los movimientos y accio-
nes realizadas sobre una página y guardarlos como una prueba. Mientras se graba una
sesión de navegación, se pueden añadir pruebas de tipo asertos pulsando el botón dere-
cho sobre la ventana del navegador y seleccionando la opción apropiada del menún “Ap-
pend Selenium Command”.

Una vez realizados todos los movimientos y añadidos todos los comandos, se pueden
guardar en un archivo HTML para añadirlo al conjunto de pruebas. La extensión de Fire-
fox incluso permite ejecutar las pruebas de Selenium que se han creado con la extensión.

  NOTA
  No debe olvidarse reinicializar los datos de prueba antes de lanzar cada prueba de Selenium.


15.6. Resumen
La automatización de pruebas abarca tanto las pruebas unitarias (que validan métodos o
funciones) como las pruebas funcionales (que validan características completas de la
aplicación). Symfony utiliza el framework de pruebas Lime para las pruebas unitarias y
proporciona la clase sfTestBrowser para las pruebas funcionales. En ambos casos, se dis-
pone de métodos para realizar todo tipo de asertos, desde los más sencillos hasta los
más complejos, como por ejemplo los que se realizan mediante los selectores de CSS. La
línea de comandos de Symfony permite ejecutar las pruebas de una en una (mediante
las tareas test-unit y test-functional) o en grupo (mediante la tarea test-all). En lo
que respecta a los datos, las pruebas automatizadas utilizan stubs (clases falsas) y fixtu-
res (datos de prueba complejos) y Symfony simplifica su uso desde las pruebas unitarias.

Si se definen las suficientes pruebas unitarias como para probar la mayor parte de una
aplicación (quizás aplicando la metodología TDD), es mucho más seguro refactorizar el
código de la aplicación y añadir nuevas características. Incluso, en ocasiones, las pruebas
pueden reducir el tiempo requerido para la documentación técnica del proyecto.




www.librosweb.es                                                                                 344
Symfony, la guía definitiva          Capítulo 16. Herramientas para la administración de aplicaciones




Capítulo 16. Herramientas para la administración de
aplicaciones
Durante el desarrollo y la instalación de las aplicaciones, los programadores necesitan to-
da la información posible para determinar si la aplicación está funcionando como debería.
Normalmente, esta información se obtiene mediante los archivos de log y las herramien-
tas de depuración o debug. Los frameworks como Symfony son el núcleo de las aplicacio-
nes, por lo que es esencial que el propio framework disponga de las herramientas nece-
sarias para asegurar un desarrollo eficiente de las aplicaciones.

Durante la ejecución de una aplicación en el servidor de producción, el administrador de
sistemas repite una serie de tareas, como la rotación de los logs, la actualización de las
aplicaciones, etc. Por este motivo, un framework también debe proporcionar las herram-
ientas necesarias para automatizar lo más posible estas tareas.

En este capítulo se detallan las herramientas de gestión de aplicaciones que dispone
Symfony para realizar todas las tareas anteriores.


16.1. Logs
La única forma de comprender lo sucedido cuando falla la ejecución de una petición, con-
siste en echar un vistazo a la traza generada por el proceso que se ejecuta. Afortunada-
mente, y como se va a ver en esta sección, tanto PHP como Symfony guardan mucha in-
formación de este tipo en archivos de log.

16.1.1. Logs de PHP
PHP dispone de una directiva llamada error_reporting, que se define en el archivo de
configuración php.ini, y que especifica los eventos de PHP que se guardan en el archivo
de log. Symfony permite redefinir el valor de esta opción, tanto a nivel de aplicación co-
mo de entorno, en el archivo settings.yml, tal y como se muestra en el listado 16-1.

Listado 16-1 - Indicando el valor de la directiva error_reporting, en miaplicacion/
config/settings.yml
   prod:
    .settings:
       error_reporting:       257

   dev:
     .settings:
        error_reporting:      4095

Los números que se indican son una forma abreviada de referirse a los distintos niveles
de error (la documentación de PHP contiene toda la información relativa a estos niveles).
Básicamente, el valor 4095 es la forma abreviada del valor E_ALL | E_STRICT, y el valor
257 se refiere a E_ERROR | E_USER_ERROR (que es el valor por defecto de cualquier nuevo
entorno definido).


www.librosweb.es                                                                                345
Symfony, la guía definitiva            Capítulo 16. Herramientas para la administración de aplicaciones


Para no penalizar el rendimiento de la aplicación en el entorno de producción, el servidor
solamente guarda en el archivo de log los errores críticos de PHP. No obstante, en el en-
torno de desarrollo, se guardan en el log todos los tipos de eventos, de forma que el pro-
gramador puede disponer de la máxima información para seguir la pista a los errores.

El lugar en el que se guardan los archivos de log de PHP depende de la configuración del
archivo php.ini. Si no se ha modificado su valor, PHP utiliza las herramientas de log del
servidor web (como por ejemplo los logs de error del servidor Apache). En este caso, los
archivos de log de PHP se encuentran en el directorio de logs del servidor web.

16.1.2. Logs de Symfony
Además de los archivos de log creados por PHP, Symfony también guarda mucha infor-
mación de sus propios eventos en otros archivos de log. Los archivos de log creados por
Symfony se encuentran en el directorio miproyecto/log/. Symfony crea un archivo por
cada aplicación y cada entorno. El archivo del entorno de desarrollo de una aplicación lla-
mada miaplicacion sería miaplicacion_dev.log y el archivo de log del entorno de pro-
ducción de la misma aplicación se llamaría miaplicacion_prod.log.

Si se dispone de una aplicación Symfony ejecutándose, se puede observar que la sintaxis
de los archivos de log generados es muy sencilla. Cada evento resulta en una nueva línea
en el archivo de log de la aplicación. Cada línea incluye la fecha y hora a la que se ha
producido, el tipo de evento, el objeto que ha sido procesado y otros detalles relevantes
que dependen de cada tipo de evento y/o objeto procesado. El listado 16-2 muestra un
ejemplo del contenido de un archivo de log de Symfony.

Listado       16-2   -    Contenido      de     un   archivo   de   log   de   Symfony,      en   log/
miaplicacion_dev.php
   Nov   15   16:30:25   symfony   [info ]   {sfAction} call "barActions->executemessages()"
   Nov   15   16:30:25   symfony   [debug]   SELECT bd_message.ID, bd_message.SENDER_ID, bd_...
   Nov   15   16:30:25   symfony   [info ]   {sfCreole} executeQuery(): SELECT bd_message.ID...
   Nov   15   16:30:25   symfony   [info ]   {sfView} set slot "leftbar" (bar/index)
   Nov   15   16:30:25   symfony   [info ]   {sfView} set slot "messageblock" (bar/mes...
   Nov   15   16:30:25   symfony   [info ]   {sfView} execute view for template "messa...
   Nov   15   16:30:25   symfony   [info ]   {sfView} render "/home/production/miproyecto/...
   Nov   15   16:30:25   symfony   [info ]   {sfView} render to client

Estos archivos de log contienen mucha información, como por ejemplo las consultas SQL
enviadas a la base de datos, las plantillas que se han procesado, las llamadas realizadas
entre objetos, etc.

16.1.2.1. Configuración del nivel de log de Symfony
Symfony define ocho niveles diferentes para los mensajes de log: emerg, alert, crit, err,
warning, notice, info y debug, que son los mismos niveles que define el paquete
PEAR::Log (http://pear.php.net/package/Log/). El archivo de configuración logging.yml de
cada aplicación permite definir el nivel de los mensajes que se guardan en el archivo de
log, como se muestra en el listado 16-3.




www.librosweb.es                                                                                  346
Symfony, la guía definitiva        Capítulo 16. Herramientas para la administración de aplicaciones


Listado 16-3 - Configuración por defecto de los archivos de log en Symfony, en
miaplicacion/config/logging.yml
   prod:
     enabled:   off
     level:     err
     rotate:    on
     purge:     off

   dev:

   test:

   #all:
   # enabled:      on
   # level:        debug
   # rotate:       off
   # period:       7
   # history:      10
   # purge:        on

Por defecto, en todos los entornos salvo en el de producción, se guardan en los archivos
de log todos los mensajes (hasta el nivel menos importante, el nivel debug). En el entor-
no de producción, no se utilizan por defecto los archivos de log. Además, en este mismo
entorno, si se activan los logs asignando el valor on a la opción enabled, solamente se
guardan los mensajes más importantes (de crit a emerg).

En el archivo logging.yml se puede modificar el nivel de los mensajes guardados para ca-
da entorno de ejecución, de forma que se limite el tipo de mensajes que se guardan en el
archivo de log. Las opciones rotate, period, history y purge se describen más adelante
en la sección “Borrando y rotando archivos de log”.

  SUGERENCIA
  Los valores de las opciones de log son accesibles durante la ejecución de la aplicación mediante el
  objeto sfConfig y el uso del prefijo sf_logging_. Para determinar si están habilitados los archivos
  de log, se utilizaría por ejemplo la siguiente llamada: sfConfig::get(’sf_ logging_enabled’).


16.1.2.2. Añadiendo un mensaje de log
Además de los mensajes generados por Symfony, también es posible añadir mensajes
propios en el archivo de log desde el código de la aplicación, utilizando alguna de las téc-
nicas mostradas en el listado 16-4.

Listado 16-4 - Añadiendo un mensaje de log propio
   // Desde la acción
   $this->logMessage($mensaje, $nivel);

   // Desde una plantilla
   <?php use_helper('Debug') ?>
   <?php log_message($mensaje, $nivel) ?>




www.librosweb.es                                                                                347
Symfony, la guía definitiva       Capítulo 16. Herramientas para la administración de aplicaciones


El valor de la opción $nivel puede ser uno de los valores definidos para los mensajes de
log de Symfony.

Además, para escribir un mensaje en el log desde cualquier punto de la aplicación, se
pueden utilizar directamente los métodos de sfLogger, como se muestra en el listado 16-
5. Los métodos disponibles comparten el mismo nombre que los niveles de log definidos.

Listado 16-5 - Añadiendo un mensaje de log propio desde cualquier punto de la
aplicación
   if (sfConfig::get('sf_logging_enabled'))
   {
     sfContext::getInstance()->getLogger()->info($mensaje);
   }

  Personalizando el mecanismo de logs

  Solamente disponible en las últimas versiones de Symfony

  El mecanismo de log de Symfony es muy sencillo y es muy fácil de personalizar.

  El unico requisito es que las clases del nuevo mecanismo de log deben implementar la interfaz
  sfLoggerInterface, que define un método llamado log(). Symfony invoca el método log() con
  2 parámetros: $mensaje (el mensaje que se quiere guardar en el log), $prioridad (el nivel del
  mensaje).

  La siguiente clase miPropioLog define un mecanismo de log muy sencillo que utiliza la función
  error_log de PHP:
      class miPropioLog implements sfLoggerInterface
      {
        public function log($mensaje, $prioridad)
        {
          error_log(sprintf('%s (%s)', $mensaje, sfLogger::getPriorityName($prioridad)));
        }
      }

  Para   registrar una   clase  propia  de   log,           se    puede     utilizar   el   método
  sfLogger::getInstance()->registerLogger().

  Si por ejemplo se quiere utilizar PEAR::Log, se añade lo siguiente al archivo config.php de la
  aplicación:
      require_once('Log.php');
      require_once('Log/error_log.php');

      // Clase sencilla que implementa la interfaz del mecanismo de log
      // que se quiere utilizar con Symfony
      class Log_my_error_log extends Log_error_log implements sfLoggerInterface
      {
      }

      // Registrar la clase
      $log = Log::singleton('my_error_log', PEAR_LOG_TYPE_SYSTEM, 'symfony');
      sfLogger::getInstance()->registerLogger($log);




www.librosweb.es                                                                              348
Symfony, la guía definitiva       Capítulo 16. Herramientas para la administración de aplicaciones


16.1.2.3. Borrando y rotando archivos de log
Periódicamente es necesario borrar los archivos del directorio log/ de las aplicaciones, ya
que estos archivos suelen crecer en tamaño varios MB cada pocos días, aunque todo de-
pende del tráfico de la aplicación. Symfony proporciona la tarea log-purge para este
propósito, y se puede ejecutar de forma periódica manualmente o mediante una tarea
programada. El siguiente comando borra los archivos de log de todas las aplicaciones y
entornos para los que el archivo logging.yml especifique un valor on a la opción purge (q-
ue es el valor por defecto):
   > symfony log-purge

Para mejorar el rendimiento y la seguridad de la aplicación, suele ser habitual almacenar
los archivos de log de Symfony en varios archivos pequeños en vez de en un solo archivo
muy grande. La estrategia de almacenamiento ideal para los archivos de log es la de vac-
iar y hacer una copia de seguridad cada poco tiempo del archivo de log principal y man-
tener un número limitado de copias de seguridad. Esta estrategia se denomina rotación
de archivos de log y se puede activar desde el archivo de configuración logging.yml. Si
se establece por ejemplo un period de 7 días y un history (número de copias de seguri-
dad) de 10, como se muestra en el listado 16-6, es posible trabajar con un archivo de log
activo y tener otras 10 copias de seguridad, cada una con los mensajes de log de 7 días
diferentes. Cuando transcurren otros 7 días, el archivo de log activo se transforma en
una copia de seguridad y se borra el archivo de la copia de seguridad más antigua.

Listado 16-6 - Configurando la rotación de logs, en miaplicacion/config/
logging.yml
   prod:
     rotate: on
     period: 7           ## Por defecto, los archivos se rotan cada 7 días
     history: 10         ## Se mantienen 10 archivos de log como copia de seguridad

Para ejecutar la rotación de los logs, se debe utilizar periódicamente la tarea log-rotate.
Esta tarea sólo borra los archivos para los que la opción rotate vale on. Se puede indicar
una aplicación y un entorno específicos al utilizar esta tarea:
   > symfony log-rotate miaplicacion prod

Las copias de seguridad de los archivos de log se almacenan en el directorio logs/his-
tory/ y a su nombre se les añade un sufijo con la fecha completa en la que fueron
guardados.


16.2. Depuración de aplicaciones
No importa lo buenos que sean los programadores o lo bueno que sea Symfony, siempre
se acaban cometiendo errores. Una de las claves para el desarrollo rápido de aplicaciones
es la detección y comprensión de los errores producidos. Afortunadamente, Symfony pro-
porciona varias herramientas para depurar las aplicaciones.




www.librosweb.es                                                                             349
Symfony, la guía definitiva      Capítulo 16. Herramientas para la administración de aplicaciones


16.2.1. Modo debug de Symfony
Symfony dispone de un modo llamado “debug” que facilita el desarrollo y la depuración
de las aplicaciones. Cuando se activa este modo, ocurre lo siguiente:

      ▪ La configuración de la aplicación se comprueba en cada petición, por lo que cual-
        quier cambio en la configuración se aplica inmediatamente, sin necesidad de bo-
        rrar la cache de configuración.

      ▪ Los mensajes de error muestran la traza completa de ejecución de forma clara y
        útil, para que sea más fácil de encontrar el elemento que está fallando.

      ▪ Se muestran más herramientas de depuración (como por ejemplo, todas las con-
        sultas a la base de datos).

      ▪ También se activa el modo debug de Propel, por lo que cualquier error en la lla-
        mada a un objeto de Propel, muestra una lista completa de los errores produci-
        dos en toda la arquitectura Propel.

Por otra parte, cuando se desactiva el modo debug, las peticiones se procesan de la sigu-
iente forma:

      ▪ Los archivos de configuración YAML se procesan una sola vez y se transforman
        en archivos PHP que se almacenan en la carpeta cache/config/. Todas las petic-
        iones que se realizan después de la primera petición, no tienen en cuenta los ar-
        chivos YAML de configuración y utilizan en su lugar la configuración guardada en
        la cache. Por tanto, el procesamiento de cada petición es mucho más rápido.

      ▪ Para forzar a que se vuelva a procesar la configuración de la aplicación, es nece-
        sario borrar a mano la cache de configuración.

      ▪ Cualquier error que se produzca durante el procesamiento de la petición, devuel-
        ve una respuesta con el código de estado 500 (Error Interno del Servidor) y no
        se muestran los detalles internos del error.

El modo debug se activa para cada aplicación en su controlador frontal. Este modo se
controla mediante la constante SF_DEBUG, como se muestra en el listado 16-7.

Listado 16-7 - Controlador frontal de ejemplo con el modo debug activado, en
web/miaplicacion_dev.php
   <?php

   define('SF_ROOT_DIR',      realpath(dirname(__FILE__).'/..'));
   define('SF_APP',           'miaplicacion');
   define('SF_ENVIRONMENT',   'dev');
   define('SF_DEBUG',         true);

   require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'

   sfContext::getInstance()->getController()->dispatch();




www.librosweb.es                                                                            350
Symfony, la guía definitiva        Capítulo 16. Herramientas para la administración de aplicaciones


  ATENCIÓN
  En el servidor de producción, no se debería activar el modo debug y no se debería guardar ningún
  controlador frontal con este modo activado. El modo debug no solo penaliza el rendimiento de la
  aplicación, sino que revela información interna de la aplicación. Aunque las herramientas de depu-
  ración nunca desvelan la información necesaria para conectarse con la base de datos, la traza ge-
  nerada en las excepciones está llena de información demasiado sensible y que puede ser aprove-
  chada por un usuario malintencionado.


16.2.2. Excepciones Symfony
Cuando se produce una excepción y está activado el modo debug, Symfony muestra un
mensaje de error muy útil que contiene toda la información necesaria para descubrir la
causa del problema.

Los mensajes que produce la excepción están escritos de forma clara y hacen referencia
a la causa más probable del problema. Normalmente ofrecen posibles soluciones para
arreglar el error y para la mayoría de problemas comunes, incluso se muestra un enlace
a la página del sitio web de Symfony que contiene más información sobre la excepción.
La página con el mensaje de la excepción muestra en qué parte del código PHP se ha
producido el error y la lista completa de los métodos que se han invocado, como se
muestra en la figura 16-1. De esta forma, es posible seguir la traza de ejecución hasta la
primera llamada que causó el problema. También se muestran los argumentos que se
pasan a cada método.

  NOTA
  Symfony se basa en las excepciones de PHP para notificar los errores, que es un método mucho
  mejor que el funcionamiento de las aplicaciones desarrolladas con PHP 4. Para notificar un error de
  tipo 404, se utiliza el método sfError404Exception.




www.librosweb.es                                                                                351
Symfony, la guía definitiva    Capítulo 16. Herramientas para la administración de aplicaciones




      Figura 16.1. Mensaje mostrado por una excepción de una aplicación Symfony


Mientras se desarrolla la aplicación, las excepciones Symfony son de gran utilidad para
depurar el funcionamiento de las aplicaciones.

16.2.3. Extensión Xdebug
La extensión Xdebug de PHP (http://xdebug.org/) permite ampliar la cantidad de informa-
ción que el servidor web almacena en los archivos de log. Symfony es capaz de integrar
los mensajes de Xdebug en sus propios mensajes de error, por lo que es una buena idea
activar esta extensión cuando se están depurando las aplicaciones. La instalación de la
extensión depende de la plataforma en la que se realiza, por lo que se debe consultar la
información disponible en el sitio web de Xdebug. Una vez instalada, se activa manual-
mente en el archivo de configuración php.ini. En los sistemas *nix, se activa añadiendo
la siguiente línea:
   zend_extension="/usr/local/lib/php/extensions/no-debug-non-zts-20041030/xdebug.so"

En los sistemas Windows, la activación de Xdebug se realiza mediante:
   extension=php_xdebug.dll




www.librosweb.es                                                                          352
Symfony, la guía definitiva       Capítulo 16. Herramientas para la administración de aplicaciones


El listado 16-8 muestra un ejemplo de la configuración de Xdebug, que también se debe
añadir al archivo php.ini.

Listado 16-8 - Configuración de ejemplo para Xdebug
   ;xdebug.profiler_enable=1
   ;xdebug.profiler_output_dir="/tmp/xdebug"
   xdebug.auto_trace=1             ; enable tracing
   xdebug.trace_format=0
   ;xdebug.show_mem_delta=0        ; memory difference
   ;xdebug.show_local_vars=1
   ;xdebug.max_nesting_level=100

Por último, para activar la extensión Xdebug, se debe reiniciar el servidor web.

  ATENCIÓN
  No debe olvidarse desactivar la extensión Xdebug en el servidor de producción. Si no se desactiva,
  el rendimiento de la aplicación disminuye notablemente.


16.2.4. Barra de depuración web
Los archivos de log guardan información muy útil, pero no siempre son fáciles de leer. La
tarea más básica, que consiste en localizar las líneas del archivo de log correspondientes
a una determinada petición, suele complicarse cuando existen varios usuarios simultáne-
os en la aplicación y cuando el archivo de log es muy grande. En ese momento es cuando
se hace necesaria la barra de depuración web.

Esta barra de depuración se muestra como una caja semitransparente superpuesta sobre
el contenido de la ventana del navegador y que aparece en la esquina superior derecha,
como se ve en la figura 16-2. Esta barra permite acceder directamente a los eventos
guardados en el log, a la configuración actual, las propiedades de los objetos de la peti-
ción y de la respuesta, los detalles de las consultas realizadas a la base de datos y una
tabla con los tiempos empleados en cada elemento de la petición.




Figura 16.2. La barra de depuración web se muestra en la esquina superior derecha de la
                                 ventana del navegador


El color de fondo de la barra de depuración web depende del máximo nivel de los mensa-
jes de log producidos durante la petición. Si ningún mensaje pasa del nivel debug, la

www.librosweb.es                                                                               353
Symfony, la guía definitiva       Capítulo 16. Herramientas para la administración de aplicaciones


barra se muestra con color de fondo gris. Si al menos un mensaje alcanza el nivel err, la
barra muestra un color de fondo rojo.

  NOTA
  No debe confundirse el modo debug y la barra de depuración web. La barra se puede mostrar inclu-
  so cuando el modo debug está desactivado, aunque en este caso, muestra mucha menos
  información.

Para activar la barra de depuración web en una aplicación, se utiliza la opción web_debug
del archivo de configuración settings.yml. En los entornos de ejecución prod y test, el
valor por defecto de la opción web_debug es off, por lo que se debe activar manualmente
si se necesita. En el entorno de ejecución dev, esta opción tiene un valor por defecto de
on, tal y como muestra el listado 16-9.

Listado 16-9 - Activando la barra de depuración web, en miaplicacion/config/
settings.yml
   dev:
     .settings:
        web_debug:               on

Cuando se muestra la barra de depuración web, ofrece mucha información:

      ▪ Si se pincha sobre el logotipo de Symfony, la barra se oculta. Cuando está mini-
        mizada, la barra no oculta los elementos de la página que se encuentran en la
        esquina superior derecha.

      ▪ Como muestra la figura 16-3, cuando se pincha sobre la opción vars & config, se
         muestran los detalles de la petición, de la respuesta, de las opciones de configu-
         ración, de las opciones globales y de las propiedades PHP. La línea superior resu-
         me el estado de las opciones de configuración más importantes, como el modo
         debug, la cache y la presencia/ausencia de un acelerador de PHP (su nombre
         aparece en rojo si está desactivado y en color verde si se encuentra activado).




  Figura 16.3. La sección "vars & config" muestra todas las variables y constantes de la
                                         petición



www.librosweb.es                                                                             354
Symfony, la guía definitiva        Capítulo 16. Herramientas para la administración de aplicaciones


      ▪ Cuando la cache se encuentra activada, se muestra una flecha verde en la barra
        de depuración web. Si se pulsa sobre esta flecha, la página se vuelve a procesar
        entera, independientemente de si se encuentra almacenada en la cache (no obs-
        tante, la cache no se vacía al pulsar sobre esta flecha).

      ▪ Como muestra la figura 16-4, al pulsar sobre la sección logs & msgs, se muestran
         los mensajes de log para la petición actual. En función de la importancia de los
         eventos, las líneas se muestran en gris, amarillo o rojo. Mediante los enlaces que
         se muestran en forma de lista en la parte superior, es posible filtrar los mensajes
         de log en función de su categoría.




 Figura 16.4. La sección "logs & msgs" muestra los mensajes de log de la petición actual



  NOTA
  Cuando la acción es el resultado de una redirección, solamente se muestran los mensajes de log
  de la última petición, por lo que es imprescindible consultar los archivos de log completos para de-
  purar las aplicaciones.

      ▪ Si durante el procesamiento de la petición se han ejecutado consultas SQL, se
        muestra un icono de una base de datos en la barra de depuración web. Si se pul-
        sa sobre este icono, se muestra el detalle de las consultas realizadas, como se
         muestra en la figura 16-5.

      ▪ A la derecha del icono del reloj se muestra el tiempo total de procesamiento req-
        uerido por la petición. Como el modo debug y la propia barra de depuración

www.librosweb.es                                                                                 355
Symfony, la guía definitiva     Capítulo 16. Herramientas para la administración de aplicaciones


        consumen muchos recursos, el tiempo que se muestra es mucho más lento que
        la ejecución real de la petición. Por tanto, es más importante fijarse en las dife-
        rencias de tiempos producidas por los cambios introducidos que en el propio
        tiempo mostrado. Si se pulsa sobre el icono del reloj, se muestran los detalles del
        tiempo de procesamiento de cada categoría, tal y como se muestra en la figura
        16-6. Symfony muestra el tiempo consumido en las diferentes partes que compo-
        nen el procesamiento de la petición. Como solamente tiene sentido optimizar el
        tiempo de procesamiento propio de la petición, no se muestra el tiempo consumi-
        do por el núcleo de Symfony. Esta es la razón por la que la suma de todos los
        tiempos individuales no es igual al tiempo total mostrado.

      ▪ Si se pulsa sobre la X roja a la derecha de la barra, se oculta la barra de depura-
        ción web.




Figura 16.5. La sección de consultas a la base de datos muestra las consultas ejecutadas
                               durante la petición actual




www.librosweb.es                                                                           356
Symfony, la guía definitiva        Capítulo 16. Herramientas para la administración de aplicaciones




  Figura 16.6. El icono del reloj muestra el tiempo de ejecución dividido por categorías


  Creando tu propio contador de tiempo

  Symfony utiliza la clase sfTimer para calcular el tiempo empleado en la configuración, el modelo,
  la acción y la vista. Utilizando el mismo objeto, se puede calcular el tiempo empleado por un proce-
  so propio y mostrar el resultado junto con el resto de tiempos de la barra de depuración web. Se
  trata de algo muy útil cuando se está trabajando en la optimización del rendimiento de la aplicación.

  Para inicializar el control del tiempo para un fragmento de código, se utiliza el método getTimer().
  Este método devuelve un objeto de tipo sfTimer y comienza a contar el tiempo. Para detener el
  avance del contador de tiempo, se invoca el método addTime(). En ese instante, se puede obtener
  el tiempo transcurrido mediante el método getElapsedTime() y se muestra automáticamente junto
  con el resto de tiempos en la barra de depuración web.
      // Inicializar el contador y empezar a contar el tiempo
      $contador = sfTimerManager::getTimer('miContador');

      // Otras instrucciones y código
      ...

      // Detener el contador y sumar el tiempo transcurrido
      $contador->addTime();

      // Obtener el resultado (y detener el contador si no estaba detenido)
      $tiempoTranscurrido = $contador->getElapsedTime();

  La ventaja de asignar un nombre a cada contador, es que se puede utilizar varias veces para acu-
  mular diferentes tiempos. Si por ejemplo el contador miContador se utiliza en un método que se lla-
  ma 2 veces en cada petición, la segunda llamada al método getTimer(’miContador’) comienza a
  contar el tiempo desde donde se quedó la última vez que se llamó a addTime(), por lo que el tiem-
  po transcurrido se sumará al tiempo anterior. El método getCalls() del contador devuelve el




www.librosweb.es                                                                                  357
Symfony, la guía definitiva        Capítulo 16. Herramientas para la administración de aplicaciones


  número de veces que ha sido utilizado el contador desde que se inició la petición, y este dato tam-
  bién se muestra en la barra de depuración web.
      // Obtener el número de veces que ha sido utilizado el contador
      $llamadas = $contador->getCalls();

  Si se utiliza Xdebug, los mensajes de log son mucho más completos. Se guarda en el log todos los
  archivos PHP y todas las funciones que han sido llamadas, y Symfony integra esta información con
  su propio log interno. Cada fila de la tabla de mensajes de log dispone de una flecha bidireccional
  que se puede pulsar para obtener más detalles sobre la petición relacionada. Si algo no va bien, el
  modo Xdebug es el que más información proporciona para averiguar la causa.

  NOTA
  La barra de depuración web no se incluye por defecto en las respuestas de tipo Ajax y en los docu-
  mentos cuyo Content-Type no es de tipo HTML. Para el resto de las páginas, se puede deshabili-
  tar la barra de depuración web manualmente desde la acción mediante la llamada a sfConfig::-
  set(’sf_web_debug’, false).


16.2.5. Depuración manual
Aunque muchas veces es suficiente con acceder a los mensajes de log generados por el
framework, en ocasiones es mejor poder generar mensajes de log propios. Symfony dis-
pone de utilidades, que se pueden acceder desde las acciones y desde las plantillas, para
crear trazas sobre los eventos y/o valores presentes durante la ejecución de la petición.

Los mensajes de log propios aparecen en el archivo de log de Symfony y en la barra de
depuración web, como cualquier otro mensaje de Symfony. (El listado 16-4 anterior
muestra un ejemplo de la sintaxis de un mensaje de log propio). Los mensajes de log
propios se pueden utilizar por ejemplo para comprobar el valor de una variable en una
plantilla. El listado 16-10 muestra cómo utilizar la barra de depuración web desde una
plantilla para obtener información para el programador (también se puede utilizar el mé-
todo $this->logMessage() desde una acción).

Listado 16-10 - Creando un mensaje de log propio para depurar la aplicación
   <?php use_helper('Debug') ?>
   ...
   <?php if ($problem): ?>
     <?php log_message('{sfAction} ha pasado por aquí', 'err') ?>
     ...
   <?php endif ?>

Si se utiliza el nivel err, se garantiza que el evento sea claramente visible en la lista de
mensajes, como se muestra en la figura 16-7.




www.librosweb.es                                                                                358
Symfony, la guía definitiva    Capítulo 16. Herramientas para la administración de aplicaciones




Figura 16.7. Mensaje de log propio en la sección "logs & msgs" de la barra de depuración
                                          web


Si no se quiere añadir una línea al log, sino que solo se necesita mostrar un mensaje cor-
to o un valor, se debería utilizar debug_message en vez de log_message. Este método de la
acción (para el que también existe un helper con el mismo nombre) muestra un mensaje
en la barra de depuración web, en la parte superior de la sección logs & msgs. El listado
16-11 muestra un ejemplo de uso de esta utilidad.

Listado 16-11 - Mostrando un mensaje en la barra de depuración web
   // En una acción
   $this->debugMessage($mensaje);

   // En una plantilla
   <?php use_helper('Debug') ?>
   <?php debug_message($mensaje) ?>


16.3. Cargando datos en una base de datos
Durante el desarrollo de una aplicación, uno de los problemas recurrentes es el de la car-
ga inicial de datos en la base de datos. Algunos sistemas de bases de datos disponen de
soluciones específicas para esta tarea, pero ninguna se puede utilizar junto en el ORM de
Symfony. Gracias al uso de YAML y al objeto sfPropelData, Symfony puede transferir


www.librosweb.es                                                                          359
Symfony, la guía definitiva     Capítulo 16. Herramientas para la administración de aplicaciones


automáticamente los datos almacenados en un archivo de texto a una base de datos.
Aunque puede parecer que crear el archivo de texto con los datos iniciales de la aplica-
ción cuesta más tiempo que insertarlos directamente en la base de datos, a la larga se
ahorra mucho tiempo. Se trata de una utilidad muy práctica para la carga automática de
datos de prueba para la aplicación.

16.3.1. Sintaxis del archivo de datos
Symfony es capaz de procesar todos los archivos que siguen una sintaxis YAML definida
muy simple y que se encuentren en el directorio data/fixtures/. Los archivos de datos,
también llamados “fixtures”, se organizan por clases y cada sección de clase utiliza una
cabecera con el valor del nombre de la clase. Para cada clase, las filas de datos disponen
de una etiqueta que las identifica de forma única y una serie de pares nombre_campo: va-
lor. El listado 16-12 muestra un ejemplo de un archivo preparado para cargar sus datos
en una base de datos.

Listado 16-12 - Archivo de datos de ejemplo, en data/fixtures/import_data.yml
   Article:                             ## Crea filas de datos en la tabla blog_article
     first_post:                        ## Etiqueta de la primera fila de datos
       title:       My first memories
       content: |
         For a long time I used to go to bed early. Sometimes, when I had put
         out my candle, my eyes would close so quickly that I had not even time
         to say "I am going to sleep."

      second_post:                       ## Etiqueta de la segunda fila de datos
        title:       Things got worse
        content: |
          Sometimes he hoped that she would die, painlessly, in some accident,
          she who was out of doors in the streets, crossing busy thoroughfares,
          from morning to night.

Symfony transforma el nombre indicado para las columnas, en métodos setter utilizando
la conversión de tipo camelCase (la columna title se transforma en setTitle(), la co-
lumna content se transforma en setContent(), etc.). La ventaja de esta transformación
es que se puede definir, por ejemplo, una columna llamada password para la que no exis-
te una columna en la tabla de la base de datos; solamente es necesario definir un méto-
do llamado setPassword() en el objeto User y ya es posible asignar valores a otras co-
lumnas de datos en función de este dato, como por ejemplo una columna que guarde la
contraseña encriptada.

No es necesario definir el valor de la columna de la clave primaria. Como es un campo
cuyo valor se autoincrementa, la capa de base de datos es capaz de determinar su valor.

A las columnas created_at tampoco es necesario asignarles un valor, ya que Symfony
sabe que a las columnas que se llaman así, les debe asignar la fecha actual del sistema a
la hora de crearlas.




www.librosweb.es                                                                           360
Symfony, la guía definitiva      Capítulo 16. Herramientas para la administración de aplicaciones


16.3.2. Importando los datos
La tarea propel-load-data importa los datos de un archivo YAML en una base de datos.
Las opciones de conexión con la base de datos se obtienen del archivo de configuración
databases.yml, por lo que es necesario indicar a la tarea el nombre de una aplicación.
Además, es posible indicar como argumento el nombre de un entorno de ejecución (su
valor por defecro es dev).
   > symfony propel-load-data frontend

Al ejecutar este comando, se leen todos los archivos de datos YAML del directorio data/
fixtures y se insertan las filas de datos en la base de datos. Por defecto, se reemplaza
todo el contenido existente en la base de datos, aunque si se utiliza un argumento opcio-
nal llamado append, el comando no borra los datos existentes.
   > symfony propel-load-data frontend append

También es posible especificar otro archivo de datos u otro directorio, indicando su valor
como una ruta relativa respecto del directorio del proyecto.
   > symfony propel-load-data frontend data/misfixtures/miarchivo.yml


16.3.3. Usando tablas relacionadas
Ahora ya es posible añadir filas de datos a una tabla, pero de esta forma no es posible
añadir filas con claves externas que hacen relación a otra tabla. Como los archivos de da-
tos no incluyen la clave primaria, se necesita un método alternativo para relacionar los
diferentes registros de datos entre sí.

Volviendo al ejemplo del Capítulo 8, donde la tabla blog_article está relacionada con la
tabla blog_comment, de la forma que se muestra en la figura 16-8.




              Figura 16.8. Ejemplo de modelo relacional de una base de datos


En esta situación es en la que se utilizan las etiquetas únicas de cada fila de datos. Para
añadir un campo de tipo Comment al artículo llamado first_post, simplemente es necesar-
io añadir las siguientes líneas del listado 16-13 al archivo de datos import_data.yml.

Listado 16-13 - Añadiendo un registro relacionado con otra tabla, en data/fixtu-
res/import_data.yml
   Comment:
     first_comment:
       article_id:     first_post
       author:         Anonymous
       content:        Your prose is too verbose. Write shorter sentences.



www.librosweb.es                                                                            361
Symfony, la guía definitiva      Capítulo 16. Herramientas para la administración de aplicaciones


La tarea propel-load-data es capaz de reconocer la etiqueta que se asignó anteriormente
al artículo en el archivo import_data.yml y es capaz de obtener la clave primaria del re-
gistro de tipo Article correspondiente en la base de datos, para asignar ese valor al
campo article_id. No es necesario trabajar con los valores de las columnas de tipo ID,
solo es necesario enlazar las filas de datos mediante sus etiquetas, por lo que su funcio-
namiento es realmente simple.

La única restricción para las filas de datos enlazadas es que los objetos utilizados en una
clave externa tienen que estar definidos anteriormente en el archivo; es decir, igual que
si se tuvieran que definir uno a uno. Los archivos de datos se procesan desde el principio
hasta el final y por tanto, el orden en el que se escriben las filas de datos es muy
importante.

Un solo archivo de datos puede contener la declaración de varias clases diferentes. Sin
embargo, si se necesitan insertar muchos datos en muchas tablas diferentes, es posible
que el archivo de datos sea demasiado largo como para manejarlo fácilmente.

Como la tarea propel-load-data procesa todos los archivos que encuentra en el directorio
fixtures/, es posible dividir el archivo de datos YAML en otros archivos más pequeños.
Lo único que hay que tener en cuenta es que las claves externas obligan a definir un de-
terminado orden al procesar los datos. Para asegurar que los archivos se procesan en el
orden adecuado, se puede añadir un número como prefijo del nombre del archivo, de for-
ma que se procesen en el orden establecido.
   100_article_import_data.yml
   200_comment_import_data.yml
   300_rating_import_data.yml


16.4. Instalando aplicaciones
Symfony dispone de comandos para sincronizar 2 versiones diferentes de un mismo sitio
web. La utilidad de estos comandos es la de poder instalar una aplicación o sitio web des-
de un servidor de desarrollo hasta un servidor de producción, desde donde los usuarios
accederán a la aplicación pública. Este proceso también se conoce como el “deploy” de
una aplicación, por lo que a veces se utiliza la palabra “deployar” (o “desplegar”) para re-
ferirse a la instalación de una aplicación.

16.4.1. Preparando un proyecto para transferirlo con FTP
La forma habitual de instalar las aplicaciones en los servidores de producción consiste en
trasferir todos los archivos de la aplicación mediante FTP (o SFTP). Sin embargo, los pro-
yectos desarrollados con Symfony utilizan las librerías internas de Symfony y, salvo que
se desarrolle con el archivo de pruebas sandbox (lo que no se recomienda) o salvo que
los directorios lib/ y data/ estén enlazados mediante svn:externals, estas librerías no
se encuentran dentro de los directorios del proyecto. Independientemente de que se rea-
lice una instalación PEAR o se utilicen enlaces simbólicos, trasladar la misma estructura
de directorios al servidor de producción suele ser una tarea costosa y no muy sencilla.

Por este motivo, Symfony dispone de una utilidad que congela los proyectos, es decir,
copia todas las librerías de Symfony necesarias en los directorios data/, lib/ y web/ del

www.librosweb.es                                                                            362
Symfony, la guía definitiva      Capítulo 16. Herramientas para la administración de aplicaciones


proyecto. Una vez congelado, el proyecto se transforma en una aplicación independiente
y completamente ejecutable por sí misma, tal y como el entorno de pruebas sandbox.
   > symfony freeze

Una vez que un proyecto ha sido congelado, se puede transferir directamente el director-
io raíz completo del proyecto al servidor de producción y funciona sin necesidad de PEAR,
enlaces simbólicos o cualquier otro elemento.

  SUGERENCIA
  En un mismo servidor se pueden ejecutar simultáneamente varios proyectos congelados, cada uno
  con su propia, e incluso diferente, versión de Symfony.

Para devolver un proyecto a su estado original, se utiliza la tarea unfreeze (descongelar).
Esta tarea borra los directorios data/symfony/, lib/symfony/ y web/sf/.
   > symfony unfreeze

Si antes de congelar el proyecto existían enlaces simbólicos, Symfony es capaz de reco-
nocerlos y al descongelar el proyecto, vuelve a crear los enlaces simbólicos originales.

16.4.2. Usando rsync para transferir archivos incrementalmente
Cuando se realiza el primer traspaso de la aplicación web, es útil transferir mediante FTP
(o SFTP) el directorio raíz completo del proyecto, pero cuando se trata de actualizar una
aplicación para la que solamente se han modificado unos pocos archivos, la solución me-
diante FTP no es la ideal. Si se utiliza FTP, o se vuelve a transferir completo el proyecto,
con el consiguiente gasto de tiempo y ancho de banda, o se accede manualmente a todos
los directorios con archivos modificados y se suben de uno en uno. Este último método,
no solo es costoso en tiempo, sino que es muy propenso a cometer errores. Además, el
sitio web puede estar no disponible o puede mostrar muchos errores durante el traspaso
de las modificaciones.

La solución que propone Symfony es el uso de la herramienta de syncronización rsyn me-
diante SSH. Rsync (http://samba.anu.edu.au/rsync/) es una utilidad de la línea de coman-
dos que permite realizar una transferencia incremental de archivos de forma muy rápida,
además de que es una herramienta de software libre. En una transferencia incremental,
solamente se transfieren los datos modificados. Si un archivo no ha sido modificado des-
de la última sincronización, no se vuelve a enviar al servidor. Si un archivo solamente
tiene un cambio parcial, solamente se envían los cambios realizados. La principal ventaja
de rsync es que las sincronizaciones requieren el envío de muy pocos datos y por tanto,
son muy rápidas.

Symfony utiliza SSH conjuntamente con rsync para hacer más segura la transferencia de
datos. La mayoría de servicios de hosting soportan el uso de SSH para aportar más segu-
ridad a la transferencia de archivos hasta sus servidores.

El cliente SSH utilizado por Symfony utiliza las opciones de conexión del archivo config/
properties.ini. El listado 16-14 muestra un ejemplo de las opciones de conexión para
un servidor de producción. Antes de realizar la sincronización de la aplicación, se deben



www.librosweb.es                                                                            363
Symfony, la guía definitiva        Capítulo 16. Herramientas para la administración de aplicaciones


establecer las opciones de conexión en este archivo. También es posible definir una op-
ción llamada parameters para utilizar parámetros propios con rsync.

Listado 16-14 - Opciones de conexión para la sincronización con un servidor, en
miproyecto/config/properties.ini
   [symfony]
     name=miproyecto

   [production]
     host=miaplicacion.example.com
     port=22
     user=myuser
     dir=/home/myaccount/miproyecto/


  NOTA
  No debe confundirse el servidor de producción (que es el servidor definido en el archivo propert-
  ies.ini del proyecto) con el entorno de producción (el controlador frontal y la configuración que se
  utiliza en producción).

Como la sincronización de rsync mediante SSH requiere de varios comandos, y la sincro-
nización suele ocurrir muchas veces durante la vida de una aplicación, Symfony automa-
tiza esta tarea mediante un único comando:
   > symfony sync production

El comando anterior ejecuta el comando rsync en el modo de prueba; es decir, muestra
los archivos que tienen que ser sincronizados, pero no los sincroniza realmente. Para rea-
lizar la sincronización, se debe indicar explícitamente mediante la opción go.
   > symfony sync production go

No debe olvidarse borrar la cache en el servidor de producción después de la
sincronización.

  SUGERENCIA
  En ocasiones, se producen errores en el servidor de producción que no existían en el servidor de
  desarrollo. El 90% de las veces el problema reside en una diferencia en las versiones de las aplica-
  ciones (de PHP, del servidor web o de la base de datos) o en la configuración de la aplicación. Para
  evitar sorpresas desagradables, se debe definir la configuración de PHP del servidor de producción
  en un archivo llamado php.yml, para poder comprobar que el entorno de desarrollo aplica las mis-
  mas ocpiones. El Capítulo 19 incluye más información sobre este archivo de configuración.

  ¿Está terminada la aplicación?

  Antes de subir la aplicación al servidor de producción, es necesario asegurarse de que está lista
  para ser utilizada por los usuarios. Antes de instalar la aplicación en el servidor de producción, es
  recomendable comprobar que se ha completado lo siguiente:

  Las páginas de error deberían mostrar un aspecto integrado con el del resto de la aplicación. El
  Capítulo 19 explica cómo personalizar las páginas del error 500, del error 400 y las de las páginas
  relacionadas con la seguridad. La sección “Administrando una aplicación en producción” explica,



www.librosweb.es                                                                                  364
Symfony, la guía definitiva        Capítulo 16. Herramientas para la administración de aplicaciones


  más adelante en este capítulo, cómo personalizar las páginas que se muestran cuando la aplica-
  ción no está disponible.

  El módulo default se debe eliminar del array enabled_modules del archivo settings.yml, de
  modo que no se muestren por error páginas del propio Symfony.

  El mecanismo de gestión sesiones utiliza una cookie para el navegador del usuario y esta cookie se
  llama symfony. Antes de instalar la aplicación en producción, puede ser una buena idea cambiarle
  el nombre para no mostrar que la aplicación está desarrollada con Symfony. El Capítulo 6 explica
  cómo modificar el nombre de la cookie en el archivo factories.yml.

  Por defecto, el archivo robots.txt del directorio web/ está vacío. Normalmente, es una buena
  idea modificar este archivo para indicar a los buscadores las partes de la aplicación que pueden ac-
  ceder y las partes que deberían evitar. También se suele utilizar este archivo para excluir ciertas
  URL de la indexación realizada por los buscadores, como por ejemplo las URL que consumen mu-
  cho tiempo de proceso o las páginas que no interesa indexar.

  Los navegadores más modernos buscan un archivo llamado favicon.ico cuando el usuario acce-
  de por primera vez a la aplicación. Este archivo es el icono que representa a la aplicación en la ba-
  rra de direcciones y en la carpeta de favoritos. Además de que este icono ayuda a completar el as-
  pecto de la aplicación, evita que se produzcan muchos errores de tipo 404 cuando los navegadores
  lo solicitan y no se encuentra disponible.


16.4.3. Ignorando los archivos innecesarios
Cuando se sincroniza un proyecto Symfony con un servidor de producción, algunos archi-
vos y directorios no deberían transferirse:

      ▪ Todos los directorios del versionado del código (.svn/, CVS/, etc.) y su contenido,
        solamente es necesario para el desarrollo e integración de la aplicación.

      ▪ El controlador frontal del entorno de desarrollo no debería ser accesible por los
        usuarios finales. Las herramientas de depuración y de log disponibles en este
        controlador frontal penalizan el rendimiento de la aplicación y proporcionan mu-
        cha información sobre las variables internas utilizadas por las acciones. Siempre
        debería eliminarse este controlador frontal en la aplicación pública.

      ▪ Los directorios cache/ y log/ del proyecto no deben borrarse cada vez que se re-
        aliza una sincronización. Estos directorios también deberían ignorarse. Si se dis-
        pone de un directorio llamado stats/, también debería ignorarse.

      ▪ Los archivos subidos por los usuarios tampoco deberían transferirse. Una de las
        buenas prácticas recomendadas por Symfony es la de guardar los archivos subi-
        dos por los usuarios en el directorio web/uploads/. De esta forma, se pueden ex-
        cluir todos estos archivos simplemente ignorando un directorio durante el traspa-
        so de la aplicación.

Para excluir los archivos en las sincronizaciones de rsync, se edita el archivo rsync_ex-
clude.txt que se encuentra en el directorio miproyecto/config/. Cada fila de ese archivo
debe contener el nombre de un archivo, el nombre de un directorio o un patrón con co-
modines *. La estructura de archivos de Symfony está organizada de forma lógica y


www.librosweb.es                                                                                  365
Symfony, la guía definitiva        Capítulo 16. Herramientas para la administración de aplicaciones


diseñada de forma que se minimice el número de archivos o directorios que se deben ex-
cluir manualmente de la sincronización. El listado 16-15 muestra un ejemplo.

Listado 16-15 - Ejemplo de exclusiones en una sincronización rsync, en mipro-
yecto/config/rsync_exclude.txt
   .svn
   /cache/*
   /log/*
   /stats/*
   /web/uploads/*
   /web/miaplicacion_dev.php


  NOTA
  Los directorios cache/ y log/ no deben sincronizarse con el servidor de producción, pero sí que
  deben existir en el servidor de producción. Si la estructura de directorios y archivos del proyecto
  miproyecto/ no los contiene, deben crearse manualmente.


16.4.4. Administrando una aplicación en producción
El comando más utilizado en los servidores de producción es clear-cache. Cada vez que
se actualiza Symfony o el proyecto, se debe ejecutar esta tarea (por ejemplo después de
ejecutar la tarea sync) y también cada vez que se modifica la configuración en
producción.
   > symfony clear-cache


  SUGERENCIA
  Si en el servidor de producción no está disponible la línea de comandos de Symfony, se puede bo-
  rrar la cache manualmente borrando todos los contenidos del directorio cache/.

También es posible deshabilitar temporalmente la aplicación, por ejemplo cuando se ne-
cesita actualizar una librería o cuando se tiene que actualizar una gran cantidad de datos.
   > symfony disable NOMBRE_APLICACION NOMBRE_ENTORNO

Por defecto, una aplicación deshabilitada muestra la acción default/unavailable, definida
en el propio framework. En el archivo settings.yml se puede definir el módulo y la acción
que se utiliza cuando una aplicación está deshabilitada. El listado 16-16 muestra un
ejemplo.

Listado 16-16 - Indicando la acción a ejecutar para una aplicación no disponi-
ble, en miaplicacion/config/settings.yml
   all:
     .settings:
        unavailable_module:       mimodulo
        unavailable_action:       maintenance

La tarea enable vuelve a habilitar la aplicación y borra su cache.
   > symfony enable NOMBRE_APLICACION NOMBRE_ENTORNO

  Mostrando una página de "no disponible" mientras se borra la cache


www.librosweb.es                                                                                366
Symfony, la guía definitiva         Capítulo 16. Herramientas para la administración de aplicaciones


  Si se establece el valor on a la opción check_lock en el archivo settings.yml, Symfony bloquea
  el acceso a la aplicación mientras se borra la cache, y todas las peticiones recibidas mientras se
  borra la cache se redirigen a una página que muestra que la aplicación está temporalmente no dis-
  ponible. Se trata de una opción muy recomendable cuando el tamaño de la cache es muy grande y
  el tiempo empleado para borrarla es mayor que unos milisegundos y el tráfico de usuarios de la
  aplicación es elevado.

  Esta página que indica que la aplicación no está disponible no es la misma que la que se muestra
  cuando se ejecuta el comando symfony disable (ya que mientras se borra la cache, Symfony no
  funciona correctamente ). Se trata de una página guardada en el directorio $sf_symfony_data_-
  dir/web/errors/, pero se puede crear un archivo unavailable.php propio en el directorio web/
  errors/, para que Symfony lo utilice en su lugar. La opción check_lock está deshabilitada por de-
  fecto porque tiene un pequeño impacto sobre el rendimiento de la aplicación.

  La tarea clear-controllers elimina todos los controladores frontales del directorio web/ que no
  sean los controladores frontales utilizados en el entorno de producción. Si no se incluyen los contro-
  ladores frontales de desarrollo en el archivo rsync_exclude.txt, este comando garantiza que no
  se sube al servidor una puerta trasera que revele información interna sobre la aplicación.
      symfony clear-controllers

  Los permisos de los archivos y directorios del proyecto pueden cambiarse si se realiza un checkout
  desde un repositorio de Subversion. La tarea fix-perms arregla los permisos de los directorios y
  cambia por ejemplo los permisos de log/ y cache/ a un valor de 0777 (estos directorios deben te-
  ner permiso de escritura para que el framework funcione correctamente).
      symfony fix-perms

  Accediendo a los comandos de Symfony en el servidor de producción

  Si el servidor de producción dispone de una instalación de Symfony realizada con PEAR, la línea
  de comandos de Symfony está disponible en todos los directorios y funciona igual que en el servi-
  dor de desarrollo. Sin embargo, en los proyectos congelados, es necesario añadir php antes del co-
  mando symfony para poder ejecutar las tareas:
      // Con Symfony instalado mediante PEAR
      > symfony [opciones] <TAREA> [parametros]

      // Con un proyecto Symfony congelado
      > php symfony [opciones] <TAREA> [parametros]


16.5. Resumen
Mediante los archivos de log de PHP y los de Symfony, es posible monitorizar y depurar
las aplicaciones fácilmente. Durante el desarrollo de la aplicación, el modo debug, las ex-
cepciones y la barra de depuración web ayudan a localizar la causa de los problemas. Pa-
ra facilitar la depuración de la aplicación, es posible incluso insertar mensajes propios en
el archivo de log y en la barra de depuración web.

La interfaz de línea de comandos dispone de muchas utilidades para facilitar la gestión y
administración de las aplicaciones durante las fases de desarrollo y de producción. Las
tareas para cargar de forma masiva datos en la base de datos, la congelación de los


www.librosweb.es                                                                                   367
Symfony, la guía definitiva   Capítulo 16. Herramientas para la administración de aplicaciones


proyectos y la sincronización de aplicaciones entre servidores, son tareas que ahorran
mucho tiempo.




www.librosweb.es                                                                         368
Symfony, la guía definitiva                                    Capítulo 17. Personalizar Symfony




Capítulo 17. Personalizar Symfony
Antes o después, algún proyecto deberá modificar el comportamiento de Symfony. Sea
una modificación del comportamiento de una clase o sea una nueva característica que
hay que añadir al framework, el momento en el que es necesario modificar Symfony lle-
gará de forma inevitable, ya que todos los clientes para los que se desarrollan aplicacio-
nes tienen requerimientos muy específicos que ningún framework puede predecir.

De hecho, como esta situación es tan común, Symfony dispone de un mecanismo llama-
do mixin para extender y modificar las clases existentes. Incluso es posible reemplazar
las clases del núcleo de Symfony por clases propias, utilizando las opciones de las fac-
torías utilizadas por Symfony (las factorías se basan en el patrón de diseño “factories”).
Una vez realizadas las modificaciones, se pueden encapsular en forma de plugin para po-
der reutilizarlas en otras aplicaciones o por parte de otros programadores de Symfony.


17.1. Mixins
Una de las limitaciones actuales de PHP más molestas es que una clase no puede heredar
de más de una clase. Además, tampoco se pueden añadir nuevos métodos a una clase ya
existente y no se pueden redefinir los métodos existentes. Para paliar estas dos limitacio-
nes y para hacer el framework realmente modificable, Symfony proporciona una clase
llamada sfMixer. Su nombre viene del concepto de mixin utilizado en la programación or-
ientada a objetos. Un mixin es un grupo de métodos o funciones que se juntan en una
clase para que otras clases hereden de ella.

17.1.1. Comprendiendo la herencia múltiple
La herencia múltiple es la cualidad por la que una clase hereda de varias clases a la vez,
heredando todas sus propiedades y métodos. A continuación se utiliza el ejemplo de una
clase llamada Story (historia, relato) y otra clase llamada Book (libro), cada una de las
cuales tiene sus propios métodos y propiedades, que se muestran en el listado 17-1.

Listado 17-1 - Dos clases de ejemplo
   class Story
   {
     protected $title = '';
     protected $topic = '';
     protected $characters = array();

      public function __construct($title = '', $topic = '', $characters = array())
      {
        $this->title = $title;
        $this->topic = $topic;
        $this->characters = $characters;
      }

      public function getSummary()
      {
        return $this->title.', a story about '.$this->topic;


www.librosweb.es                                                                           369
Symfony, la guía definitiva                              Capítulo 17. Personalizar Symfony

       }
   }

   class Book
   {
     protected $isbn = 0;

       function setISBN($isbn = 0)
       {
         $this->isbn = $isbn;
       }

       public function getISBN()
       {
         return $this->isbn;
       }
   }

Una clase llamada ShortStory (relato corto) hereda de Story, una clase ComputerBook (li-
bro sobre informática) hereda de Book, y como es lógico, una clase llamada Novel (nove-
la) debería heredar tanto de Story como de Book para aprovechar todos sus métodos.
Desafortunadamente, PHP no permite realizar esta herencia múltiple. No es posible crear
una declaración para la clase Novel como la que se muestra en el listado 17-2.

Listado 17-2 - PHP no permite la herencia múltiple
   class Novel extends Story, Book
   {
   }

   $myNovel = new Novel();
   $myNovel->getISBN();

Una posibilidad para este ejemplo es que la clase Novel implemente dos interfaces en vez
de heredar de dos clases, pero esta solución implica que las clases padre no pueden con-
tener código en los métodos que definen.

17.1.2. Clases de tipo mixing
La clase sfMixer intenta solucionar este problema desde otro punto de vista, permitiendo
heredar de una clase a posteriori , siempre que la clase esté diseñada de forma adecua-
da. El proceso completo está formado por 2 pasos:

       ▪ Declarar que una clase puede heredar de otras

       ▪ Registrar las herencias realizadas (o mixins), después de la declaración de la
         clase

El listado 17-3 muestra cómo implementar la clase Novel anterior mediante sfMixer.

Listado 17-3 - sfMixer permite la herencia múltiple
   class Novel extends Story
   {
     public function __call($method, $arguments)
     {

www.librosweb.es                                                                     370
Symfony, la guía definitiva                                         Capítulo 17. Personalizar Symfony

           return sfMixer::callMixins();
       }
   }

   sfMixer::register('Novel', array('Book', 'getISBN'));
   $miNovela = new Novel();
   $miNovela->getISBN();

Una de las clases que se quieren heredar (en este caso, Story) se utiliza como clase pa-
dre principal, de forma que la clase hereda de ella directamente mediante PHP. La clase
Novel se declara que es extensible mediante el código del método __call(). El método de
la otra clase de la que se quiere heredar (en este caso, Book) se añade posteriormente a
la clase Novel mediante la llamada a sfMixer::register(). Las próximas secciones deta-
llan el funcionamiento completo de este proceso.

Cuando se invoca el método getISBN() de la clase Novel, el funcionamiento es idéntico a
si la clase Novel hubiera heredado también de la otra clase (como en el listado 17-2),
salvo que en este caso, el funcionamiento se debe a la magia del método __call() y a
los métodos estáticos de la clase sfMixer. El método getISBN() se dice que ha sido mez-
clado (en inglés, “mixed”, y de ahí el nombre mixin) en la clase Novel.

  Cuando utilizar los mixins

  El mecanismo de mixin que utiliza Symfony es muy útil en muchas situaciones. La simulación de
  una herencia múltiple descrita anteriormente es sólo una de sus posibilidades.

  Los mixins también se pueden utilizar para modificar un método después de haber sido declarado.
  Si se crea por ejemplo una librería gráfica, es probable que se defina un objeto de tipo Line para
  representar una línea. Su clase dispone de 4 atributos (las coordenadas de sus 2 extremos) y un
  método llamado draw() para dibujar la propia línea. Un objeto llamado ColoredLine debería dis-
  poner de las mismas propiedades y argumentos, pero debería añadir un atributo adicional llamado
  color para especificar su color. Además, el método draw() de ColoredLine debería ser diferente
  al de Line, para tener en consideración el color de la línea. Por otra parte, todas las funciones rela-
  tivas al color se podrían encapsular en una clase llamada ColoredElement. De esta forma, se
  podrían reutilizar los métodos del color para otros elementos gráficos (Dot, Polygon, etc). En este
  caso, la forma ideal de implementar la clase ColoredLine sería como heredera de la clase Line y
  con una mezcla (mixin) de métodos de la clase ColoredElement. El método draw() final es una
  mezcla del método original de Line y del método de ColoredElement.

  Los mixins también se pueden utilizar para añadir nuevos métodos a clases ya existentes. La clase
  sfActions, por ejemplo, la utiliza Symfony para manejar las acciones y se define en el propio fra-
  mework. Una de las limitaciones de PHP es que no se puede modificar la definición de sfActions
  después de su declaración inicial. Si una aplicación tiene un requerimiento muy específico (por
  ejemplo, redirigir una petición a un servicio web especial), PHP no permitiría redefinir los métodos
  de sfActions, pero el mecanismo de mixins de Symfony es una solución ideal en este caso.


17.1.3. Declarar que una clase se puede extender
Para declarar que una clase puede ser extendida, se debe preparar su código de forma
que la clase sfMixer pueda identicarla como tal. Para preparar la clase, se añaden lo que

www.librosweb.es                                                                                    371
Symfony, la guía definitiva                                Capítulo 17. Personalizar Symfony


se denomina “hooks”, que en este caso consisten en llamadas al método sfMixer::-
callMixins(). Muchas de las clases propias de Symfony ya incluyen estos hooks, entre
otras, sfRequest, sfResponse, sfController, sfUser y sfAction.

En función del grado hasta el que se quiere hacer extensible a una clase, los hooks se co-
locan en diferentes partes de la clase.:

      ▪ Para permitir que se puedan añadir métodos a una clase, se inserta el hook en el
        método __call() y se devuelve su valor, como se muestra en el listado 17-4.

Listado 17-4 - Permitiendo que se puedan añadir nuevos métodos a una clase
   class unaClase
   {
     public function __call($method, $arguments)
     {
       return sfMixer::callMixins();
     }
   }

      ▪ Para permitir modificar la forma en la que funciona un método, se debe insertar
        el hook dentro de ese método, como muestra el listado 17-5. El código que aña-
        de la clase de tipo mixin se ejecuta en el mismo lugar en el que se encuentra el
        hook.

Listado 17-5 - Permitiendo que se pueda modificar un método
   class otraClase
   {
     public function unMetodo()
     {
       echo "Haciendo cosas...";
       sfMixer::callMixins();
     }
   }

En ocasiones es necesario insertar más de un hook en un método. En este caso, se debe
asignar un nombre a cada hook, de forma que posteriormente se pueda definir el hook
que se quiere utilizar, tal y como muestra el listado 17-6. Para crear un hook con nom-
bre, se utiliza el mismo método callMixins(), pero con un argumento que indica el nom-
bre del hook. Cuando se registra el mixin posteriormente, se utiliza este nombre para in-
dicar en que lugar del método se debe ejecutar el código del mixin.

Listado 17-6 - Si un método contiene más de un hook, se les debe asignar un
nombre
   class otraClase
   {
     public function otroMetodo()
     {
       echo "Empezando...";
       sfMixer::callMixins('comienzo');
       echo "Haciendo cosas...";
       sfMixer::callMixins('fin');
       echo "Finalizado";



www.librosweb.es                                                                       372
Symfony, la guía definitiva                                       Capítulo 17. Personalizar Symfony

       }
   }

Como muestra el listado 17-7, se pueden combinar todas estas técnicas para crear clases
con la habilidad de poder insertar y/o modificar sus métodos.

Listado 17-7 - Extendiendo una clase de diversas formas a la vez
           class BicycleRider
           {
             protected $name = 'John';

               public function getName()
               {
                 return $this->name;
               }

               public function sprint($distance)
               {
                 echo $this->name." sprints ".$distance." metersn";
                 sfMixer::callMixins(); // El método sprint() se puede extender
               }

               public function climb()
               {
                 echo $this->name.' climbs';
                 sfMixer::callMixins('slope'); // El método climb() se puede extender aquí...
                 echo $this->name.' gets to the top';
                 sfMixer::callMixins('top'); // ...y en este otro punto también
               }

               public function __call($method, $arguments)
               {
                 return sfMixer::callMixins(); // La clase BicyleRider se puede extender
               }
           }


  ATENCIÓN
  sfMixer solamente puede extender las clases que lo han declarado de forma explícita. Por tanto,
  no se puede utilizar este mecanismo para modificar una clase que no lo ha indicado en su código.
  En otras palabras, es como si las clases que quieren utilizar los servicios de sfMixer debieran sus-
  cribirse antes a esos servicios.


17.1.4. Registrando las extensiones
Para registrar una extensión en un hook previamente definido, se utiliza el método sfMi-
xer::register(). El primer argumento de este método es el elemento que se va a exten-
der y el segundo argumento es una función de PHP que representa el mixin que se va a
incluir.

El formato del primer argumento depende de lo que se quiere extender:

       ▪ Si se extiende una clase, se utiliza el nombre de la clase.



www.librosweb.es                                                                                 373
Symfony, la guía definitiva                                  Capítulo 17. Personalizar Symfony


       ▪ Si se extiende un método con un hook sin nombre, se utiliza el patrón
         clase:metodo.

       ▪ Si se extiende un método con un hook que dispone de nombre, se utiliza el
         patrón clase:metodo:hook.

El listado 17-8 ilustra estas normas extendiendo la clase que se definió en el listado 17-7.
El objeto que se extiende se pasa automáticamente como el primer argumento a los mé-
todos del mixin (salvo, evidentemente, si el método que se ha extendido es de tipo sta-
tic). El método del mixin también puede acceder a los parámetros de la llamada al mé-
todo original.

Listado 17-8 - Registrando extensiones
   class Steroids
   {
     protected $brand = 'foobar';

       public function partyAllNight($bicycleRider)
       {
         echo $bicycleRider->getName()." spends the night dancing.n";
         echo "Thanks ".$brand."!n";
       }

       public function breakRecord($bicycleRider, $distance)
       {
         echo "Nobody ever made ".$distance." meters that fast before!n";
       }

       static function pass()
       {
         echo " and passes half the peloton.n";
       }
   }

   sfMixer::register('BicycleRider', array('Steroids', 'partyAllNight'));
   sfMixer::register('BicycleRider:sprint', array('Steroids', 'breakRecord'));
   sfMixer::register('BicycleRider:climb:slope', array('Steroids', 'pass'));
   sfMixer::register('BicycleRider:climb:top', array('Steroids', 'pass'));

   $superRider = new BicycleRider();
   $superRider->climb();
   => John climbs and passes half the peloton
   => John gets to the top and passes half the peloton
   $superRider->sprint(2000);
   => John sprints 2000 meters
   => Nobody ever made 2000 meters that fast before!
   $superRider->partyAllNight();
   => John spends the night dancing.
   => Thanks foobar!

Le mecanismo de extensiones no solo permite añadir nuevos métodos. El método
partyAllNight() anterior utiliza un atributo de la clase Steroids. Por tanto, cuando se




www.librosweb.es                                                                         374
Symfony, la guía definitiva                                   Capítulo 17. Personalizar Symfony


extiende la clase BicycleRider con un método de la clase Steroids, en realidad se está
creando una nueva instancia de la clase Steroids dentro del objeto BicycleRider.

  ATENCIÓN
  No se pueden añadir 2 métodos con el mismo nombre a una clase ya existente. La razón es que la
  llamada a callMixins() en los métodos __call() utiliza el nombre del método del mixin como
  una clave. Además, no se puede añadir un método a una clase que ya dispone de un método con
  el mismo nombre, ya que el mecanismo de mixin depende del método mágico __call(), por lo que
  en este caso, nunca se llamaría a este segundo método.

El segundo argumento de la llamada al método register() debe ser cualquier elemento
PHP que se pueda invocar, por lo que puede ser un array de clase::metodo, un array de
objeto->metodo o incluso el nombre de una función. El listado 17-9 muestra algunos
ejemplos:

Listado 17-9 - Cualquier código PHP que se pueda invocar puede ser utilizado
para registrar una extensión
   // Registrnado el método de una clase
   sfMixer::register('BicycleRider', array('Steroids', 'partyAllNight'));

   // Registrando el método de un objeto
   $mySteroids = new Steroids();
   sfMixer::register('BicycleRider', array($mySteroids, 'partyAllNight'));

   // Registrando una función
   sfMixer::register('BicycleRider', 'die');

El mecanismo de extensión es dinámico, lo que significa que si ya se ha instanciado un
objeto, puede tener acceso a las extensiones realizadas en su clase. El listado 17-10
muestra un ejemplo.

Listado 17-10 - El mecanismo de extensión es dinámico y se puede utilizar in-
cluso después de instanciar los objetos
   $simpleRider = new BicycleRider();
   $simpleRider->sprint(500);
   => John sprints 500 meters
   sfMixer::register('BicycleRider:sprint', array('Steroids', 'breakRecord'));
   $simpleRider->sprint(500);
   => John sprints 500 meters
   => Nobody ever made 500 meters that fast before!


17.1.5. Extendiendo de forma más precisa
La instrucción sfMixer::callMixins() en realidad es un atajo de algo mucho más com-
plejo. Esta instrucción recorre todos los elementos de la lista de mixins que se han regis-
trado y se van ejecutando uno a uno, pasandoles el objeto actual y los parámetros del
método que se está ejecutando. En otras palabras, una llamada a la función sfMixer::-
callMixins() se comporta más o menos como el código del listado 17-11.

Listado 17-11 - callMixin() recorre todos los mixins registrados y los ejecuta


www.librosweb.es                                                                           375
Symfony, la guía definitiva                                       Capítulo 17. Personalizar Symfony

   foreach (sfMixer::getCallables($class.':'.$method.':'.$hookName) as $callable)
   {
     call_user_func_array($callable, $parameters);
   }

Si se necesitan pasar otros parámetros o se quiere procesar de forma especial el valor
devuelto, se puede recorrer la lista de mixins de forma explícita en lugar de utilizar sfMi-
xer::callMixins(). El listado 17-12 muestra un ejemplo de un mixin más integrado en la
propia clase.

Listado 17-12 - Reemplazando callMixin() por un código propio
   class Income
   {
     protected $amount = 0;

       public function calculateTaxes($rate = 0)
       {
         $taxes = $this->amount * $rate;
         foreach (sfMixer::getCallables('Income:calculateTaxes') as $callable)
         {
           $taxes += call_user_func($callable, $this->amount, $rate);
         }

           return $taxes;
       }
   }

   class FixedTax
   {
     protected $minIncome = 10000;
     protected $taxAmount = 500;

       public function calculateTaxes($amount)
       {
         return ($amount > $this->minIncome) ? $this->taxAmount : 0;
       }
   }

   sfMixer::register('Income:calculateTaxes', array('FixedTax', 'calculateTaxes'));

  Behaviors de Propel

  Los behaviors de Propel, que se vieron en el capítulo 8, son un tipo especial de mixin, ya que ext-
  ienden los objetos generados por Propel. A continuación se muestra un ejemplo.

  Los objetos Propel correspondientes a las tablas de la base de datos disponen de un método llama-
  do delete(), que borra el registro correspondiente de la base de datos. Sin embargo, si se dispone
  de una clase llamada Factura, para la que no se deben borrar las filas de datos, se podría modifi-
  car el método delete() para mantener el registro en la base de datos y modificar el valor de una
  columna llamada is_deleted que indica si el registro ha sido borrado. Los métodos que se utilizan
  para obtener los registros (doSelect(), retrieveByPk()) solamente deberían tener en cuenta las
  filas de datos para las que la columna is_deleted vale false. Además, se debería añadir otro mé-
  todo llamado forceDelete(), que es el que borraría realmente de la base de datos. Todas las


www.librosweb.es                                                                                376
Symfony, la guía definitiva                                        Capítulo 17. Personalizar Symfony


  modificaciones anteriores se podrían encapsular en una nueva clase llamada por ejemplo Paran-
  oidBehavior. La clase Factura completa extiende la clase BaseFactura de Propel y tiene los mé-
  todos de ParanoidBehavior mediante un mixin.

  De esta forma, un behavior en realidad es un mixin realizado sobre un objeto Propel. Además, la
  palabra “behavior” en Symfony implica otra característica: el hecho de que el mixin se encapsula en
  forma de plugin. La clase ParanoidBehavior mencionada anteriormente se corresponde con un
  plugin real de Symfony, llamado sfPropelParanoidBehaviorPlugin. El wiki de Symfony
  (http://trac.symfony-project.com/wiki/sfPropelParanoidBehaviorPlugin) dispone de más deta-
  lles sobre la instalación y uso de este plugin.

  Un último comentario sobre los behaviors: para poder utilizarlos, los objetos generados por Propel
  deben contener un gran número de hooks. Como esto puede penalizar el rendimiento de la aplica-
  ción si no se utilizan los behaviors, por defecto los hooks no están activados. Para añadir los hooks
  y activar el soporte de los behaviors, se debe establecer la propiedad propel.builder.addBehav-
  iors a true en el archivo propel.ini y se debe volver a construir el modelo.


17.2. Factorías
Una factoría consiste en la definición de una clase que realiza una determinada tarea.
Symfony utiliza las factorias en su funcionamiento interno, como por ejemplo para los
controladores y para las sesiones. Cuando el framework necesita por ejemplo crear un
nuevo objeto para una petición, busca en la definición de la factoría el nombre de la clase
que se debe utilizar para esta tarea. Como la definición por defecto de la factoría para las
peticiones es sfWebRequest, Symfony crea un objeto de esta clase para tratar con las pe-
ticiones. La principal ventaja de utilizar las definiciones de las factorías es que es muy
sencillo modificar las características internas de Symfony: simplemente es necesario mo-
dificar la definición de la factoría y Symfony utiliza la clase propia indicada en vez de la
clase por defecto.

Las definiciones para las factorías se guardan en el archivo de configuración factor-
ies.yml. El listado 17-13 muestra el contenido por defecto de ese archivo. Cada defini-
ción consta del nombre de una clase y opcionalmente, de una serie de parámetros. Por
ejemplo, la factoría para el almacenamiento de la sesión (que se indica bajo la clave sto-
rage:) utiliza un parámetro llamado session_name para establecer el nombre de la cookie
que se crea para el lado del cliente, de forma que se puedan realizar sesiones
persistentes.

Listado 17-13 - Archivo por defecto para las factorias, en miaplicacion/config/
factories.yml
   cli:
     controller:
        class: sfConsoleController
     request:
        class: sfConsoleRequest

   test:
     storage:
       class: sfSessionTestStorage

www.librosweb.es                                                                                  377
Symfony, la guía definitiva                                    Capítulo 17. Personalizar Symfony



   #all:
   # controller:
   #     class: sfFrontWebController
   #
   # request:
   #     class: sfWebRequest
   #
   # response:
   #     class: sfWebResponse
   #
   # user:
   #     class: myUser
   #
   # storage:
   #     class: sfSessionStorage
   #     param:
   #       session_name: symfony
   #
   # view_cache:
   #     class: sfFileCache
   #     param:
   #       automaticCleaningFactor: 0
   #       cacheDir:                 %SF_TEMPLATE_CACHE_DIR%

La mejor forma de crear una nueva factoría consiste en crear una nueva clase que here-
de de la clase por defecto y añadirle nuevos métodos. La factoría para las sesiones de
usuario se establece a la clase myUser (localizada en miaplicacion/lib) y hereda de la
clase sfUser. Se puede utilizar el mismo mecanismo para aprovechar las factorías ya
existentes. El listado 17-14 muestra el ejemplo de una factoría para el objeto de la
petición.

Listado 17-14 - Redefiniendo factorías
   // Se crea la clase miRequest.class.php en un directorio para
   // el que esté activada la carga automática de clases, por ejemplo
   // miaplicacion/lib/
   <?php

   class miRequest extends sfRequest
   {
     // El código de la nueva factoría
   }
   # Se declara en el archivo factories.yml que esta nueva
   # clase es la factoría para las peticiones
   all:
     request:
        class: miRequest


17.3. Utilizando componentes de otros frameworks
Si se requiere utilizar una clase externa y no se copia esa clase en algún directorio lib/
de Symfony, la clase se encontrará en algún directorio en el que Symfony no la puede
encontrar. En este caso, si se utiliza esta clase en el código, es necesario incluir


www.librosweb.es                                                                           378
Symfony, la guía definitiva                                 Capítulo 17. Personalizar Symfony


manualmente una instrucción require, a menos que se utilicen las propiedades de Sym-
fony para enlazar y permitir la carga automática de otros componentes externos.

Symfony de momento no proporciona utilidades y herramientas para resolver cualquier
tipo de problema. Si se necesita un generador de archivos PDF, una API para interactuar
con los mapas de Google o una implementación en PHP del motor de búsqueda Lucene,
es    necesario   hacer    uso    de    algunas  librerías del    framework   de   Zend
(http://framework.zend.com/). Si se quieren manipular imágenes directamente con PHP, co-
nectarse con una cuenta POP3 para obtener los emails o diseñar una interfaz para la con-
sola de comandos, seguramente se utilizarán los eZcomponents (http://ez.no/ezcompo-
nents). Afortunadamente, si se utilizan las opciones correctas, se pueden utilizar directa-
mente en Symfony todos los componentes de estas librerías externas.

Lo primero que hay que hacer es declarar la ruta al directorio raíz de cada librería, a me-
nos que se hayan instalado mediante PEAR. Esta configuración se realiza en el archivo
settings.yml.
   .settings:
     zend_lib_dir:     /usr/local/zend/library/
     ez_lib_dir:       /usr/local/ezcomponents/

A continuación, se configura la carga automática de clases para especificar las librerías
que se deben utilizar cuando la carga automática falla en Symfony:
   .settings:
     autoloading_functions:
       - [sfZendFrameworkBridge, autoload]
       - [sfEzComponentsBridge, autoload]

Esta opción es diferente a las reglas definidas en el archivo autoload.yml (el capítulo 19
contiene más información sobre este archivo). La opción autoloading_functions especifi-
ca las clases utilizadas como enlace o puente, mientras que el archivo autoload.yml es-
pecifica las rutas y reglas utilizadas para buscar las clases. A continuación se describe lo
que sucede cuando se crea un nuevo objeto de una clase que no ha sido cargada:

    1. La función de Symfony encargada de la carga automática de clases
       (sfCore::splAutoload()) busca la clase en las rutas especificadas en el archivo
        autoload.yml.

    2. Si no se encuentra ninguna clase, se invocan uno a uno los métodos declarados
       en la opción sf_autoloading_functions hasta que uno de ellos devuelva el valor
        true.

    3. sfZendFrameworkBridge::autoload()

    4. sfEzComponentsBridge::autoload()

    5. Si todos los métodos anteriores devuelven false, si se utiliza una versión de PHP
        5.0.X Symfony lanza una excepción indicando que la clase no existe. Si se utiliza
        una versión de PHP 5.1 o superior, el propio PHP genera el error.

De esta forma, los componentes de otros frameworks pueden aprovecharse también del
mecanismo de carga automática de clases, por lo que es incluso más sencillo que

www.librosweb.es                                                                        379
Symfony, la guía definitiva                                  Capítulo 17. Personalizar Symfony


utilizarlos dentro de los frameworks originales. El siguiente código muestra por ejemplo
cómo utilizar el componente Zend_Search (que implementa el motor de búsqueda Lucene
en PHP) desde el propio framework Zend:
   require_once 'Zend/Search/Lucene.php';
   $doc = new Zend_Search_Lucene_Document();
   $doc->addField(Zend_Search_Lucene_Field::Text('url', $docUrl));
   ...

Utilizando Symfony y el enlace con el framework Zend, es mucho más fácil utilizar este
componente:
   $doc = new Zend_Search_Lucene_Document(); // The class is autoloaded
   $doc->addField(Zend_Search_Lucene_Field::Text('url', $docUrl));
   ...

Los enlaces o puentes disponibles se guardan en el directorio $sf_symfony_lib_dir/ad-
don/bridge/.


17.4. Plugins
En ocasiones, es necesario reutilizar una porción de código desarrollada para alguna apli-
cación Symfony. Si se puede encapsular ese código en una clase, tan sólo es necesario
guardar la clase en algún directorio lib/ para que otras aplicaciones puedan encontrarla.
Sin embargo, si el código se encuentra desperdigado en varios archivos, como por ejem-
plo un tema para el generador de administraciones o una serie de archivos JavaScript y
helpers que permiten utilizar fácilmente un efecto visual complejo, es muy complicado
copiar todo este código en una clase.

Los plugins permiten agrupar todo el código diseminado por diferentes archivos y reutili-
zar este código en otros proyectos. Los plugins permiten encapsular clases, filtros, mi-
xins, helpers, archivos de configuración, tareas, módulos, esquemas y extensiones para
el modelo, fixtures, archivos estáticos, etc. Los plugins son fáciles de instalar, de actuali-
zar y de desinstalar. Se pueden distribuir en forma de archivo comprimido .tgz, un paq-
uete PEAR o directamente desde el repositorio de código. La ventaja de los paquetes
PEAR es que pueden controlar las dependencias, lo que simplifica su actualización. La for-
ma en la que Symfony carga los plugins permite que los proyectos puedan utilizarlos co-
mo si fueran parte del propio framework.

Básicamente, un plugin es una extensión encapsulada para un proyecto Symfony. Los
plugins permiten no solamente reutilizar código propio, sino que permiten aprovechar los
desarrollos realizados por otros programadores y permiten añadir al núcleo de Symfony
extensiones realizadas por otros desarrolladores.

17.4.1. Plugins disponibles para Symfony
El sitio web del proyecto Symfony dispone de una página dedicada a los plugins de Sym-
fony. La página se encuentra dentro del wiki de Symfony, en la dirección:
http://trac.symfony-project.com/wiki/SymfonyPlugins

Cada plugin que se muestra en ese listado, cuenta con su propia página con instrucciones
para su instalación y toda la documentación necesaria.

www.librosweb.es                                                                          380
Symfony, la guía definitiva                                Capítulo 17. Personalizar Symfony


Algunos plugins están desarrollados por voluntarios de la comunidad Symfony y otros
han sido desarrollados por los mismos creadores de Symfony. Entre estos últimos se enc-
uentran los siguientes:

      ▪ sfFeedPlugin: automatiza la manipulación de los canales RSS y Atom.

      ▪ sfThumbnailPlugin: crea imágenes en miniatura, por ejemplo para las imágenes
        subidas por los usuarios.

      ▪ sfMediaLibraryPlugin: permite gestionar la subida de archivos multimedia, inclu-
        yendo una extensión para los editores avanzados de texto que permite incluir las
        imágenes denro de los textos creados.

      ▪ sfShoppingCartPlugin: permite gestionar un carrito de la compra.

      ▪ sfPagerNavigationPlugin: dispone de controles para paginar elementos de forma
        clásica y mediante Ajax, basados en el objeto sfPager.

      ▪ sfGuardPlugin: permite incluir autenticación, autorización y otras opciones de
        gestión de usuarios más avanzadas que las que proporciona por defecto
        Symfony.

      ▪ sfPrototypePlugin: permite incluir los archivos de prototype y script.aculo.us co-
        mo librerías de JavaScript independientes.

      ▪ sfSuperCachePlugin: crea versiones cacheadas de las páginas web en el director-
        io de la cache bajo el directorio web raíz del proyecto, de forma que el servidor
        web pueda servirlas lo más rápidamente posible.

      ▪ sfOptimizerPlugin: optimiza el código fuente de la aplicación para que se ejecute
        más rápidamente en el entorno de producción (el próximo capítulo muestra los
        detalles).

      ▪ sfErrorLoggerPlugin: guarda un registro de todos los errores de tipo 404 y 500 en
        una base de datos e incluye un módulo de administración para gestionar estos
        errores.

      ▪ sfSslRequirementPlugin: proporciona soporte para la encriptación SSL en las
        acciones.

El wiki también contiene otros plugins utilizados para extender los objetos Propel, que
también se suelen llamar behaviors. Entre otros, están disponibles los siguientes:

      ▪ sfPropelParanoidBehaviorPlugin: deshabilita el borrado de los objetos y lo reem-
        plaza por la actualización de una columna llamada deleted_at.

      ▪ sfPropelOptimisticLockBehaviorPlugin: implementa la estrategia optimistic loc-
        king para los objetos Propel.

Se recomienda visitar de forma habitual el wiki de Symfony, ya que se añaden plugins
constantemente y normalmente proporcionan utilidades muy empleadas en el desarrollo
de aplicaciones web.




www.librosweb.es                                                                       381
Symfony, la guía definitiva                                 Capítulo 17. Personalizar Symfony


Además del wiki de Symfony, también se pueden distribuir los plugins en forma de archi-
vo para bajar, se puede crear un canal PEAR o se pueden almacenar en un repositorio
público.

17.4.2. Instalando un plugin
El proceso de instalación de los plugins varía en función del método utilizado para distrib-
uirlo. Siempre es recomendable leer el archivo README incluido en el plugin o las instrucc-
iones de instalación disponibles en la página de descarga del plugin. Además, siempre se
debe borrar la cache de Symfony después de la instalación de un plugin.

Los plugins se instalan en cada proyecto. Todos los métodos descritos en las siguientes
secciones resultan en la copia de los archivos de cada plugin en el directorio miproyecto/
plugins/[NOMBRE PLUGIN]/.


17.4.2.1. Plugins PEAR
Los plugins listados en el wiki de Symfony se distribuyen en forma de paquete PEAR aso-
ciados con una página del wiki. Para instalar un plugin de este tipo, se utiliza la tarea
plugin-install con la URL completa del plugin, tal y como muestra el listado 17-15.

Listado 17-15 - Instalando un plugin del wiki de Symfony
   > cd miproyecto
   > php symfony plugin-install http://plugins.symfony-project.com/nombrePlugin
   > php symfony cc

También es posible descargar los archivos del plugin e instalarlo desde un directorio del
sistema. En este caso, se reemplaza la URL del plugin por la ruta absoluta hasta el archi-
vo del paquete descargado, como se muestra en el listado 17-16.

Listado 17-16 - Instalando un plugin mediante un paquete PEAR descargado
   > cd miproyecto
   > php symfony plugin-install /ruta/hasta/el/archivo/descargado/nombrePlugin.tgz
   > php symfony cc

Algunos plugins disponen de su propio canal PEAR. En este caso, se pueden instalar med-
iante la tarea plugin-install y el nombre del canal, como se muestra en el listado
17-17.

Listado 17-17 - Instalando un plugin desde un canal PEAR
   > cd miproyecto
   > php symfony plugin-install nombreCanal/nombrePlugin
   > php symfony cc

Estos tres tipos de instalaciones utilizan paquetes PEAR, por lo que se utiliza el término
común “Plugins PEAR” para referirse a los plugins del wiki, de canales PEAR o de un paq-
uete PEAR descargado.

17.4.2.2. Plugins de archivo
Algunos plugins se distribuyen en forma de un archivo o un conjunto de archivos. Para
instalarlos, simplemente se descomprimen los archivos en el directorio plugins/ del

www.librosweb.es                                                                        382
Symfony, la guía definitiva                                       Capítulo 17. Personalizar Symfony


proyecto. Si el plugin contiene un subdirectorio llamado web/, se copia o se realiza un en-
lace simbólico a este directorio desde el directorio web/ del proyecto, como se muestra en
el listado 17-18. Por último, siempre se debe borrar la cache después de instalar el
plugin.

Listado 17-18 - Instalando un plugin desde un archivo
   >   cd plugins
   >   tar -zxpf miPlugin.tgz
   >   cd ..
   >   ln -sf plugins/miPlugin/web web/miPlugin
   >   php symfony cc


17.4.2.3. Instalando plugins desde un repositorio de código
En ocasiones, los plugins disponen de su propio repositorio de código para el versionado
de su código fuente. Estos plugins se pueden instalar simplemente descargando el código
desde el repositorio hasta el directorio plugins/, pero este método puede ser problemáti-
co si el propio proyecto también utiliza el versionado de su código fuente.

Un método alternativo consiste en declarar el plugin como una dependencia externa, de
forma que cada vez que se actualice el código fuente del proyecto, también se actualice
el código fuente del plugin. Los repositorios de tipo Subversion, guardan las dependenc-
ias externas en la propiedad svn:externals. Como se muestra en el listado 17-19, se
puede añadir un plugin simplemente editando esta propiedad y actualizando posterior-
mente el código fuente del proyecto.

Listado 17-19 - Instalando un plugin desde un repositorio de código
   > cd miproyecto
   > svn propedit svn:externals plugins
     nombrePlugin   http://svn.ejemplo.com/nombrePlugin/trunk
   > svn up
   > php symfony cc


  NOTA
  Si el plugin contiene un directorio llamado web/, se debe crear un enlace simbólico de la misma for-
  ma que la explicada para los plugins de archivos.

17.4.2.4. Activando un módulo de plugin
Algunos plugins contienen módulos enteros. La única diferencia entre los módulos de plu-
gins y los módulos normales es que los de los plugins no se guardan en el directorio mi-
proyecto/apps/miaplicacion/modules/ (para facilitar su actualización). Además, se deben
activar en el archivo settings.yml, como se muestra en el listado 17-20.

Listado 17-20 - Activando un módulo de plugin, en miaplicacion/config/
settings.yml
         all:
           .settings:
              enabled_modules:   [default, sfMiPluginModule]



www.librosweb.es                                                                                 383
Symfony, la guía definitiva                                    Capítulo 17. Personalizar Symfony


Este funcionamiento se ha establecido para evitar las situaciones en las que los módulos
de un plugin se puedan habilitar de forma errónea para una aplicación que no los requie-
re, lo que podría provocar un agujero de seguridad. Si un plugin dispone de dos módulos
llamados frontend y backend, se debería habilitar el módulo frontend solamente para la
aplicación frontend y el módulo backend en la aplicación backend. Este es el motivo por el
que los módulos de los plugins no se activan automáticamente.

  SUGERENCIA
  El módulo default es el único módulo activado por defecto. Realmente no es un módulo de plugin,
  ya que es del propio framework (se guarda en el directorio $sf_symfony_data_dir/modules/def-
  ault/). Este módulo se encarga de mostrar las páginas de bienvenida, las páginas de error 404 y
  las de los errores de seguridad por no haber proporcionado las credenciales adecuadas. Si no se
  quieren utilizar las páginas por defecto de Symfony, se puede eliminar este módulo de la opción
  enabled_modules.


17.4.2.5. Listando los plugins instalados
Accediendo al directorio plugins/ del proyecto, se pueden observar los plugins instala-
dos, pero la tarea plugin-list proporciona más información: el número de versión y el
nombre del canal para cada plugin instalado (ver el listado 17-21).

Listado 17-21 - Listando los plugins instalados
   > cd miproyecto
   > php symfony plugin-list

   Installed plugins:
   sfPrototypePlugin                 1.0.0-stable # pear.symfony-project.com (symfony)
   sfSuperCachePlugin                1.0.0-stable # pear.symfony-project.com (symfony)
   sfThumbnail                       1.1.0-stable # pear.symfony-project.com (symfony)


17.4.2.6. Actualizando y desinstalando plugins
Los plugins PEAR se pueden desinstalar ejecutando la tarea plugin-uninstall desde el
directorio raíz del proyecto, como muestra el listado 17-22. Para desinstalar el plugin, se
debe indicar también el nombre del canal desde el que se instaló (se puede obtener el
nombre del canal mediante la tarea plugin-list).

Listado 17-22 - Desinstalando un plugin
   > cd miproyecto
   > php symfony plugin-uninstall pear.symfony-project.com/sfPrototypePlugin
   > php symfony cc


  SUGERENCIA
  Algunos canales disponen de un alias para su nombre. El canal pear.symfony-project.com por
  ejemplo, también se puede llamar symfony, por lo que se puede desinstalar el plugin sfPrototy-
  pePlugin del listado 17-22, simplemente ejecutando php symfony plugin-uninstall symfony/
  sfPrototypePlugin.



www.librosweb.es                                                                            384
Symfony, la guía definitiva                                   Capítulo 17. Personalizar Symfony


Para desinstalar un plugin de archivo o un plugin instalado desde un repositorio, se bo-
rran manualmente los archivos del plugin que se encuentran en los directorios plugins/ y
web/ y se borra la cache.

Para actualizar un plugin, se puede utilizar la tarea plugin-upgrade (para los plugins
PEAR) o se puede ejecutar directamente svn update (si el plugin se ha instalado desde un
repositorio de código). Los plugins de archivo no se pueden actualizar de una forma tan
sencilla.

17.4.3. Estructura de un plugin
Los plugins se crean mediante el lenguaje PHP. Si se entiende la forma en la que se es-
tructura una aplicación, es posible comprender la estructura de un plugin.

17.4.3.1. Estructura de archivos de un plugin
El directorio de un plugin se organiza de forma muy similar al directorio de un proyecto.
Los archivos de un plugin se deben organizar de forma adecuada para que Symfony pue-
da cargarlos automáticamente cuando sea necesario. El listado 17-23 muestra la estruc-
tura de archivos de un plugin.

Listado 17-23 - Estructura de archivos de un plugin
   nombrePlugin/
     config/
       *schema.yml         //   Esquema de datos
       *schema.xml
       config.php          //   Configuración específica del plugin
     data/
       generator/
          sfPropelAdmin
            */             //   Temas para el generador de administraciones
               template/
               skeleton/
       fixtures/
          *.yml            //   Archivos de fixtures
       tasks/
          *.php            //   Tareas de Pake
     lib/
       *.php               //   Clases
       helper/
          *.php            //   Helpers
       model/
          *.php            //   Clases del modelo
     modules/
       */                  //   Módulos
          actions/
            actions.class.php
          config/
            module.yml
            view.yml
            security.yml
          templates/
            *.php

www.librosweb.es                                                                          385
Symfony, la guía definitiva                                 Capítulo 17. Personalizar Symfony

             validate/
               *.yml
      web/
        *                     // Archivos estáticos


17.4.3.2. Posibilidades de los plugins
Los plugins pueden contener numerosos elementos. Su contenido se tiene en considera-
ción durante la ejecución de la aplicación y cuando se ejecutan tareas mediante la línea
de comandos. Sin embargo, para que los plugins funcionen correctamente, es necesario
seguir una serie de convenciones:

      ▪ Los esquemas de bases de datos los detectan las tareas propel-. Cuando se eje-
        cuta la tarea propel-build-model para el proyecto, se reconstruye el modelo del
        proyecto y los modelos de todos los plugins que dispongan de un modelo. Los es-
        quemas de los plugins siempre deben contener un atributo package que siga la
        notación plugins.nombrePlugin. lib.model, como se muestra en el listado 17-24.

Listado 17-24 - Ejemplo de declaración de un esquema de un plugin, en miPlu-
gin/config/schema.yml
        propel:
          _attributes:    { package: plugins.miPlugin.lib.model }
          mi_plugin_foobar:
            _attributes:    { phpName: miPluginFoobar }
              id:
              name:           { type: varchar, size: 255, index: unique }
              ...

      ▪ La configuración del plugin se incluye en el script de inicio del plugin
        (config.php). Este archivo se ejecuta después de las configuraciones de la aplica-
        ción y del proyecto, por lo que Symfony ya se ha iniciado cuando se procesa esta
        configuración. Se puede utilizar este archivo por ejemplo para añadir nuevos di-
        rectorios a la ruta de directorios incluidos por PHP o para extender las clases
        existentes con mixins.

      ▪ Los archivos de datos o fixtures del directorio data/fixtures/ del plugin se proce-
        san mediante la tarea propel-load-data.

      ▪ Las tareas del plugin están disponibles en la línea de comandos de Symfony tan
        pronto como se instala el plugin. Una buena práctica consiste en prefijar el nom-
        bre de la tarea con una palabra significativa, por ejemplo el nombre del plugin. Si
        se teclea simplemente symfony en la línea de comandos, se puede ver la lista
        completa de tareas disponibles, incluyendo las tareas proporcionadas por todos
        los plugins instalados.

      ▪ Las clases propias se cargan automáticamente de la misma forma que las clases
        que se guardan en las carpetas lib/ del proyecto.

      ▪ Cuando se realiza una llamada a use_helper() en las plantillas, se cargan au-
        tomáticamente los helpers de los plugins. Estos helpers deben encontrarse en un
        subdirectorio llamado helper/ dentro de cualquier directorio lib/ del plugin.


www.librosweb.es                                                                        386
Symfony, la guía definitiva                                 Capítulo 17. Personalizar Symfony


      ▪ Las clases del modelo en el directorio miplugin/lib/model/ se utilizan para espec-
        ializar las clases del modelo generadas por Propel (en miplugin/lib/model/om/ y
        miplugin/lib/model/map/). Todas estas clases también se cargan automática-
        mente. Las clases del modelo generado para un plugin no se pueden redefinir en
        los directorios del proyecto.

      ▪ Los módulos proporcionan nuevas acciones, siempre que se declaren en la opción
        enabled_modules de la aplicación.

      ▪ Los archivos estáticos (imágenes, scripts, hojas de estilos, etc.) se sirven como el
        resto de archivos estáticos del proyecto. Cuando se instala un plugin mediante la
        línea de comandos, Symfony crea un enlace simbólico al directorio web/ del pro-
        yecto si el sistema operativo lo permite, o copia el contenido del directorio web/
        del módulo en el directorio web/ del proyecto. Si el plugin se instala mediante un
        archivo o mediante un repositorio de código, se debe copiar manualmente el di-
        rectorio web/ del plugin (como debería indicar el archivo README incluido en el
        plugin).

17.4.3.3. Configuración manual de plugins
Algunas tareas no las puede realizar automáticamente el comando plugin-install, por lo
que se deben realizar manualmente durante la instalación del plugin:

      ▪ El código de los plugins puede hacer uso de una configuración propia (por ejem-
        plo mediante sfConfig::get(’app_miplugin_opcion’)), pero no se pueden indicar
        los valores por defecto en un archivo de configuración app.yml dentro del directo-
        rio config/ del plugin. Para trabajar con valores por defecto, se utilizan los se-
        gundos argumentos opcionales en las llamadas a los métodos sfConfig::get().
        Las opciones de configuración se pueden redefinir en el nivel de la aplicación (el
        listado 17-25 muestra un ejemplo).

      ▪ Las reglas de enrutamiento propias se deben añadir manualmente en el archivo
        routing.yml.

      ▪ Los filtros propios también se deben añadir manualmente al archivo filters.yml
        de la aplicación.

      ▪ Las factorías propias se deben añadir manualmente al archivo factories.yml de
        la aplicación.

En general, todas las configuraciones que deben realizarse sobre los archivos de configu-
ración de las aplicaciones, se tienen que añadir manualmente. Los plugins que requieran
esta instalación manual, deberían indicarlo en el archivo README incluido.


17.4.3.4. Personalizando un plugin para una aplicación
Cuando se necesita personalizar el funcionamiento de un plugin, nunca se debería modifi-
car el código del directorio plugins/. Si se realizan cambios en ese directorio, se per-
derían todos los cambios al actualizar el plugin. Los plugins disponen de opciones y la


www.librosweb.es                                                                        387
Symfony, la guía definitiva                                Capítulo 17. Personalizar Symfony


posibilidad de redefinir su funcionamiento, de forma que se puedan personalizar sus
características.

Los plugins que están bien diseñados disponen de opciones que se pueden modificar en
el archivo app.yml de la aplicación, tal y como se muestra en el listado 17-25.

Listado 17-25 - Personalizando un plugin que utiliza la configuración de la
aplicación
   // Ejemplo de código del plugin
   $foo = sfConfig::get('app_mi_plugin_opcion', 'valor');
   # Modificar el valor por defecto de 'opcion' en el archivo
   # app.yml de la aplicación
   all:
     mi_plugin:
        opcion:      otrovalor

Las opciones del módulo y sus valores por defecto normalmente se describen en el archi-
vo README del plugin.

Se pueden modificar los contenidos por defecto de un módulo del plugin creando un mó-
dulo con el mismo nombre en la aplicación. Realmente no se redefine el comportamiento
del módulo original, sino que se sustituye, ya que se utilizan los elementos del módulo de
la aplicación y no los del plugin. Funciona correctamente si se crean plantillas y archivos
de configuración con el mismo nombre que los del plugin.

Por otra parte, si un plugin quiere ofrecer un módulo cuyo comportamiento se pueda re-
definir, el archivo actions.class.php del módulo del plugin debe estar vacío y heredar de
una clase que se cargue automáticamente, de forma que esta clase pueda ser heredada
también por el actions.class.php del módulo de la aplicación. El listado 17-26 muestra
un ejemplo.

Listado 17-26 - Personalizando la acción de un plugin
   // En miPlugin/modules/mimodulo/lib/miPluginmimoduloActions.class.php
   class miPluginmimoduloActions extends sfActions
   {
     public function executeIndex()
     {
       // Instrucciones y código
     }
   }

   // En miPlugin/modules/mimodulo/actions/actions.class.php

   require_once dirname(__FILE__).'/../lib/miPluginmimoduloActions.class.php';

   class mimoduloActions extends miPluginmimoduloActions
   {
     // Vacío
   }

   // En miaplicacion/modules/mimodulo/actions/actions.class.php
   class mimoduloActions extends miPluginmimoduloActions
   {


www.librosweb.es                                                                       388
Symfony, la guía definitiva                                 Capítulo 17. Personalizar Symfony

       public function executeIndex()
       {
         // Aquí se redefine el código del plugin
       }
   }


17.4.4. Cómo crear un plugin
Solamente los plugins creados como paquetes PEAR se pueden instalar mediante la tarea
plugin-install. Este tipo de plugins se pueden distribuir mediante el wiki de Symfony,
mediante un canal PEAR o mediante la descarga de un archivo. Por tanto, si que quiere
crear un plugin, es mejor publicarlo como paquete PEAR en vez de como archivo normal
y corriente. Además, los plugins instalados mediante paquetes PEAR son más fáciles de
actualizar, pueden declarar las dependencias que tienen y copian automáticamente los
archivos estáticos en el directorio web/.


17.4.4.1. Organización de archivos
Si se ha creado una nueva característica para Symfony, puede ser útil encapsularla en un
plugin para poder reutilizarla en otros proyectos. El primer paso es el de organizar los ar-
chivos de forma lógica para que los mecanismos de carga automática de Symfony pue-
dan cargarlos cuando sea necesario. Para ello, se debe seguir la estructura de archivos
mostrada en el listado 17-23. El listado 17-27 muestra un ejemplo de estructura de ar-
chivos para un plugin llamado sfSamplePlugin.

Listado 17-27 - Ejemplo de los archivos que se encapsulan en un plugin
   sfSamplePlugin/
     README
     LICENSE
     config/
       schema.yml
     data/
       fixtures/
          fixtures.yml
       tasks/
          sfSampleTask.php
     lib/
       model/
          sfSampleFooBar.php
          sfSampleFooBarPeer.php
       validator/
          sfSampleValidator.class.php
     modules/
       sfSampleModule/
          actions/
            actions.class.php
          config/
            security.yml
          lib/
            BasesfSampleModuleActions.class.php
          templates/
            indexSuccess.php


www.librosweb.es                                                                        389
Symfony, la guía definitiva                                       Capítulo 17. Personalizar Symfony

      web/
        css/
           sfSampleStyle.css
        images/
           sfSampleImage.png

Para la creación de los plugins, no es importante la localización del directorio del plugin
(sfSamplePlugin/ en el caso del listado 17-27), ya que puede encontrarse en cualquier si-
tio del sistema de archivos.

  SUGERENCIA
  Se aconseja ver la estructura de archivos de los plugins existentes antes de crear plugins propios,
  de forma que se puedan utilizar las mismas convenciones para el nombrado de archivos y la misma
  estructura de archivos.

17.4.4.2. Creando el archivo package.xml
El siguiente paso en la creación del plugin es añadir un archivo llamado package.xml en el
directorio raíz del plugin. El archivo package.xml sigue la misma sintaxis de PEAR. El lista-
do 17-28 muestra el aspecto típico de un archivo package.xml de un plugin.

Listado 17-28 - Ejemplo de archivo package.xml de un plugin de Symfony
   <?xml version="1.0" encoding="UTF-8"?>
   <package packagerversion="1.4.6" version="2.0"
            xmlns="http://pear.php.net/dtd/package-2.0"
            xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/
   tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/
   package-2.0.xsd">
     <name>sfSamplePlugin</name>
     <channel>pear.symfony-project.com</channel>
     <summary>symfony sample plugin</summary>
     <description>Just a sample plugin to illustrate PEAR packaging</description>
     <lead>
       <name>Fabien POTENCIER</name>
       <user>fabpot</user>
       <email>fabien.potencier@symfony-project.com</email>
       <active>yes</active>
     </lead>
     <date>2006-01-18</date>
     <time>15:54:35</time>
     <version>
       <release>1.0.0</release>
       <api>1.0.0</api>
     </version>
     <stability>
       <release>stable</release>
       <api>stable</api>
     </stability>
     <license uri="http://www.symfony-project.org/license">MIT license</license>
     <notes>-</notes>
     <contents>

www.librosweb.es                                                                                390
Symfony, la guía definitiva                                  Capítulo 17. Personalizar Symfony

        <dir name="/">
          <file role="data" name="README" />
          <file role="data" name="LICENSE" />
          <dir name="config">
            <!-- model -->
            <file role="data" name="schema.yml" />
          </dir>
          <dir name="data">
            <dir name="fixtures">
               <!-- fixtures -->
               <file role="data" name="fixtures.yml" />
            </dir>
            <dir name="tasks">
               <!-- tasks -->
               <file role="data" name="sfSampleTask.php" />
            </dir>
          </dir>
          <dir name="lib">
            <dir name="model">
               <!-- model classes -->
               <file role="data" name="sfSampleFooBar.php" />
               <file role="data" name="sfSampleFooBarPeer.php" />
            </dir>
            <dir name="validator">
               <!-- validators -->
               <file role="data" name="sfSampleValidator.class.php" />
            </dir>
          </dir>
          <dir name="modules">
            <dir name="sfSampleModule">
               <file role="data" name="actions/actions.class.php" />
               <file role="data" name="config/security.yml" />
               <file role="data" name="lib/BasesfSampleModuleActions.class.php" />
               <file role="data" name="templates/indexSuccess.php" />
            </dir>
          </dir>
          <dir name="web">
            <dir name="css">
               <!-- stylesheets -->
               <file role="data" name="sfSampleStyle.css" />
            </dir>
            <dir name="images">
               <!-- images -->
               <file role="data" name="sfSampleImage.png" />
            </dir>
          </dir>
        </dir>
      </contents>
      <dependencies>
        <required>
          <php>
            <min>5.0.0</min>
          </php>
          <pearinstaller>
            <min>1.4.1</min>


www.librosweb.es                                                                         391
Symfony, la guía definitiva                                        Capítulo 17. Personalizar Symfony

         </pearinstaller>
         <package>
           <name>symfony</name>
           <channel>pear.symfony-project.com</channel>
           <min>1.0.0</min>
           <max>1.1.0</max>
           <exclude>1.1.0</exclude>
         </package>
       </required>
     </dependencies>
     <phprelease />
     <changelog />
   </package>

Las partes más interesates del archivo anterior son las etiquetas <contents> y <dependen-
cies>, que se describen a continuación. Como el resto de etiquetas no son específicas de
Symfony, se puede consultar la documentación de PEAR (http://pear.php.net/manual/en/)
para obtener más información sobre el formato de package.xml.


17.4.4.3. Contenidos
La etiqueta <contents> se utiliza para describir la estructura de archivos de los plugins.
Mediante esta etiqueta se dice a PEAR los archivos que debe copiar y el lugar en el que
los debe copiar. La estructura de archivos se define mediante etiquetas <dir> y <file>.
Todas las etiquetas de tipo <file> deben contener un atributo role=”data”. La sección
<contents> del listado 17-28 describe la estructura de directorios exacta del listado
17-27.

  NOTA
  El uso de etiquetas <dir> no es obligatorio, ya que se pueden utilizar rutas relativas como valor de
  los atributos name de las etiquetas <file>. No obstante, se recomienda utilizarlas para que el archi-
  vo package.xml sea fácil de leer.


17.4.4.4. Dependencias de los plugins
Los plugins están diseñados para funcionar con una serie de versiones de PHP, PEAR,
Symfony, paquetes PEAR y otros plugins. La etiqueta <dependencies> declara todas estas
dependencias y ayuda a PEAR a comprobar si se encuentran instalados todos los paque-
tes requeridos, lanzando una excepción si alguno no está disponible.

Siempre se deberían declarar las dependencias de PHP, PEAR y Symfony; al menos se
deberían declarar las correspondientes a la instalación propia del autor del plugin, como
requerimiento mínimo de instalación. Si no se sabe qué requerimientos establecer, se
pueden indicar como requisitos PHP 5.0, PEAR 1.4 y Symfony 1.0.

También es recomendable añadir un número correspondiente a la versión más avanzada
de Symfony para la que el plugin funciona correctamente. De esta forma, se producirá un
error al intentar utilizar un plugin con una versión muy avanzada de Symfony. Así, el au-
tor del plugin se ve obligado a asegurar que el plugin funciona con las nuevas versiones
de Symfony antes de lanzar una nueva versión del plugin. Siempre es mejor que se


www.librosweb.es                                                                                  392
Symfony, la guía definitiva                                Capítulo 17. Personalizar Symfony


muestre un mensaje de error y se obligue a actualizar el plugin, que no simplemente de-
jar que el plugin no funcione y no avise de ninguna manera.

17.4.4.5. Construyendo el plugin
PEAR dispone de un comando (pear package) que construye un archivo comprimido de ti-
po .tgz con los contenidos del paquete, siempre que se ejecute el comando desde un di-
rectorio que contiene un archivo package.xml, tal y como muestra el listado 17-29:

Listado 17-29 - Creando un paquete PEAR para el plugin
   > cd sfSamplePlugin
   > pear package

   Package sfSamplePlugin-1.0.0.tgz done

Una vez construido el plugin, se puede comprobar que funciona correctamente instalan-
dolo en el propio sistema, como se muestra en el listado 17-30.

Listado 17-30 - Instalando el plugin
   > cp sfSamplePlugin-1.0.0.tgz /home/production/miproyecto/
   > cd /home/production/miproyecto/
   > php symfony plugin-install sfSamplePlugin-1.0.0.tgz

Según la descripción de la etiqueta <contents>, los archivos del plugin se instalarán en
diferentes directorios del proyecto. El listado 17-31 muestra donde acaban los archivos
del plugin sfSamplePlugin después de su instalación.

Listado 17-31 - Los archivos del plugin se instalan en los directorios plugins/ y
web/
   plugins/
     sfSamplePlugin/
       README
       LICENSE
       config/
         schema.yml
       data/
         fixtures/
            fixtures.yml
         tasks/
            sfSampleTask.php
       lib/
         model/
            sfSampleFooBar.php
            sfSampleFooBarPeer.php
         validator/
            sfSampleValidator.class.php
       modules/
         sfSampleModule/
            actions/
              actions.class.php
            config/
              security.yml
            lib/

www.librosweb.es                                                                       393
Symfony, la guía definitiva                                  Capítulo 17. Personalizar Symfony

              BasesfSampleModuleActions.class.php
            templates/
              indexSuccess.php
   web/
     sfSamplePlugin/    ## Copia o enlace simbólico, dependiendo del sistema operativo
        css/
          sfSampleStyle.css
        images/
          sfSampleImage.png

Posteriormente, se comprueba si el plugin funciona correctamente dentro de la aplica-
ción. Si todo funciona bien, el plugin ya está listo para ser utilizado en otros proyectos y
para compartirlo con el resto de la comunidad de Symfony.

17.4.4.6. Distribuir un plugin desde el sitio web del proyecto Symfony
La mejor forma de publicitar un plugin es distribuirlo desde el sitio web symfony-
project.com. Cualquier plugin desarrollado por cualquier programador se puede distribuir
desde este sitio web, siempre que se realicen los siguientes pasos:

    1. El archivo README del plugin debe describir la instalación y uso del plugin y el ar-
        chivo LICENSE debe indicar el tipo de licencia de uso del plugin. El archivo README
        debe      escribirse    con      el    formato       común       de     los      wikis
        (http://trac.symfony-project.com/wiki/WikiFormatting ).

    2. Se crea un paquete PEAR para el plugin mediante el comando pear package y se
        prueba su funcionamiento. El nombre del paquete PEAR debe seguir la notación
        sfSamplePlugin-1.0.0.tgz (1.0.0 es la versión del plugin).

    3. Se crea una nueva página en el wiki de Symfony llamada sfSamplePlugin (es
        obligatorio utilizar el sufijo Plugin). En esta página, se describe el uso del plugin,
        la licencia, las dependencias y el proceso de instalación. Se pueden reutilizar los
        contenidos del archivo README del plugin. Se pueden comprobar las páginas de los
        plugins existentes para utilizarlas como ejemplo.

    4. Se adjunta el paquete PEAR a la página del wiki (sfSamplePlugin-1.0.0.tgz).

    5. Se añade la página del plugin ([wiki:sfSamplePlugin]) a la lista de plugins dispo-
        nibles, que también es una página del wiki                 y   está   disponible    en
        (http://trac.symfony-project.com/wiki/SymfonyPlugins ).

Si se siguen todos estos pasos, cualquier usuario puede instalar el plugin ejecutando el
siguiente comando en el directorio de un proyecto Symfony:
   > php symfony plugin-install http://plugins.symfony-project.com/sfSamplePlugin


17.4.4.7. Convenciones sobre el nombre de los plugins
Para mantener el directorio plugins/ limpio, todos los nombres de los plugins deberían
seguir la notación camelCase y deben contener el sufijo Plugin, como por ejemplo carri-
toCompraPlugin, feedPlugin, etc. Antes de elegir el nombre de un plugin, se debe com-
probar que no exista otro plugin con el mismo nombre.


www.librosweb.es                                                                           394
Symfony, la guía definitiva                                     Capítulo 17. Personalizar Symfony


  NOTA
  Los plugins relacionados con Propel deberían contener la palabra Propel en su nombre. Un plugin
  que por ejemplo se encargue de la autenticación mediante el uso de objetos Propel, podría llamar-
  se sfPropelAuth.

Los plugins siempre deberían incluir un archivo LICENSE que desriba las condiciones de
uso del plugin y la licencia seleccionada por su autor. También se debería incluir en el ar-
chivo README información sobre los cambios producidos en cada versión, lo que realiza el
plugin, las instrucciones sobre su intalación y configuración, etc.


17.5. Resumen
Las clases de Symfony contienen hooks utilizados por sfMixer para permitir ser modifica-
das a nivel de la aplicación. El mecanismo de mixins permite la herencia múltiple y la re-
definición de métodos durante la ejecución de la aplicación, aunque las limitaciones de
PHP no lo permitirían. De esta forma, es fácil extender las características de Symfony, in-
cluso cuando se quieren reemplazar por completo las clases internas de Symfony, para lo
que se dispone del mecanismo de factorías.

Muchas de las extensiones que se pueden realizar ya existen en forma de plugins, que se
pueden instalar, actualizar y desinstalar fácilmente desde la línea de comandos de Sym-
fony. Crear un plugin es tan sencillo como crear un paquete de PEAR y permite reutilizar
un mismo código en varias aplicaciones diferentes.

El wiki de Symfony incluye muchos plugins y también es posible añadir plugins propios.
Ahora que se sabe cómo hacerlo, los creadores de Symfony esperan que muchos progra-
madores realicen mejoras a Symfony y las distribuyan a toda la comunidad de Symfony.




www.librosweb.es                                                                              395
Symfony, la guía definitiva                                        Capítulo 18. Rendimiento




Capítulo 18. Rendimiento
Si una aplicación está pensada para ser utilizada por muchos usuarios, su optimización y
su rendimiento son factores muy importantes a tener en cuenta durante su desarrollo.
Como es lógico, el rendimiento siempre ha sido una de las máximas preocupaciones de
los creadores de Symfony.

Aunque la gran ventaja de reducir el tiempo de desarrollo de una aplicación gracias a
Symfony conlleva una disminución de su rendimiento, los programadores de Symfony
siempre han tenido presente los requerimientos de rendimiento habituales. De esta for-
ma, se ha analizado cada clase y cada método para que sean lo más rápidos posible.

La penalización mínima en el rendimiento de la aplicación (que se puede medir mostran-
do simplemente un mensaje tipo “Hola Mundo” con y sin Symfony) es muy reducida. Por
tanto, el framework es escalable y responde correctamente a las pruebas de carga, tam-
bién llamadas pruebas de stress. La prueba definitiva de su buen rendimiento es que al-
gunos sitios con muchísimo tráfico (varios millones de usuarios y muchas interacciones
Ajax) utilizan Symfony y están muy satisfechos con su rendimiento. La lista de sitios web
desarrollados con Symfony se puede obtener en el wiki del proyecto: http://trac.symfony-
project.com/wiki/ApplicationsDevelopedWithSymfony .

Evidentemente, los sitios web con millones de usuarios tienen los recursos necesarios pa-
ra crear granjas de servidores y para mejorar el hardware de los servidores. No obstante,
si no se dispone de este tipo de recursos, existen unos pequeños trucos que se pueden
seguir para mejorar el rendimiento de las aplicaciones Symfony. En este capítulo se
muestran algunas de las optimizaciones recomendadas para mejorar el rendimiento en
todos los niveles del framework, aunque la mayoría están pensadas para usuarios avan-
zados. Aunque alguna técnica ya se ha comentado en otros capítulos anteriores, siempre
es conveniente reunir todas las técnicas en un único lugar.


18.1. Optimizando el servidor
Una aplicación bien optimizada debería ejecutarse en un servidor que también estuviera
muy optimizado. Para asegurar que no existe un cuello de botella en los elementos exter-
nos a Symfony, se deberían conocer las técnicas básicas para optimizar los servidores. A
continuación se muestran una serie de opciones que se deben comprobar para que el
rendimiento del servidor no se vea penalizado.

Si la opción magic_quotes_gpc del archivo php.ini tiene asignado un valor de on, el rendi-
miento de la aplicación disminuye, ya que PHP añade mecanismos de escape a todas las
comillas de los parámetros de la petición y Symfony después aplica los mecanismos in-
versos, por lo que el único efecto de esta opción es una pérdida de rendimiento y posi-
bles problemas en algunos sistemas. Si se tiene acceso a la configuración de PHP, se de-
bería desactivar esta opción.

Cuanto más reciente sea la versión de PHP que se utiliza, mayor será el rendimiento. La
versión PHP 5.2 es más rápida que PHP 5.1, que a su vez es mucho más rápida que PHP



www.librosweb.es                                                                       396
Symfony, la guía definitiva                                               Capítulo 18. Rendimiento


5.0. De forma que es una buena idea actualizar a la última versión de PHP para obtener
una gran mejora en su rendimiento.

El uso de un acelerador de PHP (como por ejemplo, APC, XCache, o eAccelerator) es casi
obligatorio en un servidor de producción, ya que mejora el rendimiento de PHP en un
50% y no tiene ningún inconveniente. Para disfrutar de la auténtica velocidad de ejecu-
ción de PHP, es necesario instalar algún acelerador.

Por otra parte, se deben desactivar en el servidor de producción todas las herramientas
de depuración, como las extensiones Xdebug y APD.

  NOTA
  Si te estás preguntando sobre la penalización en el rendimiento causada por el uso de la extensión
  mod_rewrite, su efecto es despreciable. Aunque es evidente que cargar por ejemplo una imagen
  mediante la reglas de reescritura de este módulo es más lento que cargar la imagen directamente,
  la penalización producida es muchas órdenes de magnitud inferior a la ejecución de cualquier sen-
  tencia PHP.

Algunos programadores de Symfony también utilizan syck, que es una extensión de PHP
que procesa archivos de tipo YAML, en vez de utilizar el procesamiento interno de Sym-
fony. Aunque esta extensión es mucho más rápida, la cache que utiliza Symfony reduce
en gran medida el tiempo de procesamiento de los archivos YAML, por lo que en un ser-
vidor de producción, el beneficio que se obtiene es muy reducido, casi inexistente.
Además, la extensión syck no es todavía lo suficientemente mandura y de hecho, puede
provocar errores si se utiliza. No obstante, se puede instalar la                        extensión
(http://whytheluckystiff.net/syck/) y Symfony la reconoce automáticamente.

  SUGERENCIA
  Cuando un solo servidor no es suficiente, se puede añadir otro servidor y hacer un balanceo de la
  carga entre ellos. Mientras que el directorio uploads/ sea compartido y se almacenen las sesiones
  en una base de datos, Symfony funciona igual de bien en una arquitectura de balanceo de carga.


18.2. Optimizando el modelo
En Symfony, la capa del modelo tiene fama de ser el componente más lento. Si las prue-
bas de rendimiento demuestran que se debe optimizar esta capa para una aplicación, a
continuación se muestran las posibles mejoras que se pueden realizar.

18.2.1. Optimizando la integración de Propel
Inicializar la capa del modelo (las clases internas de Propel) requiere cierto tiempo, ya
que se deben cargar algunas clases y se deben construir algunos objetos. No obstante,
por la forma en la que Symfony integra Propel, esta inicialización solamente se produce
cuando una acción requiere realmente utilizar el modelo, por lo que si sucede, se realiza
lo más tarde posible. Las clases Propel se inicializan solamente cuando un objeto del mo-
delo generado se carga automáticamente. Por tanto, las páginas que no utilizan el mode-
lo no se ven penalizadas por la capa del modelo.




www.librosweb.es                                                                               397
Symfony, la guía definitiva                                          Capítulo 18. Rendimiento


Si una aplicación no necesita la capa del modelo, se puede evitar la inicialización del ob-
jeto sfDatabaseManager desactivando por completo la capa del modelo mediante la sigu-
iente opción del archivo settings.yml:
   all:
     .settings:
        use_database: off

Las clases generadas para el modelo (en lib/model/om/) ya están optimizadas porque se
les han eliminado los comentarios y también se cargan de forma automática cuando es
necesario. Utilizar el sistema de carga automática en vez de incluir las clases a mano, ga-
rantiza que las clases se cargan solamente cuando son realmente necesarias. Por tanto,
si una clase del modelo no se utiliza, el mecanismo de carga automática ahorra tiempo
de ejecución, mientras que la alternativa de utilizar sentencias include de PHP no podría
ahorrarlo. En lo que respecta a los comentarios, se utilizan para documentar el uso de los
métodos generados, pero aumentan mucho el tamaño de los archivos, lo que disminuye
el rendimiento en los sistemas con discos duros lentos. Como los métodos de las clases
generadas tienen nombres muy explícitos, los comentarios se desactivan por defecto.

Estas 2 mejoras son específicas de Symfony, pero se puede volver a las opciones por de-
fecto de Propel cambiando estas 2 opciones en el archivo propel.ini, como se muestra a
continuación:
   propel.builder.addIncludes = true     # Añadir sentencias "include" en las clases
   generadas
                                         # en vez de utiliza la carga automática de clases
   propel.builder.addComments = true     # Añadir comentarios a las clases generadas


18.2.2. Limitando el número de objetos que se procesan
Cuando se utiliza un método de una clase peer para obtener los objetos, el resultado de
la consulta pasa el proceso de “hidratación” (“hydrating” en inglés) en el que se crean los
objetos y se cargan con los datos de las filas devueltas en el resultado de la consulta. Pa-
ra obtener por ejemplo todas las filas de la tabla articulo mediante Propel, se ejecuta la
siguiente instrucción:
   $articulos = ArticuloPeer::doSelect(new Criteria());

La variable $articulos resultante es un array con los objetos de tipo Article. Cada obje-
to se crea e inicializa, lo que requiere cierta cantidad de tiempo. La consecuencia de este
comportamiento es que, al contrario de lo que sucede con las consultas a la base de da-
tos, la velocidad de ejecución de una consulta Propel es directamente proporcional al nú-
mero de resultados que devuelve. De esta forma, los métodos del modelo deberían opti-
mizarse para devolver solamente un número limitado de resultados. Si no se necesitan
todos los resultados devueltos por Criteria, se deberían limitar mediante los métodos
setLimit() y setOffset(). Si solamente se necesitan por ejemplo las filas de datos de la
10 a la 20 para una consulta determinada, se puede refinar el objeto Criteria como se
muestra en el listado 18-1.

Listado 18-1 - Limitando el número de resultados devueltos por Criteria



www.librosweb.es                                                                         398
Symfony, la guía definitiva                                         Capítulo 18. Rendimiento

   $c = new Criteria();
   $c->setOffset(10); // Posición de la primera fila que se obtiene
   $c->setLimit(10);    // Número de filas devueltas
   $articulos = ArticuloPeer::doSelect($c);

El código anterior se puede automatizar utilizando un paginador. El objeto sfPropelPager
gestiona de forma automática los valores offset y limit para una consulta Propel, de
forma que solamente se crean los objetos mostrados en cada página. La documentación
del paginador (http://www.symfony-project.org/cookbook/trunk/pager) dispone de más infor-
mación sobre esta clase.

18.2.3. Minimizando el número de consultas mediante Joins
Mientras se desarrolla una aplicación, se debe controlar el número de consultas a la base
de datos que realiza cada petición. La barra de depuración web muestra el número de
consultas realizadas para cada página y al pulsar sobre el pequeño icono de una base de
datos, se muestra el código SQL de las consultas realizadas. Si el número de consultas
crece de forma desproporcionada, seguramente es necesario utilizar una Join.

Antes de explicar los métodos para Joins, se muestra lo que sucede cuando se recorre un
array de objetos y se utiliza un método getter de Propel para obtener los detalles de la
clase relacionada, como se ve en el listado 18-2. Este ejemplo supone que el esquema
describe una tabla llamada articulo con una clave externa relacionada con la tabla
autor.

Listado 18-2 - Obteniendo los detalles de una clase relacionada dentro de un
bucle
   // En la acción
   $this->articulos = ArticuloPeer::doSelect(new Criteria());

   // Consulta realizada en la base de datos por doSelect()
   SELECT articulo.id, articulo.titulo, articulo.autor_id, ...
   FROM   articulo

   // En la plantilla
   <ul>
   <?php foreach ($articulos as $articulo): ?>
     <li><?php echo $articulo->getTitulo() ?>,
        escrito por <?php echo $articulo->getAutor()->getNombre() ?></li>
   <?php endforeach; ?>
   </ul>

Si el array $articulos contiene 10 objetos, el método getAutor() se llama 10 veces, lo
que implica una consulta con la base de datos cada vez que se tiene que crear un objeto
de tipo Autor, como se muestra en el listado 18-3.

Listado 18-3 - Los métodos getter de las claves externas, implican una consulta
a la base de datos
   // En la plantilla
   $articulo->getAutor()

   // Consulta a la base de datos producida por getAutor()

www.librosweb.es                                                                        399
Symfony, la guía definitiva                                           Capítulo 18. Rendimiento

   SELECT autor.id, autor.nombre, ...
   FROM   autor
   WHERE autor.id = ?                 // ? es articulo.autor_id

Por tanto, la página que genera el listado 18-2 implica 11 consultas a la base de datos:
una consulta para construir el array de objetos Articulo y otras 10 consultas para obte-
ner el objeto Autor asociado a cada objeto anterior. Evidentemente, se trata de un nú-
mero de consultas muy grande para mostrar simplemente un listado de los artículos dis-
ponibles y sus autores.

Si se utiliza directamente SQL, es muy fácil reducir el número de consultas a solamente
1, obteniendo las columnas de la tabla articulo y las de la tabla autor mediante una úni-
ca consulta. Esto es exactamente lo que hace el método doSelectJoinAutor() de la clase
ArticuloPeer. Este método realiza una consulta más compleja que un simple doSelect(),
y las columnas adicionales que están presentes en el resultado obtenido permiten a Pro-
pel “hidratar” tanto los objetos de tipo Articulo como los objetos de tipo Autor. El código
del listado 18-4 produce el mismo resultado que el del listado 18-2, pero solamente req-
uiere 1 consulta con la base de datos en vez de 11 consultas, por lo que es mucho más
rápido.

Listado 18-4 - Obteniendo los detalles de los artículos y sus autores en la misma
consulta
   // En la acción
   $this->articulos = ArticuloPeer::doSelectJoinAutor(new Criteria());

   // Consulta a la base de datos realizada por doSelectJoinAutor()
   SELECT articulo.id, articulo.titulo, articulo.autor_id, ...
          autor.id, autor.name, ...
   FROM   articulo, autor
   WHERE articulo.autor_id = autor.id

   // En la plantilla no hay cambios
   <ul>
   <?php foreach ($articulos as $articulo): ?>
     <li><?php echo $articulo->getTitulo() ?>,
        escrito por <?php echo $articulo->getAutor()->getNombre() ?></li>
   <?php endforeach; ?>
   </ul>

No existen diferencias entre el resultado devuelto por doSelect() y el resultado devuelto
por doSelectJoinXXX(); los dos métodos devuelven el mismo array de objetos (de tipo
Articulo en este ejemplo). La diferencia se hace evidente cuando se utiliza un método
getter asociado con una clave externa. En el caso del método doSelect(), se realiza una
consulta a la base de datos y se crea un nuevo objeto con el resultado; en el caso del
método doSelectJoinXXX(), el objeto asociado ya existe y no se realiza la consulta con la
base de datos, por lo que el proceso es mucho más rápido. Por tanto, si se sabe de ante-
mano que se van a utilizar los objetos relacionados, se debe utilizar el método doSe-
lectJoinXXX() para reducir el número de consultas a la base de datos y por tanto, para
mejorar el rendimiento de la página.



www.librosweb.es                                                                          400
Symfony, la guía definitiva                                            Capítulo 18. Rendimiento


El método doSelectJoinAutor() se genera automáticamente cuando se ejecuta la tarea
propel-build-model, debido a la relación entre las tablas articulo y autor. Si existen
otras claves externas en la tabla del artículo, por ejemplo una tabla de categorías, la cla-
se BaseArticuloPeer generada contendría otros métodos Join, como se muestra en el lis-
tado 18-5.

Listado 18-5 - Ejemplo de métodos doSelect disponibles para una clase
ArticuloPeer
   // Obtiene objetos "Articulo"
   doSelect()

   // Obtiene objetos "Articulo" y crea los objetos "Autor" relacionados
   doSelectJoinAutor()

   // Obtiene objetos "Articulo" y crea los objetos "Categoria" relacionados
   doSelectJoinCategoria()

   // Obtiene objetos "Articulo" y crea todos los objetos relacionados salvo "Autor"
   doSelectJoinAllExceptAutor()

   // Obtiene objetos "Articulo" y crea todos los objetos relacionados
   doSelectJoinAll()

Las clases peer también disponen de métodos Join para doCount(). Las clases que sopor-
tan la internacionalización (ver Capítulo 13) disponen de un método doSelectWithI18n(),
que se comporta igual que los métodos Join, pero con los objetos de tipo i18n. Para des-
cubrir todos los métodos de tipo Join generados para las clases del modelo, es conven-
iente inspeccionar las clases peer generadas en el directorio lib/model/om/. Si no se enc-
uentra el método Join necesario para una consulta (por ejemplo no se crean automática-
mente los métodos Join para las relaciones muchos-a-muchos), se puede crear un méto-
do propio que extienda el modelo.

  SUGERENCIA
  Evidentemente, la llamada al método doSelectJoinXXX() es un poco más lenta que la llamada a
  un método simple doSelect(), por lo que solamente mejora el rendimiento global de la página si
  se utilizan los objetos relacionados.


18.2.4. Evitar el uso de arrays temporales
Cuando se utiliza Propel, los objetos creados ya contienen todos los datos, por lo que no
es necesario crear un array temporal de datos para la plantilla. Los programadores que
no están acostumbrados a trabajar con ORM suelen caer en este error. Estos programa-
dores suelen preparar un array de cadenas de texto o de números para las plantillas,
mientras que, en realidad, las plantillas pueden trabajar directamente con los arrays de
objetos. Si la plantilla por ejemplo muestra la lista de títulos de todos los artículos de la
base de datos, un programador que no está acostumbrado a trabajar de esta forma pue-
de crear un código similar al del listado 18-6.




www.librosweb.es                                                                           401
Symfony, la guía definitiva                                        Capítulo 18. Rendimiento


Listado 18-6 - Crear un array temporal en la acción es inútil si ya se dispone de
un array de objetos
   // En la acción
   $articulos = ArticuloPeer::doSelect(new Criteria());
   $titulos = array();
   foreach ($articulos as $articulo)
   {
     $titulos[] = $articulo->getTitulo();
   }
   $this->titulos = $titulos;

   // En la plantilla
   <ul>
   <?php foreach ($titulos as $titulo): ?>
     <li><?php echo $titulo ?></li>
   <?php endforeach; ?>
   </ul>

El problema del código anterior es que el proceso de creación de objetos del método
doSelect() hace que crear el array $titulos sea inútil, ya que el mismo código se puede
reescribir como muestra el listado 18-7. De esta forma, el tiempo que se pierde creando
el array $titulos se puede aprovechar para mejorar el rendimiento de la aplicación.

Listado 18-7 - Utilizando el array de objetos, no es necesario crear un array
temporal
   // En la acción
   $this->articulos = ArticuloPeer::doSelect(new Criteria());

   // En la plantilla
   <ul>
   <?php foreach ($articulos as $articulo): ?>
     <li><?php echo $articulo->getTitulo() ?></li>
   <?php endforeach; ?>
   </ul>

Si realmente es necesario crear un array temporal porque se realiza cierto procesamiento
con los objetos, la mejor solución es la de crear un nuevo método en la clase del modelo
que devuelva directamente ese array. Si por ejemplo se necesita un array con los títulos
de los artículos y el número de comentarios de cada artículo, la acción y la plantilla de-
berían ser similares a las del listado 18-8.

Listado 18-8 - Creando un método propio para preparar un array temporal
   // En la acción
   $this->articulos = ArticuloPeer::getArticuloTitulosConNumeroComentarios();

   // En la plantilla
   <ul>
   <?php foreach ($articulos as $articulo): ?>
     <li><?php echo $articulo[0] ?> (<?php echo $articulo[1] ?> comentarios)</li>
   <?php endforeach; ?>
   </ul>




www.librosweb.es                                                                       402
Symfony, la guía definitiva                                              Capítulo 18. Rendimiento


Solamente falta crear un método getArticuloTitulosConNumeroComentarios() muy rápido
en el modelo, que se puede crear saltándose por completo el ORM y todas las capas de
abstracción de bases de datos.

18.2.5. Saltándose el ORM
Cuando no se quieren utilizar los objetos completos, sino que solamente son necesarias
algunas columnas de cada tabla (como en el ejemplo anterior) se pueden crear métodos
específicos en el modelo que se salten por completo la capa del ORM. Se puede utilizar
por ejemplo Creole para acceder directamente a la base de datos y devolver un array con
un formato propio, como se muestra en el listado 18-9.

Listado 18-9 - Accediendo directamente con Creole para optimizar los métodos
del modelo, en lib/model/ArticuloPeer.php
   class ArticuloPeer extends BaseArticuloPeer
   {
     public static function getArticuloTitulosConNumeroComentarios()
     {
       $conexion = Propel::getConnection();
       $consulta = 'SELECT %s as titulo, COUNT(%s) AS num_comentarios FROM %s LEFT JOIN %s
   ON %s = %s GROUP BY %s';
       $consulta = sprintf($consulta,
          ArticuloPeer::TITULO, ComentarioPeer::ID,
          ArticuloPeer::TABLE_NAME, ComentarioPeer::TABLE_NAME,
          ArticuloPeer::ID, ComentarioPeer::ARTICULO_ID,
          ArticuloPeer::ID
       );
       $sentencia = $conexion->prepareStatement($consulta);
       $resultset = $sentencia->executeQuery();
       $resultados = array();
       while ($resultset->next())
       {
          $resultados[] = array($resultset->getString('titulo'),
   $resultset->getInt('num_comentarios'));
       }

           return $resultados;
       }
   }

Si se crean muchos métodos de este tipo, se puede acabar creando un método específico
para cada acción, perdiendo la ventaja de la separación en capas y la abstracción de la
base de datos.

  SUGERENCIA
  Si Propel no es adecuado para la capa del modelo de algún proyecto, es mejor considerar el uso de
  otros ORM antes de escribir todas las consultas a mano. El plugin sfDoctrine proporciona una in-
  terfaz para el ORM PhpDoctrine. Además, se puede utilizar otra capa de abstracción de bases de
  datos en vez de Creole. Desde la versión PHP 5.1, se encuentra incluida en PHP la capa de abs-
  tracción PDO, que ofrece una alternativa mucho más rápida que Creole.



www.librosweb.es                                                                              403
Symfony, la guía definitiva                                                 Capítulo 18. Rendimiento


18.2.6. Optimizando la base de datos
Existen numerosas técnicas para optimizar la base de datos y que pueden ser aplicadas
independientemente de Symfony. En esta sección, se repasan brevemente algunas de las
estrategias más utilizadas, aunque es necesario un buen conocimiento de motores de ba-
ses de datos para optimizar la capa del modelo.

  SUGERENCIA
  Recuerda que la barra de depuración web muestra el tiempo de ejecución de cada consulta realiza-
  da por la página, por lo que cada cambio que se realice debería comprobarse para ver si realmente
  reduce el tiempo de ejecución.

A menudo, las consultas a las bases de datos se realizan sobre columnas que no son cla-
ves primarias. Para aumentar la velocidad de ejecución de esas consultas, se deben crear
índices en el esquema de la base de datos. Para añadir un índice a una columna, se aña-
de la propiedad index: true a la definición de la columna, tal y como muestra el listado
18-10.

Listado 18-10 - Añadiendo un índice a una sola columna, en config/schema.yml
   propel:
     articulo:
       id:
       autor_id:
       titulo:   { type: varchar(100), index: true }

Se puede utilizar de forma alternativa el valor index: unique para definir un índice único
en vez de un índice normal. El archivo schema.yml también permite definir índices sobre
varias columnas (el Capítulo 8 contiene más información sobre la sintaxis de los índices).
El uso de índices es muy recomendable, ya que es una buena form
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva
Symfony guia definitiva

Más contenido relacionado

PDF
Ovogenesis
PDF
SEMANA 5 Biomoléculas orgánicas carbo hidratos, lipidos.pdf
PPT
hongos - Estructura.ppt
PPTX
3 1 espermatogénesis
PPT
Clase 1 estructura bact. y fung.
PPTX
Segmentación e implantación embrionaria
DOCX
Interacciones microbianas con organismos superiores
PPTX
Slideshare gametogenesis richard
Ovogenesis
SEMANA 5 Biomoléculas orgánicas carbo hidratos, lipidos.pdf
hongos - Estructura.ppt
3 1 espermatogénesis
Clase 1 estructura bact. y fung.
Segmentación e implantación embrionaria
Interacciones microbianas con organismos superiores
Slideshare gametogenesis richard

Similar a Symfony guia definitiva (20)

PDF
Manual de moodle
PDF
Fedora 14-software management-guide-es-es
PDF
Introduccion poo con_java
PDF
PDF
Apunts dintel ligencia_artificial
PDF
PDF
Introduccion a Joomla
PDF
Manual de programacion_con_robots_para_la_escuela
PDF
Manual de programacion_con_robots_para_la_escuela
PDF
Algoritmos y programacion_i_-_con_lengua
PDF
Algoritmos programacion-python
PDF
Guadalinex manual
PDF
Una guía comprensiva de la Arquitectura de Componentes de Zope
PDF
Fundamentos de Programacion.pdf
PDF
Hibernate reference
PDF
Enseñanza mesclada
PDF
Manual Jsf
PDF
PDF
Algoritmos programacion-python
PDF
Symfony. La guia definitiva
Manual de moodle
Fedora 14-software management-guide-es-es
Introduccion poo con_java
Apunts dintel ligencia_artificial
Introduccion a Joomla
Manual de programacion_con_robots_para_la_escuela
Manual de programacion_con_robots_para_la_escuela
Algoritmos y programacion_i_-_con_lengua
Algoritmos programacion-python
Guadalinex manual
Una guía comprensiva de la Arquitectura de Componentes de Zope
Fundamentos de Programacion.pdf
Hibernate reference
Enseñanza mesclada
Manual Jsf
Algoritmos programacion-python
Symfony. La guia definitiva
Publicidad

Symfony guia definitiva

  • 1. www.librosweb.es Symfony la guía definitiva Fabien Potencier, François Zaninotto
  • 2. Symfony, la guía definitiva Sobre este libro... ▪ Esta versión impresa se creó el 20 de febrero de 2008 y todavía está in- completa. La versión más actualizada de los contenidos de este libro se puede encontrar en http://www.librosweb.es/symfony ▪ Si quieres aportar sugerencias, comentarios, críticas o informar sobre errores, puedes contactarnos en contacto@librosweb.es www.librosweb.es 2
  • 3. Symfony, la guía definitiva Capítulo 1. Introducción a Symfony............................................................... 13 1.1. Symfony en pocas palabras ...................................................................... 13 1.1.1. Características de Symfony ................................................................ 13 1.1.2. ¿Quién ha desarrollado Symfony y por qué motivo? ...............................15 1.1.3. La comunidad Symfony ...................................................................... 16 1.1.4. ¿Es adecuado Symfony para mí? ......................................................... 16 1.2. Conceptos básicos................................................................................... 17 1.2.1. PHP 5 .............................................................................................. 17 1.2.2. Programación Orientada a Objetos (OOP) ............................................. 17 1.2.3. Métodos mágicos .............................................................................. 18 1.2.4. PEAR (PHP Extension and Application Repository) ..................................18 1.2.5. Mapeo de Objetos a Bases de datos (ORM) ...........................................19 1.2.6. Desarrollo rápido de aplicaciones (RAD) ............................................... 20 1.2.7. YAML ............................................................................................... 22 Capítulo 2. Explorando el interior de Symfony ............................................... 24 2.1. El patrón MVC ........................................................................................ 24 2.1.1. Las capas de la arquitectura MVC ........................................................ 25 2.1.2. Separación en capas más allá del MVC ................................................. 29 2.1.3. La implementación del MVC que realiza Symfony ...................................32 2.1.4. Las clases que forman el núcleo de Symfony.........................................35 2.2. Organización del código ........................................................................... 35 2.2.1. Estructura del proyecto: Aplicaciones, Módulos y Acciones ......................35 2.2.2. Estructura del árbol de archivos .......................................................... 37 2.3. Herramientas comunes ............................................................................ 41 2.3.1. Contenedores de parámetros .............................................................. 41 2.3.2. Constantes ....................................................................................... 43 2.3.3. Carga automática de clases ................................................................ 43 Capítulo 3. Ejecutar aplicaciones Symfony .................................................... 45 3.1. Instalando el entorno de pruebas .............................................................. 45 3.2. Instalando las librerías de Symfony ........................................................... 47 3.2.1. Instalando Symfony con PEAR............................................................. 48 3.2.2. Obtener Symfony mediante el repositorio SVN ......................................49 3.3. Crear una aplicación web ......................................................................... 49 3.3.1. Crear el Proyecto .............................................................................. 49 3.3.2. Crear la Aplicación............................................................................. 50 3.4. Configurar el servidor web ....................................................................... 51 3.4.1. Configurar los servidores virtuales ....................................................... 51 3.4.2. Configurar un servidor compartido....................................................... 52 3.5. Resolución de problemas.......................................................................... 54 3.5.1. Problemas típicos .............................................................................. 54 3.5.2. Recursos relacionados con Symfony..................................................... 55 3.6. Versionado del código fuente .................................................................... 55 www.librosweb.es 3
  • 4. Symfony, la guía definitiva Capítulo 4. Introducción a la creación de páginas ......................................... 58 4.1. Crear el esqueleto del módulo................................................................... 58 4.1.1. Añadir una página ............................................................................. 60 4.1.2. Transfiriendo información de la acción a la plantilla ................................63 4.2. Obteniendo información del usuario a través de formularios..........................63 4.3. Enlazando a otra acción ........................................................................... 65 4.4. Obteniendo información de la petición........................................................ 66 Capítulo 5. Configurar Symfony ..................................................................... 69 5.1. El sistema de configuración ...................................................................... 69 5.1.1. Sintaxis YAML y convenciones de Symfony ...........................................70 5.1.2. ¡Socorro, los archivos YAML han roto la aplicación! ................................73 5.2. Un vistazo general a los archivos de configuración .......................................74 5.2.1. Configuración del Proyecto ................................................................. 74 5.2.2. Configuración de la Aplicación ............................................................. 75 5.2.3. Configuración de los Módulos .............................................................. 79 5.3. Entornos................................................................................................ 80 5.3.1. ¿Qué es un entorno?.......................................................................... 80 5.3.2. Configuration en cascada ................................................................... 82 5.4. La cache de configuración ........................................................................ 84 5.5. Accediendo a la configuración desde la aplicación ........................................85 5.5.1. La clase sfConfig ............................................................................... 85 5.5.2. El archivo app.yml y la configuración propia de la aplicación ...................87 5.6. Trucos para los archivos de configuración ................................................... 87 5.6.1. Uso de constantes en los archivos de configuración YAML .......................88 5.6.2. Uso de programación en los archivos de configuración ...........................88 5.6.3. Utilizar tu propio archivo YAML ............................................................ 89 Capítulo 6. El Controlador.............................................................................. 91 6.1. El Controlador Frontal.............................................................................. 91 6.1.1. El Trabajo del Controlador Frontal en Detalle ........................................91 6.1.2. El Controlador Frontal por defecto ....................................................... 92 6.1.3. Llamando a Otro Controlador Frontal para Cambiar el Entorno.................92 6.1.4. Archivos por Lotes............................................................................. 93 6.2. Acciones ................................................................................................ 94 6.2.1. La clase de la acción .......................................................................... 94 6.2.2. Sintaxis alternativa para las clases de las Acciones ................................96 6.2.3. Obteniendo Información en las Acciones............................................... 96 6.2.4. Terminación de las Acciones ............................................................... 97 6.2.5. Saltando a Otra Acción....................................................................... 99 6.2.6. Repitiendo Código para varias Acciones de un Modulo .......................... 101 6.3. Accediendo a la Petición......................................................................... 101 6.4. Sesiones de Usuario .............................................................................. 104 6.4.1. Accediendo a la Sesión de Usuario ..................................................... 104 www.librosweb.es 4
  • 5. Symfony, la guía definitiva 6.4.2. Atributos Flash................................................................................ 105 6.4.3. Manejo de Sesiones ......................................................................... 106 6.5. Seguridad de la Acción .......................................................................... 107 6.5.1. Restricción de Acceso ...................................................................... 108 6.5.2. Otorgando Acceso ........................................................................... 109 6.5.3. Credenciales Complejas ................................................................... 111 6.6. Métodos de Validación y Manejo de Errores .............................................. 111 6.7. Filtros.................................................................................................. 113 6.7.1. La Cadena de Filtros ........................................................................ 113 6.7.2. Construyendo Tu Propio Filtro ........................................................... 116 6.7.3. Activación de Filtros y Parámetros ..................................................... 117 6.7.4. Filtros de Ejemplo ........................................................................... 118 6.8. Configuración del Módulo ....................................................................... 119 Capítulo 7. La Vista ..................................................................................... 121 7.1. Plantillas .............................................................................................. 121 7.1.1. Helpers .......................................................................................... 122 7.1.2. Layout de las páginas ...................................................................... 125 7.1.3. Atajos de plantilla ........................................................................... 126 7.2. Fragmentos de código ........................................................................... 127 7.2.1. Elementos parciales......................................................................... 128 7.2.2. Componentes ................................................................................. 129 7.2.3. Slots ............................................................................................. 132 7.3. Configuración de la vista ........................................................................ 134 7.3.1. El archivo view.yml ......................................................................... 135 7.3.2. El objeto respuesta (response).......................................................... 136 7.3.3. Opciones de configuración de la vista ................................................. 137 7.4. Slots de componentes ........................................................................... 142 7.5. Mecanismo de escape ............................................................................ 144 7.5.1. Activar el mecanismo de escape ........................................................ 145 7.5.2. Opción escaping_strategy................................................................. 146 7.5.3. Los helpers útiles para el mecanismo de escape .................................. 147 7.5.4. Aplicando el mecanismo de escape a los arrays y los objetos ................ 147 Capítulo 8. El modelo................................................................................... 150 8.1. ¿Por qué utilizar un ORM y una capa de abstracción? ................................. 150 8.2. Esquema de base de datos de Symfony ................................................... 152 8.2.1. Ejemplo de esquema ....................................................................... 152 8.2.2. Sintaxis básica de los esquemas........................................................ 153 8.3. Las clases del modelo ............................................................................ 154 8.3.1. Clases base y clases personalizadas ................................................... 155 8.3.2. Clases objeto y clases "peer" ............................................................ 155 8.4. Acceso a los datos................................................................................. 156 8.4.1. Obtener el valor de una columna ....................................................... 157 www.librosweb.es 5
  • 6. Symfony, la guía definitiva 8.4.2. Obtener los registros relacionados ..................................................... 157 8.4.3. Guardar y borrar datos .................................................................... 158 8.4.4. Obtener registros mediante la clave primaria ...................................... 159 8.4.5. Obtener registros mediante Criteria ................................................... 160 8.4.6. Uso de consultas con código SQL....................................................... 163 8.4.7. Uso de columnas especiales de fechas................................................ 164 8.5. Conexiones con la base de datos ............................................................. 165 8.6. Extender el modelo ............................................................................... 167 8.6.1. Añadir nuevos métodos.................................................................... 167 8.6.2. Redefinir métodos existentes ............................................................ 168 8.6.3. Uso de comportamientos en el modelo ............................................... 168 8.7. Sintaxis extendida del esquema .............................................................. 169 8.7.1. Atributos........................................................................................ 169 8.7.2. Detalles de las columnas .................................................................. 171 8.7.3. Claves externas .............................................................................. 172 8.7.4. Índices .......................................................................................... 173 8.7.5. Columnas vacías ............................................................................. 173 8.7.6. Tablas i18n .................................................................................... 174 8.7.7. Más allá del schema.yml: schema.xml................................................ 174 8.8. No crees el modelo dos veces ................................................................. 175 8.8.1. Construir la estructura SQL de la base de datos en función de un esquema existente ................................................................................................. 176 8.8.2. Construir un modelo de datos en formato YAML a partir de una base de datos existente ........................................................................................ 176 Capítulo 9. Enlaces y sistema de enrutamiento ........................................... 179 9.1. ¿Qué es el enrutamiento?....................................................................... 179 9.1.1. URL como instrucciones de servidor ................................................... 179 9.1.2. URL como parte de la interfaz ........................................................... 180 9.1.3. Cómo funciona................................................................................ 182 9.2. Reescritura de URL................................................................................ 183 9.3. Helpers de enlaces ................................................................................ 185 9.3.1. Hiperenlaces, botones y formularios .................................................. 186 9.3.2. Opciones de los helpers de enlaces .................................................... 187 9.3.3. Opciones GET y POST falsas ............................................................. 187 9.3.4. Forzando los parámetros de la petición como variables de tipo GET ........ 188 9.3.5. Utilizando rutas absolutas................................................................. 189 9.4. Configuración del sistema de enrutamiento............................................... 190 9.4.1. Reglas y patrones ........................................................................... 191 9.4.2. Restricciones en los patrones ............................................................ 192 9.4.3. Asignando valores por defecto .......................................................... 194 9.4.4. Acelerando el sistema de enrutamiento mediante el uso de los nombres de las reglas ................................................................................................ 194 9.4.5. Añadiendo la extensión .html ............................................................ 195 www.librosweb.es 6
  • 7. Symfony, la guía definitiva 9.4.6. Creando reglas sin el archivo routing.yml ........................................... 196 9.5. Trabajando con rutas en las acciones....................................................... 197 Capítulo 10. Formularios ............................................................................. 198 10.1. Helpers de formularios ......................................................................... 198 10.1.1. Etiqueta principal de los formularios................................................. 198 10.1.2. Elementos comunes de formulario ................................................... 199 10.1.3. Campos para introducir fechas ........................................................ 202 10.1.4. Editor de textos avanzado .............................................................. 203 10.1.5. Selección de idioma y país .............................................................. 204 10.2. Helpers de formularios para objetos....................................................... 205 10.2.1. Llenando listas desplegables con objetos .......................................... 206 10.2.2. Creando una lista desplegable a partir de una columna que es clave externa ................................................................................................... 207 10.2.3. Modificando objetos ....................................................................... 208 10.3. Validación de formularios ..................................................................... 209 10.3.1. Validadores .................................................................................. 210 10.3.2. Archivo de validación ..................................................................... 211 10.3.3. Mostrando el formulario de nuevo .................................................... 212 10.3.4. Mostrando los mensajes de error en el formulario .............................. 213 10.3.5. Mostrando de nuevo los datos introducidos ....................................... 215 10.3.6. Validadores estándar de Symfony .................................................... 216 10.3.7. Validadores con nombre ................................................................. 219 10.3.8. Restringiendo la validación a un método ........................................... 220 10.3.9. ¿Cuál es el aspecto de un archivo de validación?................................ 220 10.4. Validaciones complejas ........................................................................ 221 10.4.1. Creando un validador propio ........................................................... 221 10.4.2. Utilizando la sintaxis de los arrays para los campos de formulario ........ 223 10.4.3. Ejecutando un validador en un campo vacío ...................................... 223 Capítulo 11. Integración con Ajax ............................................................... 226 11.1. Helpers básicos de JavaScript ............................................................... 226 11.1.1. JavaScript en las plantillas .............................................................. 227 11.1.2. Actualizando un elemento DOM ....................................................... 228 11.1.3. Aplicaciones que se degradan correctamente..................................... 229 11.2. Prototype ........................................................................................... 229 11.3. Helpers de Ajax .................................................................................. 231 11.3.1. Enlaces Ajax ................................................................................. 233 11.3.2. Formularios Ajax ........................................................................... 233 11.3.3. Ejecución periódica de funciones remotas ......................................... 236 11.4. Parámetros para la ejecución remota ..................................................... 236 11.4.1. Actualizar elementos diferentes en función del estado de la respuesta .. 236 11.4.2. Actualizar un elemento según su posición ......................................... 237 11.4.3. Actualizar un elemento en función de una condición ........................... 238 www.librosweb.es 7
  • 8. Symfony, la guía definitiva 11.4.4. Determinando el método de una petición Ajax ................................... 238 11.4.5. Permitiendo la ejecución de un script ............................................... 239 11.4.6. Creando callbacks.......................................................................... 239 11.5. Creando efectos visuales ...................................................................... 240 11.6. JSON ................................................................................................. 241 11.7. Interacciones complejas con Ajax .......................................................... 243 11.7.1. Autocompletar .............................................................................. 244 11.7.2. Arrastrar y soltar ........................................................................... 245 11.7.3. Listas ordenables........................................................................... 246 11.7.4. Edición directa de contenidos .......................................................... 247 Capítulo 12. Uso de la cache........................................................................ 249 12.1. Guardando la respuesta en la cache....................................................... 249 12.1.1. Opciones de la cache global ............................................................ 249 12.1.2. Guardando una acción en la cache ................................................... 250 12.1.3. Guardando un elemento parcial, un componente o un slot de componentes en la cache .............................................................................................. 252 12.1.4. Guardando un fragmento de plantilla en la cache ............................... 253 12.1.5. Configuración dinámica de la cache.................................................. 255 12.1.6. Uso de la cache super rápida........................................................... 257 12.2. Eliminando elementos de la cache ......................................................... 258 12.2.1. Borrando toda la cache .................................................................. 258 12.2.2. Borrando partes de la cache............................................................ 258 12.2.3. Estructura del directorio de la cache................................................. 261 12.2.4. Borrado manual de la cache ............................................................ 261 12.3. Probando y monitorizando la cache ........................................................ 262 12.3.1. Creando un entorno de ejecución intermedio ..................................... 262 12.3.2. Monitorizando el rendimiento .......................................................... 263 12.3.3. Pruebas de rendimiento (benchmarking)........................................... 264 12.3.4. Identificando elementos de la cache................................................. 264 12.4. HTTP 1.1 y la cache del lado del cliente .................................................. 264 12.4.1. Uso de la cabecera ETag para evitar el envío de contenidos no modificados ............................................................................................. 265 12.4.2. Añadiendo la cabecera Last-Modified para evitar el envío de contenidos todavía válidos......................................................................................... 265 12.4.3. Añadiendo cabeceras Vary para permitir varias versiones de la página en la cache...................................................................................................... 266 12.4.4. Añadiendo la cabecera Cache-Control para permitir la cache en el lado del cliente .................................................................................................... 266 Capítulo 13. Internacionalización y localización .......................................... 268 13.1. Cultura del usuario .............................................................................. 268 13.1.1. Indicando la cultura por defecto ...................................................... 268 13.1.2. Modificando la cultura de un usuario ................................................ 269 13.1.3. Determinando la cultura de forma automática ................................... 270 www.librosweb.es 8
  • 9. Symfony, la guía definitiva 13.2. Estándares y formatos ......................................................................... 270 13.2.1. Mostrando datos según la cultura del usuario .................................... 270 13.2.2. Obteniendo información en una aplicación localizada .......................... 272 13.3. Información textual en la base de datos ................................................. 272 13.3.1. Creando un esquema para una aplicación localizada ........................... 272 13.3.2. Usando los objetos i18n generados .................................................. 273 13.4. Traducción de la interfaz ...................................................................... 274 13.4.1. Configurando la traducción ............................................................. 275 13.4.2. Usando el helper de traducción........................................................ 275 13.4.3. Utilizando un archivo de diccionario ................................................. 275 13.4.4. Trabajando con diccionarios ............................................................ 277 13.4.5. Trabajando con otros elementos que requieren traducción .................. 277 13.4.6. Cómo realizar traducciones complejas .............................................. 278 13.4.7. Utilizando el helper de traducción fuera de una plantilla ...................... 279 Capítulo 14. Generadores ............................................................................ 281 14.1. Generación de código en función del modelo ........................................... 281 14.1.1. Scaffolding y administración ........................................................... 281 14.1.2. Generando e iniciando el código ...................................................... 282 14.1.3. Modelo de datos de ejemplo............................................................ 283 14.2. Scaffolding ......................................................................................... 283 14.2.1. Generando el scaffolding ................................................................ 283 14.2.2. Iniciando el scaffolding ................................................................... 286 14.3. Creando la parte de administración de las aplicaciones ............................. 286 14.3.1. Iniciando un módulo de administración ............................................. 287 14.3.2. Un vistazo al código generado ......................................................... 288 14.3.3. Conceptos básicos del archivo de configuración generator.yml ............. 289 14.4. Configuración del generador ................................................................. 290 14.4.1. Campos ....................................................................................... 291 14.4.2. Opciones de los campos ................................................................. 291 14.4.3. Modificando la vista ....................................................................... 297 14.4.4. Opciones específicas para la vista "list"............................................. 299 14.4.5. Opciones específicas para la vista "edit" ........................................... 305 14.4.6. Trabajando con claves externas....................................................... 308 14.4.7. Añadiendo nuevas interacciones ...................................................... 310 14.4.8. Validación de formularios................................................................ 209 14.4.9. Restringiendo las acciones del usuario mediante credenciales .............. 313 14.5. Modificando el aspecto de los módulos generados .................................... 313 14.5.1. Utilizando una hoja de estilos propia ................................................ 314 14.5.2. Creando una cabecera y un pie propios ............................................ 314 14.5.3. Modificando el tema....................................................................... 315 Capítulo 15. Pruebas unitarias y funcionales ............................................... 318 15.1. Automatización de pruebas................................................................... 318 www.librosweb.es 9
  • 10. Symfony, la guía definitiva 15.1.1. Pruebas unitarias y funcionales ....................................................... 318 15.1.2. Desarrollo basado en pruebas ......................................................... 319 15.1.3. El framework de pruebas Lime ........................................................ 320 15.2. Pruebas unitarias ................................................................................ 321 15.2.1. ¿Qué aspecto tienen las pruebas unitarias? ....................................... 321 15.2.2. Métodos para las pruebas unitarias .................................................. 322 15.2.3. Parámetros para las pruebas........................................................... 324 15.2.4. La tarea test-unit .......................................................................... 325 15.2.5. Stubs, Fixtures y carga automática de clases .................................... 326 15.3. Pruebas funcionales ............................................................................. 329 15.3.1. ¿Cómo son las pruebas funcionales? ................................................ 329 15.3.2. Navegando con el objeto sfTestBrowser ............................................ 330 15.3.3. Utilizando asertos .......................................................................... 333 15.3.4. Utilizando los selectores CSS........................................................... 335 15.3.5. Trabajando en el entorno de pruebas ............................................... 337 15.3.6. La tarea test-functional .................................................................. 338 15.4. Recomendaciones sobre el nombre de las pruebas ................................... 338 15.5. Otras utilidades para pruebas ............................................................... 339 15.5.1. Ejecutando las pruebas en grupos.................................................... 340 15.5.2. Acceso a la base de datos ............................................................... 340 15.5.3. Probando la cache ......................................................................... 341 15.5.4. Probando las interacciones en el lado del cliente ................................ 342 Capítulo 16. Herramientas para la administración de aplicaciones .............. 345 16.1. Logs .................................................................................................. 345 16.1.1. Logs de PHP ................................................................................. 345 16.1.2. Logs de Symfony........................................................................... 346 16.2. Depuración de aplicaciones................................................................... 349 16.2.1. Modo debug de Symfony ................................................................ 350 16.2.2. Excepciones Symfony..................................................................... 351 16.2.3. Extensión Xdebug.......................................................................... 352 16.2.4. Barra de depuración web ................................................................ 353 16.2.5. Depuración manual........................................................................ 358 16.3. Cargando datos en una base de datos .................................................... 359 16.3.1. Sintaxis del archivo de datos........................................................... 360 16.3.2. Importando los datos ..................................................................... 361 16.3.3. Usando tablas relacionadas ............................................................. 361 16.4. Instalando aplicaciones ........................................................................ 362 16.4.1. Preparando un proyecto para transferirlo con FTP .............................. 362 16.4.2. Usando rsync para transferir archivos incrementalmente..................... 363 16.4.3. Ignorando los archivos innecesarios ................................................. 365 16.4.4. Administrando una aplicación en producción...................................... 366 Capítulo 17. Personalizar Symfony .............................................................. 369 www.librosweb.es 10
  • 11. Symfony, la guía definitiva 17.1. Mixins................................................................................................ 369 17.1.1. Comprendiendo la herencia múltiple................................................. 369 17.1.2. Clases de tipo mixing ..................................................................... 370 17.1.3. Declarar que una clase se puede extender ........................................ 371 17.1.4. Registrando las extensiones ............................................................ 373 17.1.5. Extendiendo de forma más precisa................................................... 375 17.2. Factorías ............................................................................................ 377 17.3. Utilizando componentes de otros frameworks .......................................... 378 17.4. Plugins .............................................................................................. 380 17.4.1. Plugins disponibles para Symfony .................................................... 380 17.4.2. Instalando un plugin ...................................................................... 382 17.4.3. Estructura de un plugin .................................................................. 385 17.4.4. Cómo crear un plugin..................................................................... 389 Capítulo 18. Rendimiento ............................................................................ 396 18.1. Optimizando el servidor ....................................................................... 396 18.2. Optimizando el modelo ........................................................................ 397 18.2.1. Optimizando la integración de Propel................................................ 397 18.2.2. Limitando el número de objetos que se procesan ............................... 398 18.2.3. Minimizando el número de consultas mediante Joins .......................... 399 18.2.4. Evitar el uso de arrays temporales ................................................... 401 18.2.5. Saltándose el ORM......................................................................... 403 18.2.6. Optimizando la base de datos.......................................................... 404 18.3. Optimizando la vista ............................................................................ 405 18.3.1. Utilizando el fragmento de código más rápido.................................... 405 18.3.2. Optimizando el sistema de enrutamiento .......................................... 406 18.3.3. Saltándose la plantilla .................................................................... 406 18.3.4. Reduciendo los helpers por defecto .................................................. 407 18.3.5. Comprimiendo la respuesta............................................................. 407 18.4. Optimizando la cache........................................................................... 408 18.4.1. Borrando partes de la cache de forma selectiva ................................. 408 18.4.2. Generando páginas para la cache .................................................... 409 18.4.3. Guardando los datos de la cache en una base de datos ....................... 410 18.4.4. Saltándose Symfony ...................................................................... 411 18.4.5. Guardando en la cache el resultado de una función ............................ 411 18.4.6. Guardando datos en la cache del servidor ......................................... 412 18.5. Desactivando las características que no se utilizan ................................... 413 18.6. Optimizando el código fuente ................................................................ 414 18.6.1. Compilación del núcleo de Symfony ................................................. 414 18.6.2. El plugin sfOptimizer ...................................................................... 415 Capítulo 19. Configuración avanzada........................................................... 417 19.1. Opciones de Symfony .......................................................................... 417 19.1.1. Acciones y módulos por defecto....................................................... 417 www.librosweb.es 11
  • 12. Symfony, la guía definitiva 19.1.2. Activando características opcionales ................................................. 419 19.1.3. Configuración de cada característica................................................. 421 19.2. Extendiendo la carga automática de clases ............................................. 424 19.3. Estructura de archivos propia................................................................ 426 19.3.1. La estructura de archivos básica ...................................................... 427 19.3.2. Modificando la estructura de archivos ............................................... 428 19.3.3. Modificando el directorio raíz del proyecto......................................... 429 19.3.4. Enlazando las librerías de Symfony .................................................. 429 19.4. Comprendiendo el funcionamiento de los manejadores de configuración ..... 430 19.4.1. Manejadores de configuración por defecto......................................... 431 19.4.2. Creando un manejador propio ......................................................... 432 19.5. Controlando las opciones de PHP ........................................................... 434 www.librosweb.es 12
  • 13. Symfony, la guía definitiva Capítulo 1. Introducción a Symfony Capítulo 1. Introducción a Symfony ¿Qué puedes hacer con Symfony? ¿Qué necesitas para utilizarlo? Este capítulo responde a todas estas preguntas. 1.1. Symfony en pocas palabras Un framework simplifica el desarrollo de una aplicación mediante la automatización de al- gunos de los patrones utilizados para resolver las tareas comunes. Además, un frame- work proporciona estructura al código fuente, forzando al desarrollador a crear código más legible y más fácil de mantener. Por último, un framework facilita la programación de aplicaciones, ya que encapsula operaciones complejas en instrucciones sencillas. Symfony es un completo framework diseñado para optimizar, gracias a sus característi- cas, el desarrollo de las aplicaciones web. Para empezar, separa la lógica de negocio, la lógica de servidor y la presentación de la aplicación web. Proporciona varias herramientas y clases encaminadas a reducir el tiempo de desarrollo de una aplicación web compleja. Además, automatiza las tareas más comunes, permitiendo al desarrollador dedicarse por completo a los aspectos específicos de cada aplicación. El resultado de todas estas venta- jas es que no se debe reinventar la rueda cada vez que se crea una nueva aplicación web. Symfony está desarrollado completamente con PHP 5. Ha sido probado en numerosos pr- oyectos reales y se utiliza en sitios web de comercio electrónico de primer nivel. Symfony es compatible con la mayoría de gestores de bases de datos, como MySQL, PostgreSQL, Oracle y SQL Server de Microsoft. Se puede ejecutar tanto en plataformas *nix (Unix, Li- nux, etc.) como en plataformas Windows. A continuación se muestran algunas de sus características. 1.1.1. Características de Symfony Symfony se diseñó para que se ajustara a los siguientes requisitos: ▪ Fácil de instalar y configurar en la mayoría de plataformas (y con la garantía de que funciona correctamente en los sistemas Windows y *nix estándares) ▪ Independiente del sistema gestor de bases de datos ▪ Sencillo de usar en la mayoría de casos, pero lo suficientemente flexible como para adaptarse a los casos más complejos ▪ Basado en la premisa de “convenir en vez de configurar”, en la que el desarrolla- dor solo debe configurar aquello que no es convencional ▪ Sigue la mayoría de mejores prácticas y patrones de diseño para la web ▪ Preparado para aplicaciones empresariales y adaptable a las políticas y arquitec- turas propias de cada empresa, además de ser lo suficientemente estable como para desarrollar aplicaciones a largo plazo www.librosweb.es 13
  • 14. Symfony, la guía definitiva Capítulo 1. Introducción a Symfony ▪ Código fácil de leer que incluye comentarios de phpDocumentor y que permite un mantenimiento muy sencillo ▪ Fácil de extender, lo que permite su integración con librerías desarrolladas por terceros 1.1.1.1. Automatización de características de proyectos web Symfony automatiza la mayoría de elementos comunes de los proyectos web, como por ejemplo: ▪ La capa de internacionalización que incluye Symfony permite la traducción de los datos y de la interfaz, así como la adaptación local de los contenidos. ▪ La capa de presentación utiliza plantillas y layouts que pueden ser creados por diseñadores HTML sin ningún tipo de conocimiento del framework. Los helpers in- cluidos permiten minimizar el código utilizado en la presentación, ya que encap- sulan grandes bloques de código en llamadas simples a funciones. ▪ Los formularios incluyen validación automatizada y relleno automático de datos (“repopulation”), lo que asegura la obtención de datos correctos y mejora la ex- periencia de usuario. ▪ Los datos incluyen mecanismos de escape que permiten una mejor protección contra los ataques producidos por datos corruptos. ▪ La gestión de la caché reduce el ancho de banda utilizado y la carga del servidor. ▪ La autenticación y la gestión de credenciales simplifican la creación de secciones restringidas y la gestión de la seguridad de usuario. ▪ El sistema de enrutamiento y las URL limpias permiten considerar a las direccio- nes de las páginas como parte de la interfaz, además de estar optimizadas para los buscadores. ▪ El soporte de e-mail incluido y la gestión de APIs permiten a las aplicaciones web interactuar más allá de los navegadores. ▪ Los listados son más fáciles de utilizar debido a la paginación automatizada, el fil- trado y la ordenación de datos. ▪ Los plugins, las factorías (patrón de diseño “Factory”) y los “mixin” permiten rea- lizar extensiones a medida de Symfony. ▪ Las interacciones con Ajax son muy fáciles de implementar mediante los helpers que permiten encapsular los efectos JavaScript compatibles con todos los nave- gadores en una única línea de código. 1.1.1.2. Entorno de desarrollo y herramientas Symfony puede ser completamente personalizado para cumplir con los requisitos de las empresas que disponen de sus propias políticas y reglas para la gestión de proyectos y la programación de aplicaciones. Por defecto incorpora varios entornos de desarrollo dife- rentes e incluye varias herramientas que permiten automatizar las tareas más comunes de la ingeniería del software: www.librosweb.es 14
  • 15. Symfony, la guía definitiva Capítulo 1. Introducción a Symfony ▪ Las herramientas que generan automáticamente código han sido diseñadas para hacer prototipos de aplicaciones y para crear fácilmente la parte de gestión de las aplicaciones. ▪ El framework de desarrollo de pruebas unitarias y funcionales proporciona las he- rramientas ideales para el desarrollo basado en pruebas (“test-driven develop- ment”). ▪ La barra de depuración web simplifica la depuración de las aplicaciones, ya que muestra toda la información que los programadores necesitan sobre la página en la que están trabajando. ▪ La interfaz de línea de comandos automatiza la instalación de las aplicaciones en- tre servidores. ▪ Es posible realizar cambios “en caliente” de la configuración (sin necesidad de reiniciar el servidor). ▪ El completo sistema de log permite a los administradores acceder hasta el último detalle de las actividades que realiza la aplicación. 1.1.2. ¿Quién ha desarrollado Symfony y por qué motivo? La primera versión de Symfony fue publicada en Octubre de 2005 por Fabien Potencier, fundador del proyecto y coautor de este libro. Fabien es el presidente de Sensio (http://www.sensio.com/), una empresa francesa de desarrollo de aplicaciones web conoci- da por sus innovaciones en este campo. En el año 2003, Fabien realizó una investigación sobre las herramientas de software libre disponibles para el desarrollo de aplicaciones web con PHP. Fabien llegó a la conclusión de que no existía ninguna herramienta con esas características. Después del lanzamiento de la versión 5 de PHP, decidió que las herramientas disponibles habían alcanzado un grado de madurez suficiente como para integrarlas en un framework completo. Fabien empleó un año entero para desarrollar el núcleo de Symfony, basando su trabajo en el framework Mojavi (que también era un framework que seguía el funcionamiento MVC), en la herramienta Propel para el mapeo de objetos a bases de datos (conocido como ORM, de “object-relational mapping”) y en los helpers empleados por Ruby on Rails en sus plantillas. Fabien desarrolló originalmente Symfony para utilizarlo en los proyectos de Sensio, ya que disponer de un framework efectivo es la mejor ayuda para el desarrollo eficiente y rápido de las aplicaciones. Además, el desarrollo web se hace más intuitivo y las aplicac- iones resultantes son más robustas y más fáciles de mantener. El framework se utilizó por primera vez en el desarrollo de un sitio de comercio electrónico para un vendedor de lencería y posteriormente se utilizó en otros proyectos. Después de utilizar Symfony en algunos proyectos, Fabien decidió publicarlo bajo una li- cencia de software libre. Sus razones para liberar el proyecto fueron para donar su traba- jo a la comunidad, aprovechar la respuesta de los usuarios, mostrar la experiencia de Sensio y porque considera que es divertido hacerlo. www.librosweb.es 15
  • 16. Symfony, la guía definitiva Capítulo 1. Introducción a Symfony NOTA ¿Por qué lo llamaron “Symfony” y no “CualquierNombreFramework”? Porque Fabien quería una nombre corto que tuviera una letra ‘s’ (de Sensio) y una letra ‘f’ (de framework), que fuera fácil de recordar y que no estuviera asociado a otra herramienta de desarrollo. Además, no le gustan las mayúsculas. “Symfony” era muy parecido a lo que estaba buscando, aunque no es una palabra co- rrecta en el idioma inglés (la palabra correcta es “symphony”), y además estaba libre como nombre de proyecto. La otra alternativa era “baguette”. Para que Symfony fuera un proyecto de software libre exitoso, debía tener una documen- tación amplia y en inglés, para aumentar la incorporación de usuarios al proyecto. Fabien pidió a su compañero de trabajo François Zaninotto, el otro coautor de este libro, que in- vestigara el código fuente del programa y escribiera un libro sobre Symfony. Aunque el proceso fue arduo, cuando el proyecto se lanzó públicamente, la documentación era sufi- ciente como para atraer a muchos desarrolladores. El resto es historia. 1.1.3. La comunidad Symfony En cuanto se abrió al público el sitio web de Symfony (http://www.symfony-project.org/) muchos desarrolladores de todo el mundo se descargaron e instalaron el framework, co- menzaron a leer la documentación y construyeron sus primeras aplicaciones con Sym- fony, aumentando poco a poco la popularidad de Symfony. En ese momento, los frameworks para el desarrollo de aplicaciones web estaban en pleno apogeo, y era muy necesario disponer de un completo framework realizado con PHP. Symfony proporcionaba una solución irresistible a esa carencia, debido a la calidad de su código fuente y a la gran cantidad de documentación disponible, dos ventajas muy im- portantes sobre otros frameworks disponibles. Los colaboradores aparecieron en seguida proponiendo parches y mejoras, detectando los errores de la documentación y realizando otras tareas muy importantes. El repositorio público de código fuente y el sistema de notificación de errores y mejoras mediante tickets permite varias formas de contribuir al proyecto y todos los voluntarios son bienvenidos. Fabien continua siendo el mayor contribuidor de código al repositorio y se encarga de garantizar la calidad del código. Actualmente, el foro de Symfony, las listas de correo y el IRC ofrecen otras alternativas válidas para el soporte del framework, con el que cada pregunta suele obtener una media de 4 respuestas. Cada día nuevos usuarios instalan Symfony y el wiki y la sección de fragmentos de código almacenan una gran cantidad de documentación generada por los usuarios. Cada semana el número de aplicaciones conocidas desarrolladas con Symfony se incrementa en 5 y el aumento continua. La comunidad Symfony es el tercer pilar del framework y esperamos que tu también te unas a ella después de leer este libro. 1.1.4. ¿Es adecuado Symfony para mí? Independientemente de que seas un experto programador de PHP 5 o un principiante en el desarrollo de aplicaciones web, podrás utilizar Symfony de forma sencilla. El principal argumento para decidir si deberías o no utilizar Symfony es el tamaño del proyecto. www.librosweb.es 16
  • 17. Symfony, la guía definitiva Capítulo 1. Introducción a Symfony Si tu proyecto consiste en desarrollar un sitio web sencillo con 5 o 10 páginas diferentes, acceso simple a bases de datos y no es importante asegurar un gran rendimiento o una documentación adecuada, deberías realizar tu proyecto solo con PHP. En ese caso, no vas a obtener grandes ventajas por utilizar un framework de desarrollo de aplicaciones web, además de que utilizar objetos y el modelo MVC (Modelo Vista Controlador) sola- mente va a ralentizar el desarrollo de tu proyecto. Además, Symfony no está optimizado para ejecutarse de forma eficiente en un servidor compartido en el que los scripts de PHP se ejecutan solamente mediante CGI (Common Gateway Interface). Por otra parte, si desarrollas aplicaciones web complejas con mucha lógica de negocio, no es recomendable utilizar solo PHP. Para asegurar el mantenimiento y las ampliaciones fu- turas de la aplicación, es necesario que el código sea ligero, legible y efectivo. Si quieres incorporar los últimos avances en interacción con usuarios (como por ejemplo Ajax), puedes acabar escribiendo cientos de líneas de JavaScript. Si quieres desarrollar aplicac- iones de forma divertida y muy rápida, no es aconsejable utilizar solo PHP. En todos es- tos casos, deberías utilizar Symfony. Si eres un desarrollador web profesional, ya conoces todas las ventajas de utilizar un fra- mework de desarrollo de aplicaciones web y solo necesitas un framework que sea madu- ro, bien documentado y con una gran comunidad que lo apoye. En este caso, deberías dejar de buscar porque Symfony es lo que necesitas. SUGERENCIA Si quieres ver una demostración visual de las posibilidades de Symfony, deberías ver los vídeos o screencasts que están disponibles en el sitio web de Symfony. En estas demostraciones se ve lo rápido y divertido que es desarrollar aplicaciones web con Symfony. 1.2. Conceptos básicos Antes de empezar con Symfony, deberías conocer algunos conceptos básicos. Puedes sal- tarte esta sección si conoces el significado de OOP, ORM, RAD, DRY, KISS, TDD, YAML y PEAR. 1.2.1. PHP 5 Symfony está programado en PHP 5 (http://www.php.net/) y está enfocado al desarrollo de aplicaciones web en el mismo lenguaje de programación. Por este motivo, es obligatorio disponer de unos conocimientos avanzados de PHP 5 para sacar el máximo partido al framework. Los programadores que conocen PHP 4 pero que no han trabajado con PHP 5 deberían centrarse en el nuevo modelo orientado a objetos de PHP. 1.2.2. Programación Orientada a Objetos (OOP) La programación orientada a objetos (OOP, por sus siglas en inglés Object-oriented pro- gramming) no va a ser explicada en este capítulo, ya que se necesitaría un libro entero para ello. Como Symfony hace un uso continuo de los mecanismos orientados a objetos www.librosweb.es 17
  • 18. Symfony, la guía definitiva Capítulo 1. Introducción a Symfony disponibles en PHP 5, es un requisito obligatorio el conocer la OOP antes de aprender Symfony. En la Wikipedia se explica la OOP de la siguiente manera: “ La idea de la programación orientada a objetos es que una aplicación se puede considerar como una colección de unidades individuales, llamadas objetos, que interactúan entre sí. Los programas tradicionales pueden considerarse como una colección de funciones o como una lista de instrucciones de programación.” PHP 5 incluye los conceptos de clase, objeto, método, herencia y muchos otros propios de la programación orientada a objetos. Aquellos que no estén familiarizados con estos conceptos, deberían consultar la documentación oficial de PHP disponible en http://www.php.net/manual/es/language.oop5.basic.php . 1.2.3. Métodos mágicos Uno de los puntos fuertes de los objetos de PHP es la utilización de los “métodos mági- cos”. Este tipo de métodos permiten redefinir el comportamiento de las clases sin modifi- car el código externo. Con estos métodos es posible que la sintaxis de PHP sea más con- cisa y más fácil de extender. Además, estos métodos son fáciles de reconocer ya que sus nombres siempre empiezan con 2 guiones bajos seguidos (__). Por ejemplo, al mostrar un objeto, PHP busca de forma implícita un método llamado __- toString() en ese objeto y que permite comprobar si se ha creado una visualización per- sonalizada para ese objeto: $miObjeto = new miClase(); echo $miObjeto; // Se busca el método mágico echo $miObjeto->__toString(); Symfony utiliza los métodos mágicos de PHP, por lo que deberías conocer su funcionam- iento. La documentación oficial de PHP también explica los métodos mágicos (http://www.php.net/manual/es/language.oop5.magic.php ) 1.2.4. PEAR (PHP Extension and Application Repository) PEAR es un “framework y sistema de distribución para componentes PHP reutilizables”. PEAR permite descargar, instalar, actualizar y desinstalar scripts de PHP. Si se utiliza un paquete de PEAR, no es necesario decidir donde guardar los scripts, cómo hacer que se puedan utilizar o cómo extender la línea de comandos (CLI). PEAR es un proyecto creado por la comunidad de usuarios de PHP, está desarrollado con PHP y se incluye en las distribuciones estándar de PHP. SUGERENCIA El sitio web de PEAR, http://pear.php.net/, incluye documentación y muchos paquetes agrupa- dos en categorías. www.librosweb.es 18
  • 19. Symfony, la guía definitiva Capítulo 1. Introducción a Symfony PEAR es el método más profesional para instalar librerías externas en PHP. Symfony aconseja el uso de PEAR para disponer de una instalación única y centralizada que pueda ser utilizada en varios proyectos. Los plugins de Symfony son paquetes de PEAR con una configuración especial. El propio framework Symfony también está disponible como paq- uete de PEAR. Afortunadamente, no es necesario conocer la sintaxis de PEAR para utilizar Symfony. Lo único necesario es entender su funcionamiento y tenerlo instalado. Para comprobar si PEAR está instalado en el sistema, se puede escribir lo siguiente en una línea de comandos: > pear info pear El comando anterior muestra la versión de PEAR instalada en el sistema. El proyecto Symfony dispone de su propio repositorio PEAR, también llamado canal. Los canales de PEAR solamente se pueden utilizar a partir de la versión 1.4.0, por lo que es necesario actualizar PEAR si se dispone de una versión anterior. Para actualizar PEAR, se debe ejecutar el siguiente comando: > pear upgrade PEAR 1.2.5. Mapeo de Objetos a Bases de datos (ORM) Las bases de datos siguen una estructura relacional. PHP 5 y Symfony por el contrario son orientados a objetos. Por este motivo, para acceder a la base de datos como si fuera orientada a objetos, es necesario una interfaz que traduzca la lógica de los objetos a la lógica relacional. Esta interfaz se denomina “mapeo de objetos a bases de datos” (ORM, de sus siglas en inglés “object-relational mapping”). Un ORM consiste en una serie de objetos que permiten acceder a los datos y que contie- nen en su interior cierta lógica de negocio. Una de las ventajas de utilizar estas capas de abstracción de objetos/relacional es que evita utilizar una sintaxis específica de un sistema de bases de datos concreto. Esta capa transforma automáticamente las llamadas a los objetos en consultas SQL optimizadas para el sistema gestor de bases de datos que se está utilizando en cada momento. De esta forma, es muy sencillo cambiar a otro sistema de bases de datos completamente diferente en mitad del desarrollo de un proyecto. Estas técnicas son útiles por ejemplo cuando se debe desarrollar un prototipo rápido de una aplicación y el cliente aun no ha decidido el sistema de bases de datos que más le conviene. El prototipo se puede realizar utilizando SQLite y después se puede cambiar fácilmente a MySQL, PostgreSQL u Oracle cuando el cliente se haya decidido. El cambio se puede realizar modificando solamente una línea en un archivo de configuración. La capa de abstracción utilizada encapsula toda la lógica de los datos. El resto de la apli- cación no tiene que preocuparse por las consultas SQL y el código SQL que se encarga del acceso a la base de datos es fácil de encontrar. Los desarrolladores especializados en la programación con bases de datos pueden localizar fácilmente el código. Utilizar objetos en vez de registros y clases en vez de tablas tiene otra ventaja: se pue- den definir nuevos métodos de acceso a las tablas. Por ejemplo, si se dispone de una www.librosweb.es 19
  • 20. Symfony, la guía definitiva Capítulo 1. Introducción a Symfony tabla llamada Cliente con 2 campos, Nombre y Apellido, puede que sea necesario acce- der directamente al nombre completo (NombreCompleto). Con la programación orientada a objetos, este problema se resuelve añadiendo un nuevo método de acceso a la clase Cl- iente de la siguiente forma: public function getNombreCompleto() { return $this->getNombre().' '.$this->getApellido(); } Todas las funciones comunes de acceso a los datos y toda la lógica de negocio relaciona- da con los datos se puede mantener dentro de ese tipo de objetos. Por ejemplo, la sigu- iente clase CarritoCompra almacena los productos (que son objetos). Para obtener el pre- cio total de los productos del carrito y así realizar el pago, se puede añadir un método llamado getTotal() de la siguiente forma: public function getTotal() { $total = 0; foreach ($this->getProductos() as $producto) { $total += $producto->getPrecio() * $item->getCantidad(); } return $total; } Y eso es todo. Imagina cuanto te hubiera costado escribir una consulta SQL que hiciera lo mismo. Propel, que también es un proyecto de software libre, es una de las mejores capas de abstracción de objetos/relacional disponibles en PHP 5. Propel está completamente inte- grado en Symfony, por lo que la mayoría de las manipulaciones de datos realizadas en este libro siguen la sintaxis de Propel. En el libro se describe la utilización de los objetos de Propel, pero se puede encontrar una referencia más completa en el sitio web de Pro- pel (http://propel.phpdb.org/trac/). 1.2.6. Desarrollo rápido de aplicaciones (RAD) Durante mucho tiempo, la programación de aplicaciones web fue un tarea tediosa y muy lenta. Siguiendo los ciclos habituales de la ingeniería del software (como los propuestos por el Proceso Racional Unificado o Rational Unified Process) el desarrollo de una aplica- ción web no puede comenzar hasta que se han establecido por escrito una serie de requi- sitos, se han creado los diagramas UML (Unified Modeling Language) y se ha producido abundante documentación sobre el proyecto. Este modelo se veía favorecido por la baja velocidad de desarrollo, la falta de versatilidad de los lenguajes de programación (antes de ejecutar el programa se debe construir, compilar y reiniciar) y sobre todo por el hecho de que los clientes no estaban dispuestos a adaptarse a otras metodologías. Hoy en día, las empresas reaccionan más rápidamente y los clientes cambian de opinión constantemente durante el desarrollo de los proyectos. De este modo, los equipos de de- sarrollo deben adaptarse a esas necesidades y tienen que poder cambiar la estructura de una aplicación de forma rápida. Afortunadamente, el uso de lenguajes de script como www.librosweb.es 20
  • 21. Symfony, la guía definitiva Capítulo 1. Introducción a Symfony Perl y PHP permiten seguir otras estrategias de programación, como RAD (desarrollo rá- pido de aplicaciones) y el desarrollo ágil de software. Una de las ideas centrales de esta metodología es que el desarrollo empieza lo antes po- sible para que el cliente pueda revisar un prototipo que funciona y pueda indicar el cami- no a seguir. A partir de ahí, la aplicación se desarrolla de forma iterativa, en la que cada nueva versión incorpora nuevas funcionalidades y se desarrolla en un breve espacio de tiempo. Las consecuencias de estas metodologías para el desarrollador son numerosas. El progra- mador no debe pensar acerca de las versiones futuras al incluir una nueva funcionalidad. Los métodos utilizados deben ser lo más sencillos y directos posibles. Estas ideas se re- sumen en el principio denominado KISS: ¡Haz las cosas sencillas, idiota! (Keep It Simple, Stupid) Cuando se modifican los requisitos o cuando se añade una nueva funcionalidad, normal- mente se debe reescribir parte del código existente. Este proceso se llama refactorización y sucede a menudo durante el desarrollo de una aplicación web. El código suele moverse a otros lugares en función de su naturaleza. Los bloques de código repetidos se refactori- zan en un único lugar, aplicando el principio DRY: No te repitas (Don’t Repeat Yourself). Para asegurar que la aplicación sigue funcionando correctamente a pesar de los cambios constantes, se necesita una serie de pruebas unitarias que puedan ser automatizadas. Si están bien escritas, las pruebas unitarias permiten asegurar que nada ha dejado de func- ionar después de haber refactorizado parte del código de la aplicación. Algunas metodo- logías de desarrollo de aplicaciones obligan a escribir las pruebas antes que el propio có- digo, lo que se conoce como TDD: desarrollo basado en pruebas (test-driven develop- ment). NOTA Existen otros principios y hábitos relacionados con el desarrollo ágil de aplicaciones. Una de las metodologías más efectivas se conoce como XP: programación extrema (Extreme Programming). La documentación relacionada con XP puede enseñarte mucho sobre el desarrollo rápido y efectivo de las aplicaciones. Una buena forma de empezar con XP son los libros escritos por Kent Beck en la editorial Addison-Wesley. Symfony es la herramienta ideal para el RAD. De hecho, el framework ha sido desarrolla- do por una empresa que aplica el RAD a sus propios proyectos. Por este motivo, apren- der a utilizar Symfony no es como aprender un nuevo lenguaje de programación, sino que consite en aprender a tomar las decisiones correctas para desarrollar las aplicaciones de forma más efectiva. El sitio web del proyecto Symfony incluye un tutorial en el que se explica paso a paso el desarrollo de una aplicación utilizando las técnicas de desarrollo ágil de aplicaciones. La aplicación se llama Askeet (http://www.symfony-project.org/askeet) y su lectura es muy re- comendada para todos aquellos que quieran adentrarse en el desarrollo ágil de aplicaciones. www.librosweb.es 21
  • 22. Symfony, la guía definitiva Capítulo 1. Introducción a Symfony 1.2.7. YAML Según el sitio web oficial de YAML (http://www.yaml.org/), YAML es “un formato para seria- lizar datos que es fácil de procesar por las máquinas, fácil de leer para las personas y fá- cil de interactuar con los lenguajes de script”. Dicho de otra forma, YAML es un lenguaje muy sencillo que permite describir los datos como en XML, pero con una sintaxis mucho más sencilla. YAML es un formato especialmente útil para describir datos que pueden ser transformados en arrays simples y asociativos, como por ejemplo: $casa = array( 'familia' => array( 'apellido' => 'García', 'padres' => array('Antonio', 'María'), 'hijos' => array('Jose', 'Manuel', 'Carmen') ), 'direccion' => array( 'numero' => 34, 'calle' => 'Gran Vía', 'ciudad' => 'Cualquiera', 'codigopostal' => '12345' ) ); Este array de PHP se puede crear directamente procesando esta cadena de texto en for- mato YAML: casa: familia: apellido: García padres: - Antonio - María hijos: - Jose - Manuel - Carmen direccion: numero: 34 calle: Gran Vía ciudad: Cualquiera codigopostal: "12345" YAML utiliza la tabulación para indicar su estructura, los elementos que forman una sec- uencia utilizan un guión medio y los pares clave/valor de los array asociativos se separan con dos puntos. YAML también dispone de una notación resumida para describir la misma estructura con menos líneas: los arrays simples se definen con [] y los arrays asociativos se definen con {}. Por tanto, los datos YAML anteriores se pueden escribir de forma abre- viada de la siguiente manera: casa: familia: { apellido: García, padres: [Antonio, María], hijos: [Jose, Manuel, Carmen] } direccion: { numero: 34, direccion: Gran Vía, ciudad: Cualquiera, codigopostal: "12345" } www.librosweb.es 22
  • 23. Symfony, la guía definitiva Capítulo 1. Introducción a Symfony YAML es el acrónimo de “YAML Ain’t Markup Language” (”YAML No es un Lenguaje de Marcado”) y se pronuncia “yamel”. El formato se lleva utilizando desde 2001 y existen utilidades para procesar YAML en una gran variedad de lenguajes de programación. SUGERENCIA La especificación completa del formato YAML se puede encontrar en http://www.yaml.org/. Como se ha visto, YAML es mucho más rápido de escribir que XML (ya que no hacen falta las etiquetas de cierre y el uso continuo de las comillas) y es mucho más poderoso que los tradicionales archivos .ini (ya que estos últimos no soportan la herencia y las estruc- turas complejas). Por este motivo, Symfony utiliza el formato YAML como el lenguaje preferido para almacenar su configuración. Este libro contiene muchos archivos YAML, pero como es tan sencillo, probablemente no necesites aprender más detalles de este formato. 1.3. Resumen Symfony es un framework para desarrollar aplicaciones web creado con PHP 5. Añade una nueva capa por encima de PHP y proporciona herramientas que simplifican el desa- rrollo de las aplicaciones web complejas. Este libro contiene todos los detalles del funcio- namiento de Symfony y para entenderlo, solamente es necesario estar familiarizado con los conceptos básicos de la programación moderna, sobre todo la programación orientada a objetos (OOP), el mapeo de objetos a bases de datos (ORM) y el desarrollo rápido de aplicaciones (RAD). El único requisito técnico obligatorio es el conocimiento de PHP 5. www.librosweb.es 23
  • 24. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony Capítulo 2. Explorando el interior de Symfony La primera vez que se accede al código fuente de una aplicación realizada con Symfony, puede desanimar un poco a los nuevos desarrolladores. El código está dividido en mu- chos directorios y muchos scripts y los archivos son un conjunto de clases PHP, código HTML e incluso una mezcla de los dos. Además, existen referencias a clases que no se pueden encontrar dentro del directorio del proyecto y la anidación de directorios puede llegar hasta los seis niveles. Sin embargo, cuando comprendas las razones que están detrás de esta aparente complejidad, lo verás como algo completamente natural y no querrás cambiar la estructura de una aplicación Symfony por ninguna otra. En este capí- tulo se explica con detalle toda esa estructura. 2.1. El patrón MVC Symfony está basado en un patrón clásico del diseño web conocido como arquitectura MVC, que está formado por tres niveles: ▪ El modelo representa la información con la que trabaja la aplicación, es decir, su lógica de negocio. ▪ La vista transforma el modelo en una página web que permite al usuario interact- uar con ella. ▪ El controlador se encarga de procesar las interacciones del usuario y realiza los cambios apropiados en el modelo o en la vista. La Figura 2-1 ilustra el funcionamiento del patrón MVC. La arquitectura MVC separa la lógica de negocio (el modelo) y la presentación (la vista) por lo que se consigue un mantenimiento más sencillo de las aplicaciones. Si por ejemplo una misma aplicación debe ejecutarse tanto en un navegador estándar como un un nave- gador de un dispositivo móvil, solamente es necesario crear una vista nueva para cada dispositivo; manteniendo el controlador y el modelo original. El controlador se encarga de aislar al modelo y a la vista de los detalles del protocolo utilizado para las peticiones (HTTP, consola de comandos, email, etc.). El modelo se encarga de la abstracción de la lógica relacionada con los datos, haciendo que la vista y las acciones sean independientes de, por ejemplo, el tipo de gestor de bases de datos utilizado por la aplicación. www.librosweb.es 24
  • 25. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony Figura 2.1. El patrón MVC 2.1.1. Las capas de la arquitectura MVC Para poder entender las ventajas de utilizar el patrón MVC, se va a transformar una apli- cación simple realizada con PHP en una aplicación que sigue la arquitectura MVC. Un buen ejemplo para ilustrar esta explicación es el de mostrar una lista con las últimas en- tradas o artículos de un blog. 2.1.1.1. Programación simple Utilizando solamente PHP normal y corriente, el script necesario para mostrar los artícu- los almacenados en una base de datos se muestra en el siguiente listado: Listado 2-1 - Un script simple <?php // Conectar con la base de datos y seleccionarla $conexion = mysql_connect('localhost', 'miusuario', 'micontrasena'); mysql_select_db('blog_db', $conexion); // Ejecutar la consulta SQL $resultado = mysql_query('SELECT fecha, titulo FROM articulo', $conexion); ?> <html> <head> <title>Listado de Artículos</title> </head> <body> <h1>Listado de Artículos</h1> <table> <tr><th>Fecha</th><th>Titulo</th></tr> www.librosweb.es 25
  • 26. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony <?php // Mostrar los resultados con HTML while ($fila = mysql_fetch_array($resultado, MYSQL_ASSOC)) { echo "t<tr>n"; printf("tt<td> %s </td>n", $fila['fecha']); printf("tt<td> %s </td>n", $fila['titulo']); echo "t</tr>n"; } ?> </table> </body> </html> <?php // Cerrar la conexion mysql_close($conexion); ?> El script anterior es fácil de escribir y rápido de ejecutar, pero muy difícil de mantener y actualizar. Los principales problemas del código anterior son: ▪ No existe protección frente a errores (¿qué ocurre si falla la conexión con la base de datos?). ▪ El código HTML y el código PHP están mezclados en el mismo archivo e incluso en algunas partes están entrelazados. ▪ El código solo funciona si la base de datos es MySQL. 2.1.1.2. Separando la presentación Las llamadas a echo y printf del listado 2-1 dificultan la lectura del código. De hecho, modificar el código HTML del script anterior para mejorar la presentación es un follón de- bido a cómo está programado. Así que el código va a ser dividido en dos partes. En pri- mer lugar, el código PHP puro con toda la lógica de negocio se incluye en el script del controlador, como se muestra en el listado 2-2. Listado 2-2 - La parte del controlador, en index.php <?php // Conectar con la base de datos y seleccionarla $conexion = mysql_connect('localhost', 'miusuario', 'micontrasena'); mysql_select_db('blog_db', $conexion); // Ejecutar la consulta SQL $resultado = mysql_query('SELECT fecha, titulo FROM articulo', $conexion); // Crear el array de elementos para la capa de la vista $articulos = array(); while ($fila = mysql_fetch_array($resultado, MYSQL_ASSOC)) { $articulos[] = $fila; www.librosweb.es 26
  • 27. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony } // Cerrar la conexión mysql_close($conexion); // Incluir la lógica de la vista require('vista.php'); ?> El código HTML, que contiene cierto código PHP a modo de plantilla, se almacena en el script de la vista, como se muestra en el listado 2-3. Listado 2-3 - La parte de la vista, en vista.php <html> <head> <title>Listado de Artículos</title> </head> <body> <h1>Listado de Artículos</h1> <table> <tr><th>Fecha</th><th>Título</th></tr> <?php foreach ($articulos as $articulo): ?> <tr> <td><?php echo $articulo['fecha'] ?></td> <td><?php echo $articulo['titulo'] ?></td> </tr> <?php endforeach; ?> </table> </body> </html> Una buena regla general para determinar si la parte de la vista está suficientemente lim- pia de código es que debería contener una cantidad mínima de código PHP, la suficiente como para que un diseñador HTML sin conocimientos de PHP pueda entenderla. Las ins- trucciones más comunes en la parte de la vista suelen ser echo, if/else, foreach/endfo- reach y poco más. Además, no se deben incluir instrucciones PHP que generen etiquetas HTML. Toda la lógica se ha centralizado en el script del controlador, que solamente contiene có- digo PHP y ningún tipo de HTML. De hecho, y como puedes imaginar, el mismo controla- dor se puede reutilizar para otros tipos de presentaciones completamente diferentes, co- mo por ejemplo un archivo PDF o una estructura de tipo XML. 2.1.1.3. Separando la manipulación de los datos La mayor parte del script del controlador se encarga de la manipulación de los datos. Pe- ro, ¿qué ocurre si se necesita la lista de entradas del blog para otro controlador, por ejemplo uno que se dedica a generar el canal RSS de las entradas del blog? ¿Y si se quie- ren centralizar todas las consultas a la base de datos en un único sitio para evitar duplici- dades? ¿Qué ocurre si cambia el modelo de datos y la tabla articulo pasa a llamarse ar- ticulo_blog? ¿Y si se quiere cambiar a PostgreSQL en vez de MySQL? Para poder hacer www.librosweb.es 27
  • 28. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony todo esto, es imprescindible eliminar del controlador todo el código que se encarga de la manipulación de los datos y ponerlo en otro script, llamado el modelo, tal y como se muestra en el listado 2-4. Listado 2-4 - La parte del modelo, en modelo.php <?php function getTodosLosArticulos() { // Conectar con la base de datos y seleccionarla $conexion = mysql_connect('localhost', 'miusuario', 'micontrasena'); mysql_select_db('blog_db', $conexion); // Ejecutar la consulta SQL $resultado = mysql_query('SELECT fecha, titulo FROM articulo', $conexion); // Crear el array de elementos para la capa de la vista $articulos = array(); while ($fila = mysql_fetch_array($resultado, MYSQL_ASSOC)) { $articulos[] = $fila; } // Cerrar la conexión mysql_close($conexion); return $articulos; } ?> El controlador modificado se puede ver en el listado 2-5. Listado 2-5 - La parte del controlador, modificada, en index.php <?php // Incluir la lógica del modelo require_once('modelo.php'); // Obtener la lista de artículos $articulos = getTodosLosArticulos(); // Incluir la lógica de la vista require('vista.php'); ?> Ahora el controlador es mucho más fácil de leer. Su única tarea es la de obtener los da- tos del modelo y pasárselos a la vista. En las aplicaciones más complejas, el controlador se encarga además de procesar las peticiones, las sesiones de los usuarios, la autentica- ción, etc. El uso de nombres apropiados para las funciones del modelo hacen que sea in- necesario añadir comentarios al código del controlador. www.librosweb.es 28
  • 29. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony El script del modelo solamente se encarga del acceso a los datos y puede ser reorganiza- do a tal efecto. Todos los parámetros que no dependen de la capa de datos (como por ejemplo los parámetros de la petición del usuario) se deben obtener a través del contro- lador y por tanto, no se puede acceder a ellos directamente desde el modelo. Las funcio- nes del modelo se pueden reutilizar fácilmente en otros controladores. 2.1.2. Separación en capas más allá del MVC El principio más importante de la arquitectura MVC es la separación del código del pro- grama en tres capas, dependiendo de su naturaleza. La lógica relacionada con los datos se incluye en el modelo, el código de la presentación en la vista y la lógica de la aplica- ción en el controlador. La programación se puede simplificar si se utilizan otros patrones de diseño. De esta for- ma, las capas del modelo, la vista y el controlador se pueden subidividir en más capas. 2.1.2.1. Abstracción de la base de datos La capa del modelo se puede dividir en la capa de acceso a los datos y en la capa de abs- tracción de la base de datos. De esta forma, las funciones que acceden a los datos no utilizan sentencias ni consultas que dependen de una base de datos, sino que utilizan otras funciones para realizar las consultas. Así, si se cambia de sistema gestor de bases de datos, solamente es necesario actualizar la capa de abstracción de la base de datos. El listado 2-6 muestra una capa de acceso a datos específica para MySQL y el listado 2-7 muestra una capa sencilla de abstracción de la base de datos. Listado 2-6 - La parte del modelo correspondiente a la abstracción de la base de datos <?php function crear_conexion($servidor, $usuario, $contrasena) { return mysql_connect($servidor, $usuario, $contrasena); } function cerrar_conexion($conexion) { mysql_close($conexion); } function consulta_base_de_datos($consulta, $base_datos, $conexion) { mysql_select_db($base_datos, $conexion); return mysql_query($consulta, $conexion); } function obtener_resultados($resultado) { return mysql_fetch_array($resultado, MYSQL_ASSOC); } www.librosweb.es 29
  • 30. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony Listado 2-7 - La parte del modelo correspondiente al acceso a los datos function getTodosLosArticulos() { // Conectar con la base de datos $conexion = crear_conexion('localhost', 'miusuario', 'micontrasena'); // Ejecutar la consulta SQL $resultado = consulta_base_de_datos('SELECT fecha, titulo FROM articulo', 'blog_db', $conexion); // Crear el array de elementos para la capa de la vista $articulos = array(); while ($fila = obtener_resultados($resultado)) { $articulos[] = $fila; } // Cerrar la conexión cerrar_conexion($conexion); return $articulos; } ?> Como se puede comprobar, la capa de acceso a datos no contiene funciones dependien- tes de ningún sistema gestor de bases de datos, por lo que es independiente de la base de datos utilizada. Además, las funciones creadas en la capa de abstracción de la base de datos se pueden reutilizar en otras funciones del modelo que necesiten acceder a la base de datos. NOTA Los ejemplos de los listados 2-6 y 2-7 no son completos, y todavía hace falta añadir algo de código para tener una completa abstracción de la base de datos (abstraer el código SQL mediante un constructor de consultas independiente de la base de datos, añadir todas las funciones a una clase, etc.) El propósito de este libro no es mostrar cómo se puede escribir todo ese código, ya que en el capítulo 8 se muestra cómo Symfony realiza de forma automática toda la abstracción. 2.1.2.2. Los elementos de la vista La capa de la vista también puede aprovechar la separación de código. Las páginas web suelen contener elementos que se muestran de forma idéntica a lo largo de toda la apli- cación: cabeceras de la página, el layout genérico, el pie de página y la navegación glo- bal. Normalmente sólo cambia el interior de la página. Por este motivo, la vista se separa en un layout y en una plantilla. Normalmente, el layout es global en toda la aplicación o al menos en un grupo de páginas. La plantilla sólo se encarga de visualizar las variables definidas en el controlador. Para que estos componentes interaccionen entre sí correcta- mente, es necesario añadir cierto código. Siguiendo estos principios, la parte de la vista del listado 2-3 se puede separar en tres partes, como se muestra en los listados 2-8, 2-9 y 2-10. www.librosweb.es 30
  • 31. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony Listado 2-8 - La parte de la plantilla de la vista, en miplantilla.php <h1>Listado de Artículos</h1> <table> <tr><th>Fecha</th><th>Título</th></tr> <?php foreach ($articulos as $articulo): ?> <tr> <td><?php echo $articulo['fecha'] ?></td> <td><?php echo $articulo['titulo'] ?></td> </tr> <?php endforeach; ?> </table> Listado 2-9 - La parte de la lógica de la vista <?php $titulo = 'Listado de Artículos'; $contenido = include('miplantilla.php'); ?> Listado 2-10 - La parte del layout de la vista <html> <head> <title><?php echo $titulo ?></title> </head> <body> <?php echo $contenido ?> </body> </html> 2.1.2.3. Acciones y controlador frontal En el ejemplo anterior, el controlador no se encargaba de realizar muchas tareas, pero en las aplicaciones web reales el controlador suele tener mucho trabajo. Una parte im- portante de su trabajo es común a todos los controladores de la aplicación. Entre las ta- reas comunes se encuentran el manejo de las peticiones del usuario, el manejo de la se- guridad, cargar la configuración de la aplicación y otras tareas similares. Por este motivo, el controlador normalmente se divide en un controlador frontal, que es único para cada aplicación, y las acciones, que incluyen el código específico del controlador de cada página. Una de las principales ventajas de utilizar un controlador frontal es que ofrece un punto de entrada único para toda la aplicación. Así, en caso de que sea necesario impedir el ac- ceso a la aplicación, solamente es necesario editar el script correspondiente al controla- dor frontal. Si la aplicación no dispone de controlador frontal, se debería modificar cada uno de los controladores. 2.1.2.4. Orientación a objetos Los ejemplos anteriores utilizan la programación procedimental. Las posibilidades que ofrecen los lenguajes de programación modernos para trabajar con objetos permiten simplificar la programación, ya que los objetos pueden encapsular la lógica, pueden www.librosweb.es 31
  • 32. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony heredar métodos y atributos entre diferentes objetos y proporcionan una serie de con- venciones claras sobre la forma de nombrar a los objetos. La implementación de una arquitectura MVC en un lenguaje de programación que no está orientado a objetos puede encontrarse con problemas de namespaces y código duplicado, dificultando la lectura del código de la aplicación. La orientación a objetos permite a los desarrolladores trabajar con objetos de la vista, objetos del controlador y clases del modelo, transformando las funciones de los ejemplos anteriores en métodos. Se trata de un requisito obligatorio para las arquitecturas de tipo MVC. SUGERENCIA Si quieres profundizar en el tema de los patrones de diseño para las aplicaciones web en el contex- to de la orientación a objetos, puedes leer “Patterns of Enterprise Application Architecture” de Mar- tin Fowler (Addison-Wesley, ISBN: 0-32112-742-0). El código de ejemplo del libro de Fowler está escrito en Java y en C#, pero es bastante fácil de leer para los programadores de PHP. 2.1.3. La implementación del MVC que realiza Symfony Piensa por un momento cuántos componentes se necesitarían para realizar una página sencilla que muestre un listado de las entradas o artículos de un blog. Como se muestra en la figura 2-2, son necesarios los siguientes componentes: ▪ La capa del Modelo ▪ Abstracción de la base de datos ▪ Acceso a los datos ▪ La capa de la Vista ▪ Vista ▪ Plantilla ▪ Layout ▪ La capa del Controlador ▪ Controlador frontal ▪ Acción En total son siete scripts, lo que parecen muchos archivos para abrir y modificar cada vez que se crea una página. Afortunadamente, Symfony simplifica este proceso. Symfony to- ma lo mejor de la arquitectura MVC y la implementa de forma que el desarrollo de aplica- ciones sea rápido y sencillo. En primer lugar, el controlador frontal y el layout son comunes para todas las acciones de la aplicación. Se pueden tener varios controladores y varios layouts, pero solamente es obligatorio tener uno de cada. El controlador frontal es un componente que sólo tiene có- digo relativo al MVC, por lo que no es necesario crear uno, ya que Symfony lo genera de forma automática. www.librosweb.es 32
  • 33. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony La otra buena noticia es que las clases de la capa del modelo también se generan au- tomáticamente, en función de la estructura de datos de la aplicación. La librería Propel se encarga de esta generación automática, ya que crea el esqueleto o estructura básica de las clases y genera automáticamente el código necesario. Cuando Propel encuentra res- tricciones de claves foráneas (o externas) o cuando encuentra datos de tipo fecha, crea métodos especiales para acceder y modificar esos datos, por lo que la manipulación de datos se convierte en un juego de niños. La abstracción de la base de datos es completa- mente invisible al programador, ya que la realiza otro componente específico llamado Creole. Así, si se cambia el sistema gestor de bases de datos en cualquier momento, no se debe reescribir ni una línea de código, ya que tan sólo es necesario modificar un pará- metro en un archivo de configuración. Por último, la lógica de la vista se puede transformar en un archivo de configuración sen- cillo, sin necesidad de programarla. Figura 2.2. El flujo de trabajo de Symfony Considerando todo lo anterior, el ejemplo de la página que muestra un listado con todas las entradas del blog solamente requiere de tres archivos en Symfony, que se muestran en los listados 2-11, 2-12 y 2-13. Listado 2-11 - Acción listado, en miproyecto/apps/miaplicacion/modules/weblog/act- ions/actions.class.php www.librosweb.es 33
  • 34. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony <?php class weblogActions extends sfActions { public function executeListado() { $this->articulos = ArticuloPeer::doSelect(new Criteria()); } } ?> Listado 2-12 - Plantilla listado, en miproyecto/apps/miaplicacion/modules/weblog/ templates/listadoSuccess.php <h1>Listado de Artículos</h1> <table> <tr><th>Fecha</th><th>Título</th></tr> <?php foreach ($articulos as $articulo): ?> <tr> <td><?php echo $articulo->getFecha() ?></td> <td><?php echo $articulo->getTitulo() ?></td> </tr> <?php endforeach; ?> </table> Listado 2-13 - Vista listado, en miproyecto/apps/miaplicacion/modules/weblog/con- fig/view.yml listadoSuccess: metas: { title: Listado de Artículos } También es necesario definir el layout, como el del listado 2-14, pero el mismo layout se puede reutilizar muchas veces. Listado 2-14 - Layout, en miproyecto/apps/miaplicacion/templates/layout.php <html> <head> <?php echo include_title() ?> </head> <body> <?php echo $sf_data->getRaw('sf_content') ?> </body> </html> Estos scripts son todo lo que necesita la aplicación del ejemplo. El código mostrado es el necesario para crear la misma página que generaba el script simple del listado 2-1. Sym- fony se encarga del resto de tareas, como hacer que los componentes interactuen entre sí. Si se considera el número de líneas de código, el listado de entradas de blog creado según la arquitectura MVC no requiere más líneas ni más tiempo de programación que un script simple. Sin embargo, la arquitectura MVC proporciona grandes ventajas, como la organización del código, la reutilización, la flexibilidad y una programación mucho más entretenida. Por si fuera poco, crear la aplicación con Symfony permite crear páginas XHTML válidas, depurar fácilmente las aplicaciones, crear una configuración sencilla, www.librosweb.es 34
  • 35. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony abstracción de la base de datos utilizada, enrutamiento con URL limpias, varios entornos de desarrollo y muchas otras utilidades para el desarrollo de aplicaciones. 2.1.4. Las clases que forman el núcleo de Symfony La implementación que realiza Symfony de la arquitectura MVC incluye varias clases que se mencionan una y otra vez a lo largo del libro: ▪ sfController es la clase del controlador. Se encarga de decodificar la petición y transferirla a la acción correspondiente. ▪ sfRequest almacena todos los elementos que forman la petición (parámetros, co- okies, cabeceras, etc.) ▪ sfResponse contiene las cabeceras de la respuesta y los contenidos. El contenido de este objeto se transforma en la respuesta HTML que se envía al usuario. ▪ El singleton de contexto (que se obtiene mediante sfContext::getInstance()) al- macena una referencia a todos los objetos que forman el núcleo de Symfony y puede ser accedido desde cualquier punto de la aplicación. El capítulo 6 explica en detalle todos estos objetos. Como se ha visto, todas las clases de Symfony utilizan el prefijo sf, como también hacen todas las variables principales de Symfony en las plantillas. De esta forma, se evitan las colisiones en los nombres de clases y variables de Symfony y los nombres de tus propias clases y variables, además de que las clases del framework son más fáciles de reconocer. NOTA Entre las normas seguidas por el código de Symfony, se encuentra el estándar “UpperCamelCase” para el nombre de las clases y variables. Solamente existen dos excepciones: las clases del núcleo de Symfony empiezan por sf (por tanto en minúsculas) y las variables utilizadas en las plantillas que utilizan la sintaxis de separar las palabras con guiones bajos. Nota del traductor La notación “CamelCase” consiste en escribir frases o palabras compuestas eli- minando los espacios intermedios y poniendo en mayúscula la primera letra de cada palabra. La variante “UpperCamelCase” también pone en mayúscula la primera letra de todas. 2.2. Organización del código Ahora que ya conoces los componentes que forman una aplicación de Symfony, a lo me- jor te estás preguntando sobre cómo están organizados. Symfony organiza el código fuente en una estructura de tipo proyecto y almacena los archivos del proyecto en una estructura estandarizada de tipo árbol. 2.2.1. Estructura del proyecto: Aplicaciones, Módulos y Acciones Symfony considera un proyecto como “un conjunto de servicios y operaciones disponibles bajo un determinado nombre de dominio y que comparten el mismo modelo de objetos”. Dentro de un proyecto, las operaciones se agrupan de forma lógica en aplicaciones. Nor- malmente, una aplicación se ejecuta de forma independiente respecto de otras www.librosweb.es 35
  • 36. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony aplicaciones del mismo proyecto. Lo habitual es que un proyecto contenga dos aplicacio- nes: una para la parte pública y otra para la parte de gestión, compartiendo ambas la misma base de datos. También es posible definir proyectos que estén formados por var- ios sitios web pequeños, cada uno de ellos considerado como una aplicación. En este ca- so, es importante tener en cuenta que los enlaces entre aplicaciones se deben indicar de forma absoluta. Cada aplicación está formada por uno o más módulos. Un módulo normalmente repre- senta a una página web o a un grupo de páginas con un propósito relacionado. Por ejem- plo, una aplicación podría tener módulos como home, articulos, ayuda, carritoCompra, cuenta, etc. Los módulos almacenan las acciones, que representan cada una de las operaciones que se puede realizar en un módulo. Por ejemplo el módulo carritoCompra puede definir acc- iones como anadir, mostrar y actualizar. Normalmente las acciones se describen med- iante verbos. Trabajar con acciones es muy similar a trabajar con las páginas de una aplicación web tradicional, aunque en este caso dos acciones diferentes pueden acabar mostrando la misma página (como por ejemplo la acción de añadir un comentario a una entrada de un blog, que acaba volviendo a mostrar la página de la entrada con el nuevo comentario). NOTA Nota del traductor En el párrafo anterior, la acción del carrito se llama anadir y no añadir, ya que el nombre de una acción también se utiliza como parte del nombre de un fichero y como parte del nombre de una función, por lo que se recomienda utilizar exclusivamente caracteres ASCII, y por tanto, no debería utilizarse la letra ñ. SUGERENCIA Si crees que todo esto es demasiado complicado para tu primer proyecto con Symfony, puedes agrupar todas las acciones en un único módulo, para simplificar la estructura de archivos. Cuando la aplicación se complique, puedes reorganizar las acciones en diferentes módulos. Como se co- menta en el capítulo 1, la acción de reescribir el código para mejorar su estructura o hacerlo más sencillo (manteniendo siempre su comportamiento original) se llama refactorización, y es algo muy común cuando se aplican los principios del RAD (“desarrollo rápido de aplicaciones”). La figura 2-3 muestra un ejemplo de organización del código para un proyecto de un blog, siguiendo la estructura de proyecto / aplicación / módulo / acción. No obstante, la estructura de directorios real del proyecto es diferente al esquema mostrado por esa figura. www.librosweb.es 36
  • 37. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony Figura 2.3. Ejemplo de organización del código 2.2.2. Estructura del árbol de archivos Normalmente, todos los proyectos web comparten el mismo tipo de contenidos, como por ejemplo: ▪ Una base de datos, como MySQL o PostgreSQL ▪ Archivo estáticos (HTML, imágenes, archivos de JavaScript, hojas de estilos, etc.) ▪ Archivos subidos al sitio web por parte de los usuarios o los administradores ▪ Clases y librerías PHP ▪ Librerías externas (scripts desarrollados por terceros) ▪ Archivos que se ejecutan por lotes (batch files) que normalmente son scripts que se ejecutan vía línea de comandos o mediante cron ▪ Archivos de log (las trazas que generan las aplicaciones y/o el servidor) ▪ Archivos de configuración Symfony proporciona una estructura en forma de árbol de archivos para organizar de for- ma lógica todos esos contenidos, además de ser consistente con la arquitectura MVC uti- lizada y con la agrupación proyecto / aplicación / módulo. Cada vez que se crea un nuevo proyecto, aplicación o módulo, se genera de forma automática la parte correspondiente de esa estructura. Además, la estructura se puede personalizar completamente, para re- organizar los archivos y directorios o para cumplir con las exigencias de organización de un cliente. 2.2.2.1. Estructura de la raíz del proyecto La raíz de cualquier proyecto Symfony contiene los siguientes directorios: apps/ frontend/ backend/ batch/ cache/ www.librosweb.es 37
  • 38. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony config/ data/ sql/ doc/ lib/ model/ log/ plugins/ test/ unit/ functional/ web/ css/ images/ js/ uploads/ La tabla 2-1 describe los contenidos de estos directorios Tabla 2-1. Directorios en la raíz de los proyectos Symfony Directorio Descripción Contiene un directorio por cada aplicación del proyecto (normalmente, frontend y apps/ backend para la parte pública y la parte de gestión respectivamente) Contiene los scripts de PHP que se ejecutan mediante la línea de comandos o mediante batch/ la programación de tareas para realizar procesos en lotes (batch processes) Contiene la versión cacheada de la configuración y (si está activada) la versión cacheada de las acciones y plantillas del proyecto. El mecanismo de cache (que se cache/ explica en el Capítulo 12) utiliza los archivos de este directorio para acelerar la respuesta a las peticiones web. Cada aplicación contiene un subdirectorio que guarda todos los archivos PHP y HTML preprocesados config/ Almacena la configuración general del proyecto En este directorio se almacenan los archivos relacionados con los datos, como por data/ ejemplo el esquema de una base de datos, el archivo que contiene las instrucciones SQL para crear las tablas e incluso un archivo de bases de datos de SQLite Contiene la documentación del proyecto, formada por tus propios documentos y por la doc/ documentación generada por PHPdoc Almacena las clases y librerías externas. Se suele guardar todo el código común a lib/ todas las aplicaciones del proyecto. El subdirectorio model/ guarda el modelo de objetos del proyecto (como se describe en el Capítulo 8) Guarda todos los archivos de log generados por Symfony. También se puede utilizar para guardar los logs del servidor web, de la base de datos o de cualquier otro log/ componente del proyecto. Symfony crea un archivo de log por cada aplicación y por cada entorno (los archivos de log se ven detalladamente en el Capítulo 16) Almacena los plugins instalados en la aplicación (el Capítulo 17 aborda el tema de los plugins/ plugins) www.librosweb.es 38
  • 39. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony Contiene las pruebas unitarias y funcionales escritas en PHP y compatibles con el test/ framework de pruebas de Symfony (que se explica en el capítulo 15). Cuando se crea un proyecto, Symfony crea algunos pruebas básicas La raíz del servidor web. Los únicos archivos accesibles desde Internet son los que se web/ encuentran en este directorio 2.2.2.2. Estructura de cada aplicación Todas las aplicaciones de Symfony tienen la misma estructura de archivos y directorios: apps/ [nombre aplicacion]/ config/ i18n/ lib/ modules/ templates/ layout.php error.php error.txt La tabla 2-2 describe los subdirectorios de una aplicación Tabla 2-2. Subdirectorios de cada aplicación Symfony Directorio Descripción Contiene un montón de archivos de configuración creados con YAML. Aquí se almacena la mayor parte de la configuración de la aplicación, salvo los parámetros config/ propios del framework. También es posible redefinir en este directorio los parámetros por defecto si es necesario. El Capítulo 5 contiene más detalles sobre la configuración de las aplicaciones Contiene todos los archivos utilizados para la internacionalización de la aplicación, sobre todo los archivos que traducen la interfaz (el Capítulo 13 detalla la i18n/ internacionalización). La internacionalización también se puede realizar con una base de datos, en cuyo caso este directorio no se utilizaría lib/ Contiene las clases y librerías utilizadas esclusivamente por la aplicación modules/ Almacena los módulos que definen las características de la aplicación Contiene las plantillas globales de la aplicación, es decir, las que utilizan todos los templates/ módulos. Por defecto contiene un archivo llamado layout.php, que es el layout principal con el que se muestran las plantillas de los módulos NOTA En las aplicaciones recién creadas, los directorios i18n/, lib/ y modules/ están vacíos. Las clases de una aplicación no pueden acceder a los métodos o atributos de otras aplica- ciones del mismo proyecto. Además, los enlaces entre 2 aplicaciones de un mismo pro- yecto se deben indicar de forma absoluta. Esta última restricción es importante durante www.librosweb.es 39
  • 40. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony la inicialización del proyecto, que es cuando debes elegir como dividir el proyecto en aplicaciones. 2.2.2.3. Estructura de cada módulo Cada aplicación contiene uno o más módulos. Cada módulo tiene su propio subdirectorio dentro del directorio modules y el nombre del directorio es el que se elige durante la crea- ción del módulo. Esta es la estructura de directorios típica de un módulo: apps/ [nombre aplicacion]/ modules/ [nombre modulo]/ actions/ actions.class.php config/ lib/ templates/ indexSuccess.php validate/ La tabla 2-3 describe los subirectorios de un módulo. Tabla 2-3. Subdirectorios de cada módulo Directorio Descripción Normalmente contiene un único archivo llamado actions.class.php y que actions/ corresponde a la clase que almacena todas las acciones del módulo. También es posible crear un archivo diferente para cada acción del módulo Puede contener archivos de configuración adicionales con parámetros exclusivos del config/ módulo lib/ Almacena las clases y librerías utilizadas exclusivamente por el módulo Contiene las plantillas correspondientes a las acciones del módulo. Cuando se crea templates/ un nuevo módulo, automáticamente se crea la plantilla llamada indexSuccess.php Contiene archivos de configuración relacionados con la validación de formularios (que validate/ se verá en el Capítulo 10) NOTA En los módulos recién creados, los directorios config/, lib/ y validate/ están vacíos. 2.2.2.4. Estructura del sitio web Existen pocas restricciones sobre la estructura del directorio web, que es el directorio que contiene los archivos que se pueden acceder de forma pública. Si se utilizan algunas con- venciones básicas en los nombres de los subdirectorios, se pueden simplificar las planti- llas. La siguiente es una estructura típica del directorio web: www.librosweb.es 40
  • 41. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony web/ css/ images/ js/ uploads/ Normalmente, los archivos estáticos se organizan según los directorios de la tabla 2-4. Tabla 2-4. Subdirectorios habituales en la carpeta web Directorio Descripción css/ Contiene los archivos de hojas de estilo creados con CSS (archivos con extensión .css images/ Contiene las imágenes del sitio con formato .jpg, .png o .gif js/ Contiene los archivos de JavaScript con extensión .js Se almacenan los archivos subidos por los usuarios. Aunque normalmente este directorio contiene imágenes, no se debe confundir con el directorio que almacena las uploads/ imágenes del sitio (images/). Esta distinción permite sincronizar los servidores de desarrollo y de producción sin afectar a las imágenes subidas por los usuarios NOTA Aunque es muy recomendable mantener la estructura definida por defecto, es posible modificarla para adaptarse a las necesidades específicas de cada proyecto, como por ejemplo los proyectos que se ejecutan en servidores con sus propias estructuras de directorios definidas y con otras políti- cas para el desarrollo de las aplicaciones. El Capítulo 19 explica en detalle cómo modificar la es- tructura de directorios definida por Symfony. 2.3. Herramientas comunes Algunas técnicas se utilizan una y otra vez en Symfony, por lo que es fácil encontrarse con ellas a lo largo de este libro y en el desarrollo de tus proyectos. Entre estas técnicas se encuentran los contenedores de parámetros (parameter holders), las constantes y la carga automática de clases. 2.3.1. Contenedores de parámetros Muchas de las clases de Symfony contienen algún contenedor de parámetros. Se trata de una forma eficiente de encapsular los atributos y así poder utilizar métodos getter y set- ter sencillos. La clase sfResponse por ejemplo incluye un contenedor de parámetros que se puede obtener mediante el método getParameterHolder(). Todos los contenedores de parámetros almacenan sus datos de la misma forma, como se muestra en el listado 2-15. Listado 2-15 - Uso del contenedor de parámetros de sfResponse $respuesta->getParameterHolder()->set('parametro', 'valor'); echo $respuesta->getParameterHolder()->get('parametro'); => 'valor' www.librosweb.es 41
  • 42. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony La mayoría de clases que contienen contenedores de parámetros proporcionan métodos abreviados para las operaciones de tipo get/set. La clase sfResponse es una de esas cla- ses, ya que el código abreviado del listado 2-16 obtiene el mismo resultado que el código original del listado 2-15. Listado 2-16 - Uso de los métodos abreviados del contenedor de parámetros de sfResponse $respuesta->setParameter('parametro', 'valor'); echo $respuesta->getParameter('parametro'); => 'valor' El método getter del contenedor de parámetros permite la definición de un segundo parámetro que actua de valor por defecto. De esta manera, se obtiene una protección efectiva y sencilla frente a los errores. El listado 2-17 contiene un ejemplo de su uso. Listado 2-17 - Uso de valores por defecto en las funciones de tipo getter // El parámetro llamado 'parametro' no está definido, por lo que el getter devuelve un valor vacío echo $respuesta->getParameter('parametro'); => null // El valor por defecto se puede obtener con sentencias condicionales if ($respuesta->hasParameter('parametro')) { echo $respuesta->getParameter('parametro'); } else { echo 'valor_por_defecto'; } => 'valor_por_defecto' // El siguiente método es mucho más rápido echo $respuesta->getParameter('parametro', 'valor_por_defecto'); => 'valor_por_defecto' Los contenedores de parámetros permiten la utilización de namespaces. Si se utiliza un tercer parámetro en un getter o en un setter, ese parámetro se utiliza como namespace del parámetro y por tanto, el parámetro sólo estará definido dentro de ese namespace. El listado 2-18 muestra un ejemplo. Listado 2-18 - Uso de un namespace en el contenedor de parámetros de sfResponse $respuesta->setParameter('parametro', 'valor1'); $respuesta->setParameter('parametro', 'valor2', 'mi/namespace'); echo $respuesta->getParameter('parametro'); => 'valor1' echo $respuesta->getParameter('parametro', null, 'mi/namespace'); => 'valor2' www.librosweb.es 42
  • 43. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony También es posible añadir contenedores de parámetros a tus propias clases, para apro- vechar las ventajas de su sintaxis. El listado 2-19 muestra un ejemplo de cómo definir una clase con un contenedor de parámetros. Listado 2-19 - Añadir un contenedor de parámetros a una clase class MiClase { protected $contenedor_parametros = null; public function initialize ($parametros = array()) { $this->contenedor_parametros = new sfParameterHolder(); $this->contenedor_parametros->add($parametros); } public function getContenedorParametros() { return $this->contenedor_parametros; } } 2.3.2. Constantes Aunque pueda parecer sorprendente, Symfony no define casi ninguna constante. La razón es que las constantes tienen un inconveniente en PHP: no se puede modificar su valor una vez definidas. Por este motivo, Symfony utiliza su propio objeto para almace- nar la configuración, llamado sfConfig, y que reemplaza a las constantes. Este objeto proporciona métodos estáticos para poder acceder a los parámetros desde cualquier pun- to de la aplicación. El listado 2-20 muestra el uso de los métodos de la clase sfConfig. Listado 2-20 - Uso de los métodos de la clase sfConfig en vez de constantes // En vez de constantes de PHP, define('MI_CONSTANTE', 'valor'); echo MI_CONSTANTE; // Symfony utiliza el objeto sfConfig sfConfig::set('mi_constante', 'valor'); echo sfConfig::get('mi_constante'); Los métodos de sfConfig permiten definir valores por defecto y se puede invocar el mé- todo sfConfig::set() más de una vez sobre el mismo parámetro para modificar su valor. El capítulo 5 detalla el uso de los métodos de sfConfig. 2.3.3. Carga automática de clases Normalmente, cuando se utiliza un método de una clase o cuando se crea un objeto en PHP, se debe incluir antes la definición de esa clase. include 'clases/MiClase.php'; $miObjeto = new MiClase(); Sin embargo, en los proyectos complejos con muchas clases y una estructura de directo- rios con muchos niveles, requiere mucho trabajo incluir todas las clases necesarias www.librosweb.es 43
  • 44. Symfony, la guía definitiva Capítulo 2. Explorando el interior de Symfony indicando correctamente la ruta de cada clase. Symfony incluye una función spl_autolo- ad_register() para evitar la necesidad de los include y así poder escribir directamente: $miObjeto = new MiClase(); En este caso, Symfony busca la definición de la clase MiClase en todos los archivos con extensión .php que se encuentran en alguno de los directorios lib/ del proyecto. Si se encuentra la definición de la clase, se incluye de forma automática. De esta forma, si se guardan todas las clases en los directorios lib/, no es necesario in- cluir las clases de forma explícita. Por este motivo, los proyectos de Symfony no suelen incluir instrucciones de tipo include o require. NOTA Para mejorar el rendimiento, la carga automática de clases de Symfony busca durante la primera petición en una serie de directorios (que se definen en un archivo interno de configuración). Una vez realizada la búsqueda en los directorios, se guarda el nombre de todas las clases encontradas y su ruta de acceso en un array asociativo de PHP. Así, las siguientes peticiones no tienen que vol- ver a mirar todos los directorios en busca de las clases. Este comportamiento implica que se debe borrar la cache de Symfony cada vez que se añade o se mueve una clase del proyecto (salvo en el entorno de desarrollo, donde no es necesario). El comando utilizado para borrar la cache es sym- fony clear-cache. El Capítulo 12 explica con detalle el mecanismo de cache y la configuración de la carga automática de clases se muestra en el capítulo 19. 2.4. Resumen El uso de un framework que utiliza MVC obliga a dividir y organizar el código de acuerdo a las convenciones establecidas por el framework. El código de la presentación se guarda en la vista, el código de manipulación de datos se guarda en el modelo y la lógica de pro- cesamiento de las peticiones constituye el controlador. Aplicar el patrón MVC a una apli- cación resulta bastante útil además de restrictivo. Symfony es un framework de tipo MVC escrito en PHP 5. Su estructura interna se ha di- señado para obtener lo mejor del patrón MVC y la mayor facilidad de uso. Gracias a su versatilidad y sus posibilidades de configuración, Symfony es un framework adecuado pa- ra cualquier proyecto de aplicación web. Ahora que ya has aprendido la teoría que está detrás de Symfony, estas casi preparado para desarrollar tu primera aplicación. Pero antes de eso, necesitas tener instalado Sym- fony en tu servidor de desarrollo. www.librosweb.es 44
  • 45. Symfony, la guía definitiva Capítulo 3. Ejecutar aplicaciones Symfony Capítulo 3. Ejecutar aplicaciones Symfony Como se ha visto en los capítulos anteriores, el framework Symfony está formado por un conjunto de archivos escritos en PHP. Los proyectos realizados con Symfony utilizan es- tos archivos, por lo que la instalación de Symfony consiste en obtener esos archivos y hacer que estén disponibles para los proyectos. Como Symfony es un framework creado con PHP 5, es obligatorio disponer de la versión 5 de PHP. Por tanto, es necesario asegurarse de que se encuentra instalado, para lo cual se puede ejecutar el siguiente comando en la línea de comandos del sistema operativo: > php -v PHP 5.2.0 (cli) (built: Nov 2 2006 11:57:36) Copyright (c) 1997-2006 The PHP Group Zend Engine v2.2.0, Copyright (c) 1998-2006 Zend Technologies Si el número de la versión que se muestra es 5.0 o superior, ya es posible realizar la ins- talación de Symfony que se describe en este capítulo. 3.1. Instalando el entorno de pruebas Si lo único que quieres es comprobar lo que puede dar de sí Symfony, lo mejor es que te decantes por la instalación rápida. En este caso, se utiliza el “entorno de pruebas” o sandbox. El entorno de pruebas está formado por un conjunto de archivos. Contiene un proyecto vacío de Symfony e incluye todas las librerías necesarias (Symfony, Pake, Lime, Creole, Propel y Phing), una aplicación de prueba y la configuración básica. No es necesario reali- zar ninguna configuración en el servidor ni instalar ningún paquete adicional para que funcione correctamente. Para instalar el entorno de pruebas, se debe descargar su archivo comprimido desde http://www.symfony-project.org/get/sf_sandbox.tgz. Una vez descargado el archivo, es esen- cial asegurarse que tiene la extensión .tgz, ya que de otro modo no se descomprimirá correctamente. La extensión .tgz no es muy común en sistemas operativos tipo Win- dows, pero programas como WinRAR o 7-Zip lo pueden descomprimir sin problemas. A continuación, se descomprime su contenido en el directorio raíz del servidor web, que normalmente es web/ o www/. Para asegurar cierta uniformidad en la documentación, en este capítulo se supone que se ha descomprimido el entorno de pruebas en el directorio sf_sandbox/. ATENCIÓN Para hacer pruebas en un servidor local, se pueden colocar todos los archivos en la raíz del servi- dor web. Sin embargo, se trata de una mala práctica para los servidores de producción, ya que los usuarios pueden ver el funcionamiento interno de la aplicación. Se puede comprobar si se ha realizado correctamente la instalación del entorno de prue- bas mediante los comandos proporcionados por Symfony. Entra en el directorio sf_sand- box/ y ejecuta el siguiente comando en los entornos *nix: www.librosweb.es 45
  • 46. Symfony, la guía definitiva Capítulo 3. Ejecutar aplicaciones Symfony > ./symfony -V En los sistemas Windows, ejecuta el siguiente comando: > symfony -V El resultado del comando debería mostrar la versión del entorno de pruebas: symfony version 1.0.0 A continuación, se prueba si el servidor web puede acceder al entorno de pruebas med- iante la siguiente URL: http://localhost/sf_sandbox/web/frontend_dev.php/ Si todo ha ido bien, deberías ver una página de bienvenida como la que se muestra en la figura 3-1, con lo que la instalación rápida se puede dar por concluida. Si no se muestra esa página, se mostrará un mensaje de error que te indica los cambios necesarios en la configuración. También puedes consultar la sección “Resolución de problemas” que se encuentra más adelante en este capítulo. www.librosweb.es 46
  • 47. Symfony, la guía definitiva Capítulo 3. Ejecutar aplicaciones Symfony Figura 3.1. Página de bienvenida del entorno de pruebas El entorno de pruebas está pensado para que practiques con Symfony en un servidor lo- cal, no para desarrollar aplicaciones complejas que acaban siendo publicadas en la web. No obstante, la versión de Symfony que está incluida en el entorno de pruebas es com- pletamente funcional y equivalente a la que se instala vía PEAR. Para desinstalar el entorno de pruebas, borra el directorio sf_sandbox/ de la carpeta web/ de tu servidor. 3.2. Instalando las librerías de Symfony Al desarrollar aplicaciones con Symfony, es probable que tengas que instalarlo dos veces: una para el entorno de desarrollo y otra para el servidor de producción (a no ser que el servicio de hosting que utilices tenga Symfony preinstalado). En cada uno de los servido- res lo lógico es evitar duplicidades juntando todos los archivos de Symfony en un único directorio, independientemente de que desarrolles una o varias aplicaciones. Como el desarrollo de Symfony evoluciona rápidamente, es posible que esté disponible una nueva versión estable del framework unos días después de la primera instalación. La actualización del framework es algo a tener muy en cuenta, por lo que se trata de otra razón de peso para juntar en un único directorio todas las librerías de Symfony. Existen dos alternativas para instalar las librerías necesarias para el desarrollo de las aplicaciones: ▪ La instalación que utiliza PEAR es la recomendada para la mayoría de usuarios. Con este método, la instalación es bastante sencilla, además de ser fácil de com- partir y de actualizar. ▪ La instalación que utiliza Subversion (SVN) solamente se recomienda para los programadores de PHP más avanzados y es el método con el que pueden obtener los últimos parches, pueden añadir sus propias características al framework y pueden colaborar con el proyecto Symfony. Symfony integra algunos paquetes externos: ▪ pake es una utilidad para la línea de comandos. ▪ lime es una utilidad para las pruebas unitarias. ▪ Creole es un sistema de abstracción de la base de datos. Se trata de un sistema similar a los PHP Data Objects (PDO) y proporciona una interfaz entre el código PHP y el código SQL de la base de datos, permitiendo cambiar fácilmente de sis- tema gestor de bases de datos. ▪ Propel se utiliza para el ORM. Proporciona persistencia para los objetos y un ser- vicio de consultas. ▪ Phing es una utilidad que emplea Propel para generar las clases del modelo. www.librosweb.es 47
  • 48. Symfony, la guía definitiva Capítulo 3. Ejecutar aplicaciones Symfony Pake y lime han sido desarrollados por el equipo de Symfony. Creole, Propel y Phing han sido creados por otros equipos de desarrollo y se publican bajo la licencia GNU Lesser Pu- blic General License (LGPL). Todos estos paquetes están incluidos en Symfony. 3.2.1. Instalando Symfony con PEAR El paquete PEAR de Symfony incluye las librerías propias de Symfony y todas sus depen- dencias. Además, también contiene un script que permite extender la línea de comandos del sistema para que funcione el comando symfony. Para instalar Symfony de esta manera, en primer lugar se debe añadir el canal Symfony a PEAR mediante este comando: > pear channel-discover pear.symfony-project.com Para comprobar las librerías disponibles en ese canal, se puede ejecutar lo siguiente: > pear remote-list -c symfony Una vez añadido el canal, ya es posible instalar la última versión estable de Symfony me- diante el siguiente comando: > pear install symfony/symfony downloading symfony-1.0.0.tgz ... Starting to download symfony-1.0.0.tgz (1,283,270 bytes) ................................................................. ................................................................. .............done: 1,283,270 bytes install ok: channel://pear.symfony-project.com/symfony-1.0.0 Y la instalación ya ha terminado. Los archivos y las utilidades de línea de comandos de Symfony ya se han instalado. Para asegurarte de que se ha instalado correctamente, pr- ueba a ejecutar el comando symfony para que te muestre la versión de Symfony que se encuentra instalada: > symfony -V symfony version 1.0.0 SUGERENCIA Si prefieres instalar la versión beta más reciente, que tiene las últimas correcciones de errores y las últimas mejoras, puedes ejecutar el comando pear install symfony/symfony-beta. Sin embar- go, las versiones beta no son estables y por tanto no se recomiendan para los servidores de producción. Después de la instalación, las librerías de Symfony se encuentran en los siguientes directorios: ▪ $php_dir/symfony/ contiene las principales librerías. ▪ $data_dir/symfony/ contiene la estructura básica de las aplicaciones Symfony; los módulos por defecto; y la configuración, datos para i18 (internacionalización), etc. www.librosweb.es 48
  • 49. Symfony, la guía definitiva Capítulo 3. Ejecutar aplicaciones Symfony ▪ $doc_dir/symfony/ contiene la documentación. ▪ $test_dir/symfony/ contiene las pruebas unitarias. Las variables que acaban en _dir se definen en la configuración de PEAR. Para ver sus valores, puedes ejecutar el siguiente comando: > pear config-show 3.2.2. Obtener Symfony mediante el repositorio SVN En los servidores de producción, o cuando no es posible utilizar PEAR, se puede descar- gar la última versión de las librerías Symfony directamente desde el repositorio Subvers- ion que utiliza Symfony: > mkdir /ruta/a/symfony > cd /ruta/a/symfony > svn checkout http://svn.symfony-project.com/tags/RELEASE_1_0_0/ . El comando symfony, que solamente está disponible en las instalaciones PEAR, en reali- dad es una llamada al script que se encuentra en /ruta/a/symfony/data/bin/symfony. Por tanto, en una instalación realizada con SVN, el comando symfony -V es equivalente a: > php /ruta/a/symfony/data/bin/symfony -V symfony version 1.0.0 Probablemente ya tenías creado algún proyecto de Symfony antes de realizar la instala- ción mediante SVN. En este caso, es necesario modificar el valor de 2 variables en el ar- chivo de configuración config/config.php del proyecto: <?php $sf_symfony_lib_dir = '/ruta/a/symfony/lib/'; $sf_symfony_data_dir = '/ruta/a/symfony/data/'; El Capítulo 19 muestra otras opciones para enlazar un proyecto con una instalación de Symfony, incluyendo el uso de enlaces simbólicos y rutas relativas. SUGERENCIA Otra forma de instalar Symfony es bajar directamente el paquete de PEAR (http://pear.symfony-project.com/get/symfony-1.0.0.tgz) y descomprimirlo en algún directorio. El resultado de esta instalación es el mismo que si se instala mediante el repositorio de Subversion. 3.3. Crear una aplicación web Como se vio en el Capítulo 2, Symfony agrupa las aplicaciones relacionadas en proyec- tos. Todas las aplicaciones de un proyecto comparten la misma base de datos. Por tanto, para crear una aplicación web en primer lugar se debe crear un proyecto. 3.3.1. Crear el Proyecto Los proyectos de Symfony siguen una estructura de directorios predefinida. Los coman- dos que proporciona Symfony permiten automatizar la creación de nuevos proyectos, ya www.librosweb.es 49
  • 50. Symfony, la guía definitiva Capítulo 3. Ejecutar aplicaciones Symfony que se encargan de crear la estructura de directorios básica del proyecto y con los permi- sos adecuados. Por tanto, para crear un proyecto se debe crear un directorio y decirle a Symfony que cree un proyecto en su interior. Si has utilizado la instalación con PEAR, ejecuta los siguientes comandos: > mkdir ~/miproyecto > cd ~/miproyecto > symfony init-project miproyecto Si has instalado Symfony mediante SVN, puedes crear un proyecto con los siguientes comandos: > mkdir ~/miproyecto > cd ~/miproyecto > php /path/to/symfony/data/bin/symfony init-project miproyecto El comando symfony siempre debe ejecutarse en el directorio raíz del proyecto (en este ejemplo, miproyecto/) ya que todas las tareas que realiza este comando son específicas para cada proyecto. La estructura de directorios creada por Symfony se muestra a continuación: apps/ batch/ cache/ config/ data/ doc/ lib/ log/ plugins/ test/ web/ SUGERENCIA La tarea init-project añade un script llamado symfony en el directorio raíz del proyecto. Este script es idéntico al comando symfony que instala PEAR, por lo que se puede utilizar la instrucción php symfony en vez del comando symfony cuando no se dispone de las utilidades de la línea de comandos (lo que sucede cuando se instala Symfony mediante SVN). 3.3.2. Crear la Aplicación El proyecto recién creado está incompleto, ya que requiere por lo menos de una aplica- ción. Para crear la aplicación, se utiliza el comando symfony init-app, al que se le tiene que pasar como argumento el nombre de la nueva aplicación: > symfony init-app miaplicacion El comando anterior crea un directorio llamado miaplicacion/ dentro del directorio apps/ que se encuentra en la raíz del proyecto. Por defecto se crea una configuración básica de la aplicación y una serie de directorios: apps/ miaplicacion/ www.librosweb.es 50
  • 51. Symfony, la guía definitiva Capítulo 3. Ejecutar aplicaciones Symfony config/ i18n/ lib/ modules/ templates/ En el directorio web del proyecto también se crean algunos archivos PHP correspondientes a los controladores frontales de cada uno de los entornos de ejecución de la aplicación: web/ index.php miaplicacion_dev.php El archivo index.php es el controlador frontal de producción de la nueva aplicación. Como se trata de la primera aplicación, Symfony crea un archivo llamado index.php en vez de miaplicacion.php (si después se crea una nueva aplicación llamada por ejemplo minueva- aplicacion, el controlador frontal del entorno de producción que se crea se llamará min- uevaaplicacion.php). Para ejecutar la aplicación en el entorno de desarrollo, se debe eje- cutar el controlador frontal llamado miaplicacion_dev.php. El Capítulo 5 explica en detalle los distintos entornos de ejecución. 3.4. Configurar el servidor web Los scripts que se encuentran en el directorio web/ son los únicos puntos de entrada a la aplicación. Por este motivo, debe configurarse el servidor web para que puedan ser acce- didos desde Internet. En el servidor de desarrollo y en los servicios de hosting profesio- nales, se suele tener acceso a la configuración completa de Apache para poder configurar servidores virtuales (virtual host). En los servicios de alojamiento compartido, lo normal es tener acceso solamente a los archivos .htaccess. 3.4.1. Configurar los servidores virtuales El listado 3-1 muestra un ejemplo de la configuración necesaria para crear un nuevo ser- vidor virtual en Apache mediante la modificación del archivo httpd.conf. Listado 3-1 - Ejemplo de configuración de Apache, en apache/conf/httpd.conf <VirtualHost *:80> ServerName miaplicacion.ejemplo.com DocumentRoot "/home/steve/miproyecto/web" DirectoryIndex index.php Alias /sf /$sf_symfony_data_dir/web/sf <Directory "/$sf_symfony_data_dir/web/sf"> AllowOverride All Allow from All </Directory> <Directory "/home/steve/miproyecto/web"> AllowOverride All Allow from All </Directory> </VirtualHost> www.librosweb.es 51
  • 52. Symfony, la guía definitiva Capítulo 3. Ejecutar aplicaciones Symfony En la configuración del listado 3-1, se debe sustituir la variable $sf_symfony_data_dir por la ruta real del directorio de datos de Symfony. Por ejemplo, la ruta en un sistema *nix en el que se ha instalado Symfony mediante PEAR sería: Alias /sf /usr/local/lib/php/data/symfony/web/sf NOTA No es obligatorio el alias al directorio web/sf/. La finalidad del alias es permitir que Apache pueda encontrar las imágenes, hojas de estilos y archivos de JavaScript utilizados en la barra de depura- ción, en el generador automático de aplicaciones de gestión, en las páginas propias de Symfony y en las utilidades de Ajax. La alternativa a crear este alias podría ser la de crear un enlace simbólico (symlink) o copiar directamente los contenidos del directorio /$sf_symfony_data_dir/web/sf/ al directorio miproyecto/web/sf/. No te olvides reiniciar Apache para que los cambios surtan efecto. La aplicación recién creada ya se puede acceder con cualquier navegador en esta dirección: http://localhost/miaplicacion_dev.php/ Al acceder a la aplicación, se debería mostrar una imagen similar a la mostrada en la fi- gura 3-1. Reescritura de URL (URL Rewriting) Symfony utiliza la reescritura de URL para mostrar “URL limpias” en la aplicación, es decir, URL con mucho sentido, optimizadas para buscadores y que ocultan a los usuarios los detalles técnicos internos de la aplicación. El Capítulo 9 explica en detalle el sistema de enrutamiento utilizado por Symfony y su implicación en las URL de las aplicaciones. Para que funcione correctamente la reescritura de URL, es necesario que Apache esté compilado con el módulo mod_rewrite o al menos que esté instalado el módulo mod_rewrite como módulo DSO. En este último caso, la configuración de Apache debe contener las siguientes líneas en el ar- chivo httpd.conf: AddModule mod_rewrite.c LoadModule rewrite_module modules/mod_rewrite.so Para los servidores IIS (Internet Information Services) es necesario disponer de isapi/rewrite instalado y activado. El sitio web del proyecto Symfony (http://www.symfony-project.org) dispone de más documentación sobre la instalación de Symfony en servidores IIS. 3.4.2. Configurar un servidor compartido En los servidores de alojamiento compartido es un poco más complicado instalar las apli- caciones creadas con Symfony, ya que los servidores suelen tener una estructura de di- rectorios que no se puede modificar. ATENCIÓN No es recomendable hacer las pruebas y el desarrollo directamente en un servidor compartido. Una de las razones es que la aplicación es pública incluso cuando no está terminada, pudiendo mostrar su funcionamiento interno y pudiendo provocar problemas de seguridad. El otro motivo es que el rendimiento de los servidores compartidos habituales no es suficiente como para depurar la www.librosweb.es 52
  • 53. Symfony, la guía definitiva Capítulo 3. Ejecutar aplicaciones Symfony aplicación con las utilidades de Symfony. Por este motivo, no se recomienda comenzar el desarrollo de una aplicación en un servidor compartido, sino que debería desarrollarse en un servidor local y subirla al servidor compartido una vez terminada la aplicación. En el Capítulo 16 se muestran técni- cas y herramientas para la instalación de las aplicaciones. Imaginemos que el servidor compartido llama a la carpeta web www/ en vez de web/ y que no es posible modificar el archivo de configuración httpd.conf, sino que solo es posible acceder a un archivo de tipo .htaccess en ese directorio. Los proyectos creados con Symfony permiten configurar cada ruta de cada directorio. En el Capítulo 19 se detalla la configuración de los directorios, pero mientras tanto, se va a renombrar el directorio web a www y se va a modificar la configuración de la aplicación pa- ra que lo tenga en cuenta. El listado 3-2 muestra los cambios que es preciso añadir al fi- nal del archivo config.php. Listado 3-2 - Modificación de la estructura de directorios por defecto, en apps/ miaplicacion/config/config.php $sf_root_dir = sfConfig::get('sf_root_dir'); sfConfig::add(array( 'sf_web_dir_name' => $sf_web_dir_name = 'www', 'sf_web_dir' => $sf_root_dir.DIRECTORY_SEPARATOR.$sf_web_dir_name, 'sf_upload_dir' => $sf_root_dir.DIRECTORY_SEPARATOR.$sf_web_dir_name.DIRECTORY_SEPARATOR.sfConfig::get('sf_upload_dir_n )); La carpeta web de la raíz del servidor contiene por defecto un archivo de tipo .htaccess. El listado 3-3 muestra su contenido, que debe ser modificado de acuerdo a los requerim- ientos del servidor compartido. Listado 3-3 - Configuración por defecto de .htaccess, ahora guardado en mipro- yecto/www/.htaccess Options +FollowSymLinks +ExecCGI <IfModule mod_rewrite.c> RewriteEngine On # we skip all files with .something RewriteCond %{REQUEST_URI} ..+$ RewriteCond %{REQUEST_URI} !.html$ RewriteRule .* - [L] # we check if the .html version is here (caching) RewriteRule ^$ index.html [QSA] RewriteRule ^([^.]+)$ $1.html [QSA] RewriteCond %{REQUEST_FILENAME} !-f # no, so we redirect to our front web controller RewriteRule ^(.*)$ index.php [QSA,L] </IfModule> # big crash from our front web controller www.librosweb.es 53
  • 54. Symfony, la guía definitiva Capítulo 3. Ejecutar aplicaciones Symfony ErrorDocument 500 "<h2>Application error</h2>symfony applicationfailed to start properly" Después de realizar los cambios, ya debería ser posible acceder a la aplicación. Comprue- ba si se muestra la página de bienvenida accediendo a esta dirección: http://www.ejemplo.com/miaplicacion_dev.php/ Otras configuraciones de servidor Symfony permite realizar otras configuraciones de servidor. Por ejemplo se puede acceder a las aplicaciones Symfony utilizando alias en vez de servidores virtuales. También es posible ejecutar las aplicaciones Symfony en servidores IIS. Existen tantas técnicas como posibles configuraciones, aunque el propósito de este libro no es explicarlas todas. Para encontrar ayuda sobre las distintas configuraciones de servidor, puedes consultar el wiki del proyecto Symfony (http://trac.symfony-project.com/) en el que existen varios tutoriales con ex- plicaciones detalladas paso a paso. 3.5. Resolución de problemas Si se producen errores durante la instalación, lo mejor es intentar mostrar los mensajes de error en el navegador o en la consola de comandos. Normalmente los errores mues- tran pistas sobre su posible causa y hasta pueden contener enlaces a algunos recursos disponibles en Internet sobre ese problema. 3.5.1. Problemas típicos Si continuan los problemas con Symfony, puedes comprobar los siguientes errores comunes: ▪ Algunas instalaciones de PHP incluyen tanto PHP 4 como PHP 5. En este caso, suele ser habitual que el comando de PHP 5 sea php5, por lo que se debe ejecutar la instrucción php5 symfony en vez de symfony. Puede que también sea necesario añadir la directiva SetEnv PHP_VER 5 en el archivo de configuración .htaccess e incluso puede que tengas que renombrar los scripts del directorio web/ para que tengan una extensión .php5 en vez de la tradicional extensión .php. Cuando se intenta ejecutar Symfony con PHP 4, el error que se muestra es similar al siguiente: Parse error, unexpected ',', expecting '(' in .../symfony.php on line 19. ▪ El límite de memoria utilizado por PHP se define en el archivo de configuración php.ini y debería valer por lo menos 16M (equivalente a 16 MB). El síntoma común de este problema es cuando se muestra un mensaje de error al instalar Symfony mediante PEAR o cuando se utiliza la línea de comandos: Allowed memory size of 8388608 bytes exhausted ▪ La directiva zend.ze1_compatibility_mode del archivo de configuración de PHP (php.ini) debe tener un valor igual a off. Si no es así, cuando se intenta acceder a cualquier script, se muestra el siguiente mensaje de error: www.librosweb.es 54
  • 55. Symfony, la guía definitiva Capítulo 3. Ejecutar aplicaciones Symfony Strict Standards: Implicit cloning object of class 'sfTimer'because of 'zend.ze1_compatibility_mode' ▪ Los directorios log/ y cache/ del proyecto deben tener permiso de escritura para el servidor web. Si se ejecuta una aplicación sin estos permisos, se muestra la si- guiente excepción: sfCacheException [message] Unable to write cache file"/usr/miproyecto/cache/frontend/ prod/config/config_config_handlers.yml.php" ▪ La ruta del sistema debe incluir la ruta al comando php, y la directiva include_- path del archivo de configuración de PHP (php.ini) debe contener una ruta a PEAR (en el caso de que se utilice PEAR). ▪ En ocasiones, existe más de un archivo php.ini en el sistema (por ejemplo cuan- do se instala PHP mediante el paquete WAMP). En estos casos, se puede realizar una llamada a la función phpinfo() de PHP para saber la ruta exacta del archivo php.ini que está utilizando la aplicación. NOTA Aunque no es obligatorio, es muy recomendable por motivos de rendimiento establecer el valor off a las directivas magic_quotes_gpc y register_globals del archivo de configuración de PHP (php.ini). 3.5.2. Recursos relacionados con Symfony Existen varias formas de encontrar soluciones a los problemas típicos y que ya les han ocurrido a otros usuarios: ▪ El foro de instalación de Symfony (http://www.symfony-project.org/forum/) contiene muchas preguntas sobre configuraciones en diferentes plataformas, entornos y servidores. ▪ La lista de correo de usuarios de Symfony (http://groups.google.es/group/symfony- es) permite buscar en sus archivos de mensajes, por lo que es posible que enc- uentres a otros usuarios que han pasado por los mismos problemas. ▪ El wiki de Symfony (http://trac.symfony-project.com/#Installingsymfony) contiene tutoriales detallados paso a paso sobre la instalación de Symfony que han sido creados por otros usuarios. Si no encuentras la respuesta en esos recursos, puedes preguntar a la comunidad de Symfony. Las preguntas puedes hacerlas en el foro, en la lista de correo e incluso en el canal IRC de Symfony (#symfony) para obtener la respuesta de los miembros más activos de la comunidad. 3.6. Versionado del código fuente Una vez creada la aplicación, se recomienda empezar con el versionado del código fuente (también llamado control de versiones). El versionado almacena todas las modificaciones realizadas en el código, permite acceder a las versiones anteriores de cualquier archivo, www.librosweb.es 55
  • 56. Symfony, la guía definitiva Capítulo 3. Ejecutar aplicaciones Symfony simplifica la creación de parches y permite trabajar en equipo de forma eficiente. Sym- fony soporta de forma nativa el uso de CVS, aunque recomienda el uso de Subversion (http://subversion.tigris.org/). Los ejemplos que se muestran a continuación utilizan co- mandos de Subversion y presuponen que existe un servidor de Subversion instalado y que se va a crear un nuevo repositorio para el proyecto. Para los usuarios de Windows, se recomienda utilizar TortoiseSVN (http://tortoisesvn.tigris.org/) como cliente de Sub- version. La documentación oficial de Subversion es un buen recurso para ampliar los co- nocimientos sobre el versionado del código y sobre los comandos que utilizan los siguien- tes ejemplos. Los siguientes ejemplos requieren que exista una variable de entorno llamada $SVNREP_DIR y cuyo valor es la ruta completa al repositorio. Si no es posible definir la var- iable de entorno, en los siguientes comandos se debe escribir la ruta completa al reposi- torio en vez de $SVNREP_DIR. En primer lugar se crea un nuevo repositorio para el proyecto miproyecto: > svnadmin create $SVNREP_DIR/miproyecto Después se crea el layout o estructura básica del repositorio mediante los directorios trunk, tags y branches. El comando necesario es bastante largo: > svn mkdir -m "Creacion del layout" file:///$SVNREP_DIR/miproyecto/trunk file:///$SVNREP_DIR/miproyecto/tags file:///$SVNREP_DIR/miproyecto/branches A continuación se realiza la primera versión, para lo que es necesario importar todos los archivos del proyecto salvo los archivos temporales de cache/ y log/: > cd ~/miproyecto > rm -rf cache/* > rm -rf log/* > svn import -m "Primera importacion" . file:///$SVNREP_DIR/miproyecto/trunk El siguiente comando permite comprobar si se han subido correctamente los archivos: > svn ls file:///$SVNREP_DIR/miproyecto/trunk/ Por el momento todo va bien, ya que ahora el repositorio SVN contiene una versión de referencia de todos los archivos del proyecto. De esta forma, los archivos del directorio miproyecto/ deben hacer referencia a los que almacena el repositorio. Para ello, renom- bra el directorio miproyecto/ (si todo funciona correctamente lo podrás borrar) y descar- ga los contenidos del repositorio en un nuevo directorio: > cd ~ > mv miproyecto miproyecto.original > svn co file:///$SVNREP_DIR/miproyecto/trunk miproyecto > ls miproyecto Y eso es todo. Ahora ya es posible trabajar con los archivos que se encuentran en el di- rectorio miproyecto/ y subir todos los cambios al repositorio. Puedes borrar el directorio miproyecto.original/ porque ya no se utiliza. Solamente es necesario realizar una última configuración. Si se suben todos los archivos del directorio al repositorio, se van a copiar algunos archivos innecesarios, como los que se encuentran en los directorios cache/ y log/. Subversion permite establecer una lista www.librosweb.es 56
  • 57. Symfony, la guía definitiva Capítulo 3. Ejecutar aplicaciones Symfony de archivos que se ignoran al subir los contenidos al repositorio. Además, es preciso es- tablecer de nuevo los permisos correctos a los directorios cache/ y log/: > cd ~/miproyecto > chmod 777 cache > chmod 777 log > svn propedit svn:ignore log > svn propedit svn:ignore cache Al ejecutar los comandos anteriores, Subversion muestra el editor de textos configurado por defecto. Si no se muestra nada, configura el editor de textos que utiliza Subversion por defecto mediante el siguiente comando: > export SVN_EDITOR=<nombre_del_editor_de_textos> > svn propedit svn:ignore log > svn propedit svn:ignore cache Para incluir todos los archivos de los directorios, se debe escribir lo siguiente cuando se muestre el editor de textos: * Para finalizar, guarda los cambios y cierra el editor. 3.7. Resumen Para probar y jugar con Symfony en un servidor local, la mejor opción es instalar el en- torno de pruebas o sandbox, que contiene un entorno de ejecución preconfigurado para Symfony. Para desarrollar aplicaciones web reales o para instalarlo en un servidor de producción, se puede optar por la instalación via PEAR o mediante el repositorio de Subversion. Estos métodos instalan las librerías de Symfony, pero se deben crear manualmente los proyec- tos y las aplicaciones. El último paso de la configuración de las aplicaciones es la configu- ración del servidor web, que puede realizarse de muchas formas. Symfony funciona muy bien con los servidores virtuales y de hecho es el método recomendado. Si se producen errores durante la instalación, existen muchos tutoriales y preguntas frec- uentes en el sitio web de Symfony. Incluso es posible trasladar tu problema a la comuni- dad Symfony para obtener una respuesta en general rápida y efectiva. Después de crear el proyecto, se recomienda empezar con el versionado del código fuen- te para realizar el control de versiones. Una vez que ya se puede utilizar Symfony, es un buen momento para desarrollar la pri- mera aplicación web básica. www.librosweb.es 57
  • 58. Symfony, la guía definitiva Capítulo 4. Introducción a la creación de páginas Capítulo 4. Introducción a la creación de páginas Curiosamente, el primer tutorial que utilizan los programadores para aprender cualquier lenguaje de programación o framework es el que muestra por pantalla el mensaje “¡Hola Mundo!” (del inglés Hello, world!). Resulta extraño creer que un ordenador pueda ser ca- paz de saludar a todo el mundo, ya que todos los intentos que ha habido hasta ahora en el campo de la inteligencia artificial han resultado en unos sistemas artificiales de conver- sación bastante pobres. No obstante, Symfony no es más tonto que cualquier otro frame- work, y la prueba es que se puede crear una página que muestre el mensaje “Hola, <tu_nombre>”. En este capítulo se muestra como crear un módulo, que es el elemento que agrupa a las páginas. También se aprende cómo crear una página, que a su vez se divide en una ac- ción y una plantilla, siguiendo la arquitectura MVC. Las interacciones básicas con las pá- ginas se realizan mediante enlaces y formularios, por lo que también se muestra como incluirlos en las plantillas y como manejarlos en las acciones. 4.1. Crear el esqueleto del módulo Como se vio en el Capítulo 2, Symfony agrupa las páginas en módulos. Por tanto, antes de crear una página es necesario crear un módulo, que inicialmente no es más que una estructura vacía de directorios y archivos que Symfony puede reconocer. La línea de comandos de Symfony automatiza la creación de los módulos. Sólo se necesi- ta llamar a la tarea init-module indicando como argumentos el nombre de la aplicación y el nombre del nuevo módulo. En el capítulo anterior se creo una aplicación llamada mia- plicacion. Para añadirle un módulo llamado mimodulo, se deben ejecutar los siguientes comandos: > cd ~/miproyecto > symfony init-module miaplicacion mimodulo >> dir+ ~/miproyecto/apps/miaplicacion/modules/mimodulo >> dir+ ~/miproyecto/apps/miaplicacion/modules/mimodulo/actions >> file+ ~/miproyecto/apps/miaplicacion/modules/mimodulo/actions/actions.class.php >> dir+ ~/miproyecto/apps/miaplicacion/modules/mimodulo/config >> dir+ ~/miproyecto/apps/miaplicacion/modules/mimodulo/lib >> dir+ ~/miproyecto/apps/miaplicacion/modules/mimodulo/templates >> file+ ~/miproyecto/apps/miaplicacion/modules/mimodulo/templates/indexSuccess.php >> dir+ ~/miproyecto/apps/miaplicacion/modules/mimodulo/validate >> file+ ~/miproyecto/test/functional/miaplicacion/mimoduloActionsTest.php >> tokens ~/miproyecto/test/functional/miaplicacion/mimoduloActionsTest.php >> tokens ~/miproyecto/apps/miaplicacion/modules/mimodulo/actions/actions.class.php >> tokens ~/miproyecto/apps/miaplicacion/modules/mimodulo/templates/indexSuccess.php Además de los directorios actions/, config/, lib/, templates/ y validate/, este coman- do crea 3 archivos. El archivo que se encuentra en el directorio test/ tiene relación con las pruebas unitarias, que se ven en el Capítulo 15. El archivo actions.class.php (que se muestra en el listado 4-1) redirige la acción a la página de bienvenida del módulo por de- fecto. Por último, el archivo templates/indexSuccess.php está vacío. www.librosweb.es 58
  • 59. Symfony, la guía definitiva Capítulo 4. Introducción a la creación de páginas Listado 4-1 - La acción generada por defecto, en actions/actions.class.php <?php class mimoduloActions extends sfActions { public function executeIndex() { $this->forward('default', 'module'); } } NOTA Si se abre el archivo actions.class.php generado realmente, su contenido es mucho mayor que las pocas líneas mostradas anteriormente, incluyendo un montón de comentarios. Symfony recom- ienda utilizar comentarios de PHP para documentar el proyecto y por tanto añade a cada archivo de cada clase comentarios que son compatibles con el formato de la herramienta phpDocumentor (http://www.phpdoc.org/). En cada nuevo módulo, Symfony crea una acción por defecto llamada index. La acción completa se compone del método executeIndex de la acción y del archivo de su plantilla llamada indexSuccess.php. El significado del prefijo execute y del sufijo Success se expli- can detalladamente en los Capítulos 6 y 7 respectivamente. Por el momento se puede considerar que esta forma de nombrar a los archivos y métodos es una convención que sigue Symfony. Para visualizar la página generada (que se muestra en la figura 4-1) se debe acceder a la siguiente dirección en un navegador: http://localhost/miaplicacion_dev.php/mimodulo/index En este capítulo no se utiliza la acción index, por lo que se puede borrar el método exe- cuteIndex() del archivo actions.class.php y también se puede borrar el archivo in- dexSuccess.php del directorio templates/. NOTA Symfony permite crear los módulos sin necesidad de utilizar la línea de comandos. Uno de esos métodos es crear manualmente todos los directorios y archivos necesarios. En otras ocasiones, las acciones y las plantillas de un módulo se emplean para manipular los datos de una tabla de la base de datos. Como el código necesario para crear, obtener, actualizar y borrar los datos casi siempre es el mismo, Symfony incorpora una técnica llamada scaffolding (literalmente traducido como “an- damiaje”) que permite generar de forma automática todo el código PHP del módulo. El Capítulo 14 contiene los detalles de esta técnica. www.librosweb.es 59
  • 60. Symfony, la guía definitiva Capítulo 4. Introducción a la creación de páginas Figura 4.1. La página de índice generada automáticamente 4.1.1. Añadir una página En Symfony la lógica o código de las páginas se define en la acción y la presentación se define en las plantillas. Las páginas estáticas que no requieren de ninguna lógica necesi- tan definir una acción vacía. 4.1.1.1. Añadir una acción La página que muestra el mensaje “¡Hola Mundo!” será accedida mediante una acción lla- mada miAccion. Para crearla, solamente es necesario añadir el método executeMiAccion en la clase mimoduloActions, como muestra el Listado 4-2. Listado 4-2 - Añadir una acción es equivalente a añadir un método de tipo exe- cute en la clase de la acción <?php class mimoduloActions extends sfActions { www.librosweb.es 60
  • 61. Symfony, la guía definitiva Capítulo 4. Introducción a la creación de páginas public function executeMiAccion() { } } El nombre del método de la acción siempre es execute + Xxxxxxx + (), donde la segunda parte del nombre es el nombre de la acción con la primera letra en mayúsculas. Por tanto, si ahora se accede a la siguiente dirección: http://localhost/miaplicacion_dev.php/mimodulo/miAccion Symfony mostrará un mensaje de error indicando que la plantilla miAccionSuccess.php no existe. Se trata de un error normal por el momento, ya que las páginas siempre están formadas por una acción y una plantilla. ATENCIÓN Las URL (no los dominios) distinguen mayúsculas y minúsculas, y por tanto también las distingue Symfony, (aunque el nombre de los métodos en PHP no distingue mayúsculas de minúsculas). Por tanto, si se añade un método llamado executemiaccion() o executeMiaccion(), y se intenta ac- ceder desde el navegador a miAccion, Symfony muestra un mensaje de error de tipo 404 (Página no encontrada). Las URL son parte de la respuesta Symfony incluye un sistema de enrutamiento que permite separar completamente el nombre real de la acción y la forma de la URL que se utiliza para llamar a la acción. De esta forma, es posible per- sonalizar las URL como si fueran una parte más de la respuesta. La estructura de directorios del servidor o los parámetros de la petición ya no son obstáculos para construir URL con cualquier for- mato; la URL de una acción puede construirse siguiendo cualquier formato. Por ejemplo, la URL tí- pica de la acción index de un módulo llamado articulo suele tener el siguiente aspecto: http://localhost/miaplicacion_dev.php/articulo/index?id=123 Esta URL se emplea para obtener un artículo almacenado en la base de datos. En el ejemplo anter- ior, se obtiene un artículo cuyo identificador es 123, que pertenece a la sección de artículos de Eu- ropa y que trata sobre la economía en Francia. Con un simple cambio en el archivo routing.yml, la URL anterior se puede construir de la siguiente manera: http://localhost/articulos/europa/francia/economia.html La URL que se obtiene no solo es mejor desde el punto de vista de los buscadores, sino que es mucho más significativa para el usuario medio, que incluso puede utilizar la barra de direcciones como si fuera una especie de línea de comandos para realizar consultas a medida, como por ejem- plo la siguiente URL: http://localhost/articulos/etiquetas/economia+francia+euro Symfony es capaz de procesar y generar este tipo de URL inteligentes. El sistema de enrutamiento es capaz de extraer automáticamente los parámetros de la petición y ponerlos a disposición de la acción. También es capaz de formatear los enlaces incluidos en la respuesta para que también se- an enlaces de tipo inteligente. El Capítulo 9 explica en detalle el sistema de enrutamiento. En resumen, el nombrado de las acciones no se debe realizar teniendo en cuenta la URL que se utilizará para acceder a ellas, sino que se deberían nombrar según la función de la acción dentro de www.librosweb.es 61
  • 62. Symfony, la guía definitiva Capítulo 4. Introducción a la creación de páginas la aplicación. El nombre de la acción explica su funcionalidad, por lo que suele ser un verbo en su forma de infinitivo (como por ejemplo ver, listar, modificar, etc.). El nombre de las acciones se puede ocultar a los usuarios, por lo que si es necesario, se pueden utilizar nombres muy explícitos para las acciones (como por ejemplo listarPorNombre o verConComentarios). Con este tipo de nombres, no son necesarios demasiados comentarios para explicar la funcionalidad de la acción y el código fuente resultante es mucho más fácil de comprender. 4.1.1.2. Añadir una plantilla La acción espera una plantilla para mostrarse en pantalla. Una plantilla es un archivo que está ubicado en el directorio templates/ de un módulo, y su nombre está compuesto por el nombre de la acción y el resultado de la misma. El resultado por defecto es success (e- xitoso), por lo que el archivo de plantilla que se crea para la acción miAccion se llamará miAccionSuccess.php. Se supone que las plantillas sólo deben contener código de presentación, así que procura mantener la menor cantidad de código PHP en ellas como sea posible. De hecho, una pá- gina que muestre “¡Hola, mundo!” puede tener una plantilla tan simple como la del Lista- do 4-3. Listado 4-3 - La plantilla mimodulo/templates/miAccionSuccess.php <p>¡Hola, mundo!</p> Si necesitas ejecutar algún código PHP en la plantilla, es mejor evitar la sintaxis usual de PHP, como se muestra en el Listado 4-4. En su lugar, es preferible escribir las plantillas utilizando la sintaxis alternativa de PHP, mostrada en el Listado 4-5, para mantener el código entendible para personas sin conocimientos de PHP. De esta forma, no sólo el có- digo final estará correctamente indentado, sino que además ayudará a mantener el códi- go complejo de PHP en la acción, dado que sólo las estructuras de control (if, foreach, while y demás) poseen una sintaxis alternativa. Listado 4-4 - La sintaxis tradicional de PHP, buena para las acciones, pero mala para las plantillas <p>¡Hola, mundo!</p> <?php if ($prueba) { echo "<p>".time()."</p>"; } ?> Listado 4-5 - La sintaxis alternativa de PHP, buena para las plantillas <p>¡Hola, mundo!</p> <?php if ($prueba): ?> <p><?php echo time(); ?></p> <?php endif; ?> www.librosweb.es 62
  • 63. Symfony, la guía definitiva Capítulo 4. Introducción a la creación de páginas SUGERENCIA Una buena regla para comprobar si la sintaxis de la plantilla es lo suficientemente legible, es que el archivo no debe contener código HTML generado por PHP mediante la función echo, ni tampoco llaves. Y en la mayoría de los casos, al abrir una etiqueta <?php, debería cerrarse con ?> en la mis- ma línea. 4.1.2. Transfiriendo información de la acción a la plantilla La tarea de la acción es realizar los cálculos complejos, obtener datos, realizar compro- baciones, y crear o inicializar las variables necesarias para que se presenten o se utilicen en la plantilla. Symfony hace que los atributos de la clase de la acción (disponibles vía $this->nombreDeVariable en la acción), estén directamente accesibles en la plantilla en el ámbito global (vía $nombreVariable). Los listados 4-6 y 4-7 muestran cómo pasar infor- mación de la acción a la plantilla. Listado 4-6 - Configurando un atributo de acción dentro de ella para hacerlo dis- ponible para la plantilla <?php class mimoduloActions extends sfActions { public function executeMiAccion() { $hoy = getdate(); $this->hora = $hoy['hours']; } } Listado 4-7 - La plantilla tiene acceso directo a los atributos de la acción <p>¡Hola, Mundo!</p> <?php if ($hora >= 18): ?> <p>Quizás debería decir buenas tardes. Ya son las <?php echo $hora ?>.</p> <?php endif; ?> NOTA La plantilla es capaz de acceder a algunos datos sin necesidad de definir variables en la acción. Cada plantilla puede invocar métodos de los objetos $sf_context, $sf_request, sf_params y $sf_user. Esos métodos contienen datos relacionados con el contexto actual, la petición y sus parámetros, y la sesión. Muy pronto se muestra cómo utilizarlos de manera eficiente. 4.2. Obteniendo información del usuario a través de formularios Los formularios constituyen un buen método para obtener información del usuario. Di- señar formularios y sus elementos en HTML a veces puede ser tedioso, especialmente si se quieren obtener páginas que validen en XHTML. Se pueden incluir elementos de for- mulario en las plantillas de Symfony de manera tradicional, como se muestra en el Lista- do 4-8, pero Symfony provee helpers que hacen mucho más sencilla esta tarea. Listado 4-8 - Las plantillas pueden incluir código HTML tradicional www.librosweb.es 63
  • 64. Symfony, la guía definitiva Capítulo 4. Introducción a la creación de páginas <p>¡Hola, Mundo!</p> <?php if ($hora >= 18): ?> <p>Quizás debería decir buenas tardes. Ya son las <?php echo $hora ?>.</p> <?php endif; ?> <form method="post" action="/miaplicacion_dev.php/mimodulo/otraAccion"> <label for="nombre">¿Cómo te llamas?</label> <input type="text" name="nombre" id="nombre" value="" /> <input type="submit" value="Ok" /> </form> Un helper es una función PHP definida en Symfony que está pensada para ser utilizada desde las plantillas. Devuelven algo de código HTML y es más rápido de usar que insertar el código HTML manualmente. Usando los helpers de Symfony, se puede obtener el re- sultado del Listado 4-8 con el código que se muestra en el Listado 4-9. Listado 4-9 - Es más rápido y simple utilizar helpers que utilizar etiquetas HTML <p>¡Hola, Mundo!</p> <?php if ($hora >= 18): ?> <p>Quizás debería decir buenas tardes. Ya son las <?php echo $hora ?>.</p> <?php endif; ?> <?php echo form_tag('mimodulo/otraAccion') ?> <?php echo label_for('nombre', '¿Cómo te llamas?') ?> <?php echo input_tag('nombre') ?> <?php echo submit_tag('Ok') ?> </form> [side|Los helpers están aquí para ayudarte] Si en el ejemplo del Listado 4-9 crees que utilizar helpers no es tan rápido como escribir HTML, considera este otro ejemplo: <?php $lista_tarjetas = array( 'VISA' => 'Visa', 'MAST' => 'MasterCard', 'AMEX' => 'American Express', 'DISC' => 'Discover'); echo select_tag('tipo_tarjeta', options_for_select($lista_tarjetas, 'AMEX')); ?> El código anterior genera el siguiente código HTML: <select name="tipo_tarjeta" id="tipo_tarjeta"> <option value="VISA">Visa</option> <option value="MAST">MasterCard</option> <option value="AMEX" selected="selected">American Express</option> <option value="DISC">Discover</option> </select> La ventaja de utilizar helpers en las plantillas es que aumentan la velocidad de escritura del código, mejoran su claridad y lo hacen más conciso. El único inconveniente es el tiempo necesario para aprender a utilizarlos, aunque no te costará más tiempo que el de- dicado a leer este libro. Además, para escribir la instrucción <?php echo ?> necesaria, puedes utilizar los atajos que seguramente incorpora tu editor de texto favorito. En defi- nitiva, aunque es posible crear plantillas sin utilizar los helpers de Symfony, sería un gran error escribir directamente el código HTML en las plantillas y sería mucho menos diverti- do. [/note] www.librosweb.es 64
  • 65. Symfony, la guía definitiva Capítulo 4. Introducción a la creación de páginas Por otra parte, el uso de etiquetas cortas de apertura (<?=, equivalente a <?php echo) no se recomienda para aplicaciones web profesionales, debido a que el servidor web de pro- ducción puede ser capaz de entender más de un lenguaje de script, y por tanto, confun- dirse. Además, las etiquetas cortas de apertura no funcionan con la configuración por de- fecto de PHP y necesitan de ajustes en el servidor para ser activadas. Por último, a la ho- ra de lidiar con XML y validación, fallará inmediatamente porque <? tiene un significado especial en XML. Los formularios merecen un capítulo completo para ellos, debido a que Symfony provee muchas herramientas, sobre todo helpers, para facilitar las cosas. Aprenderás sobre es- tos helpers en el Capítulo 10. 4.3. Enlazando a otra acción Ya se ha comentado que existe una independencia total entre el nombre de la acción y la URL utilizada para llamarla, por lo que si se crea un enlace a otraAccion en una plantilla como en el Listado 4-10, sólo funcionará con el enrutamiento establecido por defecto. Si más tarde se decide modificar la manera de mostrar las URL, entonces será necesario ve- rificar todas las plantillas para modificar los hipervínculos. Listado 4-10 - Hipervínculos, la forma clásica <a href="/miaplicacion_dev.php/mimodulo/otraAccion?nombre=anonimo"> Nunca digo mi nombre </a> Para evitar este inconveniente, es necesario siempre utilizar el helper link_to() para cre- ar enlaces a las acciones de la aplicación. El Listado 4-11 demuestra el uso del helper de hipervínculos. Listado 4-11 - El helper link_to() <p>¡Hola, Mundo!</p> <?php if ($hora >= 18): ?> <p>Quizás debería decir buenas tardes. Ya son las <?php echo $hora ?>.</p> <?php endif; ?> <?php echo form_tag('mimodulo/otraAccion') ?> <?php echo label_for('nombre', '¿Cómo te llamas?') ?> <?php echo input_tag('nombre') ?> <?php echo submit_tag('Ok') ?> <?php echo link_to('Nunca digo mi nombre','mimodulo/otraAccion?nombre=anonimo') ?> </form> El código HTML resultante será el mismo que el anterior, excepto que, al modificar las re- glas de enrutamiento, todas las plantillas funcionarán correctamente y modificarán las URL como corresponde. El helper link_to(), al igual que muchos otros, acepta un argumento para opciones es- peciales y atributos de etiqueta adicionales. El Listado 4-12 muestra un ejemplo de un argumento option y su HTML resultante. El argumento option puede ser tanto un array asociativo como una simple cadena de texto mostrando pares de clave=valor separadas por espacios. www.librosweb.es 65
  • 66. Symfony, la guía definitiva Capítulo 4. Introducción a la creación de páginas Listado 4-12 - La mayoría de los helpers aceptan un argumento option // Argumento "option" como un array asociativo <?php echo link_to('Nunca digo mi nombre', 'mimodulo/otraAccion?nombre=anonimo', array( 'class' => 'enlace_especial', 'confirm' => '¿Estás seguro?', 'absolute' => true )) ?> // Argumento "option" como una cadena de texto <?php echo link_to('Nunca digo mi nombre', 'mimodulo/otraAccion?nombre=anonimo', 'class=enlace_especial confirm=¿Estás seguro? absolute=true') ?> // Las 2 funciones devuelven el mismo resultado => <a class="enlace_especial" onclick="return confirm('¿Estás seguro?');" href="http://localhost/miaplicacion_dev.php/mimodulo/otraAccion/nombre/anonimo"> Nunca digo mi nombre</a> Siempre que se utiliza un helper de Symfony que devuelve una etiqueta HTML, es posible insertar atributos de etiqueta adicionales (como el atributo class en el ejemplo del Lista- do 4-12) en el argumento option. Incluso es posible escribir estos atributos a la vieja usanza que utiliza HTML 4.0 (sin comillas dobles), y Symfony se encargará de mostrarlos correctamente formateados en XHTML. Esta es otra razón por la que los helpers son más rápidos de escribir que el HTML puro. NOTA Dado que requiere un procesado y transformación adicional, la sintaxis de cadena de texto es un poco más lenta que la sintaxis en forma de array. Al igual que los helpers para formulario, los helpers de hipervínculo son muchos y tienen muchas opciones. El Capítulo 9 los describirá en detalle. 4.4. Obteniendo información de la petición El método getRequestParameter() del objeto sfActions permite recuperar desde la acción los datos relacionados con la información que envía el usuario a través de un formulario (normalmente en una petición POST) o a través de la URL (mediante una petición GET). El Listado 4-13 muestra cómo es posible obtener el valor del parámetro name en otraAccion. Listado 4-13 - Recuperando datos de la petición desde el parámetro de petición, dentro de una acción <?php class mimoduloActions extends sfActions { ... public function executeOtraAccion() { $this->nombre = $this->getRequestParameter('nombre'); www.librosweb.es 66
  • 67. Symfony, la guía definitiva Capítulo 4. Introducción a la creación de páginas } } Si la manipulación de datos es simple, ni siquiera es necesario utilizar la acción para re- cuperar los parámetros de petición. La plantilla tiene acceso a un objeto llamado $sf_pa- rams, el cual ofrece un método get() para recuperar los parámetros de la petición, tal y como el método getRequestParameter() en la acción. Dada una acción executeOtraAccion vacía, el Listado 4-14 muestra cómo la plantilla otraAccionSuccess.php recupera el mismo parámetro name. Listado 4-14 - Obteniendo datos del parámetro de petición directamente en la plantilla <p>Hola, <?php echo $sf_params->get('nombre') ?>!</p> NOTA ¿Y por qué no utilizar en cambio las variables $_POST, $_GET, or $_REQUEST? Porque entonces las URL serían formateadas de manera diferente (como en http://localhost/articulos/europa/fran- cia/economia.html, sin ? ni =), las variables comunes de PHP ya no funcionarían, y sólo el sistema de enrutamiento sería capaz de recuperar los parámetros de petición. Además, seguramente quie- ras agregar un filtro a los datos del de la petición para prevenir código malicioso, lo cual sólo es po- sible si se mantienen todos los parámetros de petición en un contenedor de parámetros. El objeto $sf_params es más potente que simplemente añadir una especie de getter a un array. Por ejemplo, si sólo se desea probar la existencia de un parámetro de petición, se puede utilizar simplemente el método $sf_parameter->has(), en lugar de comprobar el valor en cuestión con get(), tal como en el Listado 4-15. Listado 4-15 - Comprobando la existencia de un parámetro de petición en la plantilla <?php if ($sf_params->has('nombre')): ?> <p>¡Hola, <?php echo $sf_params->get('nombre') ?>!</p> <?php else: ?> <p>¡Hola, Juan Pérez!</p> <?php endif; ?> Como puede que hayas adivinado, el código anterior puede escribirse en una sola línea. Al igual que la mayoría de los métodos getter de Symfony, tanto el método getReq- uestParameter() en la acción, como el método $sf_params->get() en la plantilla (el cual, de hecho, llama al mismo método en el mismo objeto), acepta un segundo argumento: el valor por defecto a utilizar si dicho parámetro de petición no está presente. <p>¡Hola, <?php echo $sf_params->get('nombre', 'Juan Pérez') ?>!</p> 4.5. Resumen En Symfony, las páginas están compuestas por una acción (un método del archivo act- ions/actions.class.php con el prefijo execute) y una plantilla (un archivo en el directorio templates/, normalmente terminado en Success.php). Las páginas se agrupan en módu- los, de acuerdo a su función en la aplicación. Escribir plantillas es muy sencillo con la ayuda de los helpers, funciones provistas por Symfony para generar código HTML. www.librosweb.es 67
  • 68. Symfony, la guía definitiva Capítulo 4. Introducción a la creación de páginas Además es necesario pensar que la URL es parte de la respuesta, por lo que se puede formatear de cualquier forma que se necesite, sólo debes abstenerte de utilizar cualquier referencia directa a la URL en el nombre de la acción o al recuperar un parámetro de petición. Una vez aprendidos estos principios básicos, es posible escribir una aplicación web com- pleta con Symfony. Pero te costaría mucho tiempo, dado que casi cualquier tarea a com- pletar durante el transcurso del desarrollo de la aplicación, se simplifica de una forma u otra por alguna funcionalidad de Symfony...motivo por el que este libro aún no termina. www.librosweb.es 68
  • 69. Symfony, la guía definitiva Capítulo 5. Configurar Symfony Capítulo 5. Configurar Symfony Para simplificar su uso, Symfony define una serie de convenciones o normas que se ajus- tan a los requisitos habituales de las aplicaciones web estándar. De todas formas, los ar- chivos de configuración, a pesar de ser tan sencillos de utilizar, son lo suficientemente potentes como para personalizar cualquier aspecto del framework y la forma en que inte- ractúan las aplicaciones. También es posible con estos archivos de configuración añadir parámetros específicos para las aplicaciones. En este capítulo se explica cómo funciona el mecanismo de configuración: ▪ La configuración de Symfony se guarda en archivos escritos con YAML, aunque se puede utilizar otro formato. ▪ En la estructura de directorios del proyecto, existen archivos de configuración a nivel de proyecto, de aplicación y de módulo. ▪ También es posible definir conjuntos de opciones de configuración. En Symfony, un conjunto de opciones de configuración se llama entorno. ▪ Desde cualquier punto del código de la aplicación se puede acceder a los valores establecidos en los archivos de configuración. ▪ Además, Symfony permite utilizar código PHP dentro de los archivos YAML y algún que otro truco más para hacer más flexible el sistema de configuración. 5.1. El sistema de configuración La mayoría de aplicaciones web comparten una serie de características, independiente- mente de su finalidad. Por ejemplo, es habitual restringir algunas partes de la aplicación a una serie de usuarios, utilizar un layout común para mostrar todas las páginas, los for- mularios deben volver a mostrar los datos que ha introducido el usuario después de una validación errónea. El framework define el comportamiento básico de estas característi- cas y el programador puede adaptar cada una mediante las opciones de configuración. Esta forma de trabajar ahorra mucho tiempo de desarrollo, ya que muchos cambios im- portantes no necesitan modificar ni siquiera una línea de código, aunque estos cambios impliquen muchos cambios internos. Además se trata de una forma mucho más eficiente, ya que permite asegurar que toda la configuración se encuentra en un lugar único y fácil- mente localizable. No obstante, este método también tiene dos inconvenientes muy importantes: ▪ Los programadores acaban escribiendo archivos XML complejos y muy largos. ▪ En una arquitectura basada en PHP, cada petición consumiría mucho más tiempo de proceso. Considerando todas estas desventajas, Symfony utiliza solamente lo mejor de los archi- vos de configuración. De hecho, el objetivo del sistema de configuración de Symfony es ser: www.librosweb.es 69
  • 70. Symfony, la guía definitiva Capítulo 5. Configurar Symfony ▪ Potente: todo lo que puede ser gestionado con archivos de configuración, se ges- tiona con archivos de configuración. ▪ Simple: muchas de las características de la configuración no se utilizan habitual- mente, por lo que las aplicaciones normales no tienen que tratar con ellas. ▪ Sencillo: los archivos de configuración son sencillos de leer, de modificar y de crear por parte de los desarrolladores. ▪ Personalizable: el lenguaje que se utiliza por defecto en los archivos de configu- ración es YAML, pero se puede cambiar por archivos INI, XML o cualquier otro formato que prefiera el programador. ▪ Rápido: la aplicación nunca procesa los archivos de configuración, sino que se encarga de ello el sistema de configuración, que compila todos los archivos de configuración en trozos de código PHP que se pueden procesar muy rápidamente. 5.1.1. Sintaxis YAML y convenciones de Symfony Symfony utiliza por defecto el formato YAML para la configuración, en vez de los tradicio- nales formatos INI y XML. El formato YAML indica su estructura mediante la tabulación y es muy rápido de escribir. El Capítulo 1 ya describe algunas de sus ventajas y las reglas más básicas. No obstante, se deben tener presentes algunas convenciones al escribir ar- chivos YAML. En esta sección se mencionan las convenciones o normas más importantes. El sitio web de YAML (http://www.yaml.org/) contiene la lista completa de normas del formato. En primer lugar, no se deben utilizar tabuladores en los archivos YAML, sino que siempre se deben utilizar espacios en blanco. Los sistemas que procesan YAML no son capaces de tratar con los tabuladores, por lo que la tabulación de los archivos se debe crear con espacios en blanco como se muestra en el listado 5-1 (en YAML un tabulador se indica mediante 2 espacios en blanco seguidos). Listado 5-1 - Los archivos YAML no permiten los tabuladores # No utilices tabuladores all: -> mail: -> -> webmaster: webmaster@ejemplo.com # Utiliza espacios en blanco all: mail: webmaster: webmaster@ejemplo.com Si los parámetros son cadenas de texto que contienen espacios en blanco al principio o al final, se debe encerrar la cadena entera entre comillas simples. Si la cadena de texto contiene caracteres especiales, también se encierran con comillas simples, como se muestra en el listado 5-2. Listado 5-2 - Las cadenas de texto especiales deben encerrarse entre comillas simples www.librosweb.es 70
  • 71. Symfony, la guía definitiva Capítulo 5. Configurar Symfony error1: Este campo es obligatorio error2: ' Este campo es obligatorio ' # Las comillas simples que aparecen dentro de las cadenas de # texto, se deben escribir dos veces error3: 'Este <nowiki>''campo''</nowiki> es obligatorio' Se pueden escribir cadenas de texto muy largas en varias líneas, además de juntar cade- nas escritas en varias líneas. En este último caso, se debe utilizar un carácter especial para indicar que se van a escribir varias líneas (se puede utilizar > o |) y se debe añadir una pequeña tabulación (dos espacios en blanco) a cada línea del grupo de cadenas de texto. El listado 5-3 muestra este caso. Listado 5-3 - Definir cadenas de texto muy largas y cadenas de texto multi-línea # Las cadenas de texto muy largas se pueden escribir en # varias líneas utilizando el carácter > # Posteriormente, cada nueva línea se transforma en un # espacio en blanco para formar la cadena de texto original. # De esta forma, el archivo YAML es más fácil de leer frase_para_recordar: > Vive como si fueras a morir mañana y aprende como si fueras a vivir para siempre. # Si un texto está formado por varias líneas, se utiliza # el carácter | para separar cada nueva línea. Los espacios # en blanco utilizados para tabular las líneas no se tienen # en cuenta. direccion: | Mi calle, número X Nombre de mi ciudad CP XXXXX Los arrays se definen mediante corchetes que encierran a los elementos o mediante la sintaxis expandida que utiliza guiones medios para cada elemento del array, como se muestra en el listado 5-4. Listado 5-4 - Sintaxis de YAML para incluir arrays # Sintaxis abreviada para los arrays idiomas: [ Alemán, Francés, Inglés, Italiano ] # Sintaxis expandida para los arrays idiomas: - Alemán - Francés - Inglés - Italiano Para definir arrays asociativos, se deben encerrar los elementos mediante llaves ({ y }) y siempre se debe insertar un espacio en blanco entre la clave y el valor de cada par cla- ve: valor. También existe una sintaxis expandida que requiere indicar cada par clave: valor en una nueva línea y con una tabulación (es decir, con 2 espacios en blanco delan- te) como se muestra en el listado 5-5. Listado 5-5 - Sintaxis de YAML para incluir arrays asociativos www.librosweb.es 71
  • 72. Symfony, la guía definitiva Capítulo 5. Configurar Symfony # Sintaxis incorrecta, falta un espacio después de los 2 puntos mail: {webmaster:webmaster@ejemplo.com,contacto:contacto@ejemplo.com} # Sintaxis abreviada correcta para los array asociativos mail: { webmaster: webmaster@ejemplo.com, contacto: contacto@ejemplo.com } # Sintaxis expandida para los arrays asociativos mail: webmaster: webmaster@ejemplo.com contacto: contacto@ejemplo.com Para los parámetros booleanos, se pueden utilizar los valores on, 1 o true para los valo- res verdaderos y off, 0 o false para los valores falsos. El listado 5-6 muestra los posibles valores booleanos. Listado 5-6 - Sintaxis de YAML para los valores booleanos valores_verdaderos: [ on, 1, true ] valores_falsos: [ off, 0, false ] Es recomendable añadir comentarios (que se definen mediante el carácter #) y todos los espacios en blanco adicionales que hagan falta para hacer más fáciles de leer los archivos YAML, como se muestra en el listado 5-7. Listado 5-7 - Comentarios en YAML y espacios adicionales para alinear valores # Esta línea es un comentario mail: webmaster: webmaster@ejemplo.com contacto: contacto@ejemplo.com admin: admin@ejemplo.com # se añaden espacios en blanco para alinear los valores En algunos archivos de configuración de Symfony, se ven líneas que empiezan por # (y por tanto se consideran comentarios y se ignoran) pero que parecen opciones de configu- ración correctas. Se trata de una de las convenciones de Symfony: la configuración por defecto, heredada de los archivos YAML del núcleo de Symfony, se repite en forma de lí- neas comentadas a lo largo de los archivos de configuracion de cada aplicación, con el ú- nico objetivo de informar al desarrollador. De esta forma, para modificar esa opción de configuración, solamente es necesario eliminar el carácter de los comentarios y estable- cer su nuevo valor. El listado 5-8 muestra un ejemplo. Listado 5-8 - La configuración por defecto se muestra en forma de comentarios # Por defecto la cache está desactivada settings: # cache: off # Para modificar esta opción, se debe descomentar la línea settings: cache: on En ocasiones, Symfony agrupa varias opciones de configuración en categorías. Todas las opciones que pertenecen a una categoría se muestran tabuladas y bajo el nombre de esa categoría. La configuración es más sencilla de leer si se agrupan las listas largas de pares clave: valor. Los nombres de las categorías comienzan siempre con un punto (.) y el listado 5-19 muestra un ejemplo de uso de categorías. www.librosweb.es 72
  • 73. Symfony, la guía definitiva Capítulo 5. Configurar Symfony Listado 5-9 - Los nombres de categorías son como los nombres de las clave, pe- ro empiezan con un punto all: .general: impuestos: 19.6 mail: webmaster: webmaster@ejemplo.com En el ejemplo anterior, mail es una clave y general sólo es el nombre de la categoría. En realidad, el archivo YAML se procesa como si no existiera el nombre de la categoría, es decir, como se muestra en el listado 5-10. El parámetro impuestos realmente es descend- iente directo de la clave all. Listado 5-10 - Los nombres de categorías solo se utilizan para hacer más fácil de leer los archivos YAML y la aplicación los ignora all: impuestos: 19.6 mail: webmaster: webmaster@ejemplo.com Si no te gusta YAML YAML solamente es una interfaz para definir las opciones que utiliza el código PHP, por lo que la configuración definida mediante YAML se transforma en código PHP. Si ya has accedido al menos una vez a la aplicación, comprueba la cache de su configuración (por ejemplo en cache/miapli- cacion/dev/config/). En ese directorio se encuentran los archivos PHP generados por la confi- guración YAML. Más adelante se detalla la cache de la configuración. Lo mejor de todo es que si no quieres utilizar archivos YAML, puedes realizar la misma configura- ción a mano o mediante otros formatos (XML, INI, etc.) Más adelante en este libro se comentan otras formas alternativas a YAML para realizar la configuración e incluso se muestra como modifi- car las funciones de Symfony que se encargan de procesar la configuración (en el Capítulo 19). Uti- lizando estas técnicas, es posible evitar los archivos de configuración e incluso definir tu propio for- mato de configuración. 5.1.2. ¡Socorro, los archivos YAML han roto la aplicación! Los archivos YAML se procesan y se transforman en array asociativos y arrays normales de PHP. Después, estos valores transformados son los que se utilizan en la aplicación pa- ra modificar el comportamiento de la vista, el modelo y el controlador. Por este motivo, cuando existe un error en un archivo YAML, normalmente no se detecta hasta que se uti- liza ese valor específico. Para complicar las cosas, el error o la excepción que se muestra no siempre indica de forma clara que puede tratarse de un error en los archivos YAML de configuración. Si la aplicación deja de funcionar después de un cambio en la configuración, se debe comprobar que no se ha cometido alguno de los errores típicos de los desarrolladores principiantes con YAML: www.librosweb.es 73
  • 74. Symfony, la guía definitiva Capítulo 5. Configurar Symfony ▪ No existe un espacio en blanco entre la clave y su valor: clave1:valor1 # Falta un espacio después del : ▪ Alguna clave de una secuencia de valores está mal tabulada: all: clave1: valor1 clave2: valor2 # Su tabulación no es igual que los otros miembros de la secuencia clave3: valor3 ▪ Alguna clave o valor contiene caracteres reservados por YAML que no han sido encerrados por las comillas simples: mensaje: dile lo siguiente: hola # :, [, ], { y } están reservados por YAML mensaje: 'dile lo siguiente: hola' # Sintaxis correcta ▪ La línea que se modifica está comentada: # clave: valor # No se tiene en cuenta porque empieza por # ▪ Existen 2 claves iguales con diferentes valores dentro del mismo nivel: clave1: valor1 clave2: valor2 clave1: valor3 # clave1 está definida 2 veces, solo se tiene en cuenta su último valor ▪ Todos los valores se consideran cadenas de texto, salvo que se convierta de for- ma explícita su valor: ingresos: 12,345 # El valor es una cadena de texto y no un número, salvo que se convierta 5.2. Un vistazo general a los archivos de configuración La configuración de las aplicaciones realizadas con Symfony se distribuye en varios archi- vos según su propósito. Los archivos contienen definiciones de parámetros, normalmente llamadas opciones de configuración. Algunos parámetros se pueden redefinir en varios niveles de la aplicación web (proyecto, aplicación y módulo) y otros parámetros son ex- clusivos de algún nivel. En los siguientes capítulos se muestran los diversos archivos de configuración relacionados con el tema de cada capítulo. En el Capítulo 19 se explica la configuración avanzada. 5.2.1. Configuración del Proyecto Symfony crea por defecto algunos archivos de configuración relacionados con el proyec- to. El directorio miproyecto/config/ contiene los siguientes archivos: ▪ config.php: se trata del primer archivo que se ejecuta con cada petición o co- mando. Contiene la ruta a los archivos del framework y se puede modificar si se ha realizado una instalación personalizada. Se pueden añadir instrucciones defi- ne de PHP al final de este archivo para que esas constantes sean accesibles en cualquier aplicación del proyecto. El Capítulo 19 muestra el uso más avanzado de este archivo. www.librosweb.es 74
  • 75. Symfony, la guía definitiva Capítulo 5. Configurar Symfony ▪ databases.yml: contiene la definición de los accesos a bases de datos y las opcio- nes de conexión de cada acceso (servidor, nombre de usuario, contraseña, nom- bre de base de datos, etc.) El Capítulo 8 lo explica en detalle. Sus parámetros se pueden redefinir en el nivel de la aplicación. ▪ properties.ini: contiene algunos parámetros que utiliza la herramienta de línea de comandos, como son el nombre del proyecto y las opciones para conectar con servidores remotos. El Capítulo 16 muestra las opciones de este archivo. ▪ rsync_exclude.txt: indica los directorios que se excluyen durante la sincroniza- ción entre servidores. El Capítulo 16 también incluye una explicación de este archivo. ▪ schema.yml y propel.ini: son los archivos de configuración que utiliza Propel pa- ra el acceso a los datos (recuerda que Propel es el sistema ORM que incorpora Symfony). Se utilizan para que las librerías de Propel puedan interactuar con las clases de Symfony y con los datos de la aplicación. schema.yml contiene la repre- sentación del modelo de datos relacional del proyecto. propel.ini se genera de forma automática y es muy probable que no necesites modificarlo. Si no se utiliza Propel, estos dos archivos son innecesarios. El Capítulo 8 explica en detalle el uso de estos dos archivos. La mayoría de estos archivos los utilizan componentes externos o la línea de comandos e incluso algunos son procesados antes de que se inicie la herramienta que procesa archi- vos en formato YAML. Por este motivo, algunos de estos archivos no utilizan el formato YAML. 5.2.2. Configuración de la Aplicación La configuración de la aplicación es la parte más importante de la configuración. La confi- guración se distribuye de la siguiente forma: el controlador frontal (que se encuentra en el directorio web/) contiene la definición de las constantes principales, el directorio con- fig/ de la aplicación contiene diversos archivos en formato YAML, los archivos de inter- nacionalización se encuentran en el directorio i18n/ y también existen otros archivos del framework que no son visibles pero que almacenan configuración adicional de la aplicación. 5.2.2.1. Configuración del Controlador Frontal La primera configuración de la aplicación se encuentra en su controlador frontal, que es el primer script que se ejecuta con cada petición. El listado 5-11 muestra el código por defecto del controlador frontal generado automáticamente: Listado 5-11 - El controlador frontal de producción generado automáticamente <?php define('SF_ROOT_DIR', realpath(dirname(__FILE__).'/..')); define('SF_APP', 'miaplicacion'); define('SF_ENVIRONMENT', 'prod'); define('SF_DEBUG', false); www.librosweb.es 75
  • 76. Symfony, la guía definitiva Capítulo 5. Configurar Symfony require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.' sfContext::getInstance()->getController()->dispatch(); Después de definir el nombre de la aplicación (miaplicacion) y el entorno en el que se ejecuta la aplicación (prod), se carga el archivo general de configuración y se despacha la petición (dispatching). En este archivo se definen algunas constantes importantes: ▪ SF_ROOT_DIR: directorio raíz del proyecto (en general no hay que modificar su va- lor, salvo que se cambie la estructura de archivos del proyecto). ▪ SF_APP: el nombre de la aplicación. Es necesario para determinar las rutas de los archivos. ▪ SF_ENVIRONMENT: nombre del entorno en el que se ejecuta la aplicación (prod, dev o cualquier otro valor que se haya definido). Se utiliza para determinar las opcio- nes de configuración que se utilizan. Al final de este capítulo se explican los en- tornos de ejecución. ▪ SF_DEBUG: activa el modo de depuración de la aplicación (el Capítulo 16 contiene los detalles). Cuando se quiere cambiar alguno de estos valores, normalmente se crea un nuevo con- trolador frontal. El siguiente capítulo explica su funcionamiento y cómo crear nuevos controladores. El directorio raíz puede estar en cualquier parte Los únicos archivos que se pueden acceder desde Internet son los que se encuentran en el directo- rio web del proyecto (es decir, el directorio llamado web/). Los scripts de los controladores fronta- les, las imágenes, las hojas de estilos y los archivos JavaScript son públicos. El resto de archivos deben estar fuera de la raíz del servidor web, por lo que pueden estar en cualquier sitio. El controlador frontal accede a los archivos que no son públicos mediante la ruta definida en SF_ROOT_DIR. Habitualmente el directorio raíz del proyecto se encuentra en el nivel inmediata- mente superior al directorio web/. Sin embargo, es posible definir una estructura de directorios com- pletamente diferente. Suponiendo que la estructura de directorios está formada por dos directorios principales, uno público y otro privado: symfony/ # Zona privada apps/ batch/ cache/ ... www/ # Zona pública images/ css/ js/ index.php En este caso, el directorio raíz es el directorio symfony/. Por tanto, en el controlador frontal in- dex.php se debe redefinir la constante SF_ROOT_DIR con el siguiente valor para que la aplicación funcione correctamente: www.librosweb.es 76
  • 77. Symfony, la guía definitiva Capítulo 5. Configurar Symfony define('SF_ROOT_DIR', realpath(dirname(__FILE__).'/../symfony')); El Capítulo 19 explica en detalle cómo personalizar Symfony para que funcione con una estructura de directorios a medida. 5.2.2.2. Configuración principal de la Aplicación La configuración más importante de la aplicación se encuentra en el directorio miproyec- to/apps/miaplicacion/config/: ▪ app.yml: contiene la configuración específica de la aplicación; por ejemplo se pueden almacenar variables globales que se utilizan en la lógica de negocio de la aplicación y que no se almacenan en una base de datos. Los ejemplos habituales de estas variables son los porcentajes de los impuestos como el IVA, los gastos de envío, direcciones de email de contacyo, etc. Por defecto el archivo está vacío. ▪ config.php: este archivo inicia la ejecucion de la aplicación, ya que realiza todas las inicializaciones necesarias para que la aplicación se pueda ejecutar. En este archivo se puede personalizar la estructura de directorios de la aplicación y se pueden añadir constantes que manejan las aplicaciones (el Capítulo 19 lo explica con más detalle). Comienza incluyendo el arhivo config.php del proyecto al que pertenece la aplicación. ▪ factories.yml: Symfony incluye sus propias clases para el manejo de la vista, de las peticiones, de las respuestas, de la sesión, etc. No obstante, es posible definir otras clases propias para realizar estas tareas. El Capítulo 17 lo explica más detalladamente. ▪ filters.yml: los filtros son trozos de código que se ejecutan con cada petición. En este archivo se definen los filtros que se van a procesar y cada módulo puede redefinir los filtros que se procesan. El Capítulo 6 explica en detalle los filtros. ▪ logging.yml: permite definir el nivel de detalle con el que se generan los archivos de log, utilizados para el mantenimiento y la depuración de las aplicaciones. El Capítulo 16 explica más profundamente esta configuración. ▪ routing.yml: almacena las reglas de enrutamiento, que permiten transformar las URL habituales de las aplicaciones web en URL limpias y sencillas de recordar. Cada vez que se crea una aplicación se crean unas cuantas reglas básicas por de- fecto. El Capítulo 9 está dedicado a los enlaces y el sistema de enrutamiento. ▪ settings.yml: contiene las principales opciones de configuración de una aplica- ción Symfony. Entre otras, permite especificar si la aplicación utiliza la internacio- nalización, el idioma por defecto de la aplicación, el tiempo de expiración de las peticiones y si se activa o no la cache. Un cambio en una única línea de configu- ración de este archivo permite detener el acceso a la aplicación para realizar ta- reas de mantenimiento o para actualizar alguno de sus componentes. Las opcio- nes más habituales y su uso se describen en el Capítulo 19. ▪ view.yml: establece la estructura inicial de la vista por defecto: el nombre del la- yout, el título de la página y las etiquetas <meta>; las hojas de estilos y los www.librosweb.es 77
  • 78. Symfony, la guía definitiva Capítulo 5. Configurar Symfony archivos JavaScript que se incluyen; el Content-Type por defecto, etc. También permite definir el valor por defecto de las etiquetas <title> y <meta>. Cada mó- dulo puede redefinir el valor de todas estas opciones. 5.2.2.3. Configuración de la Internacionalización Las aplicaciones con soporte de internacionalización son las que pueden mostrar una mis- ma página en diferentes idiomas. Para conseguirlo, es necesario realizar una configura- ción específica. Los dos sitios donde se configura la internacionalización en Symfony son: ▪ Archivo i18n.yml del directorio config/ de la aplicación: en este archivo se esta- blecen las opciones generales de traducción de páginas, como por ejemplo el id- ioma por defecto, si las traducciones se guardan en archivos o bases de datos y su formato. ▪ Los archivos de traducción en el directorio i18n/ de la aplicación: se trata de una especie de diccionarios que indican la traducción de cada frase que utilizan las plantillas de la aplicación de forma que cuando el usuario cambie de idioma sea posible mostrar las páginas en ese idioma. Para activar el mecanismo de i18n, se debe modificar el archivo settings.yml. El Capítulo 13 profundiza en todas las características relacionadas con la i18n. NOTA Nota del traductor El término i18n es el más utilizado para referirse a la “internacionalización”. Su origen proviene de las 18 letras que existen entre la letra “i” y la letra “n” en la palabra “internaciona- lización”. Otras palabras siguen la misma técnica y así es habitual decir l10n para hablar de la “lo- calization” o adaptación local de los contenidos. 5.2.2.4. Configuración adicional de la Aplicación Algunos archivos de configuración se encuentran en el directorio de instalación de Sym- fony (en $sf_symfony_data_dir/config/) y por tanto no aparecen en los directorios de configuración de las aplicaciones. Las opciones que se encuentran en esos archivos son opciones para las que raramente se modifica su valor o que son globales a todos los pro- yectos. De todas formas, si necesitas modificar alguna de estas opciones, crea un archivo vacío con el mismo nombre en el directorio miproyecto/apps/miaplicacion/config/ y re- define todas las opciones que quieras modificar. Las opciones definidas en una aplicación siempre tienen preferencia respecto a las opciones definidas por el framework. Los archi- vos de configuración que se encuentran en el directorio config/ de la instalación de Sym- fony son los siguientes: ▪ autoload.yml: contiene las opciones relativas a la carga automática de clases. Es- ta opción permite utilizar clases propias sin necesidad de incluirlas previamente en el script que las utiliza, siempre que esas clases se encuentren en algunos di- rectorios determinados. El Capítulo 19 lo describe en detalle. www.librosweb.es 78
  • 79. Symfony, la guía definitiva Capítulo 5. Configurar Symfony ▪ constants.php: define la estructura de archivos y directorios por defecto. Para re- definir estos valores, se debe utilizar el archivo de configuración config.php de la aplicación, como se muestra en el Capítulo 19. ▪ core_compile.yml y bootstrap_compile.yml: define la lista de clases que se inclu- yen al iniciar la aplicación (en bootstrap_compile.yml) y las que se incluyen al procesar una petición (en core_compile.yml). Todas estas clases se concatenan en un único archivo PHP optimizado en el que se eliminan los comentarios y que acelera mucho la ejecución de la aplicación (ya que se reduce el número de ar- chivos que se acceden a uno solo desde los más de 40 archivos necesarios origi- nalmente para cada petición). Esta característica es muy necesaria cuando no se utiliza ningún acelerador de PHP. El Capítulo 18 incluye diversas técnicas para optimizar el rendimiento de las aplicaciones. ▪ config_handlers.yml: permite añadir o modificar los manejadores de archivos de configuración. El Capítulo 19 contiene todos los detalles. ▪ php.yml: este archivo se utiliza para comprobar que las directivas del archivo de configuración de PHP php.ini tienen los valores adecuados y permite redefinirlas si hace falta. Los detalles se explican en el Capítulo 19. 5.2.3. Configuración de los Módulos Inicialmente los módulos no tienen ninguna configuración específica. En cualquier caso, es posible modificar las opciones de la aplicación en cualquier módulo que así lo requiera. Algunos de los usos típicos son los de cambiar la descripción HTML en todas las acciones de un módulo o para incluir un archivo JavaScript específico. También se pueden añadir nuevos parámetros exclusivamente para un módulo concreto. Como se puede suponer, los archivos de configuración de los módulos se encuentran en el directorio miproyecto/apps/miaplicacion/modules/mimodulo/config/. Los archivos dis- ponibles son los siguientes: ▪ generator.yml: se utiliza para los módulos generados automáticamente a partir de una tabla de la base de datos, es decir, para los módulos utilizados en el scaf- folding y para las partes de administración creadas de forma automática. Contie- ne las opciones que definen como se muestran las filas y los registros en las pá- ginas generadas y también define las interacciones con el usuario: filtros, orde- nación, botones, etc. El Capítulo 14 explica todas estas características. ▪ module.yml: contiene la configuración de la acción y otros parámetros específicos del módulo (es un archivo equivalente al archivo app.yml de la aplicación). El Capítulo 6 lo explica en detalle. ▪ security.yml: permite restringir el acceso a determinadas acciones del módulo. En este archivo se configura que una página solamente pueda ser accedida por los usuarios registrados o por un grupo de usuarios registrados con permisos es- peciales. En el Capítulo 6 se detalla su funcionamiento. www.librosweb.es 79
  • 80. Symfony, la guía definitiva Capítulo 5. Configurar Symfony ▪ view.yml: permite configurar las vistas de una o de todas las acciones del módu- lo. Redefine las opciones del archivo view.yml de la aplicación y su funcionamien- to se describe en el Capítulo 7. ▪ Archivos de validación de datos: aunque se encuentran en el directorio validate/ en vez del directorio config/, se trata de archivos de configuración creados con YAML y que se emplean para controlar los datos introducidos en los formularios. En el Capítulo 10 se estudian en detalle. Casi todos los archivos de configuración de los módulos permiten definir parámetros para todas las vistas y/o acciones del módulo o solo para una serie de vistas y/o acciones. ¿No son demasiados archivos? Seguramente estás un poco abrumado por la cantidad de archivos de configuración que tiene la aplicación. Pero debes tener en cuenta que: Muy pocas veces tendrás que modificar la configuración, ya que las convenciones y normas esta- blecidas por Symfony suelen coincidir con los requerimientos habituales de las aplicaciones. Cada archivo de configuración se utiliza para alguna característica concreta, que se detallarán una a una en los siguientes capítulos. Cuando se estudia individualmente uno de los archivos, es muy fácil comprender su estructura y su finalidad. Para las aplicaciones más profesionales, es habitual modi- ficar la configuración por defecto. Los archivos de configuración permiten modificar fácilmente el funcionamiento de Symfony sin necesidad de añadir o modificar código a la aplicación. Imagina la cantidad de código PHP que se necesitaría para obtener un control similar al de Symfony. Si toda la configuración estuviera centralizada en un único archivo, además de ser un archivo enorme y muy difícil de leer, no sería posible redefinir el valor de las opciones en los diferentes niveles (como se ve más tarde en este capítulo en la sección “Configuraciones en cascada”). El mecanismo de configuración de Symfony es uno de sus puntos fuertes, ya que permite que el framework se pueda utilizar para crear cualquier tipo de aplicación y no solamente aquellas para las que se diseñó originalmente. 5.3. Entornos Cuando se desarrolla una aplicación, es habitual disponer de varias configuraciones dis- tintas pero relacionadas. Por ejemplo durante el desarrollo se tiene un archivo de confi- guración con los datos de conexión a la base de datos de pruebas, mientras que en el servidor de producción los datos de conexión necesarios son los de la base de datos de producción. Symfony permite definir diferentes entornos de ejecución para poder mane- jar de forma sencilla las diferentes configuraciones. 5.3.1. ¿Qué es un entorno? Las aplicaciones de Symfony se pueden ejecutar en diferentes entornos. Todos los entor- nos comparten el mismo código PHP (salvo el código del controlador frontal) pero pueden tener configuraciones completamente diferentes. Cuando se crea una aplicación, Sym- fony crea por defecto 3 entornos: producción (prod), pruebas (test) y desarrollo (dev). También es posible añadir cualquier nuevo entorno que se considere necesario. www.librosweb.es 80
  • 81. Symfony, la guía definitiva Capítulo 5. Configurar Symfony En cierta forma, un entorno y una configuración son sinónimos. Por ejemplo el entorno de pruebas registra las alertas y los errores en el archivo de log, mientras que el entorno de producción solamente registra los errores. En el entorno de desarrollo se suele desac- tivar la cache, pero está activa en los entornos de pruebas y de producción. Los entornos de pruebas y desarrollo normalmente trabajan con una base de datos que contiene datos de prueba, mientras que el servidor de producción trabaja con la base de datos buena. En este caso, la configuración de la base de datos varía en los diferentes entornos. Todos los entornos pueden ejecutarse en una misma máquina, aunque en los servidores de pro- ducción normalmente solo se instala el entorno de producción. El entorno de desarrollo tiene activadas las opciones de log y de depuración de aplicacio- nes, ya que es más importante el mantenimiento de la aplicación que su rendimiento. Sin embargo, en el entorno de producción se ajustan las opciones de configuración para ob- tener el máximo rendimiento, por lo que muchas características están desactivadas por defecto. Una buena regla general suele ser la de utilizar el entorno de desarrollo hasta que consideres que la funcionalidad de la aplicación en la que estás trabajando se enc- uentra terminada y después pasarse al entorno de producción para comprobar su rendimiento. El entorno de pruebas varía respecto del de desarrollo y el de producción. La única forma de interactuar con este entorno es mediante la línea de comandos para realizar pruebas funcionales y ejecutar scripts. De esta forma, el entorno de pruebas es parecido al de producción, pero no se accede a través de un navegador. De todas formas, simula el uso de cookies y otros componentes específicos de HTTP. Para ejecutar la aplicación en otro entorno, solamente es necesario cambiar de controla- dor frontal. Hasta ahora, en todos los ejemplos se accedía al entorno de desarrollo, ya que las URL utilizadas llamaban al controlador frontal de desarrollo: http://localhost/miaplicacion_dev.php/mimodulo/index Sin embargo, si se quiere acceder a la aplicación en el entorno de producción, es necesa- rio modificar la URL para llamar al controlador frontal de producción: http://localhost/index.php/mimodulo/index Si el servidor web tiene habilitado el mod_rewrite, es posible utilizar las reglas de reescri- tura de URL de Symfony, que se encuentran en web/.htaccess. Estas reglas definen que el controlador frontal de producción es el script que se ejecuta por defecto en las peticio- nes, por lo que se pueden utilizar URL como la siguiente: http://localhost/mimodulo/index Entornos y servidores No se deben confundir los entornos con los servidores. En Symfony, un entorno diferente es en rea- lidad una configuración diferente, que se corresponde con un controlador frontal determinado (que es el script que se encarga de procesar la petición). Sin embargo, un servidor diferente se corres- ponde con un nombre de dominio diferente en la dirección. http://localhost/miaplicacion_dev.php/mimodulo/index Servidor = localhost Entorno = miaplicacion_dev.php (es decir, entorno de desarrollo) www.librosweb.es 81
  • 82. Symfony, la guía definitiva Capítulo 5. Configurar Symfony Normalmente, los desarrolladores programan y prueban sus aplicaciones en servidores de desarro- llo, que no son accesibles desde Internet y donde se puede modificar cualquier configuración de PHP o del propio servidor. Cuando la aplicación se va a lanzar de forma pública, se transfieren los archivos de la aplicación al servidor de producción y se permite el acceso a los usuarios. Por tanto, en cada servidor existen varios entornos de ejecución. Se puede ejecutar por ejemplo la aplicación en el entorno de producción aunque el servidor sea el de desarrollo. No obstante, suele ser habitual que en el servidor de producción solamente estén disponibles los entornos de ejecu- ción de producción, para eviar que los usuarios puedan ver la configuración del servidor o puedan comprometer la seguridad del sistema. Para crear un nuevo entorno de ejecución, no es necesario recurrir a la línea de comandos o crear nuevos directorios. Lo único que hay que hacer es crear un nuevo archivo de tipo controlador fron- tal (puedes copiar uno de los existentes) y modificar el nombre de su entorno de ejecución. Este nuevo entorno hereda todas las configuraciones por defecto del framework y todas las opciones co- munes a todos los entornos. En el siguiente capítulo se detalla como realizar esta operación. 5.3.2. Configuration en cascada Una misma opción de configuración puede estar definida más de una vez en archivos di- ferentes. De esta forma es posible por ejemplo definir que el tipo MIME de las páginas de la aplicación sea text/html, pero que las páginas creadas con el módulo que se encarga del RSS tengan un tipo MIME igual a text/xml. Symfony permite establecer el primer va- lor en miaplicacion/config/view.yml y el segundo en miaplicacion/modules/rss/config/ view.yml. El sistema de configuración se encarga de que una opción establecida a nivel de módulo tenga más prioridad que la opción definida a nivel de aplicación. De hecho, Symfony define varios niveles de configuración: ▪ Niveles de granularidad: ▪ Configuración por defecto establecida por el framework ▪ Configuración global del proyecto (en miproyecto/config/) ▪ Configuración local de cada aplicación (en miproyecto/apps/miaplicac- ion/config/) ▪ Configuración local de cada módulo (en miproyecto/apps/miaplicacion/ modules/mimodulo/config/) ▪ Niveles de entornos de ejecución: ▪ Específico para un solo entorno ▪ Para todos los entornos Muchas de las opciones que se pueden establecer dependen del entorno de ejecución. Por este motivo, los archivos de configuración YAML están divididos por entornos, además de incluir una sección que se aplica a todos los entornos. De esta forma, un ar- chivo de configuración típico de Symfony se parece al que se muestra en el listado 5-12. Listado 5-12 - La estructura típica de los archivos de configuración de Symfony www.librosweb.es 82
  • 83. Symfony, la guía definitiva Capítulo 5. Configurar Symfony # Opciones para el entorno de producción prod: ... # Opciones para el entorno de desarrollo dev: ... # Opciones para el entorno de pruebas test: ... # Opciones para un entorno creado a medida mientorno: ... # Opciones para todos los entornos all: ... Además de estas opciones, el propio framework define otros valores por defecto en archi- vos que no se encuentran en la estructura de directorios del proyecto, sino que se enc- uentran en el directorio $sf_symfony_data_dir/config/ de la instalación de Symfony. El listado 5-13 muestra la configuración por defecto de estos archivos. Todas las aplicacio- nes heredan estas opciones. Listado 5-13 - La configuración por defecto, en $sf_symfony_data_dir/config/ settings.yml # Opciones por defecto: default: default_module: default default_action: index ... Las opciones de estos archivos se incluyen como opciones comentadas en los archivos de configuración del proyecto, la aplicación y los módulos, como se muestra en el listado 5- 14. De esta forma, se puede conocer el valor por defecto de algunas opciones y modifi- carlo si es necesario. Listado 5-14 - La configuración por defecto, repetida en varios archivos para co- nocer fácilmente su valor, en miaplicacion/config/settings.yml #all: # default_module: default # default_action: index ... El resultado final es que una misma opción puede ser definida varias veces y el valor que se considera en cada momento se genera mediante la configuración en cascada. Una op- ción definida en un entorno de ejecución específico tiene más prioridad sobre la misma opción definida para todos los entornos, que también tiene preferencia sobre las opciones definidas por defecto. Las opciones definidas a nivel de módulo tienen preferencia sobre las mismas opciones definidas a nivel de aplicación, que a su vez tienen preferencia www.librosweb.es 83
  • 84. Symfony, la guía definitiva Capítulo 5. Configurar Symfony sobre las definidas a nivel de proyecto. Todas estas prioridades se resumen en la siguien- te lista de prioridades, en el que el primer valor es el más prioritario de todos: 1. Módulo 2. Aplicación 3. Proyecto 4. Entorno específico 5. Todos los entornos 6. Opciones por defecto 5.4. La cache de configuración Si cada nueva petición tuviera que procesar todos los archivos YAML de configuración y tuviera que aplicar la configuración en cascada, se produciría una gran penalización en el rendimiento de la aplicación. Symfony incluye un mecanismo de cache de configuración para aumentar la velocidad de respuesta de las peticiones. Unas clases especiales, llamadas manejadores, procesan todos los archivos de configura- ción originales y los transforman en código PHP que se puede procesar de forma muy rá- pida. En el entorno de desarrollo se prima la interactividad y no el rendimiento, por lo que en cada petición se comprueba si se ha modificado la configuración. Como se proce- san siempre los archivos modificados, cualquier cambio de un archivo YAML se refleja de forma inmediata. Sin embargo, en el entorno de producción solamente se procesa la con- figuración una vez durante la primera petición y se almacena en una cache el código PHP generado, para que lo utilicen las siguientes peticiones. El rendimiento en el entorno pro- ducción no se resiente, ya que las peticiones solamente ejecutan código PHP optimizado. Si por ejemplo el archivo app.yml contiene lo siguiente: all: # Opciones para todos los entornos mail: webmaster: webmaster@ejemplo.com La carpeta cache/ del proyecto contendrá un archivo llamado config_app.yml.php y con el siguiente contenido: <?php sfConfig::add(array( 'app_mail_webmaster' => 'webmaster@ejemplo.com', )); La consecuencia es que los archivos YAML raramente son procesados por el framework, ya que se utiliza la cache de la configuración siempre que sea posible. Sin embargo, en el entorno de desarrollo cada nueva petición obliga a Symfony a comparar las fechas de modificación de los archivos YAML y las de los archivos almacenados en la cache. Sola- mente se vuelven a procesar aquellos archivos que hayan sido modificados desde la peti- ción anterior. www.librosweb.es 84
  • 85. Symfony, la guía definitiva Capítulo 5. Configurar Symfony Este mecanismo supone una gran ventaja respecto de otros frameworks de PHP, en los que se compilan los archivos de configuración en cada petición, incluso en producción. Al contrario de lo que sucede con Java, PHP no define un conexto de ejecución común a to- das las peticiones. En otros frameworks de PHP, se produce una pérdida de rendimiento importante al procesar toda la configuración con cada petición. Gracias al sistema de ca- che, Symfony no sufre esta penalización ya que la pérdida de rendimiento provocada por la configuración es muy baja. La cache de la configuración implica una consecuencia muy importante. Si se modifica la configuración en el entorno de producción, se debe forzar a Symfony a que vuelva a pro- cesar los archivos de configuración para que se tengan en cuenta los cambios. Para ello, solo es necesario borrar la cache, borrando todos los contenidos del directorio cache/ o utilizando una tarea específica proporcionada por Symfony: > symfony clear-cache 5.5. Accediendo a la configuración desde la aplicación Los archivos de configuración se transforman en código PHP y la mayoría de sus opciones solamente son utilizadas por el framework. Sin embargo, en ocasiones es necesario acce- der a los archivos de configuración desde el código de la aplicación (en las acciones, plantillas, clases propias, etc.) Se puede acceder a todas las opciones definidas en los ar- chivos settings.yml, app.yml, module.yml, logging.yml y i18n.yml mediante una clase especial llamada sfConfig. 5.5.1. La clase sfConfig Desde cualquier punto del código de la aplicación se puede acceder a las opciones de configuración mediante la clase sfConfig. Se trata de un registro de opciones de configu- ración que proporciona un método getter que puede ser utilizado en cualquier parte del código: // Obtiene una opción opcion = sfConfig::get('nombre_opcion', $valor_por_defecto); También se pueden crear o redefinir opciones desde el código de la aplicación: // Crear una nueva opción sfConfig::set('nombre_opcion', $valor); El nombre de la opción se construye concatenando varios elementos y separándolos con guiones bajos en este orden: ▪ Un prefijo relacionado con el nombre del archivo de configuración (sf_ para set- tings.yml, app_ para app.yml, mod_ para module.yml, sf_i18n_ para i18n.yml y sf_logging_ para logging.yml) ▪ Si existen, todas las claves ascendentes de la opción (y en minúsculas) ▪ El nombre de la clave, en minúsculas No es necesario incluir el nombre del entorno de ejecución, ya que el código PHP solo tie- ne acceso a los valores definidos para el entorno en el que se está ejecutando. www.librosweb.es 85
  • 86. Symfony, la guía definitiva Capítulo 5. Configurar Symfony El listado 5-16 muestra el código necesario para acceder a los valores de las opciones de- finidas en el archivo app.yml mostrado en el listado 5-15. Listado 5-15 - Ejemplo de configuración del archivo app.yml all: version: 1.5 .general: impuestos: 19.6 usuario_por_defecto: nombre: Juan Pérez email: webmaster: webmaster@ejemplo.com contacto: contacto@ejemplo.com dev: email: webmaster: otro@ejemplo.com contacto: otro@ejemplo.com Listado 5-16 - Acceso a las opciones de configuración desde el entorno de desarrollo echo sfConfig::get('app_version'); => '1.5' echo sfConfig::get('app_impuestos'); // Recuerda que se ignora el nombre de la categoría // Es decir, no es necesario incluir 'general' => '19.6' echo sfConfig::get('app_usuario_por_defecto_nombre'); => 'Juan Pérez' echo sfConfig::get('app_email_webmaster'); => 'otro@ejemplo.com' echo sfConfig::get('app_email_contacto'); => 'otro@ejemplo.com' Las opciones de configuración de Symfony tienen todas las ventajas de las constantes PHP, pero sin sus desventajas, ya que se puede modificar su valor durante la ejecución de la aplicación. Considerando el funcionamiento que se ha mostrado, el archivo settings.yml que se uti- liza para establecer las opciones del framework en cada aplicación, es equivalente a reali- zar varias llamadas a la función sfConfig::set(). Así que el listado 5-17 se interpreta de la misma forma que el listado 5-18. Listado 5-17 - Extracto del archivo de configuración settings.yml all: .settings: available: on path_info_array: SERVER path_info_key: PATH_INFO url_format: PATH Listado 5-18 - Forma en la que Symfony procesa el archivo settings.yml sfConfig::add(array( 'sf_available' => true, www.librosweb.es 86
  • 87. Symfony, la guía definitiva Capítulo 5. Configurar Symfony 'sf_path_info_array' => 'SERVER', 'sf_path_info_key' => 'PATH_INFO', 'sf_url_format' => 'PATH', )); El Capítulo 19 explica el significado de las opciones de configuración del archivo settings.yml. 5.5.2. El archivo app.yml y la configuración propia de la aplicación El archivo app.yml, que se encuentra en el directorio miproyecto/apps/miaplicacion/con- fig/, contiene la mayoría de las opciones de configuración relacionadas con la aplicación. Por defecto el archivo está vacío y sus opciones se configuran para cada entorno de eje- cución. En este archivo se deben incluir todas las opciones que necesiten modificarse rá- pidamente y se utiliza la clase sfConfig para acceder a sus valores desde el código de la aplicación. El listado 5-19 muestra un ejemplo. Listado 5-19 - Archivo app.yml que define los tipos de tarjeta de crédito acepta- dos en un sitio all: tarjetascredito: falsa: off visa: on americanexpress: on dev: tarjetascredito: falsa: on Para saber si las tarjetas de crédito falsas se aceptan en el entorno de ejecución de la aplicación, se debe utilizar la siguiente instrucción: sfConfig::get('app_tarjetascredito_falsa'); SUGERENCIA Cuando vayas a definir una constante o una opción dentro de un script, piensa si no sería mejor in- cluir esa opción en el archivo app.yml. Se trata del lugar más apropiado para guardar todas las opc- iones de la configuración. Los requerimientos de algunas aplicaciones complejas pueden dificultar el uso del archivo app.yml. En este caso, se puede almacenar la configuración en cualquier otro archivo, con el formato y la sintaxis que se prefiera y que sea procesado por un manejador reali- zado completamente a medida. El Capítulo 19 explica en detalle el funcionamiento de los manejadores de configuraciones. 5.6. Trucos para los archivos de configuración Antes de empezar a crear los primeros archivos YAML, existen algunos trucos muy útiles que es conveniente aprender. Estos trucos permiten evitar la duplicidad de la configura- ción y permiten personalizar el formato YAML. www.librosweb.es 87
  • 88. Symfony, la guía definitiva Capítulo 5. Configurar Symfony 5.6.1. Uso de constantes en los archivos de configuración YAML Algunas opciones de configuración dependen del valor de otras opciones. Para evitar es- cribir 2 veces el mismo valor, Symfony permite definir constantes dentro de los archivos YAML. Si el manejador de los archivos se encuentra con un nombre de opción todo en mayúsculas y encerrado entre los símbolos % y %, lo reemplaza por el valor que tenga en ese momento. El listado 5-20 muestra un ejemplo. Listado 5-20 - Uso de constantes en los archivos YAML, ejemplo extraído del ar- chivo autoload.yml autoload: symfony: name: symfony path: %SF_SYMFONY_LIB_DIR% recursive: on exclude: [vendor] El valor de la opción path es el que devuelve en ese momento la llamada a sfConfig::- get(’sf_symfony_lib_dir’). Si un archivo de configuración depende de otro archivo, es necesario que el archivo del que se depende sea procesado antes (en el código de Sym- fony se puede observar el orden en el que se procesan los archivos de configuración). El archivo app.yml es uno de los últimos que se procesan, por lo que sus opciones pueden depender de las opciones de otros archivos de configuración. 5.6.2. Uso de programación en los archivos de configuración Puede ocurrir que los archivos de configuración dependan de parámetros externos (como por ejemplo una base de datos u otro archivo de configuración). Para poder procesar es- te tipo de casos, Symfony procesa los archivos de configuración como si fueran archivos de PHP antes de procesarlos como archivos de tipo YAML. De esta forma, como se mues- tra en el listado 5-21, es posible incluir código PHP dentro de un archivo YAML: Listado 5-21 - Los archivos YAML puede contener código PHP all: traduccion: formato: <?php echo (sfConfig::get('sf_i18n') == true ? 'xliff' : 'none')."n" ?> El único inconveniente es que la configuración se procesa al principio de la ejecución de la petición del usuario, por lo que no están disponibles ninguno de los métodos y funcio- nes de Symfony. Además, como la instrucción echo no añade ningún retorno de carro por defecto, es nece- sario añadirlo explícitamente mediante n o mediante el uso del helper echoln para cum- plir con el formato YAML: all: traduccion: formato: <?php echoln sfConfig::get('sf_i18n') == true ? 'xliff' : 'none' ?> www.librosweb.es 88
  • 89. Symfony, la guía definitiva Capítulo 5. Configurar Symfony ATENCIÓN Recuerda que en el entorno de producción, se utiliza una cache para la configuración, por lo que los archivos de configuración solamente se procesan (y en este caso, se ejecuta su código PHP) una vez después de borrar la cache. 5.6.3. Utilizar tu propio archivo YAML La clase sfYaml permite procesar de forma sencilla cualquier archivo en formato YAML. Se trata de un procesador (parser) de archivos YAML que los convierte en arrays asocia- tivos de PHP. El listado 5-22 muestra un archivo YAML de ejemplo y el listado 5-23 muestra como transformarlo en código PHP: Listado 5-22 - Archivo de prueba llamado prueba.yml casa: familia: apellido: García padres: [Antonio, María] hijos: [Jose, Manuel, Carmen] direccion: numero: 34 calle: Gran Vía ciudad: Cualquiera codigopostal: 12345 Listado 5-23 - Uso de la clase sfYaml para transformar el archivo YAML en un array asociativo $prueba = sfYaml::load('/ruta/a/prueba.yml'); print_r($prueba); Array( [casa] => Array( [familia] => Array( [apellido] => García [padres] => Array( [0] => Antonio [1] => María ) [hijos] => Array( [0] => Jose [1] => Manuel [2] => Carmen ) ) [direccion] => Array( [numero] => 34 [calle] => Gran Vía [ciudad] => Cualquiera [codigopostal] => 12345 ) ) ) www.librosweb.es 89
  • 90. Symfony, la guía definitiva Capítulo 5. Configurar Symfony 5.7. Resumen El sistema de configuración de Symfony utiliza el lenguaje YAML por ser muy sencillo y fácil de leer. Los desarrolladores cuentan con la posibilidad de definir varios entornos de ejecución y con la opción de utilizar la configuración en cascada, lo que ofrece una gran versatilidad a su trabajo. Las opciones de configuración se pueden acceder desde el códi- go de la aplicación mediante el objeto sfConfig, sobre todo las opciones de configuración de la aplicación que se definen en el archivo app.yml. Aunque Symfony cuenta con muchos archivos de configuración, su ventaja es que así es más adaptable. Además, recuerda que solo las aplicaciones que requieren de una confi- guración muy personalizada tienen que utilizar estos archivos de configuración. www.librosweb.es 90
  • 91. Symfony, la guía definitiva Capítulo 6. El Controlador Capítulo 6. El Controlador En Symfony, la capa del controlador, que contiene el código que liga la lógica de negocio con la presentación, está dividida en varios componentes que se utilizan para diversos propósitos: ▪ El controlador frontal es el único punto de entrada a la aplicación. Carga la confi- guración y determina la acción a ejecutarse. ▪ Las acciones contienen la lógica de la aplicación. Verifican la integridad de las pe- ticiones y preparan los datos requeridos por la capa de presentación. ▪ Los objetos request, response y session dan acceso a los parámetros de la peti- ción, las cabeceras de las respuestas y a los datos persistentes del usuario. Se utilizan muy a menudo en la capa del controlador. ▪ Los filtros son trozos de código ejecutados para cada petición, antes o después de una acción. Por ejemplo, los filtros de seguridad y validación son comúnmente utilizados en aplicaciones web. Puedes extender el framework creando tus prop- ios filtros. Este capítulo describe todos estos componentes, pero no te abrumes porque sean mu- chos componentes. Para una página básica, es probable que solo necesites escribir algu- nas líneas de código en la clase de la acción, y eso es todo. Los otros componentes del controlador solamente se utilizan en situaciones específicas. 6.1. El Controlador Frontal Todas las peticiones web son manejadas por un solo controlador frontal, que es el punto de entrada único de toda la aplicación en un entorno determinado. Cuando el controlador frontal recibe una petición, utiliza el sistema de enrutamiento para asociar el nombre de una acción y el nombre de un módulo con la URL escrita (o pincha- da) por el usuario. Por ejemplo, la siguientes URL llama al script index.php (que es el controlador frontal) y será entendido como llamada a la acción miAccion del módulo mi- modulo: http://localhost/index.php/mimodulo/miAccion Si no estás interesado en los mecanismos internos de Symfony, eso es todo que necesi- tas saber sobre el controlador frontal. Es un componente imprescindible de la arquitectu- ra MVC de Symfony, pero raramente necesitarás cambiarlo. Si no quieres conocer las tri- pas del controlador frontal, puedes saltarte el resto de esta sección. 6.1.1. El Trabajo del Controlador Frontal en Detalle El controlador frontal se encarga de despachar las peticiones, lo que implica algo más que detectar la acción que se ejecuta. De hecho, ejecuta el código común a todas las ac- ciones, incluyendo: 1. Define las constantes del núcleo. www.librosweb.es 91
  • 92. Symfony, la guía definitiva Capítulo 6. El Controlador 2. Localiza la librería de Symfony 3. Carga e inicializa las clases del núcleo del framework. 4. Carga la configuración. 5. Decodifica la URL de la petición para determinar la acción a ejecutar y los pará- metros de la petición. 6. Si la acción no existe, redireccionará a la acción del error 404. 7. Activa los filtros (por ejemplo, si la petición necesita autenticación). 8. Ejecuta los filtros, primera pasada. 9. Ejecuta la acción y produce la vista. 10. Ejecuta los filtros, segunda pasada. 11. Muestra la respuesta. 6.1.2. El Controlador Frontal por defecto El controlador frontal por defecto, llamado index.php y ubicado en el directorio web/ del proyecto, es un simple script, como lo muestra el Listado 6-1. Listado 6-1 - El Controlador Frontal por Omisión <?php define('SF_ROOT_DIR', realpath(dirname(__FILE__).'/..')); define('SF_APP', 'miaplicacion'); define('SF_ENVIRONMENT', 'prod'); define('SF_DEBUG', false); require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.' sfContext::getInstance()->getController()->dispatch(); La definición de las constantes corresponde al primer paso descrito en la sección anterior. Después el controlador frontal incluye el config.php de la aplicación, que se ocupa de los pasos 2 a 4. La llamada al método dispatch() del objeto sfController (que es el objeto correspondiente al controlador del núcleo de la arquitectura MVC de Symfony) envía la petición, ocupándose de los pasos 5 a 7. Los últimos pasos son manejados por la cadena de filtros, según lo explicado más adelante este capítulo. 6.1.3. Llamando a Otro Controlador Frontal para Cambiar el Entorno Cada entorno dispone de un controlador frontal. De hecho, es la existencia del controla- dor frontal lo que define un entorno. El entorno se define en la constante SF_ENVIRONMENT. Para cambiar el entorno en el que se está viendo la aplicación, simplemente se elige otro controlador frontal. Los controladores frontales disponibles cuando creas una aplicación con la tarea Symfony init-app son index.php para el entorno de producción y miaplicac- ion_dev.php para el entorno de desarrollo (suponiendo que tu aplicación se llame www.librosweb.es 92
  • 93. Symfony, la guía definitiva Capítulo 6. El Controlador miaplicacion). La configuración por defecto de mod_rewrite utiliza index.php cuando la URL no contiene el nombre de un script correspondiente a un controlador frontal. Así que estas dos URL muestran la misma página (mimodulo/index) en el entorno de producción: http://localhost/index.php/mimodulo/index http://localhost/mimodulo/index y esta URL muestra la misma página en el entorno de desarrollo: http://localhost/miaplicacion_dev.php/mimodulo/index Crear un nuevo entorno es tan fácil como crear un nuevo controlador frontal. Por ejem- plo, puede ser necesario un entorno llamado staging que permita a tus clientes probar la aplicación antes de ir a producción. Para crear el entorno staging, simplemente copia web/miaplicacion_dev.php en web/miaplicacion_staging.php y cambia el valor de la constante SF_ENVIRONMENT a staging. Ahora en todos los archivos de configuración, pue- des añadir una nueva sección staging: para establecer los valores específicos para este entorno, como se muestra en el Listado 6-2 Listado 6-2 - Ejemplo de app.yml con valores específicos para el entorno staging staging: mail: webmaster: falso@misitio.com contacto: falso@misitio.com all: mail: webmaster: webmaster@misitio.com contacto: contacto@mysite.com Si quieres ver como reacciona la aplicación en el nuevo entorno, llama al controlador frontal asociado: http://localhost/miaplicacion_staging.php/mimodulo/index 6.1.4. Archivos por Lotes En ocasiones es necesario ejecutar un script desde la línea de comandos (o mediante una tarea programada) con acceso a todas las clases y características de Symfony, por ejem- plo para realizar tareas como el envío programado de correos electrónicos o para actuali- zar periódicamente el modelo mediante una serie de cálculos complejos. Para este tipo de scripts, es necesario incluir al principio del archivo por lotes las mismas líneas que en el controlador frontal. El listado 6-3 muestra el principio de un archivo por lotes de este tipo. Listado 6-3 - Ejemplo de archivo por lotes <?php define('SF_ROOT_DIR', realpath(dirname(__FILE__).'/..')); define('SF_APP', 'miaplicacion'); define('SF_ENVIRONMENT', 'prod'); define('SF_DEBUG', false); www.librosweb.es 93
  • 94. Symfony, la guía definitiva Capítulo 6. El Controlador require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.' // Agregar código aquí Puedes ver que la única línea que falta es la que llama al método dispatch() del objeto sfController, que solo puede ser utilizada con un navegador web, no en un proceso por lotes. Definir una aplicación y un entorno te permite disponer de una configuración es- pecífica. Incluir el archivo config.php inicializa el contexto y el cargado automático de las clases. SUGERENCIA La interfaz de comandos de Symfony ofrece la tarea init-batch, que automáticamente crea un estructura básica (esqueleto) similar al que se encuentra en el Listado 6-3 en el directorio batch/. Simplemente indica como argumentos un nombre de aplicación, un nombre de entorno y un nom- bre para el archivo de lotes. 6.2. Acciones Las acciones son el corazón de la aplicación, puesto que contienen toda la lógica de la aplicación. Las acciones utilizan el modelo y definen variables para la vista. Cuando se realiza una petición web en una aplicación Symfony, la URL define una acción y los pará- metros de la petición. 6.2.1. La clase de la acción Las acciones son métodos con el nombre executeNombreAccion de una clase llamada nom- breModuloActions que hereda de la clase sfActions y se encuentran agrupadas por mó- dulos. La clase que representa las acciones de un módulo se encuentra en el archivo act- ions.class.php, en el directorio actions/ del módulo. El listado 6-4 muestra un ejemplo de un archivo actions.class.php con una única acción index para todo el módulo mimodulo. Listado 6-4 - Ejemplo de la clase de la acción, en app/miaplicacion/modules/mimodu- lo/actions/actions.class.php class mimoduloActions extends sfActions { public function executeIndex() { } } ATENCIÓN Aunque en PHP no se distinguen las mayúsculas y minúsculas de los nombres de los métodos, Symfony si los distingue. Así que se debe tener presente que los métodos de las acciones deben comenzar con execute en minúscula, seguido por el nombre exacto de la acción con la primera le- tra en mayúscula. www.librosweb.es 94
  • 95. Symfony, la guía definitiva Capítulo 6. El Controlador Para ejecutar un acción, se debe llamar al script del controlador frontal con el nombre del módulo y de la acción como parámetros. Por defecto, se añade nombre_modulo/nombre_ac- cion al script. Esto significa que la acción del listado 6-4 se puede ejecutar llamándola con la siguiente URL: http://localhost/index.php/mimodulo/index Añadir más acciones simplemente significa agregar más métodos execute al objeto sfAc- tions, como se muestra en el listado 6-5. Listado 6-5 - Clase con dos acciones, en miaplicacion/modules/mimodulo/actions/ actions.class.php class mimoduloActions extends sfActions { public function executeIndex() { ... } public function executeList() { ... } } Si el tamaño de la clase de la acción crece demasiado, probablemente tendrás que refac- torizar la clase para mover algo de codigo a la capa del modelo. El código de las acciones debería ser muy corto (no mas que una pocas líneas), y toda la lógica del negocio de- bería encontrarse en el modelo. Aun así, el número de acciones en un módulo puede llegar a ser tan importante que sea necesario dividirlas en 2 módulos. Normas del código de Symfony En los ejemplos de código dados en este libro, probablemente has notado que la apertura y cierre de llaves ({ y }) ocupan una línea cada una. Este estándar hace al código más fácil de leer. Entre otras normas que sigue el código de Symfony, la indentación se realiza siempre con 2 espac- ios en blanco; nunca se utilizan los tabuladores. La razón es que los tabuladores se muestran con distinta anchura en función del editor de textos utilizado, y porque el código que mezcla tabuladores con espacios en blanco es bastante difícil de leer. Los archivos PHP del núcleo de Symfony y los archivos generados no terminan con la etiqueta de cierre habitual ?>. La razón es que esta etiqueta no es obligatoria y puede provocar problemas con la salida producida si se incluyen por error espacios en blanco después de la etiqueta de cierre. Y si eres de los que te fijas en los detalles, verás que ninguna línea de código de Symfony termina con un espacio en blanco. En esta ocasión la razón no es técnica, sino que simplemente las líneas de código que terminan con espacios en blancos se ven feas en el editor de texto de Fabien. www.librosweb.es 95
  • 96. Symfony, la guía definitiva Capítulo 6. El Controlador 6.2.2. Sintaxis alternativa para las clases de las Acciones Se puede utilizar una sintaxis alternativa para distribuir las acciones en archivos separa- dos, un archivo por acción. En este caso, cada clase acción extiende sfAction (en lugar de sfActions) y su nombre es nombreAccionAction. El nombre del método es simplemen- te execute. El nombre del archivo es el mismo que el de la clase. Esto significa que el eq- uivalente del Listado 6-5 puede ser escrito en dos archivos mostrados en los listados 6-6 y 6-7 Listado 6-6 - Archivo de una sola acción, en miaplicacion/modules/mimodulo/act- ions/indexAction.class.php class indexAction extends sfAction { public function execute() { ... } } Listado 6-7 - Archivo de una sola acción, en miaplicacion/modules/mimodulo/act- ions/listAction.class.php class listAction extends sfAction { public function execute() { ... } } 6.2.3. Obteniendo Información en las Acciones Las clases de las acciones ofrecen un método para acceder a la información relacionada con el controlador y los objetos del núcleo de Symfony. El listado 6-8 muestra como utilizarlos. Listado 6-8 - Métodos comunes de sfActions class mimoduloActions extends sfActions { public function executeIndex() { // Obteniendo parametros de la petición $password = $this->getRequestParameter('password'); // Obteniendo información del controlador $nombreModulo = $this->getModuleName(); $nombreAccion = $this->getActionName(); // Obteniendo objetos del núcleo del framework $peticion = $this->getRequest(); $sesionUsuario = $this->getUser(); $respuesta = $this->getResponse(); $controlador = $this->getController(); www.librosweb.es 96
  • 97. Symfony, la guía definitiva Capítulo 6. El Controlador $contexto = $this->getContext(); // Creando variables de la acción para pasar información a la plantilla $this->setVar('parametro', 'valor'); $this->parametro = 'valor'; // Versión corta. } } El "singleton" del contexto En el controlador frontal ya se ha visto una llamada a sfContext::getInstance(). En una acción, el método getContext() devuelve el mismo singleton. Se trata de un objeto muy útil que guarda una referencia a todos los objetos del núcleo de Symfony relacionados con una petición dada, y ofrece un método accesor para cada uno de ellos: ▪ sfController: El objeto controlador (->getController()) ▪ sfRequest: El objeto de la petición (->getRequest()) ▪ sfResponse: El objeto de la respuesta (->getResponse()) ▪ sfUser: El objeto de la sesión del usuario (->getUser()) ▪ sfDatabaseConnection: La conexión a la base de datos (->getDatabaseConnection()) ▪ sfLogger: El objeto para los logs (->getLogger()) ▪ sfI18N: El objeto de internacionalización (->getI18N()) Se puede llamar al singleton sfContext::getInstance() desde cualquier parte del código. 6.2.4. Terminación de las Acciones Existen varias alternativas posibles cuando se termina la ejecución de una acción. El va- lor retornado por el método de la acción determina como será producida la vista. Para especificar la plantilla que se utiliza al mostrar el resultado de la acción, se emplean las constantes de la clase sfView. Si existe una vista por defecto que se debe llamar (este es el caso más común), la acción debería terminar de la siguiente manera: return sfView::SUCCESS; Symfony buscará entonces una plantilla llamada nombreAccionSuccess.php. Este compor- tamiento se ha definido como el comportamiento por defecto, por lo que si omites la sen- tencia return en el método de la acción, Symfony también buscará una plantilla llamada nombreAccionSuccess.php. Las acciones vacías también siguen este comportamiento. El listado 6-9 muestra un ejemplo de terminaciones exitosas de acciones. Listado 6-9 - Acciones que llaman a las plantillas indexSuccess.php y listSuccess.php public function executeIndex() { return sfView::SUCCESS; www.librosweb.es 97
  • 98. Symfony, la guía definitiva Capítulo 6. El Controlador } public function executeList() { } Si existe una vista de error que se debe llamar, la acción deberá terminar de la siguiente manera: return sfView::ERROR; Symonfy entonces buscará un plantilla llamada nombreAccionError.php. Para utilizar una vista personalizada, se debe utilizar el siguiente valor de retorno: return 'MiResultado'; Symfony entonces buscará una plantilla llamada nombreAccionMiResultado.php. Si no se utiliza ninguna vista –por ejemplo, en el caso de una acción ejecutada en un ar- chivo de lotes– la acción debe terminar de la siguiente forma: return sfView::NONE; En este caso, no se ejecuta ninguna plantilla. De esta forma, se evita por completo la ca- pa de vista y se establece directamente el código HTML producido por la acción. Como muestra el Listado 6-10, Symfony provee un método renderText() específico para este caso. Este método puede ser útil cuando se necesita una respuesta muy rápida en una acción, como por ejemplo para las interacciones creadas con Ajax, como se verá en el Capítulo 11. Listado 6-10 - Evitando la vista mediante una respuesta directa y un valor de re- torno sfView::NONE public function executeIndex() { $this->getResponse()->setContent("<html><body>¡Hola Mundo!</body></html>"); return sfView::NONE; } // Es equivalente a public function executeIndex() { return $this->renderText("<html><body>¡Hola Mundo!</body></html>"); } En algunos casos, se necesita una respuesta vacía pero con algunas cabeceras definidas (sobre todo la cabecera X-JSON). Para conseguirlo, se definen las cabeceras con el objeto sfResponse, que se ve en el próximo capítulo, y se devuelve como valor de retorno la constante sfView::HEADER_ONLY, como muestra el Listado 6-11. Listado 6-11 - Evitando la producción de la vista y enviando solo cabeceras public function executeActualizar() { $salida = '<"titulo","Mi carta sencilla"],["nombre","Sr. Pérez">'; $this->getResponse()->setHttpHeader("X-JSON", '('.$salida.')'); www.librosweb.es 98
  • 99. Symfony, la guía definitiva Capítulo 6. El Controlador return sfView::HEADER_ONLY; } Si la acción debe ser producida por una plantilla específica, se debe prescindir de la sen- tencia return y se debe utilizar el método setTemplate() en su lugar. $this->setTemplate('miPlantillaPersonalizada'); 6.2.5. Saltando a Otra Acción En algunos casos, la ejecución de un acción termina solicitando la ejecución de otra ac- ción. Por ejemplo, una acción que maneja el envío de un formulario en una solicitud POST normalmente redirecciona a otra acción después de actualizar la base de datos. Otro ejemplo es el de crear un alias de una acción: la acción index normalmente se utili- za para mostrar un listado y de hecho se suele redireccionar a la acción list. La clase de la acción provee dos métodos para ejecutar otra acción: ▪ Si la acción pasa la llamada hacia otra acción (forward): $this->forward('otroModulo', 'index'); ▪ Si la acción produce un redireccionamiento web (redirect): $this->redirect('otroModulo/index'); $this->redirect('http://www.google.com/'); NOTA El código que se encuentra después de una llamada a los métodos forward o redirect en una acción nunca se ejecuta. Se puede considerar que estas llamadas son equivalentes a la sentencia return. Estos métodos lanzan una excepción sfStopException para detener la ejecución de la acción; esta excepción es interceptada más adelante por Symfony y simplemente se ignora. La elección entre redirect y forward es a veces engañosa. Para elegir la mejor solución, ten en cuenta que un forward es una llamada interna a la aplicación y transparente para el usuario. En lo que concierne al usuario, la URL mostrada es la misma que la solicitada. Por el contrario, un redirect resulta en un mensaje al navegador del usuario, involucran- do una nueva petición por parte del mismo y un cambio en la URL final resultante. Si la acción es llamada desde un formulario enviado con method=”post”, deberías siempre realizar un redirect. La principal ventaja es que si el usuario recarga la página resultan- te, el formulario no será enviado nuevamente; además, el botón de retroceder funciona como se espera, ya que muestra el formulario y no una alerta preguntando al usuario si desea reenviar una petición POST. Existe un tipo especial de forward que se utiliza comúnmente. El método forward404() redirecciona a una acción de Página no encontrada. Este método se utiliza normalmente cuando un parámetro necesario para la ejecución de la acción no está presente en la pe- tición (por tanto detectando una URL mal escrita). El Listado 6-12 muestra un ejemplo de una acción mostrar que espera un parámetro llamado id. Listado 6-12 - Uso del método forward404() www.librosweb.es 99
  • 100. Symfony, la guía definitiva Capítulo 6. El Controlador public function executeMostrar() { $articulo = ArticuloPeer::retrieveByPK($this->getRequestParameter('id')); if (!$articulo) { $this->forward404(); } } SUGERENCIA Si estás buscando la acción y la plantilla del error 404, las puedes encontrar en el directorio $sf_symfony_data_dir/modules/default/. Se puede personalizar esta página agregado un mó- dulo default a la aplicación, sobrescribiendo el del framework, y definiendo una acción error404 y una plantilla error404Success dentro del nuevo módulo. Otro método alternativo es el de estable- cer las constantes error_404_module y error_404_action en el archivo settings.yml para utili- zar una acción existente. La experiencia muestra que, la mayoría de las veces, una acción hace un redirect o un forward después de probar algo, como en el listado 6-12. Por este motivo, la clase sfAct- ions tiene algunos métodos más, llamados forwardIf(), forwardUnless(), for- ward404If(), forward404Unless(), redirectIf() y redirectUnless(). Estos métodos sim- plemente requieren un parámetro que representa la condición cuyo resultado se emplea para ejecutar el método. El método se ejecuta si el resultado de la condición es true y el método es de tipo xxxIf() o si el resultado de la condición es false y el método es de ti- po xxxUnless(), como se muestra en el listado 6-13. Listado 6-13 - Uso del método forward404If() // Esta acción es equivalente a la mostrada en el Listado 6-12 public function executeMostrar() { $articulo = ArticuloPeer::retrieveByPK($this->getRequestParameter('id')); $this->forward404If(!$articulo); } // Esta acción también es equivalente public function executeMostrar() { $articulo = ArticuloPeer::retrieveByPK($this->getRequestParameter('id')); $this->forward404Unless($articulo); } El uso de estos métodos permite mantener el código de las acciones muy corto y también lo hacen más fácil de leer. SUGERENCIA Cuando la acción llama al método forward404() o alguno de sus similares, Symfony lanza una ex- cepción sfError404Exception que maneja la respuesta al error 404. Esto significa que si se quie- re mostrar un mensaje de error de tipo 404 desde cualquier parte del código desde donde no se qu- iere acceder al controlador, se puede lanzar una excepción similar. www.librosweb.es 100
  • 101. Symfony, la guía definitiva Capítulo 6. El Controlador 6.2.6. Repitiendo Código para varias Acciones de un Modulo La convención en el nombre de las acciones executeNombreAccion() (en el caso de una clase de tipo sfActions) o execute() (en el caso de una clase sfAction) garantiza que Symfony encontrará el método de la acción. Además, permite crear métodos propios que no serán considerados como acciones, siempre que su nombre no empiece con execute. Existe otra convención útil cuando se necesita ejecutar repetidamente en cada acción una serie de sentencias antes de ejecutar la propia acción. Esas sentencias comunes se pue- den colocar en el método preExecute() de la clase de la acción. De forma análoga, se pueden definir sentencias que se ejecuten después de cada acción añadiéndolas al méto- do postExecute(). La sintaxis de estos métodos se muestra en el Listado 6-14. Listado 6-14 - Usando los métodos preExecute, postExecute, y métodos personali- zados en la clase de la acción class mimoduloActions extends sfActions { public function preExecute() { // El código insertado aquí se ejecuta al principio de cada llamada a una acción ... } public function executeIndex() { ... } public function executeListar() { ... $this->miPropioMetodo(); // Se puede acceder a cualquier método de la clase acción } public function postExecute() { // El código insertado aquí se ejecuta al final de cada llamada a la acción ... } protected function miPropioMetodo() { // Se pueden crear métodos propios, siempre que su nombre no comience por "execute" // En ese case, es mejor declarar los métodos como protected o private ... } } 6.3. Accediendo a la Petición Ya se ha presentado anteriormente el método getRequestParameter(’miparametro’), utili- zado para obtener el valor del parámetro de una petición por su nombre. De hecho, este www.librosweb.es 101
  • 102. Symfony, la guía definitiva Capítulo 6. El Controlador método es una forma rápida equivalente a la sucesión de llamadas al contenedor de los parámetros de la petición getRequest()->getParameter(’miparametro’). La clase de la ac- ción accede al objeto de la petición, llamado sfWebRequest en Symfony, y a todos sus métodos, mediante el método getRequest(). La tabla 6-1 lista los métodos más útiles de sfWebRequest. Tabla 6-1. Métodos del objeto sfWebRequest Nombre Función Ejemplo de salida producida Información sobre la petición Devuelve la constante getMethod() Método de la petición sfRequest::GET o sfRequest::POST Nombre del método de getMethodName() POST petición Valor de una cabecera Apache/2.0.59 (Unix) DAV/2 getHttpHeader(’Server’) HTTP PHP/5.1.6 getCookie(’foo’) Valor de una cookie valor ¿Es una petición isXmlHttpRequest() (1) true AJAX? isSecure() ¿Es una petición SSL? true Parámetros de la petición ¿Existe el parámetro en hasParameter(’parametro’) true la petición? getParameter(’parametro’) Valor del parámetro valor Array de todos los getParameterHolder()->getAll() parámetros de la petición Información relacionada con la URI http://localhost/ getUri() URI completa miaplicacion_dev.php/mimodulo/ miaccion getPathInfo() Información de la ruta /mimodulo/miaccion Valor del “referer” de la http://localhost/ getReferer() (2) petición miaplicacion_dev.php/ getHost() Nombre del Host localhost Nombre y ruta del getScriptName() miaplicacion_dev.php controlador frontal www.librosweb.es 102
  • 103. Symfony, la guía definitiva Capítulo 6. El Controlador Información del navegador del cliente Array de los lenguajes Array( [0] => fr [1] => fr_FR getLanguages() aceptados [2] => en_US [3] => en ) Array de los juegos de Array( [0] => ISO-8859-1 [1] getCharsets() caracteres aceptados => UTF-8 [2] => * ) Array de los tipos de Array( [0] => text/xml [1] => getAcceptableContentType() contenidos aceptados text/html (1) Funciona sólo con prototype (2) A veces es bloqueado por los proxy La clase sfActions ofrece algunos atajos para acceder a los métodos de la petición más rápidamente, como se muestra en el listado 6-15. Listado 6-15 - Accediendo a los étodos del objeto sfRequest desde una acción class mimoduloActions extends sfActions { public function executeIndex() { $tieneParametro = $this->getRequest()->hasParameter('parametro'); $tieneParametro = $this->hasRequestParameter('parametro'); // Versión corta $parametro = $this->getRequest()->getParameter('parametro'); $parametro = $this->getRequestParameter('parametro'); // Versión corta } } Para peticiones de tipo multipart utilizadas cuando el usuario adjunta archivos, el objeto sfWebRequest provee medios para acceder y mover estos archivos, como se muestra en el listado 6-16. Listado 6-16 - El objeto sfWebRequest sabe cómo manejar archivos adjuntos class mimoduloActions extends sfActions { public function executeUpload() { if ($this->getRequest()->hasFiles()) { foreach ($this->getRequest()->getFileNames() as $nombreArchivo) { $tamanoArchivo = $this->getRequest()->getFileSize($nombreArchivo); $tipoArchivo = $this->getRequest()->getFileType($nombreArchivo); $archivoErroneo = $this->getRequest()->hasFileError($nombreArchivo); $directorioSubidas = sfConfig::get('sf_upload_dir'); $this->getRequest()->moveFile('file', $directorioSubidas.'/'.$nombreArchivo); } } } } www.librosweb.es 103
  • 104. Symfony, la guía definitiva Capítulo 6. El Controlador No tienes que preocuparte sobre si el servidor soporta las variables de PHP $_SERVER o $_ENV, o acerca de valores por defecto o problemas de compatibilidad del servidor, ya que los métodos de ‘sfWebRequest lo hacen todo por tí. Además sus nombres son tan evi- dentes que no es necesario consultar la documentación de PHP para descubrir cómo ob- tener información sobre la petición. 6.4. Sesiones de Usuario Symfony maneja automáticamente las sesiones del usuario y es capaz de almacenar da- tos de forma persistente entre peticiones. Utiliza el mecanismo de manejo de sesiones incluido en PHP y lo mejora para hacerlo mas configurable y más fácil de usar. 6.4.1. Accediendo a la Sesión de Usuario El objeto sesión del usuario actual se accede en la acción con el método getUser(), que es una instancia de la clase sfUser. Esta clase dispone de un contenedor de parámetros que permite guardar cualquier atributo del usuario en el. Esta información estará disponi- ble en otras peticiones hasta terminar la sesión del usuario, como se muestra en el Lista- do 6-17. Los atributos de usuarios pueden guardar cualquier tipo de información (cade- nas de texto, arrays y arrays asociativos). Se pueden utilizar para cualquier usuario, in- cluso si ese usuario no se ha identificado. Listado 6-17 - El objeto sfUser puede contener atributos personalizados del us- uario disponibles en todas las peticiones class mimoduloActions extends sfActions { public function executePrimeraPagina() { $nombre = $this->getRequestParameter('nombre'); // Guardar información en la sesión del usuario $this->getUser()->setAttribute('nombre', $nombre); } public function executeSegundaPagina() { // Obtener información de la sesión del usuario con un valor por defecto $nombre = $this->getUser()->getAttribute('nombre', 'Anónimo'); } } ATENCIÓN Puedes guardar objetos en la sesión del usuario, pero no se recomienda hacerlo. El motivo es que el objeto de la sesión es seralizado entre peticiones y se guarda en un archivo. Cuando la sesión se deserializa, la clase del objeto guardado debe haber sido previamente cargada y este no es siem- pre el caso. Además, puede haber objetos de tipo “stalled” si se guardan objetos de Propel. Como muchos otros getters en Symfony, el método getAttribute() acepta un segundo parámetro, especificando el valor por defecto a ser utilizado cuando el atributo no está definido. Para verificar si un atributo ha sido definido para un usuario, se utiliza el www.librosweb.es 104
  • 105. Symfony, la guía definitiva Capítulo 6. El Controlador método hasAttribute(). Los atributos se guardan en un contenedor de parámetros que puede ser accedido por el método getAttributeHolder(). También permite un borrado rápido de los atributos del usuario con los métodos usuales del contenedor de paráme- tros, como se muestra en el listado 6-18. Listado 6-18 - Eliminando información de la sesión del usuario class mimoduloActions extends sfActions { public function executeBorraNombre() { $this->getUser()->getAttributeHolder()->remove('nombre'); } public function executeLimpia() { $this->getUser()->getAttributeHolder()->clear(); } } Los atributos de la sesión del usuario también están disponibles por defecto en las planti- llas mediante la variable $sf_user, que almacena el objeto sfUser actual, como se mues- tra en el listado 6-19. Listado 6-19 - Las plantillas también tienen acceso a los atributos de la sesión del usuario <p> Hola, <?php echo $sf_user->getAttribute('nombre') ?> </p> NOTA Si se necesita guardar la información solamente durante la petición actual (por ejemplo, para pasar información a través de una sucesión de llamadas a acciones) es preferible utilizar la clase sfReq- uest, que también tiene métodos getAttribute() y setAttribute(). Solo los atributos del obje- to sfUser son persistentes entre peticiones. 6.4.2. Atributos Flash Un problema recurrente con los atributos del usuario es la limpieza de la sesión del usua- rio una vez que el atributo no se necesita más. Por ejemplo, puede ser necesario mostrar un mensaje de confirmación después de actualizar información mediante un formulario. Como la acción que maneja el formulario realiza una redirección, la única forma de pasar información desde esta acción a la acción que ha sido redireccionada es almacenar la in- formación en la sesión del usuario. Pero una vez que se muestra el mensaje, es necesar- io borrar el atributo; ya que de otra forma, permanecerá en la sesión hasta que esta expire. El atributo de tipo flash es un atributo fugaz que permite definirlo y olvidarse de el, sab- iendo que desaparece automáticamente después de la siguiente petición y que deja la sesión limpia para las futuras peticiones. En la acción, se define el atributo flash de la si- guiente manera: www.librosweb.es 105
  • 106. Symfony, la guía definitiva Capítulo 6. El Controlador $this->setFlash('atributo', $valor); La plantilla se procesa y se envía al usuario, quien después realiza una nueva petición hacia otra acción. En esta segunda acción, es posible obtener el valor del atributo flash de esta forma: $valor = $this->getFlash('atributo'); Luego te puedes olvidar de ese parámetro. Después de mostrar la segunda página, el atributo flash atributo desaparece automáticamente. Incluso si no se utiliza el atributo durante la segunda acción, el atributo desaparece igualmente de la sesión. Si se necesita acceder un atributo flash desde la plantilla, se puede utilizar el objeto $sf_flash: <?php if ($sf_flash->has('atributo')): ?> <?php echo $sf_flash->get('atributo') ?> <?php endif; ?> O simplemente: <?php echo $sf_flash->get('atributo') ?> Los atributos de tipo flash son una forma limpia de pasar información a la próxima petición. 6.4.3. Manejo de Sesiones El manejo de sesiones de Symfony se encarga de gestionar automáticamente el almace- namiento de los IDs de sesión tanto en el cliente como en el servidor. Sin embargo, si se necesita modificar este comportamiento por defecto, es posible hacerlo. Se trata de algo que solamente lo necesitan los usuarios más avanzados. En el lado del cliente, las sesiones son manejadas por cookies. La cookie de Symfony se llama Symfony, pero se puede cambiar su nombre editando el archivo de configuración factories.yml, como se muestra en el Listado 6-20. Listado 6-20 - Cambiando el nombre de la cookie de sesión, en apps/miaplicac- ion/config/factories.yml all: storage: class: sfSessionStorage param: session_name: mi_nombre_cookie SUGERENCIA La sesión se inicializa (con la función de PHP session_start()) solo si el parámetro auto_start de factories.yml tiene un valor de true (que es el caso por defecto). Si se quiere iniciar la sesión manualmente, se debe cambiar el valor de esa opción de configuración del archivo factories.yml. El manejo de sesiones de Symfony esta basado en las sesiones de PHP. Por tanto, si la gestión de la sesión en la parte del cliente se quiere realizar mediante parámetros en la www.librosweb.es 106
  • 107. Symfony, la guía definitiva Capítulo 6. El Controlador URL en lugar de cookies, se debe modificar el valor de la directiva use_trans_sid en el archivo de configuración php.ini. No obstante, se recomienda no utilizar esta técnica. session.use_trans_sid = 1 En el lado del servidor, Symfony guarda por defecto las sesiones de usuario en archivos. Se pueden almacenar en la base de datos cambiando el valor del parámetro class en factories.yml, como se muestra en el Listado 6-21. Listado 6-21 - Cambiando el almacenamiento de las sesiones en el servidor, en apps/miaplicacion/config/factories.yml all: storage: class: sfMySQLSessionStorage param: db_table: SESSION_TABLE_NAME # Nombre de la tabla que guarda las sesiones database: DATABASE_CONNECTION # Nombre de la conexión a la base de datos que se utiliza Las clases de almacenamiento de sesiones disponibles son sfMySQLSessionStorage, sfPostgreSQLSessionStorage y sfPDOSessionStorage; la última es la preferida. La opción database define la conexión a utilizar; Symfony luego utiliza databases.yml (ver Capítulo 8) para determinar los parámetros de la conexión (host, nombre de la base de datos, us- uario, y password) para realizar la conexión. La expiración de la sesión se produce automáticamente después de sf_timeout segundos. El valor de esta constante es 30 minutos por defecto y puede ser modificado para cada entorno en el archivo de configuración settings.yml, como se muestra en el Listado 6-22. Listado 6-22 - Cambiando el tiempo de vida de la sesión, en apps/miaplicacion/ config/settings.yml default: .settings: timeout: 1800 # Tiempo de vida de la sesión en segundos 6.5. Seguridad de la Acción La posibilidad de ejecutar una acción puede ser restringida a usuarios con ciertos privile- gios. Las herramientas proporcionadas por Symfony para este propósito permiten la cre- ación de aplicaciones seguras, en las que los usuarios necesitan estar autenticados antes de acceder a alguna característica o a partes de la aplicación. Añadir esta seguridad a una aplicación requiere dos pasos: declarar los requerimientos de seguridad para cada acción y autenticar a los usuarios con privilegios para que puedan acceder estas acciones seguras. www.librosweb.es 107
  • 108. Symfony, la guía definitiva Capítulo 6. El Controlador 6.5.1. Restricción de Acceso Antes de ser ejecutada, cada acción pasa por un filtro especial que verifica si el usuario actual tiene privilegios de acceder a la acción requerida. En Symfony, los privilegios es- tan compuestos por dos partes: ▪ Las acciones seguras requieren que los usuarios esten autenticados. ▪ Las credenciales son privilegios de seguridad agrupados bajo un nombre y que permiten organizar la seguridad en grupos. Para restringir el acceso a una acción se crea y se edita un archivo de configuración YAML llamado ‘security.yml en el directorio config/ del módulo. En este archivo, se pueden especificar los requerimientos de seguridad que los usuarios deberán satisfacer para cada acción o para todas (all) las acciones. El listado 6-23 muestra un ejemplo de security.yml. Listado 6-23 - Estableciendo restricciones de acceso, en apps/miaplicacion/modu- les/mimodulo/config/security.yml ver: is_secure: off # Todos los usuarios pueden ejecutar la acción "ver" modificar: is_secure: on # La acción "modificar" es sólo para usuarios autenticados borrar: is_secure: on # Sólo para usuarios autenticados credentials: admin # Con credencial "admin" all: is_secure: off # off es el valor por defecto Las acciones no incluyen restricciones de seguridad por defecto, asi que cuando no existe el archivo security.yml o no se indica ninguna acción en ese archivo, todas las acciones son accesibles por todos los usuarios. Si existe un archivo security.yml, Syfmony busca por el nombre de la acción y si existe, verifica que se satisfagan los requerimientos de seguridad. Lo que sucede cuando un usuario trata de acceder una acción restringida de- pende de sus credenciales: ▪ Si el usuario está autenticado y tiene las credenciales apropiadas, entonces la ac- ción se ejecuta. ▪ Si el usuario no está autenticado, es redireccionado a la acción de login. ▪ Si el usuario está autenticado, pero no posee las credenciales apropiadas, será redirigido a la acción segura por defecto, como muestra la figura 6-1. Las páginas login y secure son bastante simples, por lo que seguramente será necesario personalizarlas. Se puede configurar que acciones se ejecutan en caso de no disponer de suficientes privilegios en el archivo settings.yml de la aplicación cambiando el valor de las propiedades mostradas en el listado 6-24. www.librosweb.es 108
  • 109. Symfony, la guía definitiva Capítulo 6. El Controlador Figura 6.1. La página por defecto de la acción ''secure'' Listado 6-24 - Las acciones de seguridad por defecto se definen en apps/miapli- cacion/config/settings.yml all: .actions: login_module: default login_action: login secure_module: default secure_action: secure 6.5.2. Otorgando Acceso Para obtener acceso a áreas restringidas, los usuarios necesitan estar autenticados y/o poseer ciertas credenciales. Puedes extender los privilegios del usuario mediante llama- das a métodos del objeto sfUser. El estado autenticado se estable con el método setAuthenticated() y se puede comprobar con el método isAuthenticated(). El listado 6-25 muestra un ejemplo sencillo de autenticación. Listado 6-25 - Estableciendo el estado de autenticación del usuario class miCuentaActions extends sfActions { public function executeLogin() { if ($this->getRequestParameter('login') == 'valor') { $this->getUser()->setAuthenticated(true); } www.librosweb.es 109
  • 110. Symfony, la guía definitiva Capítulo 6. El Controlador } public function executeLogout() { $this->getUser()->setAuthenticated(false); } } Las credenciales son un poco más complejas de tratar, ya que se pueden verificar, agre- gar, quitar y borrar las credenciales. El listado 6-26 describe los métodos de las credenc- iales de la clase sfUser. Listado 6-26 - Manejando las credenciales del usuario en la acción class miCuentaActions extends sfActions { public function executeEjemploDeCredenciales() { $usuario = $this->getUser(); // Agrega una o más credenciales $usuario->addCredential('parametro'); $usuario->addCredentials('parametro', 'valor'); // Verifica si el usuario tiene una credencial echo $usuario->hasCredential('parametro'); => true // Verifica si un usuario tiene una de las credenciales echo $usuario->hasCredential(array('parametro', 'valor')); => true // Verifica si el usuario tiene ambas credenciales echo $usuario->hasCredential(array('parametro', 'valor'), true); => true // Quitar una credencial $usuario->removeCredential('parametro'); echo $usuario->hasCredential('parametro'); => false // Elimina todas las credenciales (útil en el proceso de logout) $usuario->clearCredentials(); echo $usuario->hasCredential('valor'); => false } } Si el usuario tiene la credencial “parametro”, entonces ese usuario podrá acceder a las acciones para las cuales el archivo security.yml requiere esa credencial. Las credenciales se pueden utilizar también para mostrar contenido autenticado en una plantilla, como se muestra en el listado 6-27. Listado 6-27 - Tratando con credenciales de usuario en una plantilla <ul> <li><?php echo link_to('seccion1', 'content/seccion1') ?></li> <li><?php echo link_to('seccion2', 'content/seccion2') ?></li> <?php if ($sf_user->hasCredential('seccion3')): ?> <li><?php echo link_to('seccion3', 'content/seccion3') ?></li> www.librosweb.es 110
  • 111. Symfony, la guía definitiva Capítulo 6. El Controlador <?php endif; ?> </ul> Y para el estado de autenticación, las credenciales normalmente se dan a los usuarios durante el proceso de login. Este es el motivo por el que el objeto sfUser normalmente se extiende para añadir métodos de login y de logout, de forma que se pueda establecer el estado de seguridad del usuario de forma centralizada. SUGERENCIA Entre los plugins de Symfony, sfGuardPlugin extiende la clase de sesión para facilitar el proceso de login y logout. El Capitulo 17 contiene más información al respecto. 6.5.3. Credenciales Complejas La sintaxis YAML utilizada en el archivo security.yml permite restringir el acceso a usua- rios que tienen una combinación de credenciales, usando asociaciones de tipo AND y OR. Con estas combinaciones, se pueden definir flujos de trabajo y sistemas de manejo de privilegios muy complejos – como por ejemplo, un sistema de gestión de contenidos (CMS) cuya parte de gestión sea accesible solo a usuarios con credencial admin, donde los artículos pueden ser editados solo por usuarios con credenciales de editor y publica- dos solo por aquellos que tienen credencial de publisher. El listado 6-28 muestra este ejemplo. Listado 6-28 - Sintaxis de combinación de credenciales editarArticulo: credentials: [ admin, editor ] # admin AND editor publicarArticulo: credentials: [ admin, publisher ] # admin AND publisher gestionUsuarios: credentials: [[ admin, superuser ]] # admin OR superuser Cada vez que se añade un nuevo nivel de corchetes, la lógica cambia entre AND y OR. Así que se pueden crear combinaciones muy complejas de credenciales, como la siguiente: credentials: [[root, [supplier, [owner, quasiowner]] accounts]] # root OR (supplier AND (owner OR quasiowner)) OR accounts 6.6. Métodos de Validación y Manejo de Errores La validando de los datos de la acción –normalmente los parámetros de la petición– es una tarea repetitiva y tediosa. Symfony incluye un sistema de validación, utilizando mé- todos de la clase acción. Se ve en primer lugar un ejemplo. Cuando un usuario hace una petición a miAccion, Symfony siempre busca primero un método llamado validateMiAccion(). Si lo encuentra, Symfony ejecuta ese método. El valor de retorno de esta validación determina el siguien- te método que se ejecuta: si devuelve true, entonces se ejecuta el método executeMiAc- cion(); en otro caso, se ejecuta handleErrorMiAccion(). En el caso de que www.librosweb.es 111
  • 112. Symfony, la guía definitiva Capítulo 6. El Controlador handleErrorMiAccion() no exista, Symfony busca un método genérico llamado handleE- rror(). Si tampoco existe, simplemente devuelve el valor sfView::ERROR para producir la plantilla miAccionError.php. La Figura 6-2 ilustra este proceso. Figura 6.2. El proceso de validación La clave para un correcto funcionamiento de la validación es respetar la convención de nombres para los métodos de la acción: ▪ validateNombreAccion es el método de validación, que devuelve true o false. Se trata del primer método buscado cuando se solicita la acción NombreAccion. Si no existe, la acción se ejecuta directamente. ▪ handleErrorNombreAccion es el método llamado cuando el método de validación falla. Si no existe, entonces se muestra la plantilla Error. ▪ executeNombreAccion es el método de la acción. Debe existir para todoas las acciones. El listado 6-29 muestra un ejemplo de una acción con métodos de validación. Independ- ientemente de si la validación falla o no falla, en el siguiente ejemplo se ejecuta la planti- lla miAccionSuccess.php pero no con los mismos parámetros. Listado 6-29 - Ejemplo de métodos de validación www.librosweb.es 112
  • 113. Symfony, la guía definitiva Capítulo 6. El Controlador class mimoduloActions extends sfActions { public function validateMiAccion() { return ($this->getRequestParameter('id') > 0); } public function handleErrorMiAccion() { $this->message = "Parámetros no válidos"; return sfView::SUCCESS; } public function executeMiAccion() { $this->message = "Los parámetros son válidos"; } } Se puede incluir cualquier código en el método validate(). La única condición es que de- vuelva un valor true o false. Como es un método de la clase sfActions, tiene acceso a los objetos sfRequest y sfUser, que pueden ser realmente útiles para validación de los datos de la petición y del contexto. Se pueden utilizar este mecanismo para implementar la validación de los formularios (es- to es, controlar los valores introducidos por el usuario en un formulario antes de proce- sarlo), pero se trata de una tarea muy repetitiva para la que Symfony proporciona herra- mientas automatizadas, como las descritas en el Capítulo 10. 6.7. Filtros El mecanismo de seguridad puede ser entendido como un filtro, por el que debe pasar cada petición antes de ejecutar la acción. Según las comprobaciones realizadas en el fil- tro, se puede modificar el procesamiento de la petición –por ejemplo, cambiando la ac- ción ejecutada (default/secure en lugar de la acción solicitada en el caso del filtro de se- guridad). Symfony extiende esta idea a clases de filtros. Se puede especificar cualquier número de clases de filtros a ser ejecutadas antes de que se procese la respuesta, y además hacerlo de forma sistemática para todas las peticiones. Se pueden entender los filtros como una forma de empaquetar cierto código de forma similar a preExecute() y postExecute(), pero a un nivel superior (para toda una aplicación en lugar de para todo un módulo). 6.7.1. La Cadena de Filtros Symfony de hecho procesa cada petición como una cadena de filtros ejecutados de forma sucesiva. Cuando el framework recibe una petición, se ejecuta el primer filtro (que siem- pre es sfRenderingFilter). En algún punto, llama al siguiente filtro en la cadena, luego el siguiente, y asi sucesivamente. Cuando se ejecuta el último filtro (que siempre es sfExe- cutionFilter), los filtros anteriores pueden finalizar, y asi hasta el filtro de www.librosweb.es 113
  • 114. Symfony, la guía definitiva Capítulo 6. El Controlador sfRenderingFilter. La Figura 6-3 ilustra esta idea con un diagrama de secuencias, utili- zando una cadena de filtros simplificada (la cadena real tiene muchos más filtros). Figura 6.3. Ejemplo de cadena de filtros Este proceso es la razón de la estructura de la clases de tipo filtro. Todas estas clases ex- tienden la clase sfFilter y contienen un método execute() que espera un objeto de tipo $filterChain como parámetro. En algún punto de este método, el filtro pasa al siguiente filtro en la cadena, llamando a $filterChain->execute(). El listado 6-30 muestra un ejemplo. Por lo tanto, los filtros se dividen en dos partes: ▪ El código que se encuentra antes de la llamada a $filterChain->execute() se ejecuta antes de que se ejecute la acción. ▪ El código que se encuentra después de la llamada a $filterChain->execute() se ejecuta después de la acción y antes de producir la vista. Listado 6-30 - Estructura de la clase filtro class miFiltro extends sfFilter { public function execute ($filterChain) { // Código que se ejecuta antes de la ejecución de la acción ... // Ejecutar el siguiente filtro de la cadena $filterChain->execute(); // Código que se ejecuta después de la ejecuciñon de la acción y antes de que se genere la vista ... www.librosweb.es 114
  • 115. Symfony, la guía definitiva Capítulo 6. El Controlador } } La cadena de filtros por defecto se define en el archivo de configurarcion de la aplicación filters.yml, y su contenido se muestra en el listado 6-31. Este archivo lista los filtros que se ejecutan para cada petición. Listado 6-31 - Cadena de filtros por defecto, en miaplicacion/config/filters.yml rendering: ~ web_debug: ~ security: ~ # Generalmente, se insertar los filtros propios aqui cache: ~ common: ~ flash: ~ execution: ~ Estas declaraciones no tienen parámetros (el caracter tilde, ~, significa null en YAML), porque heredan los parámetros definidos en el núcleo de Symfony. En su núcleo, Sym- fony define las opciones class y param para cada uno de estos filtros. Por ejemplo, el lis- tado 6-32 muestra los parámetros por defecto para el filtro rendering. Listado 6-32 - Parámetros por defecto del filtro sfRenderingFilter, en $sf_Sym- fony_data_dir/config/filters.yml rendering: class: sfRenderingFilter # Clase del filtro param: # Parámetros del filtro type: rendering Si se deja el valor vacío (~) en el archivo filters.yml de la aplicación, Symfony aplica el filtro con las opciones por defecto definidas en su núcleo. Se pueden personalizar la cadenas de filtros en varias formas: ▪ Desactivando algún filtro de la cadena agregando un parámetro enabled: off. Por ejemplo, para desactivar el filtro de depuración web (web_debug), se añade: web_debug: enabled: off ▪ No se deben borrar las entradas del archivo filters.yml para desactivar un filtro ya que Symfony lanzará una excepción. ▪ Se pueden añadir declaraciones propias en cualquier lugar de la cadena (normal- mente después del filtro security) para agregar un filtro propio (como se verá en la próxima sección). En cualquier caso, el filtro rendering debe ser siempre la pri- mera entrada, y el filtro execution debe ser siempre la ultima entrada en la cade- na de filtros. ▪ Redefinir la clase y los parámetros por defecto del filtro por defecto (normalmen- te para modificar el sistema de seguridad y utilizar un filtro de seguridad propio). www.librosweb.es 115
  • 116. Symfony, la guía definitiva Capítulo 6. El Controlador SUGERENCIA El parámetro enabled: off funciona correctamente para desactivar los filtros propios, pero se pueden desactivar los filtros por defecto a través del archivo settings.myl, modificando los valo- res de las opciones web_debug, use_security, cache, y use_flash. El motivo es que cada uno de los filtros por defecto posee un parámetro condition que comprueba el valor de estas opciones. 6.7.2. Construyendo Tu Propio Filtro Construir un filtro propio es bastante sencillo. Se debe crear una definición de una clase similar a la demostrada en el listado 6-30, y se coloca en una de los directorios lib/ del proyecto para aprovechar la carga automática de clases. Como una acción puede pasar el control o redireccionar hacia otra acción y en consec- uencia relanzar toda la cadena de filtros, quizás sea necesario restringir la ejecución de los filtros propios a la primera acción de la petición. El método isFirstCall() de la clase sfFilter retorna un valor booleano con este propósito. Esta llamada solo tiene sentido antes de la ejecución de una acción. Este concepto se puede entender fácilmente con un ejemplo. El listado 6-33 muestra un filtro utilizado para auto-loguear a los usuarios con una cookie MiSitioWeb, que se supone que se crea en la acción login. Se trata de una forma rudimentaria pero que funciona pa- ra incluir la característica Recuérdame de un formulario de login. Listado 6-33 - Ejemplo de archivo de clase de filtro, en apps/miaplicacion/lib/ rememberFilter.class.php class rememberFilter extends sfFilter { public function execute($filterChain) { // Ejecutar este filtro solo una vez if ($this->isFirstCall()) { // Los filtros no tienen acceso directo a los objetos user y request. // Se necesita el contexto para obtenerlos $peticion = $this->getContext()->getRequest(); $usuario = $this->getContext()->getUser(); if ($peticion->getCookie('MiSitioWeb')) { // logueado $usuario->setAuthenticated(true); } } // Ejecutar el proximo filtro $filterChain->execute(); } } En ocasiones, en lugar de continuar con la ejecución de la cadena de filtros, se necesita pasar el control a una acción específica al final de un filtro. sfFilter no tiene un método www.librosweb.es 116
  • 117. Symfony, la guía definitiva Capítulo 6. El Controlador forward(), pero sfController si, por lo que simplemente se puede llamar al siguiente método: return $this->getContext()->getController()->forward('mimodulo', 'miAccion'); NOTA La clase sfFilter tiene un método initialize(), ejecutado cuando se crea el objeto filtro. Se puede redefinir en el filtro propio si se necesita trabajar de forma personalizada con los parámetros de los filtros (definidos en filters.yml, como se describe a continuación). 6.7.3. Activación de Filtros y Parámetros Crear un filtro no es suficiente para activarlo. Se necesita agregar el filtro propio a la ca- dena, y para eso, se debe declar la clase del filtro en el archivo filters.yml, localizado en el directorio config/de la aplicación o del módulo, como se muestra en el listado 6-34. Listado 6-34 - Ejemplo de archivo de activación de filtro, en apps/miaplicacion/ config/filters.yml rendering: ~ web_debug: ~ security: ~ remember: # Los filtros requieren un nombre único class: rememberFilter param: cookie_name: MiSitioWeb condition: %APP_ENABLE_REMEMBER_ME% cache: ~ common: ~ flash: ~ execution: ~ Cuando se encuentra activo, el filtro se ejecuta en cada petición. El archivo de configura- ción de los filtros puede contener una o más definiciones de parámetros en la sección pa- ram. La clase filtro puede obtener estos parámetros con el método getParameter(). El lis- tado 6-35 muestra como obtener los valores de los parámetros. Listado 6-35 - Obteniendo el valor del parámetro, en apps/miaplicacion/lib/ rememberFilter.class.php class rememberFilter extends sfFilter { public function execute($filterChain) { ... if ($request->getCookie($this->getParameter('cookie_name'))) ... } } El parámetro condition se comprueba en la cadena de filtros para ver si el filtro debe ser ejecutado. Por lo que las declaraciones del filtro propio puede basarse en la configuración www.librosweb.es 117
  • 118. Symfony, la guía definitiva Capítulo 6. El Controlador de la aplicación, como muestra el listado 6-34. El filtro remeber se ejecuta solo si el archi- vo app.yml incluye lo siguiente: all: enable_remember_me: on 6.7.4. Filtros de Ejemplo Los filtros son útiles para repetir cierto código en todas las acciones. Por ejemplo, si se utiliza un sistema remoto de estadísticas, puede ser necesario añadir un trozo de código que realice una llamada a un script de las estadísticas en cada página. Este código se puede colocar en el layout global, pero entonces estaría activo para toda la aplicación. Otra forma es colocarlo en un filtro, como se muestra el listado 6-36, y activarlo en cada módulo. Listado 6-36 - Filtro para el sistema de estadísticas de Google Analytics class sfGoogleAnalyticsFilter extends sfFilter { public function execute($filterChain) { // No se hace nada antes de la acción $filterChain->execute(); // Decorar la respuesta con el código de Google Analytics $codigoGoogle = ' <script src="http://www.google-analytics.com/urchin.js" type="text/javascript"> </script> <script type="text/javascript"> _uacct="UA-'.$this->getParameter('google_id').'";urchinTracker(); </script>'; $respuesta = $this->getContext()->getResponse(); $respuesta->setContent(str_ireplace('</body>', $codigoGoogle.'</body>',$respuesta->getContent())); } } No obstante, este filtro no es perfecto, ya que no se debería añadir el código de Google si la respuesta no es de tipo HTML. Otro ejemplo es el de un filtro que cambia las peticiones a SSL si no lo son, para hacer más segura la comunicación, como muestra el Listado 6-37. Listado 6-37 - Filtro de comunicación segura class sfSecureFilter extends sfFilter { public function execute($filterChain) { $contexto = $this->getContext(); $peticion = $context->getRequest(); if (!$peticion->isSecure()) { $urlSegura = str_replace('http', 'https', $peticion->getUri()); return $contexto->getController()->redirect($urlSegura); // No se continúa con la cadena de filtros www.librosweb.es 118
  • 119. Symfony, la guía definitiva Capítulo 6. El Controlador } else { // La petición ya es segura, asi que podemos continuar $filterChain->execute(); } } } Los filtros se utilizan mucho en los plugins, porque permiten extender las características de una aplicación de forma global. El Capítulo 17 incluye más información sobre los plu- gins, y el wiki del proyecto Symfony (http://trac.symfony-project.com/) también tiene más ejemplos de filtros. 6.8. Configuración del Módulo Algunas características de los módulos dependen de la configuración. Para modificarlas, se debe crear un archivo module.yml en el directorio config/ y se deben definir paráme- tros para cada entorno (o en la sección all: para todos los entornos). El listado 6-38 muestra un ejemplo de un archivo module.yml para el módulo mimodulo. Listing 6-38 - Configuración del módulo, en apps/miaplicacion/modules/mimodulo/ config/module.yml all: # Para todos los entornos enabled: true is_internal: false view_class: sfPHP El parámetro enabled permite desactivar todas las acciones en un módulo. En ese caso, todas las acciones se redireccionan a la acción module_disabled_modu- le/module_disabled_action (tal y como se define en el archivo settings.yml). El parámetro is_internal permite restringir la ejecución de todas las acciones de un mó- dulo a llamadas internas. Esto es útil por ejemplo para acciones de envío de correos electrónicos que se deben llamar desde otras acciones para enviar mensajes de e-mail, pero que no se deben llamar desde el exterior. El parámetro view_class define la clase de la vista. Debe heredar de sfView. Sobreescri- bir este valor permite utilizar otros sistemas de generación de vistas con otros motores de plantillas, como por ejemplo Smarty. 6.9. Resumen En Symfony, la capa del controlador esta dividida en dos partes: el controlador frontal, que es el único punto de entrada a la aplicación para un entorno dado, y las acciones, que contienen la lógia de las páginas. Una acción puede elegir la forma en la que se eje- cuta su vista, devolviendo un valor correspondiente a una de las constantes de la clase sfView. Dentro de una acción, se pueden manipular los diferentes elementos del contex- to, incluidos el objeto de la petición (sfRequest) y el objeto de la sesión del usuario actual (sfUser). www.librosweb.es 119
  • 120. Symfony, la guía definitiva Capítulo 6. El Controlador Combinando el poder del objeto de sesión, el objeto acción y las configuraciones de se- guridad proporcionan sistema de seguridad completo, con restricciones de acceso y cre- denciales. Los métodos especiales validate() y handleError() en la acciones permiten gestionar la validación de las peticiones. Y si los métodos preExecute() y postExecute() se diseñan para la reutilización de código dentro de un módulo, los filtros permiten la misma reutilización para toda la aplicación ejecutando código del controlador para cada petición. www.librosweb.es 120
  • 121. Symfony, la guía definitiva Capítulo 7. La Vista Capítulo 7. La Vista La vista se encarga de producir las páginas que se muestran como resultado de las accio- nes. La vista en Symfony está compuesta por diversas partes, estando cada una de ellas especialmente preparada para que pueda ser fácilmente modificable por la persona que normalmente trabaja con cada aspecto del diseño de las aplicaciones. ▪ Los diseñadores web normalmente trabajan con las plantillas (que son la presen- tación de los datos de la acción que se está ejecutando) y con el layout (que con- tiene el código HTML común a todas las páginas). Estas partes están formadas por código HTML que contiene pequeños trozos de código PHP, que normalmente son llamadas a los diversos helpers disponibles. ▪ Para mejorar la reutilización de código, los programadores suelen extraer trozos de las plantillas y los transforman en componentes y elementos parciales. De es- ta forma, el layout se modifica para definir zonas en las que se insertan compo- nentes externos. Los diseñadores web también pueden trabajar fácilmente con estos trozos de plantillas. ▪ Los programadores normalmente centran su trabajo relativo a la vista en los ar- chivos de configuración YAML (que permiten establecer opciones para las propie- dades de la respuesta y para otros elementos de la interfaz) y en el objeto resp- uesta. Cuando se trabaja con variables en las plantillas, deben considerarse los posibles riesgos de seguridad de XSS (cross-site scripting) por lo que es necesar- io conocer las técnicas de escape de los caracteres introducidos por los usuarios. Independientemente del tipo de trabajo, existen herramientas y utilidades para simplifi- car y acelerar el trabajo (normalmente tedioso) de presentar los resultados de las accio- nes. En este capítulo se detallan todas estas herramientas. 7.1. Plantillas El Listado 7-1 muestra el código típico de una plantilla. Su contenido está formado por código HTML y algo de código PHP sencillo, normalmente llamadas a las variables defini- das en la acción (mediante la instrucción $this->nombre_variable = ‘valor’;) y algunos helpers. Listado 7-1 - Plantilla de ejemplo indexSuccess.php <h1>Bienvenido</h1> <p>¡Hola de nuevo, <?php echo $nombre ?>!</p> <ul>¿Qué es lo que quieres hacer? <li><?php echo link_to('Leer los últimos artículos', 'articulo/leer') ?></li> <li><?php echo link_to('Escribir un nuevo artículo', 'articulo/escribir') ?></li> </ul> Como se explica en el Capítulo 4, es recomendable utilizar la sintaxis alternativa de PHP en las plantillas para hacerlas más fáciles de leer a aquellos desarrolladores que descono- cen PHP. Se debería minimizar en lo posible el uso de código PHP en las plantillas, ya que estos archivos son los que se utilizan para definir la interfaz de la aplicación, y muchas veces son diseñados y modificados por otros equipos de trabajo especializados en el www.librosweb.es 121
  • 122. Symfony, la guía definitiva Capítulo 7. La Vista diseño de la presentación y no de la lógica del programa. Además, incluir la lógica dentro de las acciones permite disponer de varias plantillas para una sola acción sin tener que duplicar el código. 7.1.1. Helpers Los helpers son funciones de PHP que devuelven código HTML y que se utilizan en las plantillas. En el listado 7-1, la función link_to() es un helper. A veces, los helpers sola- mente se utilizan para ahorrar tiempo, agrupando en una sola instrucción pequeños tro- zos de código utilizados habitualmente en las plantillas. Por ejemplo, es fácil imaginarse la definición de la función que representa a este helper: <?php echo input_tag('nick') ?> => <input type="text" name="nick" id="nick" value="" /> La función debería ser como la que se muestra en el listado 7-2. Listado 7-2 - Ejemplo de definición de helper function input_tag($name, $value = null) { return '<input type="text" name="'.$name.'" id="'.$name.'" value="'.$value.'" />'; } En realidad, la función input_tag() que incluye Symfony es un poco más complicada que eso, ya que permite indicar un tercer parámetro que contiene otros atributos de la etiq- ueta <input>. Se puede consultar su sintaxis completa y sus opciones en la documenta- ción de la API: http://www.symfony-project.org/api/symfony.html . La mayoría de las veces los helpers incluyen cierta inteligencia que evita escribir bastante código: <?php echo auto_link_text('Por favor, visita nuestro sitio web www.ejemplo.com') ?> => Por favor, visita nuestro sitio web <a href="http://www.ejemplo.com">www.ejemplo.com</a> Los helpers facilitan la creación de las plantillas y producen el mejor código HTML posible en lo que se refiere al rendimiento y a la accesibilidad. Aunque se puede usar HTML nor- mal y corriente, los helpers normalmente son más rápidos de escribir. SUGERENCIA Quizás te preguntes por qué motivo los helpers se nombran con la sintaxis de los guiones bajos en vez de utilizar el método camelCase que se utiliza en el resto de Symfony. El motivo es que los hel- pers son funciones, y todas las funciones de PHP utilizan la sintaxis de los guiones bajos. 7.1.1.1. Declarando los Helpers Los archivos de Symfony que contienen los helpers no se cargan automáticamente (ya que contienen funciones, no clases). Los helpers se agrupan según su propósito. Por ejemplo el archivo llamado TextHelper.php contiene todas las funciones de los helpers re- lacionados con el texto, que se llaman “grupo de helpers de Text”. De esta forma, si una plantilla va a utilizar un helper, se debe cargar previamente el grupo al que pertenece el www.librosweb.es 122
  • 123. Symfony, la guía definitiva Capítulo 7. La Vista helper mediante la función use_helper(). El listado 7-3 muestra una plantilla que hace uso del helper auto_link_text(), que forma parte del grupo Text. Listado 7-3 - Declarando el uso de un helper // Esta plantilla utiliza un grupo de helpers específicos <?php use_helper('Text') ?> ... <h1>Descripción</h1> <p><?php echo auto_link_text($descripcion) ?></p> SUGERENCIA Si se necesita declarar más de un grupo de helpers, se deben añadir más argumentos a la llamada de la función use_helper(). Si por ejemplo se necesitan cargar los helpers Text y Javascript, la llamada a la función debe ser <?php echo use_helper(’Text’, ‘Javascript’) ?>. Por defecto algunos de los helpers están disponibles en las plantillas sin necesidad de ser declarados. Estos helpers pertenecen a los siguientes grupos: ▪ Helper: se necesita para incluir otros helpers (de hecho, la función use_helper() también es un helper) ▪ Tag: helper básico para etiquetas y que utilizan casi todos los helpers ▪ Url: helpers para la gestión de enlaces y URL ▪ Asset: helpers que añaden elementos a la sección <head> del código HTML y que proporcionan enlaces sencillos a elementos externos (imágenes, archivos JavaS- cript, hojas de estilo, etc.) ▪ Partial: helpers que permiten incluir trozos de plantillas ▪ Cache: manipulación de los trozos de código que se han añadido a la cache ▪ Form: helpers para los formularios El archivo settings.yml permite configurar la lista de helpers que se cargan por defecto en todas las plantillas. De esta forma, se puede modificar su configuración si se sabe por ejemplo que no se van a usar los helpers relacionados con la cache o si se sabe que siempre se van a necesitar los helpers relacionados con el grupo Text. Este cambio puede aumentar ligeramente la velocidad de ejecución de la aplicación. Los 4 primeros helpers de la lista anterior (Helper, Tag, Url y Asset) no se pueden eliminar, ya que son obligato- rios para que funcione correctamente el mecanismo de las plantillas. Por este motivo ni siquiera aparecen en la lista de helpers estándares. SUGERENCIA Si se quiere utilizar un helper fuera de una plantilla, se puede cargar un grupo de helpers desde cualquier punto de la aplicación mediante la función sfLoader::loadHelpers($helpers), donde la variable $helpers es el nombre de un grupo de helpers o un array con los nombres de varios grupos de helpers. Por tanto, si se quiere utilizar auto_link_text() dentro de una acción, es ne- cesario llamar primero a sfLoader::loadHelpers(’Text’). www.librosweb.es 123
  • 124. Symfony, la guía definitiva Capítulo 7. La Vista 7.1.1.2. Los helpers habituales Algunos helpers se explican en detalle en los siguientes capítulos, en función de la carac- terística para la que han sido creados. El listado 7-4 incluye un pequeña lista de los hel- pers que más se utilizan y muestra también el código HTML que generan. Listado 7-4 - Los helpers por defecto más utilizados // Grupo Helper <?php use_helper('NombreHelper') ?> <?php use_helper('NombreHelper1', 'NombreHelper2', 'NombreHelper3') ?> // Grupo Tag <?php echo tag('input', array('name' => 'parametro', 'type' => 'text')) ?> <?php echo tag('input', 'name=parametro type=text') ?> // Sintaxis alternativa para las opciones => <input name="parametro" type="text" /> <?php echo content_tag('textarea', 'contenido de prueba', 'name=parametro') ?> => <textarea name="parametro">contenido de prueba</textarea> // Grupo Url <?php echo link_to('Pínchame', 'mimodulo/miaccion') ?> => <a href="/ruta/a/miaccion">Pínchame</a> // Depende del sistema de enrutamiento // Grupo Asset <?php echo image_tag('miimagen', 'alt=imagen size=200x100') ?> => <img src="/images/miimagen.png" alt="imagen" width="200" height="100"/> <?php echo javascript_include_tag('miscript') ?> => <script language="JavaScript" type="text/javascript" src="/js/miscript.js"></script> <?php echo stylesheet_tag('estilo') ?> => <link href="/stylesheets/estilo.css" media="screen" rel="stylesheet" type="text/css" /> Symfony incluye muchos otros helpers y describirlos todos requeriría de un libro entero. La mejor referencia para estudiar los helpers es la documentación de la API, que se pue- de consultar en http://www.symfony-project.org/api/symfony.html, donde todos los helpers incluyen documentación sobre su sintaxis, opciones y ejemplos. 7.1.1.3. Crea tus propios helpers Symfony incluye numerosos helpers que realizan distintas funcionalidades, pero si no se encuentra lo que se necesita, es probable que tengas que crear un nuevo helper. Crear un helper es muy sencillo. Las funciones del helper (funciones normales de PHP que devuelven código HTML) se de- ben guardar en un archivo llamado NombreHelper.php, donde Nombre es el nombre del nuevo grupo de helpers. El archivo se debe guardar en el directorio apps/miaplicacion/ lib/helper/ (o en cualquier directorio helper/ que esté dentro de cualquier directorio lib/ del proyecto) para que la función use_helper(’Nombre’) pueda encontrarlo de forma automática y así poder incluirlo en la plantilla. www.librosweb.es 124
  • 125. Symfony, la guía definitiva Capítulo 7. La Vista SUGERENCIA Este mecanismo permite incluso redefinir los helpers de Symfony. Para redefinir por ejemplo todos los helpers del grupo Text, se puede crear un archivo llamado TextHelper.php y guardarlo en el directorio apps/miaplicacion/lib/helper/. Cada vez que se llame a la función use_hel- per(’Text’), Symfony carga el nuevo grupo de helpers en vez del grupo por defecto. Hay que ser cuidadoso con este método, ya que como el archivo original no se carga, el nuevo grupo de helpers debe redefinir todas y cada una de las funciones del grupo original, ya que de otra forma no estarán disponibles las funciones no definidas. 7.1.2. Layout de las páginas La plantilla del listado 7-1 no es un documento XHTML válido. Le faltan la definición del DOCTYPE y las etiquetas <html> y <body>. El motivo es que estos elementos se encuentran en otro lugar de la aplicación, un archivo llamado layout.php que contiene el layout de la página. Este archivo, que también se denomina plantilla global, almacena el código HTML que es común a todas las páginas de la aplicación, para no tener que repetirlo en cada página. El contenido de la plantilla se integra en el layout, o si se mira desde el otro pun- to de vista, el layout decora la plantilla. Este comportamiento es una implementación del patrón de diseño llamado “decorator” y que se muestra en la figura 7-1. SUGERENCIA Para obtener más información sobre el patrón “decorator” y sobre otros patrones de diseño, se pue- de consultar el libro “Patterns of Enterprise Application Architecture” escrito por Martin Fowler (Addison-Wesley, ISBN: 0-32112-742-0). Figura 7.1. Plantilla decorada con un layout El listado 7-5 muestra el layout por defecto, que se encuentra en el directorio templates/. Listado 7-5 - Layout por defecto, en miproyecto/apps/miaplicacion/templates/ layout.php <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/ 2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <?php echo include_http_metas() ?> <?php echo include_metas() ?> <?php echo include_title() ?> <link rel="shortcut icon" href="/favicon.ico" /> </head> <body> www.librosweb.es 125
  • 126. Symfony, la guía definitiva Capítulo 7. La Vista <?php echo $sf_data->getRaw('sf_content') ?> </body> </html> Los helpers utilizados en la sección <head> obtienen información del objeto respuesta y en la configuración de la vista. La etiqueta <body> muestra el resultado de la plantilla. Utilizando este layout, la configuración por defecto y la plantilla de ejemplo del listado 7- 1, la vista generada sería la del listado 7-6. Listado 7-6 - Unión del layout, la configuración de la vista y la plantilla <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/ 2000/REC-xhtml1-20000126/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta name="title" content="symfony project" /> <meta name="robots" content="index, follow" /> <meta name="description" content="symfony project" /> <meta name="keywords" content="symfony, project" /> <title>symfony project</title> <link rel="stylesheet" type="text/css" href="/css/main.css" /> <link rel="shortcut icon" href="/favicon.ico"> </head> <body> <h1>Bienvenido</h1> <p>¡Hola de nuevo, <?php echo $nombre ?>!</p> <ul>¿Qué es lo que quieres hacer? <li><?php echo link_to('Leer los últimos artículos', 'articulo/leer') ?></li> <li><?php echo link_to('Escribir un nuevo artículo', 'articulo/escribir') ?></li> </ul> </body> </html> La plantilla global puede ser adaptada completamente para cada aplicación. Se puede añadir todo el código HTML que sea necesario. Normalmente se utiliza el layout para mostrar la navegación, el logotipo del sitio, etc. Incluso es posible definir más de un lay- out y decidir en cada acción el layout a utilizar. No te preocupes ahora por la forma de incluir archivos de JavaScript y hojas de estilos, ya que se explica en la sección “Configu- ración de la Vista” más adelante en este capítulo. 7.1.3. Atajos de plantilla Symfony incluye una serie de variables propias en todas las plantillas. Estas variables se pueden considerar atajos que permiten el acceso directo a la información más utilizada en las plantillas, mediante los siguientes objetos internos de Symfony: ▪ $sf_context: el objeto entero de contexto (instance of sfContext) ▪ $sf_request: el objeto petición (instance of sfRequest) ▪ $sf_params: los parámetros de la petición www.librosweb.es 126
  • 127. Symfony, la guía definitiva Capítulo 7. La Vista ▪ $sf_user: el objeto de sesión del usuario actual (instance of sfUser) En el capítulo anterior se detallaban algunos métodos útiles de los objetos sfRequest y sfUser. En las plantillas se pueden invocar todos esos métodos mediante las variables $sf_request y $sf_user. Por ejemplo, si la petición incluye un parámetro llamado total, desde la plantilla se puede acceder a su valor de la siguiente manera: // Método largo <?php echo $sf_request->getParameter('total'); ?> // Método corto (atajo) <?php echo $sf_params->get('total'); ?> // Son equivalentes al siguiente código de la acción echo $this->getRequestParameter('total'); 7.2. Fragmentos de código En ocasiones es necesario incluir cierto código HTML o PHP en varias páginas. Para no te- ner que repetirlo, casi siempre es suficiente con utilizar la instrucción include(). Si por ejemplo varias de las plantillas de la aplicación utilizan el mismo fragmento de có- digo, se puede guardar en un archivo llamado miFragmento.php en el directorio global de plantillas (miproyecto/apps/miaplicacion/templates/) e incluirlo en las plantillas median- te la instrucción siguiente: <?php include(sfConfig::get('sf_app_template_dir').'/miFragmento.php') ?> Sim embargo, esta forma de trabajar con fragmentos de código no es muy limpia, sobre todo porque puede que los nombres de las variables utilizadas no coincidan en el frag- mento de código y en las distintas plantillas. Además, el sistema de cache de Symfony (que se explica en el Capítulo 12) no puede detectar el uso de include(), por lo que no se puede incluir en la cache el código del fragmento de forma independiente al de las plantillas. Symfony define 3 alternativas al uso de la instrucción include() y que permi- ten manejar de forma inteligente los fragmentos de código: ▪ Si el fragmento contiene poca lógica, se puede utilizar un archivo de plantilla al que se le pasan algunas variables. En este caso, se utilizan los elementos parcia- les (partial). ▪ Si la lógica es compleja (por ejemplo se debe acceder a los datos del modelo o se debe variar los contenidos en función de la sesión) es preferible separar la pre- sentación de la lógica. En este caso, se utilizan componentes (component). ▪ Si el fragmento va a reemplazar una zona específica del layout, para la que pue- de que exista un contenido por defecto, se utiliza un slot. NOTA Existe otro tipo de fragmento de código, llamado “slot de componentes”, que se utiliza cuando el fragmento depende del contexto (por ejemplo si el fragmento debe ser diferente para las acciones de un mismo módulo). Más tarde en este capítulo se explican los “slots de componentes”. www.librosweb.es 127
  • 128. Symfony, la guía definitiva Capítulo 7. La Vista Todos estos fragmentos se incluyen mediante los helpers del grupo llamado Partial. Es- tos helpers están disponibles en cualquier plantilla de Symfony sin necesidad de declarar- los al principio. 7.2.1. Elementos parciales Un elemento parcial es un trozo de código de plantilla que se puede reutilizar. Por ejem- plo, en una aplicación de publicación, el código de plantilla que se encarga de mostrar un artículo se utiliza en la página de detalle del artículo, en la página que lista los mejores artículo y en la página que muestra los últimos artículos. Se trata de un código perfecto para definirlo como elemento parcial, tal y como muestra la figura 7-2. Figura 7.2. Reutilización de elementos parciales en las plantillas Al igual que las plantillas, los elementos parciales son archivos que se encuentran en el directorio templates/, y que contienen código HTML y código PHP. El nombre del archivo de un elemento parcial siempre comienza con un guión bajo (_), lo que permite distinguir a los elementos parciales de las plantillas, ya que todos se encuentran en el mismo direc- torio templates/. Una plantilla puede incluir elementos parciales independientemente de que estos se enc- uentren en el mismo módulo, en otro módulo o en el directorio global templates/. Los elementos parciales se incluyen mediante el helper include_partial(), al que se le pasa como parámetro el nombre del módulo y el nombre del elemento parcial (sin incluir el guión bajo del principio y la extensión .php del final), tal y como se muestra en el listado 7-7. Listado 7-7 - Incluir elementos parciales en una plantilla del módulo mimodulo // Incluir el elemento pacial de miaplicacion/modules/mimodulo/templates/_miparcial1.php // Como la plantilla y el elemento parcial están en el mismo módulo, // se puede omitir el nombre del módulo <?php include_partial('miparcial1') ?> // Incluir el elemento parcial de miaplicacion/modules/otromodulo/templates/ _miparcial2.php // En este caso es obligatorio indicar el nombre del módulo <?php include_partial('otromodulo/miparcial2') ?> // Incluir el elemento parcial de miaplicacion/templates/_miparcial3.php // Se considera que es parte del módulo 'global' <?php include_partial('global/miparcial3') ?> Los elementos parciales pueden acceder a los helpers y atajos de plantilla que proporcio- na Symfony. Pero como los elementos parciales se pueden llamar desde cualquier punto www.librosweb.es 128
  • 129. Symfony, la guía definitiva Capítulo 7. La Vista de la aplicación, no tienen acceso automático a las variables definidas por la acción que ha incluido la plantilla en la que se encuentra el elemento parcial, a no ser que se pase esas variables explícitamente en forma de parámetro. Si por ejemplo se necesita que un elemento parcial tenga acceso a una variable llamada $total, la acción pasa esa variable a la plantilla y después la plantilla se la pasa al helper como el segundo parámetro de la llamada a la función include_partial(), como se muestra en los listado 7-8, 7-9 y 7-10. Listado 7-8 - La acción define una variable, en mimodulo/actions/actions.class.php class mimoduloActions extends sfActions { public function executeIndex() { $this->total = 100; } } Listado 7-9 - La plantilla pasa la variable al elemento parcial, en mimodulo/templa- tes/indexSuccess.php <p>¡Hola Mundo!</p> <?php include_partial('miparcial', array('mitotal' => $total) ) ?> Listado 7-10 - El elemento parcial ya puede usar la variable, en mimodulo/templa- tes/_miparcial.php <p>Total: <?php echo $mitotal ?></p> SUGERENCIA Hasta ahora, todos los helpers se llamaban con la función <?php echo nombreFuncion() ?>. Por el contrario, el helper utilizado con los elementos parciales se llama mediante <?php include_- partial() ?>, sin incluir el echo, para hacer su comportamiento más parecido a la instrucción de PHP include(). Si alguna vez se necesita obtener el contenido del elemento parcial sin mostrarlo, se puede utilizar la función get_partial(). Todos los helpers de tipo include_ de este capítulo, tienen una función asociada que comienza por get_ y que devuelve los contenidos que se pueden mostrar directamente con una instrucción echo. 7.2.2. Componentes En el Capítulo 2, el primer script de ejemplo se dividía en dos partes para separar la lógi- ca de la presentación. Al igual que el patrón MVC se aplica a las acciones y las plantillas, es posible dividir un elemento parcial en su parte de lógica y su parte de presentación. En este caso, se necesitan los componentes. Un componente es como una acción, solo que mucho más rápido. La lógica del compo- nente se guarda en una clase que hereda de sfComponents y que se debe guardar en el archivo action/components.class.php. Su presentación se guarda en un elemento parcial. Los métodos de la clase sfComponents empiezan con la palabra execute, como sucede con las acciones, y pueden pasar variables a su presentación de la misma forma en la que se www.librosweb.es 129
  • 130. Symfony, la guía definitiva Capítulo 7. La Vista pasan variables en las acciones. Los elementos parciales que se utilizan como presenta- ción de un componente, se deben llamar igual que los componentes, sustituyendo la pa- labra execute por un guión bajo. La tabla 7-1 compara las convenciones en los nombres de las acciones y los componentes. Tabla 7-1. Convenciones en el nombrado de las acciones y de los componentes Convención Acciones Componentes Archivo de la lógica actions.class.php components.class.php Clase de la que hereda la lógica sfActions sfComponents Nombre de los métodos executeMiAccion() executeMiComponente() Nombre del archivo de presentación miAccionSuccess.php _miComponente.php SUGERENCIA De la misma forma que es posible separar los archivos de las acciones, la clase sfComponents dis- pone de una equivalente llamada sfComponent y que permite crear archivos individuales para cada componente siguiendo una sintaxis similar. Por ejemplo, se puede definir una zona lateral que muestra las últimas noticias de un de- terminado tema que depende del perfil del usuario y que se va a reutilizar en varias pági- nas. Las consultas necesarias para mostrar las noticias son demasiado complejas como para incluirlas en un elemento parcial, por lo que se deben incluir en un archivo similar a las acciones, es decir, en un componente. La figura 7-3 ilustra este ejemplo. Figura 7.3. Uso de componentes en las plantillas En este ejemplo, mostrado en los listados 7-11 y 7-12, el componente se define en su propio módulo (llamado news), pero se pueden mezclar componentes y acciones en un ú- nico módulo, siempre que tenga sentido hacerlo desde un punto de vista funcional. Listado 7-11 - La clase de los componentes, en modules/news/actions/ components.class.php <?php class newsComponents extends sfComponents { public function executeHeadlines() { www.librosweb.es 130
  • 131. Symfony, la guía definitiva Capítulo 7. La Vista $c = new Criteria(); $c->addDescendingOrderByColumn(NewsPeer::PUBLISHED_AT); $c->setLimit(5); $this->news = NewsPeer::doSelect($c); } } Listado 7-12 - El elemento parcial, en modules/news/templates/_headlines.php <div> <h1>Últimas noticias</h1> <ul> <?php foreach($news as $headline): ?> <li> <?php echo $headline->getPublishedAt() ?> <?php echo link_to($headline->getTitle(),'news/show?id='.$headline->getId()) ?> </li> <?php endforeach ?> </ul> </div> Ahora, cada vez que se necesite el componente en una plantilla, se puede incluir de la si- guiente forma: <?php include_component('news', 'headlines') ?> Al igual que sucede con los elementos parciales, se pueden pasar parámetros adicionales a los componentes mediante un array asociativo. Dentro del elemento parcial se puede acceder directamente a los parámetros mediante su nombre y en el componente se pue- de acceder a ellos mediante el uso de $this. El listado 7-13 muestra un ejemplo. Listado 7-13 - Paso de parámetros a un componente y a su plantilla // Llamada al componente <?php include_component('news', 'headlines', array('parametro' => 'valor')) ?> // Dentro del componente echo $this->parametro; => 'valor' // Dentro del elemento parcial _headlines.php echo $parametro; => 'valor' Se pueden incluir componentes dentro de otros componentes y también en el layout glo- bal como si fuera una plantilla normal. Al igual que en las acciones, los métodos execute de los componentes pueden pasar variables a sus elementos parciales relacionados y pueden tener acceso a los mismos atajos. Pero las similitudes se quedan solo en eso. Los componentes no pueden manejar la seguridad ni la validación, no pueden ser llamados desde Internet (solo desde la propia aplicación) y no tienen distintas posibilidades para devolver sus resultados. Por este motivo, los componentes son más rápidos que las acciones. www.librosweb.es 131
  • 132. Symfony, la guía definitiva Capítulo 7. La Vista 7.2.3. Slots Los elementos parciales y los componentes están especialmente diseñados para reutilizar código. Sin embargo, en muchas ocasiones se necesitan fragmentos de código que relle- nen un layout con más de una zona variable. Por ejemplo se puede necesitar añadir etiq- uetas personalizadas en la sección <head> del layout en función del contenido de la ac- ción. También se puede dar el caso de un layout que tiene una zona de contenidos diná- micos que se rellena con el resultado de la acción y muchas otras zonas pequeñas que tienen un contenido por defecto definido en el layout pero que puede ser modificado en la plantilla. En los casos descritos anteriormente la solución más adecuada es un slot. Básicamente, un slot es una zona que se puede definir en cualquier elemento de la vista (layout, plan- tilla o elemento parcial). La forma de rellenar esa zona es similar a establecer el valor de una variable. El código de relleno se almacena de forma global en la respuesta, por lo que se puede definir en cualquier sitio (layout, plantilla o elemento parcial). Se debe defi- nir un slot antes de utilizarlo y también hay que tener en cuenta que el layout se ejecuta después de la plantilla (durante el proceso de decoración) y que los elementos parciales se ejecutan cuando los llama una plantilla. Como todo esto suena demasiado abstracto, se va a ver su funcionamiento con un ejemplo. Imagina que se dispone de un layout con una zona para la plantilla y 2 slots: uno para el lateral de la página y otro para el pie de página. El valor de los slots se define en la plan- tilla. Durante el proceso de decoración, el layout integra en su interior el código de la plantilla, por lo que los slots se rellenan con los valores que se han definido anteriormen- te, tal y como muestra la figura 7-4. De esta forma, el lateral y el pie de página pueden depender de la acción. Se puede aproximar a la idea de tener un layout con uno o más agujeros que se rellenan con otro código. Figura 7.4. La plantilla define el valor de los slots del layout Su funcionamiento se puede comprender mejor viendo algo de código. Para incluir un slot se utiliza el helper include_slot(). El helper has_slot() devuelve un valor true si el slot ya ha sido definido antes, permitiendo de esta forma establecer un mecanismo de protección frente a errores. El listado 7-14 muestra como definir la zona para el slot la- teral en el layout y su contenido por defecto. Listado 7-14 - Incluir un slot llamado lateral en el layout <div id="lateral"> <?php if (has_slot('lateral')): ?> <?php include_slot('lateral') ?> <?php else: ?> <!-- código del lateral por defecto --> www.librosweb.es 132
  • 133. Symfony, la guía definitiva Capítulo 7. La Vista <h1>Zona cuyo contenido depende del contexto</h1> <p>Esta zona contiene enlaces e información sobre el contenido principal de la página.</p> <?php endif; ?> </div> Las plantillas pueden definir los contenidos de un slot (e incluso los elementos parciales pueden hacerlo). Como los slots se definen para mostrar código HTML, Symfony proporc- iona métodos útiles para indicar ese código HTML: se puede escribir el código del slot en- tre las llamadas a las funciones slot() y end_slot(), como se muestra en el listado 7-15. Listado 7-15 - Redefiniendo el contenido del slot lateral en la plantilla ... <?php slot('lateral') ?> <!-- Código específico para el lateral de esta plantilla --> <h1>Detalles del usuario</h1> <p>Nombre: <?php echo $user->getName() ?></p> <p>Email: <?php echo $user->getEmail() ?></p> <?php end_slot() ?> El código incluido entre las llamadas a los helpers del slot se ejecutan en el contexto de las plantillas, por lo que tienen acceso a todas las variables definidas por la acción. Sym- fony añade de forma automática en el objeto response el resultado del código anterior. No se muestra directamente en la plantilla, sino que se puede acceder a su código med- iante la llamada a la función include_slot(), como se muestra en el listado 7.14. Los slots son muy útiles cuando se tienen que definir zonas que muestran contenido que depende del contexto de la página. También se puede utilizar para añadir código HTML al layout solo para algunas acciones. Por ejemplo, una plantilla que muestra la lista de las últimas noticias puede necesitar incluir un enlace a un canal RSS dentro de la sección <head> del layout. Esto se puede conseguir añadiendo un slot llamado feed en el layout y que sea redefinido en la plantilla del listado de noticias. Dónde encontrar los fragmentos de plantillas Los usuarios que trabajan con plantillas normalmente son diseñadores web, que no conocen muy bien el funcionamiento de Symfony y que pueden tener problemas para encontrar los fragmentos de plantilla, ya que pueden estar desperdigados por todas la aplicación. Los siguientes consejos pueden hacer más fácil su trabajo con las plantillas de Symfony. En primer lugar, aunque los proyectos de Symfony contienen muchos directorios, todos los layouts, plantillas y fragmentos de plantillas son archivos que se encuentran en directorios llamados tem- plates/. Por tanto, en lo que respecta a un diseñador web, la estructura de un proyecto queda re- ducida a: miproyecto/ apps/ aplicacion1/ templates/ # Layouts de la aplicacion 1 modules/ modulo1/ templates/ # Plantillas y elementos parciales del modulo 1 modulo2/ www.librosweb.es 133
  • 134. Symfony, la guía definitiva Capítulo 7. La Vista templates/ # Plantillas y elementos parciales del modulo 2 modulo3/ templates/ # Plantillas y elementos parciales del modulo 3 El resto de directorios pueden ser ignorados por el diseñador. Cuando se encuentren con una función del tipo include_partial(), los diseñadores web sólo tie- nen que preocuparse por el primer argumento de la función. La estructura del nombre de este argu- mento es nombre_modulo/nombre_elemento_parcial, lo que significa que el código se encuentra en el archivo modules/nombre_modulo/templates/_nombre_elemento_parcial.php. En los helpers de tipo include_component(), el nombre del módulo y el nombre del elemento par- cial son los dos primeros argumentos. Por lo demás, para empezar a diseñar plantillas de aplicacio- nes Symfony sólo es necesario tener una idea general sobre lo que son los helpers y cuales son los más utilizados en las plantillas. 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 lay- out (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 Sym- fony 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 de- penden del contexto o de alguna consulta a la base de datos. Cuando se trabaja con va- lores dinámicos que cambian con cada acción, se recurre al segundo método para esta- blecer la configuración de la vista: añadir los atributos directamente en el objeto sfRes- ponse 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. www.librosweb.es 134
  • 135. Symfony, la guía definitiva Capítulo 7. La Vista 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 SUGERENCIA 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 archi- vo 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 ca- da vista solo se aplican a una vista y además sus valores tienen preferencia so- bre 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 de- finiciones 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. SUGERENCIA Por defecto no existen los archivos view.yml de cada módulo. Por tanto la primera vez que se ne- cesita configurar una opción a nivel de módulo, se debe crear un nuevo archivo llamado view.yml en el directorio config/. www.librosweb.es 135
  • 136. Symfony, la guía definitiva Capítulo 7. La Vista Después de ver la plantilla por defecto en el listado 7-5 y un ejemplo de la respuesta ge- nerada 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 de- finidas en el archivo view.yml de la aplicación que se muestra en el listado 7-17. Listado 7-17 - Archivo de configuración de la vista de la aplicación, en apps/mia- plicacion/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 configura- ción de la vista”. 7.3.2. El objeto respuesta (response) Aunque el objeto response (objeto respuesta) es parte de la vista, normalmente se modi- fica en la acción. Las acciones acceden al objeto respuesta creado por Symfony, y llama- do 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); www.librosweb.es 136
  • 137. Symfony, la guía definitiva Capítulo 7. La Vista // 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 nece- sario 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 for- ma 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 defec- to 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 men- saje más común para ese código. $respuesta->setStatusCode(404, 'Esta página no existe'); SUGERENCIA 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 configu- ración de la vista: ▪ Las opciones que tienen un único valor (el valor es una cadena de texto en el ar- chivo view.yml y el objeto respuesta utiliza un método set para ellas) www.librosweb.es 137
  • 138. Symfony, la guía definitiva Capítulo 7. La Vista ▪ 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 archi- vo 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ña- dir 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 www.librosweb.es 138
  • 139. Symfony, la guía definitiva Capítulo 7. La Vista 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 esta- blecido 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 www.librosweb.es 139
  • 140. Symfony, la guía definitiva Capítulo 7. La Vista 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 enc- uentran 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 nece- sario 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 mos- trado 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 sfCommonFil- ter. 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 archi- vo 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] www.librosweb.es 140
  • 141. Symfony, la guía definitiva Capítulo 7. La Vista 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 supe- riores, 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 algu- nos 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 utili- zar 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 (solo 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 op- ciones 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" /> www.librosweb.es 141
  • 142. Symfony, la guía definitiva Capítulo 7. La Vista 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/tem- plates/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 miaplicac- ion/templates/mi_layout.php, se debe utilizar la sintaxis del listado 7-34 para los archi- vos 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 por defecto no tienen definido ningún layout. 7.4. Slots de componentes Si se combina el poder de los componentes que se han visto anteriormente y las opcio- nes de configuración de la vista, se consigue un modelo de desarrollo de la vista comple- tamente nuevo: el sistema de slots de componentes. Se trata de una alternativa a los slots que se centra en la reutilización y en la separación en capas. De esta forma, los slots de componentes están mucho más estructurados que los slots, pero son un poco más lentos de ejecutar. Al igual que los slots, los slots de componentes son zonas que se pueden definir en los elementos de la vista. La principal diferencia reside en la forma en la que se decide qué codigo rellena esas zonas. En un slot normal, el código se establece en otro elemento de la vista; en un slot de componentes, el código es el resultado de la ejecución de un com- ponente, y el nombre de ese componente se obtiene de la configuración de la vista. Des- pués de verlos en la práctica, es sencillo entender el comportamiento de los slots de componentes. www.librosweb.es 142
  • 143. Symfony, la guía definitiva Capítulo 7. La Vista Para definir la zona del slot de componentes, se utiliza el helper include_component_s- lot(). El parámetro de esta función es el nombre que se asigna al slot de componentes. Imagina por ejemplo que el archivo layout.php de la aplicación tiene un lateral de conte- nidos cuya información depende de la página en la que se muestra. El listado 7-38 mues- tra como se incluiría este slot de componentes. Listado 7-38 - Incluir un slot de componentes de nombre lateral ... <div id="lateral"> <?php include_component_slot('lateral') ?> </div> La correspondencia entre el nombre del slot de componentes y el nombre del propio componente se define en la configuración de la vista. Por ejemplo, se puede establecer el componente por defecto para el slot lateral debajo de la clave components del archivo view.yml de la aplicación. La clave de la opción de configuración es el nombre del slot de componentes; el valor de la opción es un array que contiene el nombre del módulo y el nombre del componente. El listado 7-29 muestra un ejemplo. Listado 7-39 - Definir el slot de componentes por defecto para lateral, en mia- plicacion/config/view.yml default: components: lateral: [mimodulo, default] De esta forma, cuando se ejecuta el layout, el slot de componentes lateral se rellena con el resultado de ejecutar el método executeDefault() de la clase mimoduloComponents del módulo mimodulo, y este método utiliza la vista del elemento parcial _default.php que se encuentra en modules/mimodulo/templates/. La configuración en cascada permite redefinir esta opción en cualquier módulo. Por ejem- plo, en el módulo user puede ser más útil mostrar el nombre del usuario y el número de artículos que ha publicado. En ese caso, se puede particularizar el slot lateral mediante las siguientes opciones en el archivo view.yml del módulo, como se muestra en el listado 7-40. Listado 7-40 - Particularizando el slot de componentes lateral, en miaplicacion/ modules/user/config/view.yml all: components: lateral: [mimodulo, user] El listado 7-41 muestra el código del componente necesario para este slot. Listado 7-41 - Componentes utilizados por el slot lateral, en modules/mimodulo/ac- tions/components.class.php class mimoduloComponents extends sfComponents { public function executeDefault() { www.librosweb.es 143
  • 144. Symfony, la guía definitiva Capítulo 7. La Vista } public function executeUser() { $this->usuario_actual = $this->getUser()->getCurrentUser(); $c = new Criteria(); $c->add(ArticlePeer::AUTHOR_ID, $this->usuario_actual->getId()); $this->numero_articulos = ArticlePeer::doCount($c); } } El listado 7-42 muestra la vista de estos 2 componentes. Listado 7-42 - Elementos parciales utilizados por el slot de componentes lateral, en modules/mimodulo/templates/ // _default.php <p>El contenido de esta zona depende de la página en la que se muestra.</p> // _user.php <p>Nombre de usuario: <?php echo $usuario_actual->getName() ?></p> <p><?php echo $numero_articulos ?> artículos publicados</p> Los slots de componentes se pueden utilizar para añadir en las páginas web las “migas de pan”, los menús de navegación que dependen de cada página y cualquier otro conte- nido que se deba insertar de forma dinámica. Como componentes que son, se pueden utilizar en el layout global y en cualquier plantilla, e incluso en otros componentes. La configuración que indica el componente de un slot siempre se extrae de la configuración de la última acción que se ejecuta. Para evitar que se utilice un slot de componentes en un módulo determinado, se puede declarar un par módulo/componente vacío, tal y como muestra el listado 7-43. Listado 7-43 - Deshabilitar un slot de componentes en view.yml all: components: lateral: [] 7.5. Mecanismo de escape Cuando se insertan datos generados dinámicamente en una plantilla, se debe asegurar la integridad de los datos. Por ejemplo, si se utilizan datos obtenidos mediante formularios que pueden rellenar usuarios anónimos, existe un gran riesgo de que los contenidos pue- dan incluir scripts y otros elementos maliciosos que se encargan de realizar ataques de tipo XSS (cross-site scripting). Por tanto, se debe aplicar un mecanismo de escape a to- dos los datos mostrados, de forma que ninguna etiqueta HTML pueda ser peligrosa. Imagina por ejemplo que un usuario rellena un campo de formulario con el siguiente valor: <script>alert(document.cookie)</script> Si se muestran directamente los datos, el navegador ejecuta el código JavaScript intro- ducido por el usuario, que puede llegar a ser mucho más peligroso que el ejemplo www.librosweb.es 144
  • 145. Symfony, la guía definitiva Capítulo 7. La Vista anterior que simplemente muestra un mensaje. Por este motivo, se deben aplicar meca- nismos de escape a los valores introducidos antes de mostrarlos, para que se transfor- men en algo como: &lt;script&gt;alert(document.cookie)&lt;/script&gt; Los datos se pueden escapar manualmente utilizando la función htmlentities() de PHP, pero es un método demasiado repetitivo y muy propenso a cometer errores. En su lugar, Symfony incluye un sistema conocido como mecanismo de escape de los datos que se aplica a todos los datos mostrados mediante las variables de las plantillas. El mecanismo se activa mediante un único parámetro en el archivo settings.yml de la aplicación. 7.5.1. Activar el mecanismo de escape El mecanismo de escape de datos se configura de forma global para toda la aplicación en el archivo settings.yml. El sistema de escape se controla con 2 parámetros: la estrategia (escaping_strategy) define la forma en la que las variables están disponibles en la vista y el método (escaping_method) indica la función que se aplica a los datos. En las siguientes secciones se describen estas opciones en detalle, pero básicamente lo único necesario para activar el mecanismo de escape es establecer para la opción esca- ping_strategy el valor both en vez de su valor por defecto bc, tal y como muestra el lis- tado 7-44. Listado 7-44 - Activar el mecanismo de escape, en miaplicacion/config/ settings.yml all: .settings: escaping_strategy: both escaping_method: ESC_ENTITIES Esta configuración aplica la función htmlentities() a los datos de todas las variables mostradas. Si se define una variable llamada prueba en la acción con el siguiente contenido: $this->prueba = '<script>alert(document.cookie)</script>'; Con el sistema de escape activado, al mostrar esta variable en una plantilla, se mos- trarán los siguientes datos: echo $prueba; => &gt;&lt;script&gt;alert(document.cookie)&lt;/script&gt; Si se activa el mecanismo de escape, desde cualquier plantilla se puede acceder a una nueva variable llamada $sf_data. Se trata de un objeto contenedor que hace referencia a todas las variables que se han modificado mediante el sistema de escape. De esta forma, también es posible mostrar el contenido de la variable prueba de la siguiente manera: echo $sf_data->get('prueba'); => &gt;&lt;script&gt;alert(document.cookie)&lt;/script&gt; www.librosweb.es 145
  • 146. Symfony, la guía definitiva Capítulo 7. La Vista SUGERENCIA El objeto $sf_data implementa la interfaz Array, por lo que en vez de utilizar la sinta- xis$sf_data->get(’mivariable’), se puede obtener la variable mediante $sf_data[’mivaria- ble’]. Sin embargo, no se trata realmente de un array, por lo que no se pueden utilizar funciones como por ejemplo print_r(). Mediante este objeto también se puede acceder a los datos originales o datos en crudo de la variable. Se trata de una opción muy útil por ejemplo cuando la variable contiene código HTML que se quiere incluir directamente en el navegador para que sea interpreta- do en vez de mostrado (solo se debería utilizar esta opción si se confía plenamente en el contenido de esa variable). Para acceder a los datos originales se puede utilizar el méto- do getRaw(). echo $sf_data->getRaw('prueba'); => <script>alert(document.cookie)</script> Si una variable almacena código HTML, cada vez que se necesita el código HTML original, es necesario acceder a sus datos originales, de forma que el código HTML se interprete y no se muestre en el navegador. Por este motivo el layout por defecto utiliza la instrucción $sf_data->getRaw(’sf_content’) para incluir el contenido de la plantilla, en vez de utili- zar directamente el método $sf_content, que provocaría resultados no deseados cuando se activa el mecanismo de escape. 7.5.2. Opción escaping_strategy La opción escaping_strategy determina la forma en la que se muestra el contenido de las variables en las plantillas. Sus posibles valores son los siguientes: ▪ bc (backward compatible mode o modo retrocompatible): el contenido de las var- iables no se modifica, pero el contenedor $sf_data almacena una versión modifi- cada de cada variable. De esta forma, los datos de las variables se obtienen en crudo, a menos que se obtenga la versión modificada del objeto $sf_data. Se tra- ta del valor por defecto de la opción, aunque se trata del modo que permite los ataques de tipo XSS. ▪ both: a todas las variables se les aplica el mecanismo de escape. Los valores también están disponibles en el contenedor $sf_data. Se trata del valor recomen- dado, ya que solamente se está expuesto al riesgo si se utilizan de forma explíci- ta los datos originales. En ocasiones, se deben utilizar los valores originales, por ejemplo para incluir código HTML de forma que se interprete en el navegador y no se muestre el código HTML. Si una aplicación se encuentra medio desarrollada y se cambia la estrategia del mecanismo de escape a este valor, algunas funcio- nalidades pueden dejar de funcionar como se espera. Lo mejor es seleccionar es- ta opción desde el principio. ▪ on: los valores solamente están disponibles en el contenedor $sf_data. Se trata del método más seguro y más rapido de manejar el mecanismo de escape, ya que cada vez que se quiere mostrar el contenido de una variable, se debe elegir el método get() para los datos modificados o el método getRaw() para el www.librosweb.es 146
  • 147. Symfony, la guía definitiva Capítulo 7. La Vista contenido original. De esta forma, siempre se recuerda la posibilidad de que los datos de la variable sean corruptos. ▪ off: deshabilita el mecanismo de escape. Las plantillas no pueden hacer uso del contenedor $sf_data. Si nunca se va a necesitar el sistema de escape, es mejor utilizar esta opción y no la opción por defecto bc, ya que la aplicación se ejecuta más rápidamente. 7.5.3. Los helpers útiles para el mecanismo de escape Los helpers utilizados en el mecanismo de escape son funciones que devuelven el valor modificado correspondiente al valor que se les pasa. Se pueden utilizar como valor de la opción escaping_method en el archivo settings.yml o para especificar un método concreto de escape para los datos de una vista. Los helpers disponibles son los siguientes: ▪ ESC_RAW: no modifica el valor original. ▪ ESC_ENTITIES: aplica la función htmlentities() de PHP al valor que se le pasa y utiliza la opción ENT_QUOTES para el estilo de las comillas. ▪ ESC_JS: modifica un valor que corresponde a una cadena de JavaScript que va a ser utilizada como HTML. Se trata de una opción muy útil para escapar valores cuando se emplea JavaScript para modificar de forma dinámica el contenido HTML de la página. ▪ ESC_JS_NO_ENTITIES: modifica un valor que va a ser utilizado en una cadena de JavaScript pero no le añade las entidades HTML correspondientes. Se trata de una opción muy útil para los valores que se van a mostrar en los cuadros de diá- logo (por ejemplo para una variable llamada miCadena en la instrucción javas- cript:alert(miCadena);). 7.5.4. Aplicando el mecanismo de escape a los arrays y los objetos No solo las cadenas de caracteres pueden hacer uso del mecanismo de escape, sino que también se puede aplicar a los arrays y los objetos. El mecanismo de escape se aplica en cascada a todos los arrays u objetos. Si la estrategia empleada es both, el listado 7-45 muesta el mecanismo de escape aplicado en cascada. Listado 7-45 - El sistema de escape se puede aplicar a los arrays y los objetos // Definición de la clase class miClase { public function pruebaCaracterEspecial($valor = '') { return '<'.$valor.'>'; } } // En la acción $this->array_prueba = array('&', '<', '>'); $this->array_de_arrays = array(array('&')); www.librosweb.es 147
  • 148. Symfony, la guía definitiva Capítulo 7. La Vista $this->objeto_prueba = new miClase(); // En la plantilla <?php foreach($array_prueba as $valor): ?> <?php echo $valor ?> <?php endforeach; ?> => &amp; &lt; &gt; <?php echo $array_de_arrays[0][0] ?> => &amp; <?php echo $objeto_prueba->pruebaCaracterEspecial('&') ?> => &lt;&amp;&gt; De hecho, el tipo de las variables en la plantilla no es el tipo que le correspondería a la variable original. El mecanismo de escape “decora las variables y las transforma en obje- tos especiales: <?php echo get_class($array_prueba) ?> => sfOutputEscaperArrayDecorator <?php echo get_class($objeto_prueba) ?> => sfOutputEscaperObjectDecorator Esta es la razón por la que algunas funciones PHP habituales (como array_shift(), print_r(), etc.) no funcionan en los arrays a los que se ha aplicado el mecanismo de es- cape. No obstante, se puede seguir accediendo mediante [], se pueden recorrer con fo- reach y proporcionan el dato correcto al utilizar la función count() (aunque count() solo funciona con la versión 5.2 o posterior de PHP). Como en las plantillas los datos (casi) siempre se acceden en modo solo lectura, la mayor parte de las veces se accede a los datos mediante los métodos que sí funcionan. De todas formas, todavía es posible acceder a los datos originales mediante el objeto $sf_data. Además, los métodos de los objetos a los que se aplica el mecanismo de esca- pe se modifican para que acepten un parámetro adicional: el método de escape. Así, se puede utilizar un método de escape diferente cada vez que se accede al valor de una var- iable en una plantilla, o incluso es posible utilizar el helper ESC_RAW para desactivar el sis- tema de escape para una variable concreta. El listado 7-46 muestra un ejemplo. Listado 7-46 - Los métodos de los objetos a los que se aplica el mecanismo de escape aceptan un parámetro adicional <?php echo $objeto_prueba->pruebaCaracterEspecial('&') ?> => &lt;&amp;&gt; // Las siguientes 3 líneas producen el mismo resultado <?php echo $objeto_prueba->pruebaCaracterEspecial('&', ESC_RAW) ?> <?php echo $sf_data->getRaw('objeto_prueba')->pruebaCaracterEspecial('&') ?> <?php echo $sf_data->get('objeto_prueba', ESC_RAW)->pruebaCaracterEspecial('&') ?> => <&> Si se incluyen muchos objetos en las plantillas, el truco de añadir un parámetro adicional a los métodos se utiliza mucho, ya que es el método más rápido de obtener los datos ori- ginales al ejecutar el método. www.librosweb.es 148
  • 149. Symfony, la guía definitiva Capítulo 7. La Vista ATENCIÓN Las variables de Symfony también se modifican al activar el mecanismo de escape. Por tanto, las variables $sf_user, $sf_request, $sf_param y $sf_context siguen funcionando, pero sus méto- dos devuelven sus datos modificados, a no ser que se utilice la opción ESC_RAW como último argu- mento de las llamadas a los métodos. 7.6. Resumen Existen numerosas herramientas y utilidades para manipular la capa correspondiente a la presentación. Las plantillas se pueden construir en pocos segundos, gracias al uso de los helpers. Los layouts, los elementos parciales, los componentes y los slots de componen- tes permiten aplicar los conceptos de modularidad y reutilización de componentes. La configuración de la vista aprovecha la velocidad de YAML para manejar la mayoría de ca- beceras de las páginas. La configuración en cascada evita tener que definir todas las opc- iones para cada vista. Si una modificación de la presentación requiere el uso de datos dinámicos, se puede realizar la modificación en la acción mediante el objeto sfResponse. Además, la vista puede protegerse ante ataques de tipo XSS gracias al mecanismo de es- cape de los datos de las variables. www.librosweb.es 149
  • 150. Symfony, la guía definitiva Capítulo 8. El modelo Capítulo 8. El modelo Hasta ahora, la mayor parte de los contenidos se ha dedicado a la construcción de pági- nas y al procesado de peticiones y respuestas. Sin embargo, la lógica de negocio de las aplicaciones web depende casi siempre en su modelo de datos. El componente que se en- carga por defecto de gestionar el modelo en Symfony es una capa de tipo ORM (object/ relational mapping) realizada mediante el proyecto Propel (http://propel.phpdb.org/). En las aplicaciones Symfony, el acceso y la modificación de los datos almacenados en la ba- se de datos se realiza mediante objetos; de esta forma nunca se accede de forma explíci- ta a la base de datos. Este comportamiento permite un alto nivel de abstracción y permi- te una fácil portabilidad. En este capítulo se explica como crear el modelo de objetos de datos, y la forma en la que se acceden y modifican los datos mediante Propel. Además, se muestra la integra- ción de Propel en Symfony. 8.1. ¿Por qué utilizar un ORM y una capa de abstracción? Las bases de datos son relacionales. PHP 5 y Symfony están orientados a objetos. Para acceder de forma efectiva a la base de datos desde un contexto orientado a objetos, es necesaria una interfaz que traduzca la lógica de los objetos a la lógica relacional. Como se explicó en el Capítulo 1, esta interfaz se llama ORM (object-relational mapping) o “- mapeo de objetos a bases de datos”, y está formada por objetos que permiten acceder a los datos y que contienen en sí mismos el código necesario para hacerlo. La principal ventaja que aporta el ORM es la reutilización, permitiendo llamar a los méto- dos de un objeto de datos desde varias partes de la aplicación e incluso desde diferentes aplicaciones. La capa ORM también encapsula la lógica de los datos; como por ejemplo, el cálculo de la puntuación de un usuario de un foro en función de las aportaciones que ha realizado al foro y en función del éxito de esas aportaciones. Cuando una página quie- re mostrar esa puntuación de un usuario, simplemente invoca un método del modelo de datos, sin preocuparse de cómo se realiza el cálculo. Si el método de cálculo sufre alguna variación, solo es necesario modificar el método que calcula la puntuación en el modelo, sin necesidad de modificar el resto de la aplicación. La utilización de objetos en vez de registros y de clases en vez de tablas, tiene otra ven- taja: permite añadir métodos accesores en los objetos que no tienen relación directa con una tabla. Si se dispone por ejemplo de una tabla llamada cliente con 2 campos llama- dos nombre y apellidos, puede que se necesite un dato llamado NombreCompleto que in- cluya y combine el nombre y los apellidos. En el mundo orientado a objetos, es tan fácil como añadir un método accesor a la clase Cliente, como se muestra en el listado 8-1. Desde el punto de vista de la aplicación, no existen diferencias entre los atributos Nombre, Apellidos, NombreCompleto de la clase Cliente. Solo la propia clase es capaz de determi- nar si un atributo determinado se corresponde con una columna de la base de datos. Listado 8-1 - Los métodos accesores en la clase del modelo permiten ocultar la estructura real de la tabla de la base de datos www.librosweb.es 150
  • 151. Symfony, la guía definitiva Capítulo 8. El modelo public function getNombreCompleto() { return $this->getNombre().' '.$this->getApellidos(); } Todo el código repetitivo de acceso a los datos y toda la lógica de negocio de los propios datos se puede almacenar en esos objetos. Imagina que se ha definido la clase Carri- toCompra en la que se almacenan Productos (que son objetos). Para obtener el precio to- tal del carrito de la compra antes de realizar el pago, se puede crear un método que en- capsula el proceso de cálculo, tal y como se muestra en el listado 8-2. Listado 8-2 - Los métodos accesores ocultan la lógica de los datos public function getTotal() { $total = 0; foreach ($this->getProductos() as $producto) { $total += $producto->getPrecio() * $producto->getCantidad(); } return $total; } Existe otra consideración importante que hay que tener en cuenta cuando se crean ele- mentos de acceso a los datos: las empresas que crean las bases de datos utilizan varian- tes diferentes del lenguaje SQL. Si se cambia a otro sistema gestor de bases de datos, es necesario reescribir parte de las consultas SQL que se definieron para el sistema anterior. Si se crean las consultas mediante una sintaxis independiente de la base de datos y un componente externo se encarga de traducirlas al lenguaje SQL concreto de la base de datos, se puede cambiar fácilmente de una base de datos a otra. Este es precisamente el objetivo de las capas de abstracción de bases de datos. Esta capa obliga a utilizar una sintaxis específica para las consultas y a cambio realiza el trabajo sucio de optimizar y adaptar el lenguaje SQL a la base de datos concreta que se está utilizando. La principal ventaja de la capa de abstracción es la portabilidad, porque hace posible el cambiar la aplicación a otra base de datos, incluso en mitad del desarrollo de un proyec- to. Si se debe desarrollar rápidamente un prototipo de una aplicación y el cliente no ha decidido todavía la base de datos que mejor se ajusta a sus necesidades, se puede cons- truir la aplicación utilizando SQLite y cuando el cliente haya tomado la decisión, cambiar fácilmente a MySQL, PostgreSQL o Oracle. Solamente es necesario cambiar una línea en un archivo de configuración y todo funciona correctamente. Symfony utiliza Propel como ORM y Propel utiliza Creole como capa de abstracción de ba- ses de datos. Estos 2 componentes externos han sido desarrollados por el equipo de Pro- pel, y están completamente integrados en Symfony, por lo que se pueden considerar una parte más del framework. Su sintaxis y sus convenciones, que se describen en este capí- tulo, se han adaptado de forma que difieran lo menos posible de las de Symfony. NOTA En una aplicación de Symfony, todas las aplicaciones comparten el mismo modelo. Esa es precisa- mente la razón de ser de los proyectos: una agrupación de aplicaciones que dependen de un www.librosweb.es 151
  • 152. Symfony, la guía definitiva Capítulo 8. El modelo modelo común. Este es el motivo por el que el modelo es independiente de las aplicaciones y los archivos del modelo se guardan en el directorio lib/model/ de la raíz del proyecto. 8.2. Esquema de base de datos de Symfony Para crear el modelo de objetos de datos que utiliza Symfony, se debe traducir el modelo relacional de la base de datos a un modelo de objetos de datos. Para realizar ese mapeo o traducción, el ORM necesita una descripción del modelo relacional, que se llama “esq- uema” (schema). En el esquema se definen las tablas, sus relaciones y las características de sus columnas. La sintaxis que utiliza Symfony para definir los esquemas hace uso del formato YAML. Los archivos schema.yml deben guardarse en el directorio miproyecto/config/. NOTA Symfony también puede trabajar con el formato nativo de los esquemas en Propel, que está basado en XML. Más adelante en este capítulo se explican los detalles en la sección “Más allá del sche- ma.yml: schema.xml”. 8.2.1. Ejemplo de esquema ¿Cómo se traduce la estructura de una base de datos a un esquema? La mejor forma de entenderlo es mediante un ejemplo. En el ejemplo se supone que se tiene una base de datos de un blog con 2 tablas: blog_article y blog_comment, con la estructura que se muestra en la figura 8-1. Figura 8.1. Estructura de tablas de la base de datos del blog En este caso, el archivo schema.yml debería ser el del listado 8-3. Listado 8-3 - Ejemplo de schema.yml propel: blog_article: _attributes: { phpName: Article } id: title: varchar(255) content: longvarchar created_at: blog_comment: _attributes: { phpName: Comment } id: article_id: author: varchar(255) content: longvarchar created_at: www.librosweb.es 152
  • 153. Symfony, la guía definitiva Capítulo 8. El modelo Observa como el nombre de la propia base de datos (blog) no aparece en el archivo schema.yml. En su lugar, la base de datos se describe bajo el nombre de una conexión (propel en el ejemplo anterior). El motivo es que las opciones de conexión con la base de datos pueden depender del entorno en el que se está ejecutando la aplicación. Si se ac- cede a la aplicación en el entorno de desarrollo, es posible que se acceda a la base de datos de desarrollo (por ejemplo blog_dev) pero con el mismo esquema que en la base de datos de producción. Las opciones de conexión con la base de datos se especifican en el archivo databases.yml, que se describe más adelante en este capítulo en la sección “Conexiones con la base de datos”. El esquema no contiene ningún tipo de opción para la conexión a la base de datos, solo el nombre de la conexión, para mantener la abstracción de la base de datos. 8.2.2. Sintaxis básica de los esquemas En el archivo schema.yml, la primera clave representa el nombre de la conexión. Puede contener varias tablas, cada una con varias columnas. Siguiendo la sintaxis de YAML, las claves terminan con 2 puntos (:) y la estructura se define mediante la indentación con espacios, no con tabuladores. Cada tabla puede definir varios atributos, incluyendo el atributo phpName (que es el nom- bre de la clase PHP que será generada para esa tabla). Si no se menciona el atributo ph- pName para una tabla, Symfony crea una clase con el mismo nombre que la tabla al que se aplica las normas del camelCase. SUGERENCIA La convención camelCase elimina los guiones bajos de las palabras y pasa a mayúsculas la prime- ra letra de cada palabra. Las versiones camelCase por defecto de blog_article y blog_comment son BlogArticle y BlogComment. El nombre de esta convención para generar nombres viene del aspecto de las mayúsculas en una palabra larga, parecido a las jorobas de un camello. Las tablas contienen columnas y el valor de las columnas se puede definir de 3 formas diferentes: ▪ Si no se indica nada, Symfony intenta adivinar los atributos más adecuados para la columna en función de su nombre y de una serie de convenciones que se expli- can en la sección “Columnas vacías” un poco más adelante en este Capítulo. Por ejemplo, en el listado 8-3 no es necesario definir la columna id. Symfony por de- fecto la trata como de tipo entero (integer), cuyo valor se auto-incrementa y además, clave principal de la tabla. En la tabla blog_comment, la columna arti- cle_id se trata como una clave externa a la tabla blog_article (las columnas que acaban en _id se consideran claves externas, y su tabla relacionada se de- termina automáticamente en función de la primera parte del nombre de la colum- na). Las columnas que se llaman created_at automáticamente se consideran de tipo timestamp. Para este tipo de columnas, no es necesario definir su tipo. Esta es una de las razones por las que es tan fácil crear archivos schema.yml. www.librosweb.es 153
  • 154. Symfony, la guía definitiva Capítulo 8. El modelo ▪ Si solo se define un atributo, se considera que es el tipo de columna. Symfony entiende los tipos de columna habituales: boolean, integer, float, date, var- char(tamaño), longvarchar (que se convierte, por ejemplo, en tipo text en MySQL), etc. Para contenidos de texto de más de 256 caracteres, se utiliza el ti- po longvarchar, que no tiene tamaño definido (pero que no puede ser mayor que 65KB en MySQL). Los tipos date y timestamp tienen las limitaciones habituales de las fechas de Unix y no pueden almacenar valores anteriores al 1 de Enero de 1970. Como puede ser necesario almacenar fechas anteriores (por ejemplo para las fechas de nacimiento), existe un formato de fechas “anteriores a Unix” que son bu_date and bu_timestamp. ▪ Si se necesitan definir otros atributos a la columna (por ejemplo su valor por de- fecto, si es obligatorio o no, etc.), se indican los atributos como pares clave: va- lor. Esta sintaxis avanzada del esquema se describe más adelante en este capítulo. Las columnas también pueden definir el atributo phpName, que es la versión modificada de su nombre según las convenciones habituales (Id, Title, Content, etc) y que normalmen- te no es necesario redefinir. Las tablas también pueden definir claves externas e índices de forma explícita, además de incluir definiciones específicas de su estructura para ciertas bases de datos. En la sec- ción “Sintaxis avanzada del esquema” se detallan estos conceptos. 8.3. Las clases del modelo El esquema se utiliza para construir las clases del modelo que necesita la capa del ORM. Para reducir el tiempo de ejecución de la aplicación, estas clases se generan mediante una tarea de línea de comandos llamada propel-build-model. > symfony propel-build-model Al ejecutar ese comando, se analiza el esquema y se generan las clases base del modelo, que se almacenan en el directorio lib/model/om/ del proyecto: ▪ BaseArticle.php ▪ BaseArticlePeer.php ▪ BaseComment.php ▪ BaseCommentPeer.php Además, se crean las verdaderas clases del modelo de datos en el directorio lib/model/: ▪ Article.php ▪ ArticlePeer.php ▪ Comment.php ▪ CommentPeer.php www.librosweb.es 154
  • 155. Symfony, la guía definitiva Capítulo 8. El modelo Solo se han definido 2 tablas y se han generado 8 archivos. Aunque este hecho no es na- da extraño, merece una explicación. 8.3.1. Clases base y clases personalizadas ¿Por qué es útil mantener 2 versiones del modelo de objetos de datos en 2 directorios diferentes? Puede ser necesario añadir métodos y propiedades personalizadas en los objetos del mo- delo (piensa por ejemplo en el método getNombreCompleto() del listado 8-1). También es posible que a medida que el proyecto se esté desarrollando, se añadan tablas o colum- nas. Además, cada vez que se modifica el archivo schema.yml se deben regenerar las cla- ses del modelo de objetos mediante el comando propel-build-model. Si se añaden los métodos personalizados en las clases que se generan, se borrarían cada vez que se vuel- ven a generar esas clases. Las clases con nombre Base del directorio lib/model/om/ son las que se generan directa- mente a partir del esquema. Nunca se deberían modificar esas clases, porque cada vez que se genera el modelo, se borran todas las clases. Por otra parte, las clases de objetos propias que están en el directorio lib/model heredan de las clases con nombre Base. Estas clases no se modifican cuando se ejecuta la tarea propel-build-model, por lo que son las clases en las que se añaden los métodos propios. El listado 8-4 muestra un ejemplo de una clase propia del modelo creada la primera vez que se ejecuta la tarea propel-build-model. Listado 8-4 - Archivo de ejemplo de una clase del modelo, en lib/model/ Article.php <?php class Article extends BaseArticle { } Esta clase hereda todos los métodos de la clase BaseArticle, pero no le afectan las modi- ficaciones en el esquema. Este mecanismo de clases personalizadas que heredan de las clases base permite empe- zar a programar desde el primer momento, sin ni siquiera conocer el modelo relacional definitivo de la base de datos. La estructura de archivos creada permite personalizar y evolucionar el modelo. 8.3.2. Clases objeto y clases "peer" Article y Comment son clases objeto que representan un registro de la base de datos. Permiten acceder a las columnas de un registro y a los registros relacionados. Por tanto, es posible obtener el título de un artículo invocando un método del objeto Article, como se muestra en el listado 8-5. www.librosweb.es 155
  • 156. Symfony, la guía definitiva Capítulo 8. El modelo Listado 8-5 - Las clases objeto disponen de getters para los registros de las columnas $articulo = new Article(); ... $titulo = $articulo->getTitle(); ArticlePeer y CommentPeer son clases de tipo “peer”; es decir, clases que tienen métodos estáticos para trabajar con las tablas de la base de datos. Proporcionan los medios nece- sarios para obtener los registros de las tablas. Sus métodos devuelven normalmente un objeto o una colección de objetos de la clase objeto relacionada, como se muestra en el listado 8-6. Listado 8-6 - Las clases “peer” contienen métodos estáticos para obtener regis- tros de la base de datos $articulos = ArticlePeer::retrieveByPks(array(123, 124, 125)); // $articulos es un array de objetos de la clase Article NOTA Desde el punto de vista del modelo de datos, no puede haber objetos de tipo “peer”. Por este moti- vo los métodos de las clases “peer” se acceden mediante :: (para invocarlos de forma estática), en vez del tradicional -> (para invocar los métodos de forma tradicional). La combinación de las clases objeto y las clases “peer” y las versiones básicas y persona- lizadas de cada una hace que se generen 4 clases por cada tabla del esquema. En reali- dad, existe una quinta clase que se crea en el directorio lib/model/map/ y que contiene metainformación relativa a la tabla que es necesaria para la ejecución de la aplicación. Pero como es una clase que seguramente no se modifica nunca, es mejor olvidarse de ella. 8.4. Acceso a los datos En Symfony, el acceso a los datos se realiza mediante objetos. Si se está acostumbrado al modelo relacional y a utilizar consultas SQL para acceder y modificar los datos, los mé- todos del modelo de objetos pueden parecer complicados. No obstante, una vez que se prueba el poder de la orientación a objetos para acceder a los datos, probablemente te gustará mucho más. En primer lugar, hay que asegurarse de que se utiliza el mismo vocabulario. Aunque el modelo relacional y el modelo de objetos utilizan conceptos similares, cada uno tiene su propia nomenclatura: Relacional Orientado a objetos Tabla Clase Fila, registro Objeto Campo, columna Propiedad www.librosweb.es 156
  • 157. Symfony, la guía definitiva Capítulo 8. El modelo 8.4.1. Obtener el valor de una columna Cuando Symfony construye el modelo, crea una clase de objeto base para cada una de las tablas definidas en schema.yml. Cada una de estas clases contiene una serie de cons- tructores y accesores por defecto en función de la definición de cada columna: los méto- dos new, getXXX() y setXXX() permiten crear y obtener las propiedades de los objetos, como se muestra en el listado 8-7. Listado 8-7 - Métodos generados en una clase objeto $articulo = new Article(); $articulo->setTitle('Mi primer artículo'); $articulo->setContent('Este es mi primer artículo. n Espero que te guste.'); $titulo = $articulo->getTitle(); $contenido = $articulo->getContent(); NOTA La clase objeto generada se llama Article, que es el valor de la propiedad phpName para la tabla blog_article. Si no se hubiera definido la propiedad phpName, la clase se habría llamado BlogAr- ticle. Los métodos accesores (get y set) utilizan una variante de camelCase aplicada al nombre de las columnas, por lo que el método getTitle() obtiene el valor de la columna title. Para establecer el valor de varios campos a la vez, se puede utilizar el método fromArr- ay(), que también se genera para cada clase objeto, como se muestra en el listado 8-8. Listado 8-8 - El método fromArray() es un setter múltiple $articulo->fromArray(array( 'title' => 'Mi primer artículo', 'content' => 'Este es mi primer artículo. n Espero que te guste.' )); 8.4.2. Obtener los registros relacionados La columna article_id de la tabla blog_comment define implícitamente una clave externa a la tabla blog_article. Cada comentario está relacionado con un artículo y un artículo puede tener muchos comentarios. Las clases generadas contienen 5 métodos que tradu- cen esta relación a la forma orientada a objetos, de la siguiente manera: ▪ $comentario->getArticle(): para obtener el objeto Article relacionado ▪ $comentario->getArticleId(): para obtener el ID del objeto Article relacionado ▪ $comentario->setArticle($articulo): para definir el objeto Article relacionado ▪ $comentario->setArticleId($id): para definir el objeto Article relacionado a partir de un ID ▪ $articulo->getComments(): para obtener los objetos Comment relacionados Los métodos getArticleId() y setArticleId() demuestran que se puede utilizar la co- lumna article_id como una columna normal y que se pueden indicar las relaciones www.librosweb.es 157
  • 158. Symfony, la guía definitiva Capítulo 8. El modelo manualmente, pero esto no es muy interesante. La ventaja de la forma orientada a obje- tos es mucho más evidente en los otros 3 métodos. El listado 8-9 muestra como utilizar los setters generados. Listado 8-9 - Las claves externas se traducen en un setter especial $comentario = new Comment(); $comentario->setAuthor('Steve'); $comentario->setContent('¡Es el mejor artículo que he leído nunca!'); // Añadir este comentario al anterior objeto $articulo $comentario->setArticle($articulo); // Sintaxis alternativa // Solo es correcta cuando el objeto artículo ya // ha sido guardado anteriormente en la base de datos $comentario->setArticleId($articulo->getId()); El listado 8-10 muestra como utilizar los getters generados automáticamente. También muestra como encadenar varias llamadas a métodos en los objetos del modelo. Listado 8-10 - Las claves externas se traducen en getters especiales // Relación de "muchos a uno" echo $comentario->getArticle()->getTitle(); => Mi primer artículo echo $comentario->getArticle()->getContent(); => Este es mi primer artículo. Espero que te guste. // Relación "uno a muchos" $comentarios = $articulo->getComments(); El método getArticle() devuelve un objeto de tipo Article, que permite utilizar el méto- do accesor getTitle(). Se trata de una alternativa mucho mejor que realizar la unión de las tablas manualmente, ya que esto último necesitaría varias líneas de código (empe- zando con la llamada al método $comment->getArticleId()). La variable $comentarios del listado 8-10 contiene un array de objetos de tipo Comment. Se puede mostrar el primer comentario mediante $comentarios[0] o se puede recorrer la colección entera mediante foreach ($comentarios as $comentario). NOTA Los objetos del modelo se definen siguiendo la convención de utilizar nombres en singular, y ahora se puede entender la razón. La clave externa definida en la tabla blog_comment crea el método getComments(), cuyo nombre se crea añadiendo una letra s al nombre del objeto Comment. Si el nombre del modelo fuera plural, la generación automática llamaría getCommentss() a ese método, lo cual no tiene mucho sentido. 8.4.3. Guardar y borrar datos Al utilizar el constructor new se crea un nuevo objeto, pero no un registro en la tabla blog_article. Si se modifica el objeto, tampoco se reflejan esos cambios en la base de www.librosweb.es 158
  • 159. Symfony, la guía definitiva Capítulo 8. El modelo datos. Para guardar los datos en la base de datos, se debe invocar el método save() del objeto. $articulo->save(); El ORM de Symfony es lo bastante inteligente como para detectar las relaciones entre ob- jetos, por lo que al guardar el objeto $articulo también se guarda el objeto $comentario relacionado. También detecta si ya existía el objeto en la base de datos, por lo que el método save() a veces se traduce a una sentencia INSERT de SQL y otras veces se tradu- ce a una sentencia UPDATE. La clave primaria se establece de forma automática al llamar al método save(), por lo que después de guardado, se puede obtener la nueva clave pri- maria del objeto mediante $articulo->getId(). SUGERENCIA Para determinar si un objeto es completamente nuevo, se puede utilizar el método isNew(). Para detectar si un objeto ha sido modificado y por tanto se debe guardar en la base de datos, se puede utilizar el método isModified(). Si lees los comentarios que insertan los usuarios en tus artículos, puede que te desani- mes un poco para seguir publicando cosas en Internet. Si además no captas la ironía de los comentarios, puedes borrarlos fácilmente con el método delete(), como se muestra en el listado 8-11. Listado 8-11 - Borrar registros de la base de datos mediante el método delete() del objeto relacionado foreach ($articulo->getComments() as $comentario) { $comentario->delete(); } SUGERENCIA Después de ejecutar el método delete(), el objeto sigue disponible hasta que finaliza la ejecución de la petición actual. Para determinar si un objeto ha sido borrado de la base de datos, se puede utilizar el método isDeleted(). 8.4.4. Obtener registros mediante la clave primaria Si se conoce la clave primaria de un registro concreto, se puede utilizar el método retr- ieveByPk() de la clase peer para obtener el objeto relacionado. $articulo = ArticlePeer::retrieveByPk(7); El archivo schema.yml define el campo id como clave primaria de la tabla blog_article, por lo que la sentencia anterior obtiene el artículo cuyo id sea igual a 7. Como se ha uti- lizado una clave primaria, solo se obtiene un registro; la variable $articulo contiene un objeto de tipo Article. En algunos casos, la clave primaria está formada por más de una columna. Es esos ca- sos, el método retrieveByPK() permite indicar varios parámetros, uno para cada columna de la clave primaria. www.librosweb.es 159
  • 160. Symfony, la guía definitiva Capítulo 8. El modelo También se pueden obtener varios objetos a la vez mediante sus claves primarias, invo- cando el método retrieveByPKs(), que espera como argumento un array de claves primarias. 8.4.5. Obtener registros mediante Criteria Cuando se quiere obtener más de un registro, se debe utilizar el método doSelect() de la clase peer correspondiente a los objetos que se quieren obtener. Por ejemplo, para obte- ner objetos de la clase Article, se llama al método ArticlePeer::doSelect(). El primer parámetro del método doSelect() es un objeto de la clase Criteria, que es una clase para definir consultas simples sin utilizar SQL, para conseguir la abstracción de ba- se de datos. Un objeto Criteria vacío devuelve todos los objetos de la clase. Por ejemplo, el código del listado 8-12 obtiene todos los artículos de la base de datos. Listado 8-12 - Obtener registros mediante Criteria con el método doSelect() (Criteria vacío) $c = new Criteria(); $articulos = ArticlePeer::doSelect($c); // Genera la siguiente consulta SQL SELECT blog_article.ID, blog_article.TITLE, blog_article.CONTENT, blog_article.CREATED_AT FROM blog_article; Hydrating Invocar el método ::doSelect() es mucho más potente que una simple consulta SQL. En primer lugar, se optimiza el código SQL para la base de datos que se utiliza. En segundo lugar, todos los valores pasados a Criteria se escapan antes de insertarlos en el código SQL, para evitar los pro- blemas de SQL injection. En tercer lugar, el método devuelve un array de objetos y no un result set. El ORM crea y rellena de forma automática los objetos en función del result set de la base de datos. Este proceso se conoce con el nombre de hydrating. Para las selecciones más complejas de objetos, se necesitan equivalentes a las sentenc- ias WHERE, ORDER BY, GROUP BY y demás de SQL. El objeto Criteria dispone de métodos y parámetros para indicar todas estas condiciones. Por ejemplo, para obtener todos los co- mentarios escritos por el usuario Steve y ordenados por fecha, se puede construir un ob- jeto Criteria como el del listado 8-13. Listado 8-13 - Obtener registros mediante Criteria con el método doSelect() (Criteria con condiciones) $c = new Criteria(); $c->add(CommentPeer::AUTHOR, 'Steve'); $c->addAscendingOrderByColumn(CommentPeer::CREATED_AT); $comentarios = CommentPeer::doSelect($c); // Genera la siguiente consulta SQL SELECT blog_comment.ARTICLE_ID, blog_comment.AUTHOR, blog_comment.CONTENT, www.librosweb.es 160
  • 161. Symfony, la guía definitiva Capítulo 8. El modelo blog_comment.CREATED_AT FROM blog_comment WHERE blog_comment.author = 'Steve' ORDER BY blog_comment.CREATED_AT ASC; Las constantes de clase que se pasan como parámetros a los métodos add() hacen refe- rencia a los nombres de las propiedades. Su nombre se genera a partir del nombre de las columnas en mayúsculas. Por ejemplo, para indicar la columna content de la tabla blog_article, se utiliza la constante de clase llamada ArticlePeer::CONTENT. NOTA ¿Por qué se utiliza CommentPeer::AUTHOR en vez de blog_comment.AUTHOR, que es en definitiva el valor que se va a utilizar en la consulta SQL? Supon que se debe modificar el nombre del campo de la tabla y en vez de author ahora se llama contributor. Si se hubiera utilizado el valor blog_- comment.AUTHOR, es necesario modificar ese valor en cada llamada al modelo. Sin embargo, si se utiliza el valorCommentPeer::AUTHOR, solo es necesario cambiar el nombre de la columna en el ar- chivo schema.yml, manteniendo el atributo phpName a un valor igual a AUTHOR y reconstruir el modelo. La tabla 8-1 compara la sintaxis de SQL y del objeto Criteria. Tabla 8-1 - Sintaxis de SQL y del objeto Criteria SQL Criteria WHERE columna = valor ->add(columna, valor); ->add(columna, valor, WHERE columna <> valor Criteria::NOT_EQUAL); Otros operadores de comparación Criteria::GREATER_THAN, > , < Criteria::LESS_THAN Criteria::GREATER_EQUAL, >=, <= Criteria::LESS_EQUAL IS NULL, IS NOT NULL Criteria::ISNULL, Criteria::ISNOTNULL LIKE, ILIKE Criteria::LIKE, Criteria::ILIKE IN, NOT IN Criteria::IN, Criteria::NOT_IN Otras palabras clave de SQL ORDER BY columna ASC ->addAscendingOrderByColumn(columna); ORDER BY columna DESC ->addDescendingOrderByColumn(columna); LIMIT limite ->setLimit(limite) OFFSET desplazamiento ->setOffset(desplazamiento) www.librosweb.es 161
  • 162. Symfony, la guía definitiva Capítulo 8. El modelo FROM tabla1, tabla2 WHERE tabla1.col1 = ->addJoin(col1, col2) tabla2.col2 FROM tabla1 LEFT JOIN tabla2 ON ->addJoin(col1, col2, tabla1.col1 = tabla2.col2 Criteria::LEFT_JOIN) FROM tabla1 RIGHT JOIN tabla2 ON ->addJoin(col1, col2, tabla1.col1 = tabla2.col2 Criteria::RIGHT_JOIN) SUGERENCIA La mejor forma de descubrir y entender los métodos disponibles en las clases generadas automáti- camente es echar un vistazo a los archivos Base del directorio lib/model/om/. Los nombres de los métodos son bastante explícitos, aunque si se necesitan más comentarios sobre esos métodos, se puede establecer el parámetro propel.builder.addComments a true en el archivo de configura- ción config/propel.ini y después volver a reconstruir el modelo. El listado 8-14 muestra otro ejemplo del uso de Criteria con condiciones múltiples. En el ejemplo se obtienen todos los comentarios del usuario Steve en los artículos que contie- nen la palabra enjoy y además, ordenados por fecha. Listado 8-14 - Otro ejemplo para obtener registros mediante Criteria con el mé- todo doSelect() (Criteria con condiciones) $c = new Criteria(); $c->add(CommentPeer::AUTHOR, 'Steve'); $c->addJoin(CommentPeer::ARTICLE_ID, ArticlePeer::ID); $c->add(ArticlePeer::CONTENT, '%enjoy%', Criteria::LIKE); $c->addAscendingOrderByColumn(CommentPeer::CREATED_AT); $comentarios = CommentPeer::doSelect($c); // Genera la siguiente consulta SQL SELECT blog_comment.ID, blog_comment.ARTICLE_ID, blog_comment.AUTHOR, blog_comment.CONTENT, blog_comment.CREATED_AT FROM blog_comment, blog_article WHERE blog_comment.AUTHOR = 'Steve' AND blog_article.CONTENT LIKE '%enjoy%' AND blog_comment.ARTICLE_ID = blog_article.ID ORDER BY blog_comment.CREATED_AT ASC De la misma forma que el lenguaje SQL es sencillo pero permite construir consultas muy complejas, el objeto Criteria permite manejar condiciones de cualquier nivel de comple- jodad. Sin embargo, como muchos programadores piensan primero en el código SQL y luego lo traducen a las condiciones de la lógica orientada a objetos, es difícil comprender bien el objeto Criteria cuando se utiliza las primeras veces. La mejor forma de aprender es mediante ejemplos y aplicaciones de prueba. El sitio web del proyecto Symfony esá lleno de ejemplos de cómo construir objetos de tipo Criteria para todo tipo de situaciones. Además del método doSelect(), todas las clases peer tienen un método llamado doC- ount(), que simplemente cuenta el número de registros que satisfacen las condiciones pasadas como parámetro y devuelve ese número como un entero. Como no se devuelve www.librosweb.es 162
  • 163. Symfony, la guía definitiva Capítulo 8. El modelo ningún objeto, no se produce el proceso de hydrating y por tanto el método doCount() es mucho más rápido que doSelect(). Las clases peer también incluyen métodos doDelete(), doInsert() y doUpdate() (todos ellos requieren como parámetro un objeto de tipo Criteria). Estos métodos permiten re- alizar consultas de tipo DELETE, INSERT y UPDATE a la base de datos. Se pueden consultar las clases peer generadas automáticamente para descubrir más detalles de estos méto- dos de Propel. Por último, si solo se quiere obtener el primer objeto, se puede reemplazar el método doSelect() por doSelectOne(). Es muy útil cuando se sabe que las condiciones de Crite- ria solo van a devolver un resultado, y su ventaja es que el método devuelve directa- mente un objeto en vez de un array de objetos. SUGERENCIA Cuando una consulta doSelect() devuelve un número muy grande de resultados, normalmente sólo se quieren mostrar unos pocos en la respuesta. Symfony incluye una clase especial para pagi- nar resultados llamada sfPropelPager, que realiza la paginación de forma automática y cuya do- cumentación y ejemplos de uso se puede encontrar en http://www.symfony-project.org/cookbook/ trunk/pager. 8.4.6. Uso de consultas con código SQL A veces, no es necesario obtener los objetos, sino que solo son necesarios algunos datos calculados por la base de datos. Por ejemplo, para obtener la fecha de creación de todos los artículos, no tiene sentido obtener todos los artículos y después recorrer el array de los resultados. En este caso es preferible obtener directamente el resultado, ya que se evita el proceso de hydrating. Por otra parte, no deberían utilizarse instrucciones PHP de acceso a la base de datos, porque se perderían las ventajas de la abstracción de bases de datos. Lo que significa que se debe evitar el ORM (Propel) pero no la abstracción de bases de datos (Creole). Para realizar consultas a la base de datos con Creole, es necesario realizar los siguientes pasos: 1. Obtener la conexión con la base de datos. 2. Construir la consulta. 3. Crear una sentencia con esa consulta. 4. Iterar el result set que devuelve la ejecución de la sentencia. Aunque lo anterior puede parecer un galimatías, el código del listado 8-15 es mucho más explícito. Listado 8-15 - Consultas SQL personalizadas con Creole $conexion = Propel::getConnection(); $consulta = 'SELECT MAX(%s) AS max FROM %s'; $consulta = sprintf($consulta, ArticlePeer::CREATED_AT, ArticlePeer::TABLE_NAME); $sentencia = $conexion->prepareStatement($consulta); www.librosweb.es 163
  • 164. Symfony, la guía definitiva Capítulo 8. El modelo $resultset = $sentencia->executeQuery(); $resultset->next(); $max = $resultset->getInt('max'); Al igual que sucede con las selecciones realizadas con Propel, las consultas con Creole son un poco complicadas de usar al principio. Los ejemplos de las aplicaciones existentes y de los tutoriales pueden ser útiles para descubrir la forma de hacerlas. SUGERENCIA Si se salta esa forma de acceder y se intenta acceder de forma directa a la base de datos, se corre el riesgo de perder la seguridad y la abstracción proporcionadas por Creole. Aunque es más largo hacerlo con Creole, es la forma de utilizar las buenas prácticas que aseguran un buen rendimiento, portabilidad y seguridad a la aplicación. Esta recomendación es especialmente útil para las consul- tas que contienen parámetros cuyo origen no es confiable (como por ejemplo un usuario de Inter- net). Creole se encarga de escapar los datos para mantener la seguridad de la base de datos. Si se accede directamente a la base de datos, se corre el riesgo de sufrir ataques de tipo SQL-injection. 8.4.7. Uso de columnas especiales de fechas Normalmente, cuando una tabla tiene una columna llamada created_at, se utiliza para almacenar un timestamp de la fecha de creación del registro. La misma idea se aplica a las columnas updated_at, cuyo valor se debe actualizar cada vez que se actualiza el prop- io registro. La buena noticia es que Symfony reconoce estos nombres de columna y se ocupa de act- ualizar su valor de forma automática. No es necesario establecer manualmente el valor de las columnas created_at y updated_at; se actualizan automáticamente, tal y como muestra el listado 8-16. Lo mismo se aplica a las columnas llamadas created_on y updated_on. Listado 8-16 - Las columnas created_at y updated_at se gestionan automáticamente $comentario = new Comment(); $comentario->setAuthor('Steve'); $comentario->save(); // Muestra la fecha de creación echo $comentario->getCreatedAt(); => [fecha de la operación INSERT de la base de datos] Además, los getters de las columnas de fechas permiten indicar el formato de la fecha como argumento: echo $comentario->getCreatedAt('Y-m-d'); Refactorizando la capa de datos Cuando se desarrolla un proyecto con Symfony, normalmente se empieza escribiendo el código de la lógica de dominio en las acciones. Sin embargo, las consultas a la base de datos y la manipula- ción del modelo no se debería realizar en la capa del controlador. De forma que toda la lógica relac- ionada con los datos se debería colocar en la capa del modelo. Cada vez que se ejecuta el mismo www.librosweb.es 164
  • 165. Symfony, la guía definitiva Capítulo 8. El modelo código en más de un sitio de las acciones, se debería mover ese código al modelo. De esta forma las acciones se mantienen cortas y fáciles de leer. Por ejemplo, imagina el caso de un blog que tiene que obtener los 10 artículos más populares rela- cionados con una etiqueta determinada (que se pasa como parámetro). Este código no debería es- tar en la acción, sino en el modelo. De hecho, si se tiene que mostrar en la plantilla la lista de artí- culos, la acción debería ser similar a la siguiente: public function executeMuestraArticulosPopularesParaEtiqueta() { $etiqueta = TagPeer::retrieveByName($this->getRequestParameter('tag')); $this->foward404Unless($etiqueta); $this->articulos = $etiqueta->getArticulosPopulares(10); } La acción crea un objeto de clase Tag a partir del parámetro de la petición. Después, todo el código necesario para realizar la consulta a la base de datos se almacena en el método getArticulosPo- pulares() de esta clase. La acción es más fácil de leer y el código del modelo se puede reutilizar fácilmente en otra acción. Mover el código a un lugar más apropiado es una de las técnicas de refactorización. Si se realiza habitualmente, el código resultante es más fácil de mantener y de entender por otros programado- res. Una buena regla general sobre cuando refactorizar la capa de los datos es que el código de una acción raramente debería tener más de 10 líneas de código PHP. 8.5. Conexiones con la base de datos Aunque el modelo de datos es independiente de la base de datos utilizada, es necesario utilizar una base de datos concreta. La información mínima que necesita Symfony para realizar peticiones a la base de datos es su nombre, los datos de acceso y el tipo de base de datos. Esta información se indica en el archivo databases.yml que se encuentra en el directorio config/. El listado 8-17 muestra un ejemplo de ese archivo. Listado 8-17 - Ejemplo de opciones de conexión con la base de datos, en mipro- yecto/config/databases.yml prod: propel: param: hostspec: miservidordatos username: minombreusuario password: xxxxxxxxxx all: propel: class: sfPropelDatabase param: phptype: mysql # fabricante de la base de datos hostspec: localhost database: blog username: login password: passwd port: 80 www.librosweb.es 165
  • 166. Symfony, la guía definitiva Capítulo 8. El modelo encoding: utf8 # Codificación utilizada para crear la tabla persistent: true # Utilizar conexiones persistentes Las opciones de la conexión se establecen para cada entorno. Se pueden definir diferen- tes opciones para los entornos prod, dev y test, o para cualquier otro entorno definido en la aplicación. También es posible redefinir esta configuración en cada aplicación, estable- ciendo diferentes valores para las opciones en un archivo específico de la aplicación, co- mo por ejemplo apps/miaplicacion/config/databases.yml. De esta forma es posible por ejemplo disponer de políticas de seguridad diferentes para las aplicaciones públicas y las aplicaciones de administración del proyecto, y definir distintos usuarios de bases de datos con privilegios diferentes. Cada entorno puede definir varias conexiones. Cada conexión hace referencia a un esq- uema con el mismo nombre. En el ejemplo del listado 8-17, la conexión propel se refiere al esquema propel del listado 8-3. Los valores permitidos para el parámetro phptype corresponden a los tipos de bases de datos soportados por Creole: ▪ mysql ▪ sqlserver ▪ pgsql ▪ sqlite ▪ oracle Los parámetros hostspec, database, username y password son las opciones típicas para co- nectar con una base de datos. Estas opciones se pueden escribir de forma abreviada me- diante un nombre de origen de datos o DSN (data source name). El listado 8-18 es equi- valente a la sección all: del listado 8-17. Listado 8-18 - Opciones abreviadas de conexión con la base de datos all: propel: class: sfPropelDatabase param: dsn: mysql://login:passwd@localhost/blog Si se utiliza una base de datos de tipo SQLite, el parámetro hostspec debe indicar la ruta al archivo de base de datos. Si por ejemplo se guarda la base de datos del blog en el ar- chivo data/blog.db, las opciones del archivo databases.yml serán las del listado 8-19. Listado 8-19 - Opciones de conexión con una base de datos SQLite utilizando la ruta al archivo como host all: propel: class: sfPropelDatabase param: phptype: sqlite database: %SF_DATA_DIR%/blog.db www.librosweb.es 166
  • 167. Symfony, la guía definitiva Capítulo 8. El modelo 8.6. Extender el modelo Los métodos del modelo que se generan automáticamente están muy bien, pero no siem- pre son suficientes. Si se incluye lógica de negocio propia, es necesario extender el mo- delo añadiendo nuevos métodos o redefiniendo algunos de los existentes. 8.6.1. Añadir nuevos métodos Los nuevos métodos se pueden añadir en las clases vacías del modelo que se generan en el directorio lib/model/. Se emplea $this para invocar a los métodos del objeto actual y self:: para invocar a los métodos estáticos de la clase actual. No se debe olvidar que las clases personalizadas heredan los métodos de las clases Base del directorio lib/model/ om/. Por ejemplo, en el objeto Article generado en el listado 8-3, se puede añadir un método mágico de PHP llamado __toString() de forma que al mostrar un objeto de la clase Arti- cle se muestre su título, tal y como se indica en el listado 8-20. Listado 8-20 - Personalizar el modelo, en lib/model/Article.php <?php class Article extends BaseArticle { public function __toString() { return $this->getTitle(); // getTitle() se hereda de BaseArticle } } También se pueden extender las clases peer, como por ejemplo para obtener todos los artículos ordenados por fecha de creación, tal y como muestra el listado 8-21. Listado 8-21 - Personalizando el modelo, en lib/model/ArticlePeer.php <?php class ArticlePeer extends BaseArticlePeer { public static function getTodosOrdenadosPorFecha() { $c = new Criteria(); $c->addAscendingOrderByColumn(self::CREATED_AT); return self::doSelect($c); } } Los nuevos métodos están disponibles de la misma forma que los métodos generados au- tomáticamente, tal y como muestra el listado 8-22. Listado 8-22 - El uso de métodos personalizados del modelo es idéntico al de los métodos generados automáticamente www.librosweb.es 167
  • 168. Symfony, la guía definitiva Capítulo 8. El modelo foreach (ArticlePeer::getAllOrderedByDate() as $articulo) { echo $articulo; // Se llama al método mágico __toString() } 8.6.2. Redefinir métodos existentes Si alguno de los métodos generados automáticamente en las clases Base no satisfacen las necesidades de la aplicación, se pueden redefinir en las clases personalizadas. Sola- mente es necesario mantener el mismo número de argumentos para cada método. Por ejemplo, el método $articulo->getComments() devuelve un array de objetos Comment, sin ningún tipo de ordenamiento. Si se necesitan los resultados ordenados por fecha de creación siendo el primero el comentario más reciente, se puede redefinir el método getComments(), como muestra el listado 8-23. Como el método getComments() original (g- uardado en lib/model/om/BaseArticle.php) requiere como argumentos un objeto de tipo Criteria y una conexión, la nueva función debe contener esos mismos parámetros. Listado 8-23 - Redefiniendo los métodos existentes en el modelo, en lib/model/ Article.php public function getComments($criteria = null, $con = null) { if (is_null($criteria)) { $criteria = new Criteria(); } else { // Los objetos se pasan por referencia en PHP5, por lo que se debe clonar // el objeto original para no modificarlo $criteria = clone $criteria; } $criteria->addDescendingOrderByColumn(CommentPeer::CREATED_AT); return parent::getComments($criteria, $con); } El método personalizado acaba llamando a su método padre en la clase Base, lo que se considera una buena práctica. No obstante, es posible saltarse completamente la clase Base y devolver el resultado directamente. 8.6.3. Uso de comportamientos en el modelo Algunas de las modificaciones que se realizan en el modelo son genéricas y por tanto se pueden reutilizar. Por ejemplo, los métodos que hacen que un objeto del modelo sea re- ordenable o un bloqueo de tipo optimistic en la base de datos para evitar conflictos cuan- do se guardan de forma concurrente los objetos se pueden considerar extensiones gené- ricas que se pueden añadir a muchas clases. Symfony encapsula estas extensiones en “comportamientos” (del inglés behaviors). Los comportamientos son clases externas que proporcionan métodos extras a las clases del www.librosweb.es 168
  • 169. Symfony, la guía definitiva Capítulo 8. El modelo modelo. Las clases del modelo están definidas de forma que se puedan enganchar estas clases externas y Symfony extiende las clases del modelo mediante sfMixer (el Capítulo 17 contiene los detalles). Para habilitar los comportamientos en las clases del modelo, se debe modificar una op- ción del archivo config/propel.ini: propel.builder.AddBehaviors = true // El valor por defecto es false Symfony no incluye por defecto ningún comportamiento, pero se pueden instalar med- iante plugins. Una vez que el plugin se ha instalado, se puede asignar un comportamien- to a una clase mediante una sola línea de código. Si por ejemplo se ha instalado el plugin sfPropelParanoidBehaviorPlugin en la aplicación, se puede extender la clase Article con este comportamiento añadiendo la siguiente línea de código al final del archivo Article.- class.php: sfPropelBehavior::add('Article', array( 'paranoid' => array('column' => 'deleted_at') )); Después de volver a generar el modelo, los objetos de tipo Article que se borren perma- necerán en la base de datos, aunque será invisibles a las consultas que hacen uso de los métodos del ORM, a no ser que se deshabilite temporalmente el comportamiento med- iante sfPropelParanoidBehavior::disable(). La lista de plugins de Symfony disponible en el wiki incluye numerosos comportamientos http://trac.symfony-project.com/wiki/SymfonyPlugins#Propelbehaviorplugins. Cada comporta- miento tiene su propia documentación y su propia guía de instalación. 8.7. Sintaxis extendida del esquema Un archivo schema.yml puede ser tan sencillo como el mostrado en el listado 8-3. Sin em- bargo, los modelos relacionales suelen ser complejos. Este es el motivo por el que existe una sintaxis extendida del esquema para que se pueda utilizar en cualquier caso. 8.7.1. Atributos Se pueden definir atributos específicos para las conexiones y las tablas, tal y como se muestra en el listado 8-24. Estas opciones se establecen bajo la clave _attributes. Listado 8-24 - Atributos de las conexiones y las tablas propel: _attributes: { noXsd: false, defaultIdMethod: none, package: lib.model } blog_article: _attributes: { phpName: Article } Si se quiere validar el esquema antes de que se genere el código asociado, se debe de- sactivar en la conexión el atributo noXSD. La conexión también permite que se le indique el atributo defaultIdMethod. Si no se indica, se utilizará el método nativo de generación de IDs –por ejemplo, autoincrement en MySQL o sequences en PostgreSQL. El otro valor permitido es none. www.librosweb.es 169
  • 170. Symfony, la guía definitiva Capítulo 8. El modelo El atributo package es como un namespace; indica la ruta donde se guardan las clases generadas automáticamente. Su valor por defecto es lib/model/, pero se puede modifi- car para organizar el modelo en una estructura de subpaquetes. Si por ejemplo no se qu- ieren mezclar en el mismo directorio las clases del núcleo de la aplicación con las clases de un sistema de estadísticas, se pueden definir dos esquemas diferentes con los paque- tes lib.model.business y lib.model.stats. Ya se ha visto el atributo de tabla phpName, que se utiliza para establecer el nombre de la clase generada automáticamente para manejar cada tabla de la base de datos. Las tablas que guardan contenidos adaptados para diferentes idiomas (es decir, varias versiones del mismo contenido en una tabla relacionada, para conseguir la internacionali- zación) también pueden definir dos atributos adicionales (explicados detalladamente en el Capítulo 13), tal y como se muestra en el listado 8-25. Listado 8-25 - Atributos para las tablas de internacionalización propel: blog_article: _attributes: { isI18N: true, i18nTable: db_group_i18n } Trabajando con varios esquemas Cada aplicación puede tener más de un esquema. Symfony tiene en cuenta todos los archivos que acaben en schema.yml o schema.xml que están en el directorio config/. Se trata de una estrateg- ia muy útil cuando la aplicación tiene muchas tablas o si algunas de las tablas no comparten la mis- ma conexión. Si se consideran los 2 siguientes esquemas: // En config/business-schema.yml propel: blog_article: _attributes: { phpName: Article } id: title: varchar(50) // En config/stats-schema.yml propel: stats_hit: _attributes: { phpName: Hit } id: resource: varchar(100) created_at: Los dos esquemas comparten la misma conexión (propel), y las clases Article y Hit se gene- rarán en el mismo directorio lib/model/. El resultado es equivalente a si se hubiera escrito sola- mente un esquema. También es posible definir esquemas que utilicen diferentes conexiones (por ejemplo propel y propel_bis definidas en databases.yml) y cuyas clases generadas se guarden en subdirectorios diferentes: # En config/business-schema.yml propel: blog_article: www.librosweb.es 170
  • 171. Symfony, la guía definitiva Capítulo 8. El modelo _attributes: { phpName: Article, package: lib.model.business } id: title: varchar(50) # En config/stats-schema.yml propel_bis: stats_hit: _attributes: { phpName: Hit, package.lib.model.stat } id: resource: varchar(100) created_at: Muchas aplicaciones utilizan más de un esquema. Sobre todo los plugins, muchos de los cuales de- finen su propio esquema y paquete para evitar errores y duplicidades con las clases propias de la aplicación (más detalles en el Capítulo 17). 8.7.2. Detalles de las columnas La sintaxis básica ofrece dos posibilidades: dejar que Symfony deduzca las características de una columna a partir de su nombre (indicando un valor vacío para esa columna) o de- finir el tipo de columna con uno de los tipos predefinidos. El listado 8-26 muestra estas 2 opciones. Listado 8-26 - Atributos básicos de columna propel: blog_article: id: # Symfony se encarga de esta columna title: varchar(50) # Definir el tipo explícitamente Se pueden definir muchos más aspectos de cada columna. Si se definen, se utiliza un array asociativo para indicar las opciones de la columna, tal y como muestra el listado 8-27. Listado 8-27 - Atributos avanzados de columna propel: blog_article: id: { type: integer, required: true, primaryKey: true, autoIncrement: true } name: { type: varchar(50), default: foobar, index: true } group_id: { type: integer, foreignTable: db_group, foreignReference: id, onDelete: cascade } Los parámetros de las columnas son los siguientes: ▪ type: Tipo de columna. Se puede elegir entre boolean, tinyint, smallint, inte- ger, bigint, double, float, real, decimal, char, varchar(tamano), longvarchar, date, time, timestamp, bu_date, bu_timestamp, blob y clob. ▪ required: valor booleano. Si vale true la columna debe tener obligatoriamente un valor. ▪ default: el valor por defecto. ▪ primaryKey: valor booleano. Si vale true indica que es una clave primaria. www.librosweb.es 171
  • 172. Symfony, la guía definitiva Capítulo 8. El modelo ▪ autoIncrement: valor booleano. Si se indica true para las columnas de tipo inte- ger, su valor se auto-incrementará. ▪ sequence: el nombre de la secuencia para las bases de datos que utilizan secuen- cias para las columnas autoIncrement (por ejemplo PostgreSQL y Oracle). ▪ index: valor booleano. Si vale true, se construye un índice simple; si vale unique se construye un índice único para la columna. ▪ foreignTable: el nombre de una tabla, se utiliza para crear una clave externa a otra tabla. ▪ foreignReference: el nombre de la columna relacionada si las claves externas se definen mediante foreignTable. ▪ onDelete: determina la acción que se ejecuta cuando se borra un registro en una tabla relacionada. Si su valor es setnull, la columna de la clave externa se esta- blece a null. Si su valor es cascade, se borra el registro relacionado. Si el siste- ma gestor de bases de datos no soporta este comportamiento, el ORM lo emula. Esta opción solo tiene sentido para las columnas que definen una foreignTable y una foreignReference. ▪ isCulture: valor booleano. Su valor es true para las columnas de tipo culture en las tablas de contenidos adaptados a otros idiomas (más detalles en el Capítulo 13). 8.7.3. Claves externas Además de los atributos de columna foreignTable y foreignReference, es posible añadir claves externas bajo la clave _foreignKeys: de cada tabla. El esquema del listado 8-28 crea una clave externa en la columna user_id, que hace referencia a la columna id de la tabla blog_user. Listado 8-28 - Sintaxis alternativa para las claves externas propel: blog_article: id: title: varchar(50) user_id: { type: integer } _foreignKeys: - foreignTable: blog_user onDelete: cascade references: - { local: user_id, foreign: id } La sintaxis alternativa es muy útil para las claves externas múltiples y para indicar un nombre a cada clave externa, tal y como muestra el listado 8-29. Listado 8-29 - La sintaxis alternativa de las claves externas aplicada a una clave externa múltiple www.librosweb.es 172
  • 173. Symfony, la guía definitiva Capítulo 8. El modelo _foreignKeys: my_foreign_key: foreignTable: db_user onDelete: cascade references: - { local: user_id, foreign: id } - { local: post_id, foreign: id } 8.7.4. Índices Además del atributo de columna index, es posible añadir claves índices bajo la clave _in- dexes: de cada tabla. Si se quieren crean índices únicos, se debe utilizar la clave _uniq- ues:. El listado 8-30 muestra la sintaxis alternativa para los índices. Listado 8-30 - Sintaxis alternativa para los índices y los índices únicos propel: blog_article: id: title: varchar(50) created_at: _indexes: mi_indice: [title, user_id] _uniques: mi_otro_indice: [created_at] La sintaxis alternativa solo es útil para los índices que se construyen con más de una columna. 8.7.5. Columnas vacías Cuando Symfony se encuentra con una columna sin ningún valor, utiliza algo de magia para determinar su valor. El listado 8-31 muestra los detalles del tratamiento de las co- lumnas vacías. Listado 8-31 - Los detalles deducidos para las columnas vacías en función de su nombre // Las columnas vacías llamadas "id" se consideran claves primarias id: { type: integer, required: true, primaryKey: true, autoIncrement: true } // Las columnas vacías llamadas "XXX_id" se consideran claves externas foobar_id: { type: integer, foreignTable: db_foobar, foreignReference: id } // Las columnas vacías llamadas created_at, updated at, created_on y updated_on // se consideran fechas y automáticamente se les asigna el tipo "timestamp" created_at: { type: timestamp } updated_at: { type: timestamp } Para las claves externas, Symfony busca una tabla cuyo phpName sea igual al principio del nombre de la columna; si se encuentra, se utiliza ese nombre de tabla como foreignTable. www.librosweb.es 173
  • 174. Symfony, la guía definitiva Capítulo 8. El modelo 8.7.6. Tablas i18n Symfony permite internacionalizar los contenidos mediante tablas relacionadas. De esta forma, cuando se dispone de contenido que debe ser internacionalizado, se guarda en 2 tablas distintas: una contiene las columnas invariantes y otra las columnas que permiten la internacionalización. Todo lo anterior se considera de forma implícita cuando en el archivo schema.yml se dis- pone de una tabla con el nombre cualquiernombre_i18n. Por ejemplo, el esquema que muestra el listado 8-32 se completa automáticamente con los atributos de columna y de tabla necesarios para que funcione el mecanismo de internacionalización. De forma inter- na, Symfony entiende ese listado como si se hubiera escrito tal y como se muestra en el listado 8-33. El Capítulo 13 explica en detalle la internacionalización. Listado 8-32 - Mecanismo i18n implícito propel: db_group: id: created_at: db_group_i18n: name: varchar(50) Listado 8-33 - Mecanismo i18n explícito propel: db_group: _attributes: { isI18N: true, i18nTable: db_group_i18n } id: created_at: db_group_i18n: id: { type: integer, required: true, primaryKey: true,foreignTable: db_group, foreignReference: id, onDelete: cascade } culture: { isCulture: true, type: varchar(7), required: true,primaryKey: true } name: varchar(50) 8.7.7. Más allá del schema.yml: schema.xml En realidad, el formato de schema.yml es propio de Symfony. Cuando se ejecuta un co- mando que empieza por propel-, Symfony transforma ese archivo en otro archivo llama- do generated-schema.xml, que es el tipo de archivo que necesita Propel para realizar sus tareas sobre el modelo. El archivo schema.xml contiene la misma información que su equivalente en formato YAML. Por ejemplo, el listado 8-3 se convierte en el archivo XML del listado 8-34. Listado 8-34 - Ejemplo de schema.xml, que se corresponde con el listado 8-3 <?xml version="1.0" encoding="UTF-8"?> <database name="propel" defaultIdMethod="native" noXsd="true" package="lib.model"> <table name="blog_article" phpName="Article"> <column name="id" type="integer" required="true" primaryKey="true"autoIncrement="true" /> www.librosweb.es 174
  • 175. Symfony, la guía definitiva Capítulo 8. El modelo <column name="title" type="varchar" size="255" /> <column name="content" type="longvarchar" /> <column name="created_at" type="timestamp" /> </table> <table name="blog_comment" phpName="Comment"> <column name="id" type="integer" required="true" primaryKey="true"autoIncrement="true" /> <column name="article_id" type="integer" /> <foreign-key foreignTable="blog_article"> <reference local="article_id" foreign="id"/> </foreign-key> <column name="author" type="varchar" size="255" /> <column name="content" type="longvarchar" /> <column name="created_at" type="timestamp" /> </table> </database> La descripción del formato schema.xml se puede consultar en la documentación y la sec- ción “Getting started” del sitio web del proyecto Propel (http://propel.phpdb.org/docs/ user_guide/chapters/appendices/AppendixB-SchemaReference.html ). El formato del esquema en YAML se diseñó para que los esquemas fueran fáciles de leer y escribir, pero su inconveniente es que los esquemas más complejos no se pueden des- cribir solamente con un archivo schema.yml. Por otra parte, el formato XML permite des- cribir completamente el esquema, independientemente de su complejidad e incluye la posibilidad de incluir opciones propias de algunas bases de datos, herencia de tablas, etc. Symfony también puede trabajar con esquemas escritos en formato XML. Así que no es necesario utilizar el formato YAML propio de Symfony si el esquema es demasiado com- plejo, si ya dispones de un esquema en formato XML o si estás acostumbrado a trabajar con la sintaxis XML de Propel. Solamente es necesario crear el archivo schema.xml en el directorio config/ del proyecto y construir el modelo. Propel en Symfony Todos los detalles incluidos en este capítulo no son específicos de Symfony sino de Propel. Propel es la capa de abstracción de objetos/relacional preferida por Symfony, pero se puede utilizar cualq- uier otra. No obstante, Symfony se integra mucho mejor con Propel por las siguientes razones: Todas las clases del modelo de objetos de datos y las clases Criteria se cargan de forma au- tomática. La primera vez que se utilizan, Symfony incluye los archivos adecuados y no es necesario preocuparse por añadir las instrucciones que incluyen esos archivos. En Symfony no es necesario arrancar o inicializar Propel. Cuando un objeto utiliza Propel, la librería se inicia automáticamente. Algunos de los helpers de Symfony utilizan objetos Propel como parámetros para realizar tareas complejas, como la paginación y el filtrado. Los objetos Propel permiten crear prototipos rápidamen- te y generar de forma automática la parte de gestión de la aplicación (el Capítulo 14 incluye más detalles). El esquema es mucho más fácil de escribir mediante el archivo schema.yml. Y, como Propel es independiente de la base de datos utilizada, también lo es Symfony. 8.8. No crees el modelo dos veces www.librosweb.es 175
  • 176. Symfony, la guía definitiva Capítulo 8. El modelo La desventaja de utilizar un ORM es que se debe definir la estructura de datos 2 veces: una para la base de datos y otra para el modelo de objetos. Por suerte, Symfony dispone de utilidades de línea de comandos para generar uno en función del otro, de modo que se evita duplicar el trabajo. 8.8.1. Construir la estructura SQL de la base de datos en función de un esquema existente Si se crea la aplicación escribiendo el archivo schema.yml, Symfony puede generar las instrucciones SQL que crean las tablas directamente a partir del modelo de datos en YAML. Para generarlas, se ejecuta el siguiente comando desde el directorio raíz del proyecto: > symfony propel-build-sql El anterior comando crea un archivo lib.model.schema.sql en el directorio miproyecto/ data/sql/. El código SQL generado se optimiza para el sistema gestor de bases de datos definido en el parámetro phptype del archivo propel.ini. Se puede utilizar directamente el archivo schema.sql para construir la base de datos. Por ejemplo, en MySQL se puede ejecutar lo siguiente: > mysqladmin -u root -p create blog > mysql -u root -p blog < data/sql/lib.model.schema.sql El código SQL generado también es útil para reconstruir la base de datos en otro entorno o para cambiar de sistema gestor de bases de datos. Si el archivo propel.ini define las opciones de conexión correctas con la base de datos, el comando symfony propel- insert-sql se encarga de crear automáticamente las tablas. SUGERENCIA La línea de comandos también incluye una tarea para volcar los contenidos de un archivo de texto a la base de datos. El Capítulo 16 incluye más información sobre la tarea propel-load-data y so- bre los archivos en formato YAML llamados “fixtures”. 8.8.2. Construir un modelo de datos en formato YAML a partir de una base de datos existente Symfony puede utilizar la capa de acceso a bases de datos proporcionada por Creole para generar un archivo schema.yml a partir de una base de datos existente, gracias a la in- trospección (que es la capacidad de las bases de datos para determinar la estructura de las tablas que la forman). Se trata de una opción muy útil cuando se hace ingeniería in- versa o si se prefiere trabajar primero en la base de datos antes de trabajar con el mode- lo de objetos. Para construir el modelo, el archivo propel.ini del proyecto debe apuntar a la base de datos correcta y debe tener todas las opciones de conexión. Después, se ejecuta el co- mando propel-build-schema: > symfony propel-build-schema www.librosweb.es 176
  • 177. Symfony, la guía definitiva Capítulo 8. El modelo Se genera un nuevo archivo schema.yml a partir de la estructura de la base de datos y se almacena en el directorio config/. Ahora se puede construir el modelo a partir de este esquema. El comando para generar el esquema es bastante potente y es capaz de añadir diversa información relativa a la base de datos en el esquema. Como el formato YAML no soporta este tipo de información sobre la base de datos, se debe generar un esquema en formato XML para poder incluirla. Para ello, solo es necesario añadir el argumento xml a la tarea build-schema: > symfony propel-build-schema xml En vez de generar un archivo schema.yml, se crea un archivo schema.xml que es total- mente compatible con Propel y que contiene toda la información adicional. No obstante, los esquemas XML generados suelen ser bastante profusos y difíciles de leer. La configuración de propel.ini Los comandos propel-build-sql y propel-build-schema no emplean las opciones de conexión definidas en el archivo databases.yml. En su lugar, estos comandos utilizan las opciones de cone- xión de otro archivo llamado propel.ini que se encuentra en el directorio config/ del proyecto: propel.database.createUrl = mysql://login:passwd@localhost propel.database.url = mysql://login:passwd@localhost/blog Este archivo también contiene otras opciones que se utilizan para configurar el generador de Propel de forma que las clases del modelo generadas sean compatibles con Symfony. La mayoría de opc- iones son de uso interno y por tanto no interesan al usuario, salvo algunas de ellas: // Base classes are autoloaded in symfony // Set this to true to use include_once statements instead // (Small negative impact on performance) propel.builder.addIncludes = false // Generated classes are not commented by default // Set this to true to add comments to Base classes // (Small negative impact on performance) propel.builder.addComments = false // Behaviors are not handled by default // Set this to true to be able to handle them propel.builder.AddBehaviors = false Después de modificar las opciones del archivo propel.ini, se debe reconstruir el modelo para que los cambios surjan efecto. 8.9. Resumen Symfony utiliza Propel como ORM y Creole como la capa de abstracción de bases de da- tos. De esta forma, en primer lugar se debe describir el esquema relacional de la base de datos en formato YAML antes de generar las clases del modelo de objetos. Después, du- rante la ejecución de la aplicación, se utilizan los métodos de las clases objeto y clases peer para acceder a la información de un registro o conjunto de registros. Se puede re- definir y ampliar el modelo fácilmente añadiendo métodos a las clases personalizadas. www.librosweb.es 177
  • 178. Symfony, la guía definitiva Capítulo 8. El modelo Las opciones de conexión se definen en el archivo databases.yml, que puede definir más de una conexión. La línea de comandos contiene tareas especiales que evitan tener que definir la estructura de la base de datos más de una vez. La capa del modelo es la más compleja del framework Symfony. Una de las razones de esta complejidad es que la manipulación de datos es una tarea bastante intrincada. Las consideraciones de seguridad relacionadas con el modelo son cruciales para un sitio web y no deberían ignorarse. Otra de las razones es que Symfony se ajusta mejor a las apli- caciones medianas y grandes en un entorno empresarial. En ese tipo de aplicaciones, las tareas automáticas proporcionadas por el modelo de Symfony suponen un gran ahorro de tiempo, por lo que merece la pena el tiempo dedicado a aprender su funcionamiento interno. Así que no dudes en dedicar algo de tiempo a probar los objetos y métodos del modelo para entenderlos completamente. La recompensa será la gran solidez y escalabilidad de las aplicaciones desarrolladas. www.librosweb.es 178
  • 179. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento Capítulo 9. Enlaces y sistema de enrutamiento Los enlaces y las URL requieren de un tratamiento especial en cualquier framework para aplicaciones web. El motivo es que la definición de un único punto de entrada a la aplica- ción (mediante el controlador frontal) y el uso de helpers en las plantillas, permiten sepa- rar completamente el funcionamiento y el aspecto de las URL. Este mecanismo se conoce como “enrutamiento” (del inglés “routing”). El enrutamiento no es solo una utilidad curio- sa, sino que es una herramienta muy útil para hacer las aplicaciones web más fáciles de usar y más seguras. En este capítulo se detalla la forma de manejar las URL en las apli- caciones de Symfony: ▪ Qué es y como funciona el sistema de enrutamiento ▪ Cómo utilizar helpers de enlaces en las plantillas para enlazar URL salientes ▪ Cómo configurar las reglas de enrutamiento para modificar el aspecto de las URL Además, se incluyen una serie de trucos para mejorar el rendimiento del sistema de en- rutamiento y para añadirle algunos toques finales. 9.1. ¿Qué es el enrutamiento? El enrutamiento es un mecanismo que reescribe las URL para simplificar su aspecto. An- tes de poder comprender su importancia, es necesario dedicar unos minutos al estudio de las URL de las aplicaciones 9.1.1. URL como instrucciones de servidor Cuando el usuario realiza una acción, las URL se encargan de enviar la información desde el navegador hasta el servidor. Las URL tradicionales incluyen la ruta hasta el script del servidor y algunos parámetros necesarios para completar la petición, como se muestra en el siguiente ejemplo: http://www.ejemplo.com/web/controlador/articulo.php?id=123456&codigo_formato=6532 La URL anterior incluye información sobre la arquitectura de la aplicación y sobre su base de datos. Normalmente, los programadores evitan mostrar la estructura interna de la aplicación en la interfaz (las páginas por ejemplo se titulan “Perfil personal” y no “QZ7.65”). Desvelar detalles internos de la aplicación en la URL no solo contradice esta norma, sino que tiene otras desventajas: ▪ Los datos técnicos que se muestran en las URL son una fuente potencial de agu- jeros de seguridad. En el ejemplo anterior, ¿qué sucede si un usuario malicioso modifica el valor del parámetro id? ¿Supone este caso que la aplicación ofrece una interfaz directa a la base de datos? ¿Qué sucedería si otro usuario probara otros nombres de script, como por ejemplo admin.php? En resumen, las URL di- rectas permiten jugar de forma directa y sencilla con una aplicación y es casi im- posible manejar su seguridad. ▪ Las URL complejas son muy difíciles de leer y hoy en día las URL no solo apare- cen en la barra de direcciones. También suelen aparecer cuando un usuario pasa www.librosweb.es 179
  • 180. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento el ratón por encima de un enlace y también en los resultados de búsqueda. Cuando los usuarios buscan información, es más útil proporcionarles URL senci- llas y fáciles de entender y no URL complejas como las que se muestran en la fi- gura 9.1 Figura 9.1. Las URL aparecen en muchos lugares, como por ejemplo los resultados de búsqueda ▪ Si se modifica una URL (porque cambia el nombre del script o el de alguno de sus parámetros), se deben modificar todos los enlaces a esa URL. De esta forma, las modificaciones en la estructura del controlador son muy pesadas y costosas, lo que contradice la filosofía del desarrollo ágil de aplicaciones. La situación podría ser incluso mucho peor si Symfony no utilizara un controlador frontal; es decir, si la aplicación contiene varios scripts accesibles desde el exterior, como por ejemplo: http://www.ejemplo.com/web/galeria/album.php?nombre=mis%20vacaciones http://www.ejemplo.com/web/weblog/publico/post/listado.php http://www.ejemplo.com/web/general/contenido/pagina.php?nombre=sobre%20nosotros En este caso, los programadores deben hacer coincidir la estructura de las URL y la es- tructura del sistema de archivos, por lo que su mantenimiento se convierte en una pesa- dilla cuando cualquiera de las dos estructuras se modifica. 9.1.2. URL como parte de la interfaz Una de las ideas del sistema de enrutamiento es utilizar las URL como parte de la inter- faz. Las aplicaciones trasladan información al usuario mediante el formateo de las URL y el usuario puede utilizar las URL para acceder a los recursos de la aplicación. Lo anterior es posible en las aplicaciones Symfony porque la URL que se muestra al usua- rio no tiene que guardar obligatoriamente relación con la instrucción del servidor necesa- ria para completar la petición. En su lugar, la URL está relacionada con el recurso solicita- do, y su aspecto puede configurarse libremente. En Symfony es posible por ejemplo utili- zar la siguiente URL y obtener los mismos resultados que la primera URL mostrada en es- te capítulo: http://www.ejemplo.com/articulos/economia/2006/sectores-actividad.html Este tipo de URL tiene muchas ventajas: ▪ Las URL tienen significado y ayudan a los usuarios a decidir si la página que se cargará al pulsar sobre un enlace contiene lo que esperan. Un enlace puede con- tener detalles adicionales sobre el recurso que enlaza. Esto último es especial- mente útil para los resultados de los buscadores. Además, muchas veces las URL aparecen sin que se mencione el título de su página (por ejemplo cuando se www.librosweb.es 180
  • 181. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento copian las URL en un mensaje de email) por lo que en ese caso deberían conte- ner su propio significado. La figura 9-2 muestra una URL sencilla y fácil de entender. Figura 9.2. Las URL pueden incluir información adicional sobre una página, como por ejemplo su fecha de publicación ▪ Las URL que aparecen en los documentos impresos son más fáciles de escribir y de recordar. Si la dirección del sitio web de una empresa se muestra en una tar- jeta de visita con un aspecto similar a http://www.ejemplo.com/controlador/web/in- dex.jsp?id=ERD4, probablemente no reciba muchas visitas. ▪ La URL se puede convertir en una especie de línea de comandos, que permita re- alizar acciones u obtener información de forma intuitiva. Este tipo de aplicaciones son las que más rápidamente utilizan los usuarios más avanzados. // Listado de resultados: se puede añadir una nueva etiqueta para restringir los resultados http://del.icio.us/tag/symfony+ajax // Página de perfil de usuario: se puede modificar el nombre para obtener otro perfil http://www.askeet.com/user/francois ▪ Se puede modificar el aspecto de la URL y el del nombre de la acción o de los parámetros de forma independiente y con una sola modificación. En otras pala- bras, es posible empezar a programar la aplicación y después modificar el aspec- to de las URL sin estropear completamente la aplicación. ▪ Aunque se modifique la estructura interna de la aplicación, las URL pueden man- tener su mismo aspecto hacia el exterior. De esta forma, las URL se convierten en persistentes y pueden ser añadidas a los marcadores o favoritos. ▪ Cuando los motores de búsqueda indexan un sitio web, suelen tratar de forma di- ferente (incluso saltándoselas) a las páginas dinámicas (las que acaban en .php, .asp, etc.) Así que si se formatean las URL de esta forma, los buscadores creen que están indexando contenidos estáticos, por lo que generalmente se obtiene una mejor indexación de las páginas de la aplicación. ▪ Son más seguras. Cualquier URL no reconocida se redirige a una página especifi- cada por el programador y los usuarios no pueden navegar por el directorio raíz del servidor mediante la prueba de diferentes URL. La razón es que no se visuali- za el nombre del script utilizado o el de sus parámetros. La relación entre las URL mostradas al usuario y el nombre del script que se ejecuta y de sus parámetros está gestionada por el sistema de enrutamiento, que utiliza patrones que se pueden modificar mediante la configuración de la aplicación. www.librosweb.es 181
  • 182. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento NOTA ¿Qué sucede con los contenidos estáticos? Afortunadamente, las URL de los contenidos estáticos (imágenes, hojas de estilos y archivos de JavaScript) no suelen mostrarse durante la navegación, por lo que no es necesario utilizar el sistema de enrutamiento para este tipo de contenidos. Sym- fony almacena todos los contenidos estáticos en el directorio web/ y sus URL se corresponden con su localización en el sistema de archivos. No obstante, es posible gestionar dinámicamente los con- tenidos estáticos mediante URL generadas con un helper para contenidos estáticos. Por ejemplo, para mostrar una imagen generada dinámicamnete, se puede utilizar el helper image_tag(url_- for(’captcha/image?key=’.$key)). 9.1.3. Cómo funciona Symfony desasocia las URL externas y las URI utilizadas internamente. La corresponden- cia entre las dos es responsabilidad del sistema de enrutamiento. Symfony simplifica este mecanismo utilizando una sintaxis para las URI internas muy similar a la de las URL habi- tuales. El listado 9-1 muestra un ejemplo. Listado 9-1 - URL externas y URI internas // Sintaxis de las URI internas <modulo>/<accion>[?parametro1=valor1][&parametro2=valor2][&parametro3=valor3]... // Ejemplo de URI interna que nunca se muestra al usuario articulo/permalink?ano=2006&tema=economia&titulo=sectores-actividad // Ejemplo de URL externa que se muestra al usuario http://www.ejemplo.com/articulos/economia/2006/sectores-actividad.html El sistema de enrutamiento utiliza un archivo de configuración especial, llamado rou- ting.yml, en el que se pueden definir las reglas de enrutamiento. Si se considera la regla mostrada en el listado 9-2, se define un patrón cuyo aspecto es articulos/*/*/* y que también define el nombre de cada pieza que forma parte de la URL. Listado 9-2 - Ejemplo de regla de enrutamiento articulo_segun_titulo: url: articulos/:tema/:ano/:titulo.html param: { module: articulo, action: permalink } Todas las peticiones realizadas a una aplicación Symfony son analizadas en primer lugar por el sistema de enrutamiento (que es muy sencillo porque todas las peticiones se gest- ionan mediante un único controlador frontal). El sistema de enrutamiento busca coinci- dencias entre la URL de la petición y los patrones definidos en las reglas de enrutamien- to. Si se produce una coincidencia, las partes del patrón que tienen nombre se transfor- man en parámetros de la petición y se juntan a los parámetros definidos en la clave pa- ram:. El listado 9-3 muestra su funcionamiento. Listado 9-3 - El sistema de enrutamiento interpreta las URL de las peticiones entrantes // El usuario escribe (o pulsa) sobre esta URL externa http://www.ejemplo.com/articulos/economia/2006/sectores-actividad.html www.librosweb.es 182
  • 183. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento // El controlador frontal comprueba que coincide con la regla articulo_segun_titulo // El sistema de enrutamiento crea los siguientes parámetros de la petición 'module' => 'articulo' 'action' => 'permalink' 'tema' => 'economia' 'ano' => '2006' 'titulo' => 'sectores-actividad' SUGERENCIA La extensión .html de las URL externas es solo un adorno y por ese motivo el sistema de enrutam- iento la ignora. Su única función es la de hacer que las páginas dinámicas parezcan páginas estáti- cas. La sección “Configuración del enrutamiento” al final de este capítulo explica cómo activar esta extensión. Después, la petición se pasa a la acción permalink del módulo articulo, que dispone de toda la información necesaria en los parámetros de la petición para obtener el artículo solicitado. El mecanismo de enrutamiento también funciona en la otra dirección. Para mostrar las URL en los enlaces de una aplicación, se debe proporcionar al sistema de enrutamiento la información necesaria para determinar la regla que se debe aplicar a cada enlace. Además, no se deben escribir los enlaces directamente con etiquetas <a> (ya que de esta forma no se estaría utilizando el sistema de enrutamiento) sino con un helper especial, tal y como se muestra en el listado 9-4. Listado 9-4 - El sistema de enrutamiento formatea las URL externas mostradas en las plantillas // El helper url_for() transforma una URI interna en una URL externa <a href="<?php echo url_for('articulo/ permalink?tema=economia&ano=2006&titulo=sectores-actividad') ?>">pincha aquí</a> // El helper reconoce que la URI cumple con la regla articulo_segun_titulo // El sistema de enrutamiento crea una URL externa a partir de el => <a href="http://www.ejemplo.com/articulos/economia/2006/ sectores-actividad.html">pincha aquí</a> // El helper link_to() muestra directamente un enlace // y evita tener que mezclar PHP y HTML <?php echo link_to( 'pincha aqui', 'articulo/permalink?tema=economia&ano=2006&titulo=sectores-actividad' ) ?> // Internamente link_to() llama a url_for(), por lo que el resultado es el mismo => <a href="http://www.ejemplo.com/articulos/economia/2006/ sectores-actividad.html">pincha aquí</a> De forma que el enrutamiento es un mecanismo bidireccional y solo funciona cuando se utiliza el helper link_to() para mostrar todos los enlaces. 9.2. Reescritura de URL www.librosweb.es 183
  • 184. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento Antes de adentrarse en el funcionamiento interno del sistema de enrutamiento, se debe aclarar una cuestión importante. En los ejemplos mostrados en las secciones anteriores, las URI internas no incluyen el controlador frontal (index.php o miapp_dev.php). Como se sabe, es el controlador frontal y no otros elementos de la aplicación, el que decide el en- torno de ejecución. Por este motivo, todos los enlaces deben ser independientes del en- torno de ejecución y el nombre del controlador frontal nunca aparece en las URI internas. Además, tampoco se muestra el nombre del script PHP en las URL generadas en los ejemplos anteriores. La razón es que, por defecto, las URL no contienen el nombre de ningún script de PHP en el entorno de producción. El parámetro no_script_name del archi- vo settings.yml controla la aparición del nombre del controlador frontal en las URL gene- radas. Si se establece su valor a off, como se muestra en el listado 9-5, las URL genera- das por los helpers incluirán el nombre del script del controlador frontal en cada enlace. Listado 9-5 - Mostrando el nombre del controlador frontal en las URL, en apps/ miapp/settings.yml prod: .settings no_script_name: off Ahora, las URL generadas tienen este aspecto: http://www.ejemplo.com/index.php/articulos/economia/2006/sectores-actividad.html En todos los entornos salvo en el de producción, el parámetro no_script_name tiene un valor igual a off por defecto. Si se prueba la aplicación en el entorno de desarrollo, el nombre del controlador frontal siempre aparece en las URL. http://www.ejemplo.com/miapp_dev.php/articulos/economia/2006/sectores-actividad.html En el entorno de producción, la opción no_script_name tiene el valor de on, por lo que las URL solo muestran la información necesaria para el enrutamiento y son más sencillas pa- ra los usuarios. No se muestra ningún tipo de información técnica. http://www.ejemplo.com/articulos/economia/2006/sectores-actividad.html ¿Cómo sabe la aplicación el nombre del script del controlador frontal que tiene que ejecu- tar? En este punto es donde comienza la reescritura de URL. El servidor web se puede configurar para que se llame siempre a un mismo script cuando la URL no indica el nom- bre de ningún script. En el servidor web Apache se debe tener activado previamente el módulo mode_rewrite. Todos los proyectos de Symfony incluyen un archivo llamado .htaccess que añade las opciones necesarias para el mod_rewrite de Apache en el directorio web/. El contenido por defecto de este archivo se muestra en el listado 9-6. Listado 9-6 - Reglas de reescritura de URL por defecto para Apache, en miproyec- to/web/.htaccess <IfModule mod_rewrite.c> RewriteEngine On # we skip all files with .something www.librosweb.es 184
  • 185. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento RewriteCond %{REQUEST_URI} ..+$ RewriteCond %{REQUEST_URI} !.html$ RewriteRule .* - [L] # we check if the .html version is here (caching) RewriteRule ^$ index.html [QSA] RewriteRule ^([^.]+)$ $1.html [QSA] RewriteCond %{REQUEST_FILENAME} !-f # no, so we redirect to our front web controller RewriteRule ^(.*)$ index.php [QSA,L] </IfModule> El servidor web analiza la estructura de las URL entrantes. Si la URL no contiene ningún sufijo y no existe ninguna versión cacheada de la página disponible (el Capítulo 12 deta- lla el sistema de cache), la petición se redirige al script index.php. No obstante, el directorio web/ de un proyecto Symfony lo comparten todas las aplicacio- nes y todos los entornos de ejecución del proyecto. Por este motivo, es habitual que exis- ta más de un controlador frontal en el directorio web. Por ejemplo, si un proyecto tiene dos aplicaciones llamadas frontend y backend y dos entornos de ejecución llamados dev y prod, el directorio web/ contiene 4 controladores frontales: index.php // frontend en prod frontend_dev.php // frontend en dev backend.php // backend en prod backend_dev.php // backend en dev Las opciones de mod_rewrite solo permiten especificar un script por defecto. Si se esta- blece el valor on a la opción no_script_name de todas las aplicaciones y todos los entor- nos, todas las URL se interpretan como si fueran peticiones al controlador frontal de la aplicación frontend en el entorno de producción (prod). Esta es la razón por la que en un mismo proyecto, solo se pueden aprovechar del sistema de enrutamiento una aplicación y un entorno de ejecución concretos. SUGERENCIA Existe una forma de acceder a más de una aplicación sin indicar el nombre del script. Para ello, se crean subdirectorios en el directorio web/ y se mueven los controladores frontales a cada subdirec- torio. Después, se modifica el valor de las constantes SF_ROOT_DIR para cada uno de ellos y se crea el archivo .htaccess de configuración para cada aplicación. 9.3. Helpers de enlaces Debido al sistema de enrutamiento, es conveniente utilizar los helpers de enlaces en las plantillas en vez de etiquetas <a> normales y corrientes. Más que una molestia, el uso de estos helpers debe verse como un método sencillo de mantener la aplicación limpia y muy fácil de mantener. Además, los helpers de enlaces incluyen una serie de utilidades y atajos que no es recomendable desaprovechar. www.librosweb.es 185
  • 186. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento 9.3.1. Hiperenlaces, botones y formularios En secciones anteriores ya se ha mostrado el helper link_to(). Se utiliza para mostrar enlaces válidos según XHTML y requiere de 2 parámetros: el elemento que va a mostrar el enlace y la URI interna del recurso al que apunta el enlace. Si en vez de un enlace se necesita un botón, simplemente se utiliza el helper button_to(). Los formularios también disponen de un helper para controlar el valor del atributo action. El siguiente capítulo ex- plica los formularios en detalle. El listado 9-7 muestra algunos ejemplos de helpers de enlaces. Listado 9-7 - Helpers de enlaces para las etiquetas <a>, <input> y <form> // Enlace simple de texto <?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia') ?> => <a href="/url/con/enrutamiento/a/Economia_en_Francia">Mi artículo</a> // Enlace en una imagen <?php echo link_to(image_tag('ver.gif'), 'articulo/ver?titulo=Economia_en_Francia') ?> => <a href="/url/con/enrutamiento/a/Economia_en_Francia"><img src="/images/ver.gif" /></a> // Boton <?php echo button_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia') ?> => <input value="Mi artículo" type="button" onclick="document.location.href='/url/con/ enrutamiento/a/Economia_en_Francia';" /> // Formulario <?php echo form_tag('articulo/ver?titulo=Economia_en_Francia') ?> => <form method="post" action="/url/con/enrutamiento/a/Economia_en_Francia" /> Los helpers de enlaces aceptan URI internas y también URL absolutas (las que empiezan por http:// y para las que no se aplica el sistema de enrutamiento) y URL internas a una página (también llamadas anclas). Las aplicaciones reales suelen construir sus URI inter- nas en base a una serie de parámetros dinámicos. El listado 9-8 muestra ejemplos de to- dos estos casos. Listado 9-8 - URL que admiten los helpers de enlaces // URI interna <?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia') ?> => <a href="/url/con/enrutamiento/a/Economia_en_Francia">Mi artículo</a> // URI interna con parámetros dinámicos <?php echo link_to('Mi artículo', 'articulo/ver?titulo='.$articulo->getTitulo()) ?> // URI interna con anclas (enlaces a secciones internas de la página) <?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia#seccion1') ?> => <a href="/url/con/enrutamiento/a/Economia_en_Francia#seccion1">Mi artículo</a> // URL absolutas <?php echo link_to('Mi artículo', 'http://www.ejemplo.com/cualquierpagina.html') ?> => <a href="http://www.ejemplo.com/cualquierpagina.html">Mi artículo</a> www.librosweb.es 186
  • 187. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento 9.3.2. Opciones de los helpers de enlaces Como se explicó en el Capítulo 7, los helpers aceptan como argumento opciones adicio- nales, que se pueden indicar en forma de array asociativo o en forma de cadena de tex- to. Los helpers de enlaces también aceptan este tipo de opciones, como muestra el lista- do 9-9. Listado 9-9 - Los helpers de enlaces aceptan opciones adicionales // Opciones adicionales como array asociativo <?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia', array( 'class' => 'miclasecss', 'target' => '_blank' )) ?> // Opciones adicionales como cadena de texto (producen el mismo resultado) <?php echo link_to('Mi artículo', 'articulo/ ver?titulo=Economia_en_Francia','class=miclasecss target=_blank') ?> => <a href="/url/con/enrutamiento/a/Economia_en_Francia" class="miclasecss" target="_blank">Mi artículo</a> También se pueden utilizar otras opciones específicas de Symfony llamadas confirm y po- pup. La primera muestra una ventana JavaScript de confirmación al pinchar en el enlace y la segunda opción abre el destino del enlace en una nueva ventana, como se muestra en el listado 9-10. Listado 9-10 - Opciones confirm y popup en los helpers de enlaces <?php echo link_to('Borrar elemento', 'item/borrar?id=123', 'confirm=¿Estás seguro?') ?> => <a onclick="return confirm('¿Estás seguro?');" href="/url/con/enrutamiento/a/borrar/123.html">Borrar elemento</a> <?php echo link_to('Añadir al carrito', 'carritoCompra/anadir?id=100', 'popup=true') ?> => <a onclick="window.open(this.href);return false;" href="/url/con/enrutamiento/a/carritoCompra/anadir/id/100.html">Añadir al carrito</a> <?php echo link_to('Añadir al carrito', 'carritoCompra/anadir?id=100', array( 'popup' => array('Título de la ventana', 'width=310,height=400,left=320,top=0') )) ?> => <a onclick="window.open(this.href,'Título de la ventana','width=310,height=400,left=320,top=0');return false;" href="/url/con/enrutamiento/a/carritoCompra/anadir/id/100.html">Añadir al carrito</a> Estas opciones también se pueden combinar entre si. 9.3.3. Opciones GET y POST falsas En ocasiones, los programadores web utilizan peticiones GET para realizar acciones más propias de una petición POST. Si se considera por ejemplo la siguiente URL: http://www.ejemplo.com/index.php/carritoCompra/anadir/id/100 Este tipo de petición modifica los datos de la aplicación, ya que añade un elemento al ob- jeto que representa el carrito de la compra y que se almacena en la sesión del servidor o www.librosweb.es 187
  • 188. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento en una base de datos. Si los usuarios añaden esta URL a los favoritos de sus navegado- res o si la URL se cachea o es indexada por un buscador, se pueden producir problemas en la base de datos y en las métricas del sitio web. En realidad, esta petición debería tra- tarse como una petición de tipo POST, ya que los robots que utilizan los buscadores no hacen peticiones POST para indexar las páginas. Symfony permite transformar una llamada a los helpers link_to() o button_to() en una petición POST. Solamente es necesario añadir la opción post=true, tal y como se muestra en el listado 9-11. Listado 9-11 - Convirtiendo un enlace en una petición POST <?php echo link_to('Ver carrito de la compra', 'carritoCompra/anadir?id=100', 'post=true') ?> => <a onclick="f = document.createElement('form'); document.body.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit();return false;" href="/carritoCompra/anadir/id/100.html">Ver carrito de la compra</a> La etiqueta <a> resultante conserva el atributo href, por lo que los navegadores sin so- porte de JavaScript, como por ejemplo los robots que utilizan los buscadores, utilizan el enlace normal con la petición GET. Asi que es posible que se deba restringir la acción pa- ra que solamente responda a las peticiones de tipo POST, que se puede realizar añadien- do por ejemplo la siguiente instrucción al principio de la acción: $this->forward404If($request->getMethod() != sfRequest::POST); Esta opción no se debe utilizar en los enlaces que se encuentran dentro de los formular- ios, ya que genera su propia etiqueta <form>. Se trata de una buena práctica definir como peticiones POST los enlaces que realizan ac- ciones que modifican los datos. 9.3.4. Forzando los parámetros de la petición como variables de tipo GET Las variables que se pasan como parámetro a link_to() se transforman en patrones según las reglas del sistema de enrutamiento. Si no existe en el archivo routing.yml nin- guna regla que coincida con la URI interna, se aplica la regla por defecto que transforma modulo/accion?clave=valor en /modulo/accion/clave/valor, como se muestra en el lista- do 9-12. Listado 9-12 - Regla de enrutamiento por defecto <?php echo link_to('Mi artículo', 'articulo/ver?titulo=Economia_en_Francia') ?> => <a href="/articulo/ver/titulo/Economia_en_Francia">Mi artículo</a> Si es necesario utilizar la sintaxis de las peticiones GET (para pasar los parámetros de la petición en la forma ?clave=valor) se deben indicar los parámetros en la opción query_s- tring. Todos los helpers de enlaces admiten esta opción, como se muestra en el listado 9-13. Listado 9-13 - Forzando el uso de variables tipo GET con la opción query_string <?php echo link_to('Mi artículo', 'articulo/ver', array( 'query_string' => 'titulo=Economia_en_Francia' www.librosweb.es 188
  • 189. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento )) ?> => <a href="/articulo/ver?titulo=Economia_en_Francia">Mi artículo</a> Las URL con los parámetros en forma de variables GET se pueden interpretar por los scripts en el lado del cliente y por las variables $_GET y $_REQUEST en el lado del servidor. Helpers de contenidos estáticos El Capítulo 7 introdujo los helpers para contenidos estáticos image_tag(), stylesheet_tag() y javascript_include_ tag(), que permiten incluir imágenes, hojas de estilos y archivos JavaS- cript en la respuesta del servidor. Las rutas a los contenidos estáticos no se procesan en el sistema de enrutamiento, ya que se trata de enlaces a recursos que se guardan en el directorio web público. Además, no es necesario indicar la extensión para los contenidos estáticos. Symfony añade de for- ma automática las extensiones .png, .js o .css cuando se llama al helper de una imagen, un ar- chivo JavaScript o una hoja de estilos. Symfony también busca de forma automática estos conteni- dos estáticos en los directorios web/images/, web/js/ y web/css/. Evidentemente, es posible incl- uir otros tipos de archivos y archivos que se encuentren en otros directorios. Para ello, solo es ne- cesario indicar como argumento el nombre completo del archivo o la ruta completa al archivo. Tam- poco es necesario definir un valor para el atributo alt si el nombre del archivo enlazado es suficien- temente significativo, ya que Symfony utiliza por defecto el nombre como atributo alt. <?php echo image_tag('test') ?> <?php echo image_tag('test.gif') ?> <?php echo image_tag('/mis_imagenes/test.gif') ?> => <img href="/images/test.png" alt="Test" /> <img href="/images/test.gif" alt="Test" /> <img href="/mis_imagenes/test.gif" alt="Test" /> Para indicar un tamaño personalizado a una imagen, se utiliza la opción size. Esta opción requiere una anchura y una altura en píxel separadas por un x. <?php echo image_tag('test', 'size=100x20')) ?> => <img href="/images/test.png" alt="Test" width="100" height="20"/> Si los contenidos estáticos se tienen que añadir en la sección <head> de la página (por ejemplo pa- ra los archivos JavaScript y las hojas de estilos), se deben utilizar los helpers use_stylesheet() y use_javascript() en las plantillas, en vez de las funciones acabadas en _tag() utilizadas en el layout. Estos helpers añaden los contenidos estáticos a la respuesta y los añaden antes de que se envíe la etiqueta </head> al navegador. 9.3.5. Utilizando rutas absolutas Los helpers de enlaces y de contenidos estáticos generan rutas relativas por defecto. Pa- ra forzar el uso de rutas absolutas, se debe asignar el valor true a la opción absolute, como muestra el listado 9-14. Esta técnica es muy útil cuando se deben incluir enlaces en mensajes de email, canales RSS o respuestas de una API. Listado 9-14 - Utilizando URL absolutas en vez de relativas <?php echo url_for('articulo/ver?titulo=Economia_en_Francia') ?> => '/url/con/enrutamiento/a/Economia_en_Francia' <?php echo url_for('articulo/ver?titulo=Economia_en_Francia', true) ?> => 'http://www.ejemplo.com/url/con/enrutamiento/a/Economia_en_Francia' www.librosweb.es 189
  • 190. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento <?php echo link_to('economía', 'articulo/ver?titulo=Economia_en_Francia') ?> => <a href="/url/con/enrutamiento/a/Economia_en_Francia">economía</a> <?php echo link_to('economía', 'articulo/ ver?titulo=Economia_en_Francia','absolute=true') ?> => <a href=" http://www.ejemplo.com/url/con/enrutamiento/a/ Economia_en_Francia">economía</a> // Lo mismo sucede con los helpers de contenidos estáticos <?php echo image_tag('prueba', 'absolute=true') ?> <?php echo javascript_include_tag('miscript', 'absolute=true') ?> El helper de correo electrónico Hoy en día, existen robots que rastrean todas las páginas web en busca de direcciones de correo electrónico que puedan ser utilizadas en los envíos masivos de spam. Por este motivo, no se pue- den incluir directamente las direcciones de correo electrónico en las páginas web sin acabar siendo una víctima del spam en poco tiempo. Afortunadamente, Symfony proporciona un helper llamado mail_to(). El helper mail_to() requiere 2 parámetros: la dirección de correo electrónico real y la cadena de texto que se muestra al usuario. Como opción adicional se puede utilizar el parámetro encode, que produce un código HTML bastante difícil de leer, que los navegadores muestran correctamente, pe- ro que los robots de spam no son capaces de entender. <?php echo mail_to('midireccion@midominio.com', 'contacto') ?> => <a href="mailto:midireccion@midominio.com">contacto</a> <?php echo mail_to('midireccion@midominio.com', 'contacto', 'encode=true') ?> => <a href="&#109;&#x61;... &#111;&#x6d;">&#x63;&#x74;... e&#115;&#x73;</a> Las direcciones de email resultantes están compuestas por caracteres transformados por un codifi- cador aleatorio que los transforma en entidades decimales y hexadecimales aleatoriamente. Aunq- ue este truco funciona para la mayoría de robots de spam, las técnicas que emplean este tipo de empresas evolucionan rápidamente y podrían dejar obsoleta esta técnica en poco tiempo. 9.4. Configuración del sistema de enrutamiento El sistema de enrutamiento se encarga de 2 tareas: ▪ Interpreta las URL externas de las peticiones entrantes y las transforma en URI internas para determinar el módulo, la acción y los parámetros de la petición. ▪ Transforma las URI internas utilizadas en los enlaces en URL externas (siempre que se utilicen los helpers de enlaces). La transformación se realiza en base a una serie de reglas de enrutamiento. Todas estas reglas se almacenan en un archivo de configuración llamado routing.yml y que se enc- uentra en el directorio config/. El listado 9-15 muestra las reglas que incluyen por defec- to todos los proyectos de Symfony. Listado 9-15 - Las reglas de enrutamiento por defecto, en miapp/config/ routing.yml # default rules homepage: www.librosweb.es 190
  • 191. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento url: / param: { module: default, action: index } default_symfony: url: /symfony/:action/* param: { module: default } default_index: url: /:module param: { action: index } default: url: /:module/:action/* 9.4.1. Reglas y patrones Las reglas de enrutamiento son asociaciones biyectivas entre las URL externas y las URI internas. Una regla típica está formada por: ▪ Un identificador único en forma de texto, que se define por legibilidad y por rapi- dez, y que se puede utilizar en los helpers de enlaces ▪ El patrón que debe cumplirse (en la clave url) ▪ Un array de valores para los parámetros de la petición (en la clave param) Los patrones pueden contener comodines (que se representan con un asterisco, *) y co- modines con nombre (que empiezan por 2 puntos, :). Si se produce una coincidencia con un comodín con nombre, ese valor que coincide se transforma en un parámetro de la pe- tición. Por ejemplo, la regla anterior llamada default produce coincidencias con cualquier URL del tipo /valor1/valor2, en cuyo caso se ejecutará el módulo llamado valor1 y la ac- ción llamada valor2. Y en la regla llamada default_symfony, el valor symfony es una pala- bra clave y action es un comodín con nombre que se transforma en parámetro de la petición. El sistema de enrutamiento procesa el archivo routing.yml desde la primera línea hasta la última y se detiene en la primera regla que produzca una coincidencia. Por este motivo se deben añadir las reglas personalizadas antes que las reglas por defecto. Si se conside- ran las reglas del listado 9-16, la URL /valor/123 produce coincidencias con las 2 reglas, pero como Symfony prueba primero la regla mi_regla:, y esa regla produce una coinci- dencia, ni siquiera se llega a probar la regla default:. De esta forma, la petición se pro- cesa en la acción mimodulo/miaccion con el parámetro id inicializado con el valor 123 (no se procesa por tanto en la acción valor/123). Listado 9-16 - Las reglas se procesan de principio a fin mi_regla: url: /valor/:id param: { module: mimodulo, action: miaccion } # default rules default: url: /:module/:action/* www.librosweb.es 191
  • 192. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento NOTA No siempre que se crea una nueva acción es necesario añadir una nueva regla al sistema de enru- tamiento. Si el patrón modulo/accion es útil para la nueva acción, no es necesario añadir más re- glas al archivo routing.yml. Sin embargo, si se quieren personalizar las URL externas de la ac- ción, es necesario añadir una nueva regla por encima de las reglas por defecto. El listado 9-17 muestra el proceso de modificación del formato de la URL externa de la acción articulo/ver. Listado 9-17 - Modificación del formato de las URL externas de la acción articu- lo/ver <?php echo url_for('Mi artículo', 'articulo/ver?id=123') ?> => /articulo/ver/id/123 // Formato por defecto // Para cambiarlo por /articulo/123, se añade una nueva regla al // principio del archivo routing.yml articulo_segun_id: url: /articulo/:id param: { module: articulo, action: ver } El problema es que la regla articulo_segun_id del listado 9-17 rompe con el enrutamien- to normal de todas las otras acciones del módulo articulo. De hecho, ahora una URL co- mo articulo/borrar produce una coincidencia en esta regla, por lo que no se ejecuta la regla default, sino que se ejecuta la regla articulo_segun_id. Por tanto, esta URL no lla- ma a la acción borrar, sino que llama a la acción ver con el atributo id inicializado con el valor borrar. Para evitar estos problemas, se deben definir restricciones en el patrón, de forma que la regla articulo_segun_id solo produzca coincidencias con las URL cuyo co- modín id sea un número entero. 9.4.2. Restricciones en los patrones Cuando una URL puede producir coincidencias con varias reglas diferentes, se deben refi- nar las reglas añadiendo restricciones o requisitos a sus patrones. Un requisito es una se- rie de expresiones regulares que deben cumplir los comodines para que la regla produzca una coincidencia. Para modificar por ejemplo la regla articulo_segun_id anterior de forma que solo se apli- que a las URL cuyo atributo id sea un número entero, se debe añadir una nueva línea a la regla, como muestra el listado 9-18. Listado 9-18 - Añadiendo requisitos a las reglas de enrutamiento articulo_segun_id: url: /articulo/:id param: { module: articulo, action: ver } requirements: { id: d+ } Ahora, una URL como articulo/borrar nunca producirá una coincidencia con la regla ar- ticulo_segun_id, porque la cadena de texto borrar no cumple con los requisitos de la www.librosweb.es 192
  • 193. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento regla. Por consiguiente, el sistema de enrutamiento continua buscando posibles coinci- dencias con otras reglas hasta que al final la encuentra en la regla llamada default. Enlaces permanentes (permalinks) Una buena recomendación sobre seguridad es la de no utilizar claves primarias en las URL y sustit- uirlas por cadenas de texto siempre que sea posible. ¿Cómo sería posible acceder a los artículos a través de su título en lugar de su ID? Las URL externas resultantes serían de esta forma: http://www.ejemplo.com/articulo/Economia_en_Francia Para utilizar estas URL, se crea una nueva acción llamada permalink y que utiliza un parámetro llamado slug en vez del parámetro id habitual. (Nota del traductor: “slug” es un término adaptado del periodismo anglosajón y que hace referencia al título de una noticia o artículo en el que se han sustituido los espacios en blanco por guiones y se han eliminado todos los caracteres que no sean letras o números, lo que los hace ideales para utilizarse como parte de las URL) La nueva regla queda de la siguiente forma: articulo_segun_id: url: /articulo/:id param: { module: articulo, action: ver } requirements: { id: d+ } articulo_segun_slug: url: /articulo/:slug param: { module: articulo, action: permalink } La acción permalink debe buscar el artículo solicitado a partir de su título, por lo que el modelo de la aplicación debe proporcionar el método adecuado. public function executePermalink() { $articulo = ArticlePeer::obtieneSegunSlug($this->getRequestParameter('slug'); $this->forward404Unless($articulo); // Muestra un error 404 si no se encuentra el artículo $this->articulo = $articulo; // Pasar el objeto a la plantilla } También es necesario modificar los enlaces que apuntan a la acción ver en las plantillas por nue- vos enlaces que apunten a la acción permalink, para que se aplique correctamente el nuevo for- mato de las URI internas. // Se debe sustituir esta línea... <?php echo link_to('Mi artículo', 'articulo/ver?id='.$articulo->getId()) ?> // ...por esta otra <?php echo link_to('Mi artículo', 'articulo/permalink?slug='.$articulo->getSlug()) ?> Gracias a la definición de requirements en las reglas, las URL externas como /articulo/Econo- mia_en_Francia se procesan en la regla articulo_segun_slug aunque la regla articulo_se- gun_id aparezca antes. Por último, como ahora los artículos se buscan a partir del campo slug, se debería añadir un índice a esa columna del modelo para optimizar el rendimiento de la base de datos. www.librosweb.es 193
  • 194. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento 9.4.3. Asignando valores por defecto Para completar las reglas, se pueden asignar valores por defecto a los comodines con nombre, incluso aunque el parámetro no esté definido. Los valores por defecto se esta- blecen en el array param:. Por ejemplo, la regla articulo_segun_id no se ejecuta si no se pasa el parámetro id. El listado 9-19 muestra como forzar la presencia de ese parámetro. Listado 9-19 - Asignar un valor por defecto a un comodín articulo_segun_id: url: /articulo/:id param: { module: articulo, action: ver, id: 1 } Los parámetros por defecto no necesariamente tienen que ser comodines que se encuen- tran en el patrón de la regla de enrutamiento. En el listado 9-20, al parámetro display se le asigna el valor true, aunque ni siquiera forma parte de la URL. Listado 9-20 - Asignar un valor por defecto a un parámetro de la petición articulo_segun_id: url: /articulo/:id param: { module: articulo, action: ver, id: 1, display: true } Si se mira con un poco de detenimiento, se puede observar que articulo y ver son tam- bién valores por defecto asignados a las variables module y action que no se encuentran en el patrón de la URL. SUGERENCIA Se puede definir un parámetro por defecto para todas las reglas de enrutamiento creando un pará- metro de configuración llamado sf_routing_default. Si por ejemplo se necesita que todas las re- glas tengan un parámetro llamado tema con un valor por defecto igual a default, se debe añadir la siguiente línea al archivo config.php de la aplicación: sfConfig::set(’sf_routing_defaults’, array(’tema’ => ‘default’));. 9.4.4. Acelerando el sistema de enrutamiento mediante el uso de los nombres de las reglas Los helpers de enlaces aceptan como argumento el nombre o etiqueta de la regla en vez del par modulo/acción, siempre que la etiqueta vaya precedida del signo @, como mues- tra el listado 9-21. Listado 9-21 - Uso de la etiqueta de las reglas en vez de Modulo/Acción <?php echo link_to('Mi artículo', 'articulo/ver?id='.$articulo->getId()) ?> // también se puede escribir como... <?php echo link_to('Mi artículo', '@articulo_segun_id?id='.$articulo->getId()) ?> Esta técnica tiene sus ventajas e inconvenientes. En cuanto a las ventajas: ▪ El formateo de las URI internas es mucho más rápido, ya que Symfony no debe recorrer todas las reglas hasta encontrar la que se corresponde con el enlace. Si www.librosweb.es 194
  • 195. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento la página contiene un gran número de enlaces, el ahorro de tiempo de las reglas con nombre será apreciable respecto a los pares módulo/acción. ▪ El uso de los nombres de las reglas permite abstraer aun más la lógica de la ac- ción. Si se modifica el nombre de la acción pero se mantiene la URL, solo será necesario realizar un cambio en el archivo routing.yml. Todas las llamadas al helper link_to() funcionarán sin tener que realizar ningún cambio. ▪ La lógica que se ejecuta es más comprensible si se utiliza el nombre de la regla. Aunque los módulos y las acciones tengan nombres explícitos, normalmente es más comprensible llamar a la regla @ver_articulo_segun_slug que simplemente llamar a articulo/ver. Por otra parte, la desventaja principal es que es más complicado añadir los enlaces, ya que siempre se debe consultar el archivo routing.yml para saber el nombre de la regla que se utiliza en la acción. La mejor técnica de las 2 depende del proyecto en el que se trate, por lo que es el pro- gramador el que tendrá que tomar la decisión. SUGERENCIA Mientras se prueba la aplicación (en el entorno dev), se puede comprobar la regla que se está apli- cando para cada petición del navegador. Para ello, se debe desplegar la sección “logs and msgs” de la barra de depuración y se debe buscar la línea que dice “matched route XXX”. El Capítulo 16 contiene más información sobre el modo de depuración web. 9.4.5. Añadiendo la extensión .html Si se comparan estas dos URL: http://miapp.ejemplo.com/articulo/Economia_en_Francia http://miapp.ejemplo.com/articulo/Economia_en_Francia.html Aunque se trata de la misma página, los usuarios (y los robots que utilizan los buscado- res) las consideran como si fueran diferentes debido a sus URL. La segunda URL parece que pertenece a un directorio web de páginas estáticas correctamente organizadas, que es exactamente el tipo de sitio web que mejor saben indexar los buscadores. Para añadir un sufijo a todas las URL externas generadas en el sistema de enrutamiento, se debe modificar el valor de la opción suffix en el archivo de configuración set- tings.yml, como se muestra en el listado 9-22. Listado 9-22 - Establecer un sufijo a todas las URL, en miapp/config/settings.yml prod: .settings suffix: .html El sufijo por defecto es un punto (.), lo que significa que el sistema de enrutamiento no añade ningún sufijo a menos que se especifique uno. En ocasiones es necesario indicar un sufijo específico para una única regla de enrutam- iento. En ese caso, se indica el sufijo directamente como parte del patrón definido www.librosweb.es 195
  • 196. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento mediante url: en la regla del archivo routing.yml, como se muestra en el listado 9-23. El sufijo global se ignora en este caso. Listado 9-23 - Estableciendo un sufijo en una única URL, en miapp/config/ routing.yml articulo_listado: url: /ultimos_articulos param: { module: articulo, action: listado } articulo_listado_rss: url: /ultimos_articulos.rss param: { module: articulo, action: listado, type: feed } 9.4.6. Creando reglas sin el archivo routing.yml Como sucede con la mayoría de archivos de configuración, el archivo routing.yml es una buena solución para definir las reglas del sistema de enrutamiento, pero no es la única solución. Se pueden definir reglas en PHP, en el archivo config.php de la aplicación o en el script del controlador frontal, pero antes de llamar a la función dispatch(), ya que este método determina la acción que se ejecuta en función de las reglas de enrutamiento dis- ponibles en ese momento. Definir reglas mediante PHP permite crear reglas dinámicas que dependan de la configuración o de otros parámetros. El objeto que gestiona las reglas de enrutamiento es un singleton llamado sfRouting. Se encuentra disponible en cualquier parte del código mediante la llamada sfRouting::- getInstance(). Su método prependRoute() añade una nueva regla por encima de las re- glas definidas en el archivo routing.yml. El método espera 4 parámetros, que son los mismos que se utilizan para definir una regla: la etiqueta de la ruta, el patrón de la URL, el array asociativo con los valores por defecto y otro array asociativo con los requisitos. La regla definida en el archivo routing.yml del listado 9-18 es equivalente por ejemplo al código PHP mostrado en el listado 9-24. Listado 9-24 - Definiendo una regla en PHP sfRouting::getInstance()->prependRoute( 'articulo_segun_id', // Nombre ruta '/articulo/:id', // Patrón de la ruta array('module' => 'articulo', 'action' => 'ver'), // Valores por defecto array('id' => 'd+'), // Requisitos ); El singleton sfRouting define otros métodos muy útiles para la gestión manual de las ru- tas: clearRoutes(), hasRoutes(), getRoutesByName(), etc. La API de Symfony (http://www.symfony-project.org/api/symfony.html ) dispone de mucha más información. SUGERENCIA A medida que se profundiza en los conceptos presentados en este libro, se pueden ampliar los co- nocimientos visitando la documentación de la API disponible online o incluso, investigando el códi- go fuente de Symfony. En este libro no se describen todas las opciones y parámetros de Symfony, pero la documentación online contiene todos los detalles posibles. www.librosweb.es 196
  • 197. Symfony, la guía definitiva Capítulo 9. Enlaces y sistema de enrutamiento 9.5. Trabajando con rutas en las acciones En ocasiones es necesario obtener información sobre la ruta actual, por ejemplo para preparar un enlace típico de “Volver a la página XXX”. En estos casos, se deben utilizar los métodos disponibles en el objeto sfRouting. Las URI devueltas por el método getCu- rrentInternalUri() se pueden utilizar directamente en las llamadas al helper link_to(), como se muestra en el listado 9-25. Listado 9-25 - Uso de sfRouting para obtener información sobre la ruta actual // Si se necesita una URL como la siguiente http://miapp.ejemplo.com/articulo/21 // Se utiliza lo siguiente en la acción articulo/ver $uri = sfRouting::getInstance()->getCurrentInternalUri(); => articulo/ver?id=21 $uri = sfRouting::getInstance()->getCurrentInternalUri(true); => @articulo_segun_id?id=21 $regla = sfRouting::getInstance()->getCurrentRouteName(); => articulo_segun_id // Si se necesitan los nombres del módulo y de la acción, // se pueden utilizar los parámetros de la petición $modulo = $this->getRequestParameter('module'); $accion = $this->getRequestParameter('action'); Si se necesita transformar dentro de la acción una URI interna en una URL externa, como se hace en las plantillas con el helper url_for(), se utiliza el método genUrl() del objeto sfController, como se muestra en el listado 9-26. Listado 9-26 - Uso de sfController para transformar una URI interna $uri = 'articulo/ver?id=21'; $url = $this->getController()->genUrl($uri); => /articulo/21 $url = $this->getController()->genUrl($uri, true); => http://miapp.ejemplo.com/articulo/21 9.6. Resumen El sistema de enrutamiento es un mecanismo bidireccional diseñado para formatear las URL externas de forma que sean más fáciles para los usuarios. La reescritura de URL es necesaria para omitir el nombre del controlador frontal de las aplicaciones de cada pro- yecto. Para que el sistema de enrutamiento funcione en ambas direcciones, es necesario utilizar los helpers de enlaces cada vez que se incluye un enlace en las plantillas. El ar- chivo routing.yml configura las reglas del sistema de enrutamiento, su prioridad y sus requisitos. El archivo settings.yml controla otras opciones adicionales como la presencia del nombre del controlador frontal en las URL y el uso de sufijos en las URL generadas. www.librosweb.es 197
  • 198. Symfony, la guía definitiva Capítulo 10. Formularios Capítulo 10. Formularios Cuando se crean las plantillas, la mayor parte del tiempo se dedica a los formularios. No obstante, los formularios normalmente se diseñan bastante mal. Como se debe prestar atención a los valores por defecto, al formato de los datos, a la validación, a la recarga de los datos introducidos y al manejo en general de los formularios, algunos programa- dores tienden a olvidar otros aspectos importantes. Por este motivo, Symfony presta es- pecial atención a este tema. En este capítulo se describen las herramientas que automa- tizan partes de este proceso y que aceleran el desarrollo de los formularios: ▪ Los helpers de formulario proporcionan una manera más rápida de crear contro- les de formulario en las plantillas, sobre todo para los elementos más complejos como fechas, listas desplegables y áreas de texto con formato. ▪ Si un formulario se encarga de modificar las propiedades de un objeto, el uso de los helpers de objetos aceleran el desarrollo de las plantillas. ▪ Los archivos YAML de validación facilitan la validación de los formularios y la re- carga de los datos introducidos. ▪ Los validadores encapsulan todo el código necesario para validar los datos intro- ducidos por el usuario. Symfony incluye validadores para la mayoría de casos ha- bituales y permite añadir validadores propios de forma sencilla. 10.1. Helpers de formularios En las plantillas, es común mezclar las etiquetas HTML con código PHP. Los helpers de formularios que incluye Symfony intentan simplificar esta tarea para evitar tener que in- cluir continuamente etiquetas <?php echo en medio de las etiquetas <input>. 10.1.1. Etiqueta principal de los formularios Como se explicó en el capítulo anterior, para crear un formulario se emplea el helper form_tag(), ya que se encarga de transformar la acción que se pasa como parámetro a una URL válida para el sistema de enrutamiento. El segundo argumento se emplea para indicar opciones adicionales, como por ejemplo, cambiar el valor del method por defecto, establecer el valor de enctype o especificar otros atributos. El listado 10-1 muestra algu- nos ejemplos. Listado 10-1 - El helper form_tag() <?php echo form_tag('prueba/guardar') ?> => <form method="post" action="/ruta/a/guardar"> <?php echo form_tag('prueba/guardar', 'method=get multipart=true class=formularioSimple') ?> => <form method="get" enctype="multipart/form-data" class="formularioSimple" action="/ruta/a/guardar"> Como no se utiliza un helper para cerrar el formulario, siempre debe incluirse la etiqueta HTML </form>, aunque no quede bien en el código fuente de la plantilla. www.librosweb.es 198
  • 199. Symfony, la guía definitiva Capítulo 10. Formularios 10.1.2. Elementos comunes de formulario Los helpers de formulario asignan por defecto a cada elemento un atributo id cuyo valor coincide con su atributo name, aunque esta no es la única convención útil. El listado 10-2 muestra una lista completa de los helpers disponibles para los elementos comunes de formularios y sus opciones. Listado 10-2 - Sintaxis de los helpers para los elementos comunes de formulario // Cuadro de texto (input) <?php echo input_tag('nombre', 'valor inicial') ?> => <input type="text" name="nombre" id="nombre" value="valor inicial" /> // Todos los helpers de formularios aceptan un parámetro con opciones adicionales // De esta forma es posible añadir atributos propios a la etiqueta que se genera <?php echo input_tag('nombre', 'valor inicial', 'maxlength=20') ?> => <input type="text" name="nombre" id="nombre" value="valor inicial" maxlength="20" /> // Cuadro de texto grande (área de texto) <?php echo textarea_tag('nombre', 'valor inicial', 'size=10x20') ?> => <textarea name="nombre" id="nombre" cols="10" rows="20"> valor inicial </textarea> // Checkbox <?php echo checkbox_tag('soltero', 1, true) ?> <?php echo checkbox_tag('carnet_conducir', 'B', false) ?> => <input type="checkbox" name="soltero" id="soltero" value="1" checked="checked" /> <input type="checkbox" name="carnet_conducir" id="carnet_conducir" value="B" /> // Radio button <?php echo radiobutton_tag('estado[]', 'valor1', true) ?> <?php echo radiobutton_tag('estado[]', 'valor2', false) ?> => <input type="radio" name="estado[]" id="estado_valor1" value="valor1" checked="checked" /> <input type="radio" name="estado[]" id="estado_valor2" value="valor2" /> // Lista desplegable (select) <?php echo select_tag('pago', '<option selected="selected">Visa</option> <option>Eurocard</option> <option>Mastercard</option>') ?> => <select name="pago" id="pago"> <option selected="selected">Visa</option> <option>Eurocard</option> <option>Mastercard</option> </select> // Lista de opciones para una etiqueta select <?php echo options_for_select(array('Visa', 'Eurocard', 'Mastercard'), 0) ?> => <option value="0" selected="selected">Visa</option> <option value="1">Eurocard</option> <option value="2">Mastercard</option> www.librosweb.es 199
  • 200. Symfony, la guía definitiva Capítulo 10. Formularios // Helper de lista desplegable con una lista de opciones <?php echo select_tag('pago', options_for_select(array( 'Visa', 'Eurocard', 'Mastercard' ), 0)) ?> => <select name="pago" id="pago"> <option value="0" selected="selected">Visa</option> <option value="1">Eurocard</option> <option value="2">Mastercard</option> </select> // Para indicar el nombre de las opciones, se utiliza un array asociativo <?php echo select_tag('nombre', options_for_select(array( 'Steve' => 'Steve', 'Bob' => 'Bob', 'Albert' => 'Albert', 'Ian' => 'Ian', 'Buck' => 'Buck' ), 'Ian')) ?> => <select name="nombre" id="nombre"> <option value="Steve">Steve</option> <option value="Bob">Bob</option> <option value="Albert">Albert</option> <option value="Ian" selected="selected">Ian</option> <option value="Buck">Buck</option> </select> // Lista desplegable que permite una selección múltiple // (los valores seleccionados se pueden indicar en forma de array) <?php echo select_tag('pago', options_for_select( array('Visa' => 'Visa', 'Eurocard' => 'Eurocard', 'Mastercard' => 'Mastercard'), array('Visa', 'Mastercard'), ), array('multiple' => true))) ?> => <select name="pago[]" id="pago" multiple="multiple"> <option value="Visa" selected="selected">Visa</option> <option value="Eurocard">Eurocard</option> <option value="Mastercard">Mastercard</option> </select> // Lista desplegable que permite una selección múltiple // (los valores seleccionados se pueden indicar en forma de array) <?php echo select_tag('pago', options_for_select( array('Visa' => 'Visa', 'Eurocard' => 'Eurocard', 'Mastercard' => 'Mastercard'), array('Visa', 'Mastercard') ), 'multiple=multiple') ?> => <select name="pago" id="pago" multiple="multiple"> <option value="Visa" selected="selected"> <option value="Eurocard">Eurocard</option> <option value="Mastercard" selected="selected">Mastercard</option> </select> // Campo para adjuntar archivos <?php echo input_file_tag('nombre') ?> => <input type="file" name="nombre" id="nombre" value="" /> www.librosweb.es 200
  • 201. Symfony, la guía definitiva Capítulo 10. Formularios // Cuadro de texto de contraseña <?php echo input_password_tag('nombre', 'valor') ?> => <input type="password" name="nombre" id="nombre" value="valor" /> // Campo oculto <?php echo input_hidden_tag('nombre', 'valor') ?> => <input type="hidden" name="nombre" id="nombre" value="valor" /> // Botón de envío de formulario (botón normal de texto) <?php echo submit_tag('Guardar') ?> => <input type="submit" name="submit" value="Guardar" /> // Botón de envío de formulario (botón creado con la imagen indicada) <?php echo submit_image_tag('imagen_envio') ?> => <input type="image" name="submit" src="/images/imagen_envio.png" /> El helper submit_image_tag() utiliza la misma sintaxis y tiene las mismas características que image_tag(). NOTA En los radio button, el valor del atributo id no se copia directamente del atributo de name, sino que se construye mediante una combinación del nombre y de cada valor. El motivo es que el atributo name debe tener el mismo valor para todos los radio button que se quieren definir como mutuamen- te excluyentes, al mismo tiempo que en una página HTML dos o más elementos no pueden dispo- ner del mismo valor para su atributo id. Procesando los formularios ¿Cómo se obtienen los datos enviados por los usuarios a través de los formularios? Los datos se encuentran disponibles en los parámetros de la petición, por lo que en una acción se debe llamar a $this->getRequestParameter($nombreElemento) para obtener el valor. Una buena práctica consiste en utilizar la misma acción para mostrar y para procesar el formulario. En función del método de la solicitud (GET o POST) se muestra la plantilla del formulario o se pro- cesan los datos enviados para redirigir a otra acción. // En mimodulo/actions/actions.class.php public function executeModificarAutor() { if ($this->getRequest()->getMethod() != sfRequest::POST) { // Mostrar el formulario return sfView::SUCCESS; } else { // Procesar los datos del formulario $nombre = $this->getRequestParameter('nombre'); ... $this->redirect('mimodulo/otraaccion'); } } www.librosweb.es 201
  • 202. Symfony, la guía definitiva Capítulo 10. Formularios Para que esta técnica funcione, el destino del formulario tiene que ser la misma acción que la ac- ción que muestra el formulario. // En mimodulo/templates/modificarAutorSuccess.php <?php echo form_tag('mimodulo/modificarAutor') ?> ... Symfony también incluye helpers de formularios para realizar peticiones asíncronas en segundo plano. El siguiente capítulo se centra en Ajax y proporciona todos los detalles. 10.1.3. Campos para introducir fechas Muchos formularios permiten al usuario introducir fechas. Uno de los principales fallos en los datos de los formularios suele ser el formato incorrecto de las fechas. El helper in- put_date_tag() simplifica la introducción de fechas mostrando un calendario interactivo creado con JavaScript, tal y como muestra la figura 10-1. Para ello, se indica la opción rich con un valor de true. Figura 10.1. Etiqueta para introducir la fecha mediante un calendario Si no se utiliza la opción rich, el helper muestra 3 listas desplegables (<select>) carga- das con una serie de meses, días y años. También es posible mostrar por separado cada una de estas listas utilizando sus propios helpers (select_day_tag(), select_month_tag() y select_year_tag()). Los valores iniciales de estos elementos son el día, mes y año act- uales. El listado 10-3 muestra los helpers disponibles para introducir fechas. Listado 10-3 - Helpers para introducir datos <?php echo input_date_tag('fechanacimiento', '2005-05-03', 'rich=true') ?> => Muestra un cuadro de texto y un calendario dinámico // Los siguientes helpers requieren incluir el grupo de helpers llamado DateForm <?php use_helper('DateForm') ?> <?php echo select_day_tag('dia', 1, 'include_custom=Seleccione un día') ?> => <select name="dia" id="dia"> <option value="">Seleccione un día</option> <option value="1" selected="selected">01</option> <option value="2">02</option> www.librosweb.es 202
  • 203. Symfony, la guía definitiva Capítulo 10. Formularios ... <option value="31">31</option> </select> <?php echo select_month_tag('mes', 1, 'include_custom=Seleccione un mes use_short_month=true') ?> => <select name="mes" id="mes"> <option value="">Seleccione un mes</option> <option value="1" selected="selected">Jan</option> <option value="2">Feb</option> ... <option value="12">Dec</option> </select> <?php echo select_year_tag('ano', 2007, 'include_custom=Seleccione un año year_end=2010') ?> => <select name="ano" id="ano"> <option value="">Seleccione un año</option> <option value="2006">2006</option> <option value="2007" selected="selected">2007</option> ... </select> Los valores permitidos por el helper input_date_tag() son los mismos que admite la fun- ción strtotime() de PHP. El listado 10-4 muestra algunos de los listados que se pueden utilizar y el listado 10-5 muestra los que no se pueden emplear. Listado 10-4 - Formatos de fecha válidos para los helpers de fecha // Funcionan bien <?php echo input_date_tag('prueba', '2006-04-01', 'rich=true') ?> <?php echo input_date_tag('prueba', 1143884373, 'rich=true') ?> <?php echo input_date_tag('prueba', 'now', 'rich=true') ?> <?php echo input_date_tag('prueba', '23 October 2005', 'rich=true') ?> <?php echo input_date_tag('prueba', 'next tuesday', 'rich=true') ?> <?php echo input_date_tag('prueba', '1 week 2 days 4 hours 2 seconds', 'rich=true') ?> // Devuelven un valor null <?php echo input_date_tag('prueba', null, 'rich=true') ?> <?php echo input_date_tag('prueba', '', 'rich=true') ?> Listado 10-5 - Formatos de fecha incorrectos para los helpers de fecha // Fecha de referencia = 01/01/1970 <?php echo input_date_tag('prueba', 0, 'rich=true') ?> // Los formatos que no son válidos en inglés no funcionan <?php echo input_date_tag('prueba', '01/04/2006', 'rich=true') ?> 10.1.4. Editor de textos avanzado Las áreas de texto definidas mediante <textarea> se pueden utilizar como editor de tex- tos avanzado gracias a la integración con las herramientas TinyMCE y FCKEditor. Estos editores muestran una interfaz similar a la de un procesador de textos, incluyendo www.librosweb.es 203
  • 204. Symfony, la guía definitiva Capítulo 10. Formularios botones para formatear el texto en negrita, cursiva y otros estilos, tal y como muestra la figura 10-2. Figura 10.2. Editor de textos avanzado Los dos editores se tienen que instalar manualmente. Como el proceso es el mismo para los dos, sólo se explica cómo instalar el editor TinyMCE. En primer lugar, se descarga el editor desde la página web del proyecto (http://tinymce.moxiecode.com/) y se descomprime en una carpeta temporal. A continuación, se copia el directorio tinymce/jscripts/tiny_m- ce/ en la carpeta web/js/ del proyecto y se define la ruta a la librería en el archivo set- tings.yml, como se muestra en el listado 10-6. Listado 10-6 - Definiendo la ruta de la librería TinyMCE all: .settings: rich_text_js_dir: js/tiny_mce Una vez instalado, se puede activar el editor avanzado mediante la opción rich=true. También es posible definir opciones propias para el editor JavaScript mediante la opción tinymce_options. El listado 10-7 muestra algunos ejemplos. Listado 10-7 - Editores de texto avanzado <?php echo textarea_tag('nombre', 'valor inicial', 'rich=true size=10x20')) ?> => se muestra un editor de textos avanzado creado con TinyMCE <?php echo textarea_tag('nombre', 'valor inicial', 'rich=true size=10x20 tinymce_options=language:"fr",theme_advanced_buttons2:"separator"')) ?> => se muestra un editor de textos avanzado creado con TinyMCE y personalizado con opciones propias 10.1.5. Selección de idioma y país En ocasiones es necesario mostrar un campo de formulario para seleccionar un país. Co- mo el nombre de los países varía en función del idioma en el que se muestran, las opcio- nes de una lista desplegable de países deberían cambiar en función de la cultura del us- uario (el Capítulo 13 incluye más información sobre el concepto de culturas). Como se muestra en el listado 10-8, el helper select_country_tag() automatiza este proceso: tra- duce el nombre de todos los países y utiliza como valor los códigos estándar definidos por el ISO. Listado 10-8 - Helper para seleccionar un país <?php echo select_country_tag('pais', 'AL') ?> => <select name="pais" id="pais"> www.librosweb.es 204
  • 205. Symfony, la guía definitiva Capítulo 10. Formularios <option value="AF">Afghanistan</option> <option value="AL" selected="selected">Albania</option> <option value="DZ">Algeria</option> <option value="AS">American Samoa</option> ... De forma similar a select_country_tag(), el helper select_language_tag() muestra una lista de idiomas, tal y como indica el listado 10-9. Listado 10-9 - Helper para seleccionar un idioma <?php echo select_language_tag('idioma', 'en') ?> => <select name="idioma" id="idioma"> ... <option value="elx">Elamite</option> <option value="en" selected="selected">English</option> <option value="enm">English, Middle (1100-1500)</option> <option value="ang">English, Old (ca.450-1100)</option> <option value="myv">Erzya</option> <option value="eo">Esperanto</option> ... 10.2. Helpers de formularios para objetos Cuando se utilizan los elementos de formulario para modificar las propiedades de un ob- jeto, resulta tedioso utilizar los helpers normales. Por ejemplo, para editar el atributo te- lefono de un objeto Cliente, se podría escribir lo siguiente: <?php echo input_tag('telefono', $cliente->getTelefono()) ?> => <input type="text" name="telefono" id="telefono" value="0123456789" /> Para no tener que repetir continuamente el nombre del atributo, Symfony define un hel- per de formulario para objetos en cada uno de los helpers de formularios. Los helpers de formularios para objetos deducen el nombre y el valor inicial del elemento a partir de un objeto y del nombre de un método. El anterior input_tag() es equivalente a: <?php echo object_input_tag($cliente, 'getTelefono') ?> => <input type="text" name="telefono" id="telefono" value="0123456789" /> El ahorro de código no es muy significativo para el helper object_input_tag(). No obs- tante, todos los helpers estándar de formulario dispone del correspondiente helper para objetos y todos comparten la misma sintaxis. Utilizando estos helpers, es muy sencillo crear los formularios. Esta es la razón por la que los helpers de formulario para objetos se utilizan en el scaffolding y en los sistemas de gestión creados de forma automática (en el Capítulo 14 se definen los detalles). El listado 10-10 muestra una lista de todos los helpers de formularios para objetos. Listado 10-10 - Sintaxis de los helpers de formularios para objetos <?php echo object_input_tag($objeto, $metodo, $opciones) ?> <?php echo object_input_date_tag($objeto, $metodo, $opciones) ?> <?php echo object_input_hidden_tag($objeto, $metodo, $opciones) ?> <?php echo object_textarea_tag($objeto, $metodo, $opciones) ?> <?php echo object_checkbox_tag($objeto, $metodo, $opciones) ?> <?php echo object_select_tag($objeto, $metodo, $opciones) ?> www.librosweb.es 205
  • 206. Symfony, la guía definitiva Capítulo 10. Formularios <?php echo object_select_country_tag($objeto, $metodo, $opciones) ?> <?php echo object_select_language_tag($objeto, $metodo, $opciones) ?> No existe un helper llamado object_password_tag(), ya que no es recomendable proporc- ionar un valor por defecto en un campo de texto de contraseña basado en lo que escribió antes el usuario. ATENCIÓN Al contrario de lo que sucede con los helpers de formularios, los helpers de formularios para objetos solamente están disponibles si se incluye de forma explícita el grupo de helpers llamado Object en la plantilla, mediante use_helper(’Object’). De todos los helpers de formularios para objetos, los más interesantes son objects_for_- select() y object_select_tag(), que se emplean para construir listas desplegables. 10.2.1. Llenando listas desplegables con objetos El helper options_for_select(), descrito anteriormente junto con el resto de helpers estándar, transforma un array asociativo de PHP en una lista de opciones, como se muestra en el listado 10-11. Listado 10-11 - Creando una lista de opciones a partir de un array con options_for_select() <?php echo options_for_select(array( '1' => 'Steve', '2' => 'Bob', '3' => 'Albert', '4' => 'Ian', '5' => 'Buck' ), 4) ?> => <option value="1">Steve</option> <option value="2">Bob</option> <option value="3">Albert</option> <option value="4" selected="selected">Ian</option> <option value="5">Buck</option> Imagina que se dispone de un array de objetos de tipo Autor que ha sido obtenido med- iante una consulta realizada con Propel. Si se quiere mostrar una lista desplegable cuyas opciones se obtienen de ese array, es necesario recorrer el array para obtener el valor del id y nombre de cada objeto, tal y como muestra el listado 10-12. Listado 10-12 - Creando una lista de opciones a partir de un array de objetos con options_for_select() // En la acción $opciones = array(); foreach ($autores as $autor) { $opciones[$autor->getId()] = $autor->getNombre(); } $this->opciones = $opciones; www.librosweb.es 206
  • 207. Symfony, la guía definitiva Capítulo 10. Formularios // En la plantilla <?php echo options_for_select($opciones, 4) ?> Como esta técnica es muy habitual, Symfony incluye un helper que automatiza todo el proceso llamado objects_for_select() y que crea una lista de opciones directamente a partir de un array de objetos. El helper requiere 2 parámetros adicionales: los nombres de los métodos empleados para obtener el value y el texto de las etiquetas <option> que se generan. De esta forma, el listado 10-12 es equivalente a la siguiente línea de código: <?php echo objects_for_select($autores, 'getId', 'getNombre', 4) ?> Aunque esta instrucción es muy rápida e inteligente, Symfony va más allá cuando se em- plean claves externas. 10.2.2. Creando una lista desplegable a partir de una columna que es clave externa Los valores que puede tomar una columna que es clave externa de otra son los valores de una clave primaria que corresponden a una tabla externa. Si por ejemplo se dispone de una tabla llamada articulo con una columna autor_id que es una clave externa de la tabla autor, los posibles valores de esta columna son los de la columna id de la tabla au- tor. Básicamente, una lista desplegable para editar el autor de un artículo debería tener el aspecto del listado 10-13. Listado 10-13 - Creando una lista de opciones a partir de una clave externa con objects_for_select() <?php echo select_tag('autor_id', objects_for_select( AutorPeer::doSelect(new Criteria()), 'getId', '__toString', $articulo->getAutorId() )) ?> => <select name="autor_id" id="autor_id"> <option value="1">Steve</option> <option value="2">Bob</option> <option value="3">Albert</option> <option value="4" selected="selected">Ian</option> <option value="5">Buck</option> </select> El helper object_select_tag() automatiza todo el proceso. En el ejemplo anterior se muestra una lista desplegable con el nombre extraído de las filas de la tabla externa. El helper puede adivinar el nombre de la tabla y de la columna externa a partir del esquema de base de datos, por lo que su sintaxis es muy concisa. El listado 10-13 es equivalente a la siguiente línea de código: <?php echo object_select_tag($articulo, 'getAutorId') ?> El helper object_select_tag() adivina el nombre de la clase peer relacionada (AutorPeer en este caso) a partir del nombre del método que se pasa como parámetro. No obstante, también es posible indicar una clase propia mediante la opción related_class pasada co- mo tercer argumento. El texto que se muestra en cada etiqueta <option> es el nombre www.librosweb.es 207
  • 208. Symfony, la guía definitiva Capítulo 10. Formularios del registro de base de datos, que es el resultado de aplicar el método toString() a la clase del objeto (si no está definido el método $autor->toString(), se utiliza el va- lor de la clave primaria). Además, la lista de opciones se obtiene mediante un método doSelect() al que se pasa un objeto Criteria vacío, por lo que el método devuelve todas las filas de la tabla ordenadas por fecha de creación. Si se necesita mostrar solamente un subconjunto de filas o se quiere realizar un ordenamiento diferente, se crea un método en la clase peer que devuelve esa selección en forma de array de objetos y se indica co- mo opción peer_method en el helper. Por último, es posible añadir una opción vacía o una opción propia como primera opción de la lista desplegable gracias a las opciones inclu- de_blank y include_custom. El listado 10-14 muestra todas estas opciones del helper object_select_tag(). Listado 10-14 - Opciones del helper object_select_tag() // Sintaxis básica <?php echo object_select_tag($articulo, 'getAutorId') ?> // Construye la lista mediante AutorPeer::doSelect(new Criteria()) // Utiliza otra clase peer para obtener los valores <?php echo object_select_tag($articulo, 'getAutorId', 'related_class=Otraclase') ?> // Construye la lista mediante OtraclasePeer::doSelect(new Criteria()) // Utiliza otro método de la clase peer para obtener los valores <?php echo object_select_tag($articulo, 'getAutorId','peer_method=getAutoresMasFamosos') ?> // Construye la lista mediante AutorPeer::getAutoresMasFamosos(new Criteria()) // Añade una opción <option value="">&nbsp;</option> al principio de la lista <?php echo object_select_tag($articulo, 'getAutorId', 'include_blank=true') ?> // Añade una opción <option value="">Seleccione un autor</option> al principio de la lista <?php echo object_select_tag($articulo, 'getAutorId', 'include_custom=Seleccione un autor') ?> 10.2.3. Modificando objetos Las acciones pueden procesar de forma sencilla los formularios que permiten modificar los datos de los objetos utilizando los helpers de formularios para objetos. El listado 10- 15 muestra un ejemplo de un objeto de tipo Autor con los atributos nombre, edad y dirección. Listado 10-15 - Un formulario construido con los helpers de formularios para objetos <?php echo form_tag('autor/modificar') ?> <?php echo object_input_hidden_tag($autor, 'getId') ?> Nombre: <?php echo object_input_tag($autor, 'getNombre') ?><br /> Edad: <?php echo object_input_tag($autor, 'getEdad') ?><br /> Dirección: <br /> <?php echo object_textarea_tag($autor, 'getDireccion') ?> </form> www.librosweb.es 208
  • 209. Symfony, la guía definitiva Capítulo 10. Formularios La acción modificar del módulo autor se ejecuta cuando se envía el formulario. Esta ac- ción puede modificar los datos del objeto utilizando el modificador fromArray() generado por Propel, tal y como muestra el listado 10-16. Listado 10-16 - Procesando un formulario realizado con helpers de formularios para objetos public function executeModificar () { $autor = AutorPeer::retrieveByPk($this->getRequestParameter('id')); $this->forward404Unless($autor); $autor->fromArray($this->getRequest()->getParameterHolder()->getAll(), AutorPeer::TYPE_FIELDNAME); $autor->save(); return $this->redirect('/autor/ver?id='.$autor->getId()); } 10.3. Validación de formularios En el Capítulo 6 se explica cómo utilizar los métodos validateXXX() en las acciones para validar los parámetros de la petición. Sin embaro, si se utiliza este método para validar los datos enviados en un formulario, se acaba escribiendo una y otra vez los mismos o parecidos trozos de código. Symfony incluye un mecanismo específico de validación de formularios realizado mediante archivos YAML, en vez de utilizar código PHP en la acción. Para mostrar el funcionamiento de la validación de formularios, se va a utilizar el formu- lario del listado 10-17. Se trata del típico formulario de contacto que incluye los campos nombre, email, edad y mensaje. Listado 10-17 - Ejemplo de formulario de contacto, en modules/contacto/templa- tes/indexSuccess.php <?php echo form_tag('contacto/enviar') ?> Nombre: <?php echo input_tag('nombre') ?><br /> Email: <?php echo input_tag('email') ?><br /> Edad: <?php echo input_tag('edad') ?><br /> Mensaje: <?php echo textarea_tag('mensaje') ?><br /> <?php echo submit_tag() ?> </form> El funcionamiento básico de la validación en un formulario es que si el usuario introduce datos no válidos y envía el formulario, la próxima página que se muestra debería conte- ner los mensajes de error. La siguiente lista explica con palabras sencillas lo que se con- sideran datos válidos en el formulario de prueba: ▪ El campo nombre es obligatorio. Debe ser una cadena de texto de entre 2 y 100 caracteres. ▪ El campo email es obligatorio. Debe ser una cadena de texto de entre 2 y 100 ca- racteres y debe contener una dirección de email válida. ▪ El campo edad es obligatorio. Debe ser un número entero entre 0 y 120. www.librosweb.es 209
  • 210. Symfony, la guía definitiva Capítulo 10. Formularios ▪ El campo mensaje es obligatorio. Se podrían definir reglas de validación más complejas para el formulario de contacto, pe- ro de momento solo es un ejemplo para mostrar las posibilidades de la validación de formularios. NOTA La validación de formularios se puede realizar en el lado del servidor y/o en el lado del cliente. La validación en el servidor es obligatoria para no corromper la base de datos con datos incorrectos. La validación en el lado del cliente es opcional, pero mejora enormemente la experiencia de usuar- io. La validación en el lado del cliente debe realizarse de forma manual con JavaScript. 10.3.1. Validadores Los campos nombre y email del formulario de ejemplo comparten las mismas reglas de validación. Como algunas de las reglas de validación son tan comunes que aparecen en todos los formularios, Symfony ha creado unos validadores que encapsulan todo el códi- go PHP necesario para realizarlos. Un validador es una clase que proporciona un método llamado execute(). El método requiere de un parámetro que es el valor del campo de formulario y devuelve true si el valor es válido y false en otro caso. Symfony incluye varios validadores ya construidos (que se describen más adelante en la sección “Validadores estándar de Symfony”) aunque ahora solo se va a estudiar el valida- dor sfStringValidator. Este validador comprueba que el valor introducido es una cadena de texto y que su longitud se encuentra entre 2 límites indicados (definidos cuando se llama al método initialize()). Este validador es justo lo que se necesita para validar el campo nombre. El listado 10-18 muestra cómo utilizar este validador en un método de validación. Listado 10-18 - Validando parámetros de la petición con validadores reutiliza- bles, en modules/contacto/action/actions.class.php public function validateEnviar() { $nombre = $this->getRequestParameter('nombre'); // El campo 'nombre' es obligatorio if (!$nombre) { $this->getRequest()->setError('nombre', 'El campo nombre no se puede dejar vacío'); return false; } // El campo nombre debe ser una cadena de texto de entre 2 y 100 caracteres $miValidador = new sfStringValidator(); $miValidador->initialize($this->getContext(), array( 'min' => 2, 'min_error' => 'El nombre es muy corto (mínimo 2 caracteres)', 'max' => 100, 'max_error' => 'El nombre es muy largo (máximo 100 caracteres)', )); www.librosweb.es 210
  • 211. Symfony, la guía definitiva Capítulo 10. Formularios if (!$miValidador->execute($nombre, $error)) { return false; } return true; } Si un usuario envía el formulario del listado 10-17 con el valor a en el campo nombre, el método execute() de sfStringValidator devuelve un valor false (porque la longitud de la cadena de texto es menor que el mínimo de 2 caracteres). El método validateSend() devolverá false y se ejecutará el método handleErrorEnviar() en vez del método executeEnviar(). SUGERENCIA El método setError() del objeto sfRequest proporciona información a la plantilla para que se puedan mostrar los mensajes de error, como se explica más adelante en la sección “Mostrando mensajes de error en el formulario”. Los validadores establecen los errores de forma interna, por lo que se pueden definir diferentes errores para los diferentes casos de error en la validación. Este es precisamente el objetivo de los parámetros min_error y max_error de inicialización de sfStringValidator. Las reglas de validación definidas anteriormente se pueden traducir en validadores: ▪ nombre: sfStringValidator (min=2, max=100) ▪ email: sfStringValidator (min=2, max=100) y sfEmailValidator ▪ edad: sfNumberValidator (min=0, max=120) El hecho de que un campo sea requerido no es algo que se controle mediante un validador. 10.3.2. Archivo de validación Aunque se podría realizar de forma sencilla la validación del formulario de contacto med- iante los validadores en el método validateEnviar(), esta forma de trabajo supondría re- petir mucho código PHP. Symfony ofrece una alternativa mucho mejor para definir las re- glas de validación de un formulario, mediante el uso de archivos YAML. El listado 10-19 muestra por ejemplo como realizar la misma validación que el listado 10-18 pero med- iante un archivo de validación. Listado 10-19 - Archivo de validación, en modules/contacto/validate/enviar.yml fields: name: required: msg: El campo nombre no se puede dejar vacío sfStringValidator: min: 2 min_error: El nombre es muy corto (mínimo 2 caracteres) max: 100 max_error: El nombre es m uy largo (máximo 100 caracteres) www.librosweb.es 211
  • 212. Symfony, la guía definitiva Capítulo 10. Formularios En el archivo de validación, la clave fields define la lista de campos que tienen que ser validados, si son requeridos o no y los validadores que deben utilizarse para comprobar su validez. Los parámetros de cada validador son los mismos que se utilizan para iniciali- zar manualmente los validadores. Se pueden utilizar tantos validadores como sean nece- sarios sobre un mismo campo de formulario. NOTA El proceso de validación no termina cuando el validador falla. Symfony ejecuta todos los validado- res y determina que la validación ha fallado si al menos uno de ellos falla. Incluso cuando algunas de las reglas de validación fallan, Symfony busca el método validateXXX() y lo ejecuta. De esta forma, las 2 técnicas de validación son complementarias. La gran ventaja es que si un formulario tiene muchos errores, se muestran todos los mensajes de error. Los archivos de validación se encuentran en el directorio validate/ del módulo y su nom- bre se corresponde con el nombre de la acción que validan. El listado 10-19 por ejemplo se debe guardar en un archivo llamado validate/enviar.yml. 10.3.3. Mostrando el formulario de nuevo Cuando la validación falla, Symfony por defecto busca un método handleErrorEnviar() en la clase de la acción o muestra la plantilla enviarError.php si el método no existe. El procedimiento habitual para informar al usuario de que la validación ha fallado es el de volver a mostrar el formulario con los mensajes de error. Para ello, se debe redefinir el método handleErrorSend() para finalizar con una redirección a la acción que muestra el formulario (en este caso module/index) tal y como muestra el listado 10-20. Listado 10-20 - Volviendo a mostrar el formulario, en modules/contacto/actions/ actions.class.php class ContactoActions extends sfActions { public function executeIndex() { // Mostrar el formulario } public function handleErrorEnviar() { $this->forward('contacto', 'index'); } public function executeEnviar() { // Procesar el envío del formulario } } Si se utiliza la misma acción para mostrar el formulario y para procesarlo, el método handleErrorEnviar() puede devolver el valor sfView::SUCCESS para volver a mostrar el formulario, como se indica en el listado 10-21. www.librosweb.es 212
  • 213. Symfony, la guía definitiva Capítulo 10. Formularios Listado 10-21 - Una sola acción para mostrar y procesar el formulario, en modu- les/contacto/actions/actions.class.php class ContactoActions extends sfActions { public function executeEnviar() { if ($this->getRequest()->getMethod() != sfRequest::POST) { // Preparar los datos para la plantilla // Mostrar el formulario return sfView::SUCCESS; } else { // Procesar el formulario ... $this->redirect('mimodulo/otraaccion'); } } public function handleErrorEnviar() { // Preparar los datos para la plantilla // Mostrar el formulario return sfView::SUCCESS; } } La lógica que se emplea para preparar los datos del formulario se puede refactorizar en un método de tipo protected de la clase de la acción, para evitar su repetición en los mé- todos executeSend() y handleErrorSend(). Con esta nueva configuración, cuando el usuario introduce un nombre inválido, se vuelve a mostrar el formulario pero los datos introducidos se pierden y no se muestran los men- sajes de error. Para arreglar este último problema, se debe modificar la plantilla que muestra el formulario para insertar los mensajes de error cerca del campo que ha provo- cado el error. 10.3.4. Mostrando los mensajes de error en el formulario Cuando un campo del formulario no supera con éxito su validación, los mensajes de error definidos como parámetros del validador se añaden a la petición (de la misma forma que se añadían manualmente mediante el método setError() en el listado 10-18). El objeto sfRequest proporciona un par de métodos útiles para obtener el mensaje de error: hasE- rror() y getError(), cada uno de los cuales espera como argumento el nombre de un campo de formulario. Además, se puede mostrar un mensaje de aviso al principio del for- mulario para llamar la atención del usuario e indicarle que el formulario contiene errores mediante el método hasErrors(). Los listados 10-22 y 10-23 muestran cómo utilizar es- tos métodos. www.librosweb.es 213
  • 214. Symfony, la guía definitiva Capítulo 10. Formularios Listado 10-22 - Mostrando mensajes de error al principio del formulario, en tem- plates/indexSuccess.php <?php if ($sf_request->hasErrors()): ?> <p>Los datos introducidos no son correctos. Por favor, corrija los siguientes errores y vuelva a enviar el formulario:</p> <ul> <?php foreach($sf_request->getErrors() as $nombre => $error): ?> <li><?php echo $nombre ?>: <?php echo $error ?></li> <?php endforeach; ?> </ul> <?php endif; ?> Listado 10-23 - Mostrando mensajes de error dentro del formulario, en templa- tes/indexSuccess.php <?php echo form_tag('contacto/enviar') ?> <?php if ($sf_request->hasError('nombre')): ?> <?php echo $sf_request->getError('nombre') ?> <br /> <?php endif; ?> Nombre: <?php echo input_tag('nombre') ?><br /> ... <?php echo submit_tag() ?> </form> La condición utilizada antes del método getError() en el listado 10-23 es un poco larga de escribir. Por este motivo, Symfony incluye un helper llamado form_error() y que pue- de sustituirlo. Para poder utilizarlo, es necesario declarar de forma explícita el uso de es- te grupo de helpers llamado Validation. El listado 10-24 modifica al listado 10-23 para utilizar este helper. Listado 10-24 - Mostrando mensajes de error dentro del formulario, forma abreviada <?php use_helper('Validation') ?> <?php echo form_tag('contacto/enviar') ?> <?php echo form_error('nombre') ?><br /> Nombre: <?php echo input_tag('nombre') ?><br /> ... <?php echo submit_tag() ?> </form> El helper form_error() añade por defecto un carácter antes y después del mensaje de error para hacerlos más visibles. Por defecto, el carácter es una flecha que apunta hacia abajo (correspondiente a la entidad &darr;), pero se puede definir otro carácter en el ar- chivo settings.yml: all: .settings: validation_error_prefix: ' &darr;&nbsp;' validation_error_suffix: ' &nbsp;&darr;' www.librosweb.es 214
  • 215. Symfony, la guía definitiva Capítulo 10. Formularios Si ahora falla la validación, el formulario muestra correctamente los mensajes de error, pero los datos introducidos por el usuario se pierden. Para mejorar el formulario es nece- sario volver a mostrar los datos que introdujo anteriormente el usuario. 10.3.5. Mostrando de nuevo los datos introducidos Como los errores se manejan mediante el método forward() (como se muestra en el lis- tado 10-20), la petición original sigue siendo accesible y por tanto los datos introducidos por el usuario se encuentran en forma de parámetros de la petición. De esta forma, es posible mostrar los datos introducidos en el formulario utilizando los valores por defecto, tal y como se muestra en el listado 10-25. Listado 10-25 - Indicando valores por defecto para mostrar los datos introduci- dos por el usuario anteriormente después de un fallo en la validación, en templa- tes/indexSuccess.php <?php use_helper('Validation') ?> <?php echo form_tag('contacto/enviar') ?> <?php echo form_error('nombre') ?><br /> Nombre: <?php echo input_tag('nombre', $sf_params->get('nombre')) ?><br /> <?php echo form_error('email') ?><br /> Email: <?php echo input_tag('email', $sf_params->get('email')) ?><br /> <?php echo form_error('edad') ?><br /> Edad: <?php echo input_tag('edad', $sf_params->get('edad')) ?><br /> <?php echo form_error('mensaje') ?><br /> Mensaje: <?php echo textarea_tag('mensaje', $sf_params->get('mensaje')) ?><br /> <?php echo submit_tag() ?> </form> Una vez más, se trata de un mecanismo bastante tedioso de escribir. Symfony ofrece una alternativa para volver a mostrar los datos de todos los campos de un formulario. Esta alternativa se realiza mediante el archivo YAML de validación y no mediante la modi- ficación de los valores por defecto de los elementos. Solamente es necesario activar la opción fillin: del formulario, con la sintaxis descrita en el listado 10-26. Listado 10-26 - Activando la opción fillin para volver a mostrar los datos del formulario cuando la validación falla, en validate/enviar.yml fillin: enabled: true # Habilita volver a mostrar los datos param: name: prueba # Nombre del formulario (no es necesario indicarlo si solo hay 1 formulario en la página) skip_fields: [email] # No mostrar los datos introducidos en estos campos exclude_types: [hidden, password] # No mostrar los campos de estos tipos check_types: [text, checkbox, radio, password, hidden] # Muestra los datos de estos tipos de campos Por defecto, se vuelven a mostrar los datos de los campos de tipo cuadro de texto, checkbox, radio button, áreas de texto y listas desplegables (sencillas y múltiples). No se vuelven a mostrar los datos en los campos de tipo contraseña y en los campos ocultos. Además, la opción fillin no funciona para los campos utilizados para adjuntar archivos. www.librosweb.es 215
  • 216. Symfony, la guía definitiva Capítulo 10. Formularios NOTA La opción fillin funciona procesando el contenido XML de la respuesta antes de enviarla al usua- rio. Si la respuesta no es un documento XHTML válido, la opción fillin puede no funcionar. Antes de volver a mostrar los datos introducidos por el usuario, puede ser necesario mo- dificar sus valores. A los campos del formulario se les pueden aplicar mecanismos de es- cape, reescritura de URL, transformación de caracteres especiales en entidades y cualqu- ier otra transformación que se pueda llevar a cabo llamando a una función. Las convers- iones se definen bajo la clave converters:, como muestra el listado 10-27. Listado 10-27 - Convirtiendo los datos del usuario antes del fillin, en validate/ enviar.yml fillin: enabled: true param: name: prueba converters: # Conversiones aplicadas htmlentities: [nombre, comentarios] htmlspecialchars: [comentarios] 10.3.6. Validadores estándar de Symfony Symfony contiene varios validadores ya definidos y que se pueden utilizar directamente en los formularios: ▪ sfStringValidator ▪ sfNumberValidator ▪ sfEmailValidator ▪ sfUrlValidator ▪ sfRegexValidator ▪ sfCompareValidator ▪ sfPropelUniqueValidator ▪ sfFileValidator ▪ sfCallbackValidator Cada uno dispone de una serie de parámetros y de mensajes de error, pero se pueden redefinir fácilmente mediante el método initialize() del validador o mediante el archivo YAML. Las siguientes secciones describen los validadores y muestran ejemplos de su uso. 10.3.6.1. Validador de cadenas de texo sfStringValidator permite establecer una serie de restricciones relacionadas con las ca- denas de texto. sfStringValidator: values: [valor1, valor2] www.librosweb.es 216
  • 217. Symfony, la guía definitiva Capítulo 10. Formularios values_error: Los únicos valores aceptados son valor1 y valor2 insensitive: false # Si vale true, la comparación con los valores no tiene en cuenta mayúsculas y minúsculas min: 2 min_error: Por favor, introduce por lo menos 2 caracteres max: 100 max_error: Por favor, introduce menos de 100 caracteres 10.3.6.2. Validador de números sfNumberValidator verifica si un parámetro es un número y permite establecer una serie de restricciones sobre su valor. sfNumberValidator: nan_error: Por favor, introduce un número entero min: 0 min_error: El valor debe ser como mínimo 0 max: 100 max_error: El valor debe ser inferior o igual a 100 10.3.6.3. Validador de email sfEmailValidator verifica si el valor de un parámetro es una dirección válida de email. sfEmailValidator: strict: true email_error: Esta dirección de email no es válida La recomendación RFC822 define el formato de las direcciones de correo electrónico. No obstante, el formato válido es mucho más permisivo que el de las direcciones habituales de email. Según la recomendación, un email como yo@localhost es una dirección válida, aunque es una dirección que seguramente será poco útil. Si se establece la opción strict a true (que es su valor por defecto) solo se consideran válidas las direcciones de correo electrónico con el formato nombre@dominio.extension. Si la opción strict vale false, se utilizan las normas de la recomendación RFC822. 10.3.6.4. Validador de URL sfUrlValidator comprueba si el valor de un campo es una URL válido. sfUrlValidator: url_error: La URL no es válida 10.3.6.5. Validador de expresiones regulares sfRegexValidator permite comprar el valor de un campo con una expresión regular com- patible con Perl. sfRegexValidator: match: No match_error: Los comentarios con más de una URL se consideran spam pattern: /http.*http/si El parámetro match determina si el parámetro debe cumplir el patrón establecido (cuando vale Yes) o no debe cumplirlo para considerarse válido (cuando vale No). www.librosweb.es 217
  • 218. Symfony, la guía definitiva Capítulo 10. Formularios 10.3.6.6. Validador para comparaciones sfCompareValidator compara dos parámetros de petición. Su mayor utilidad es para com- parar dos contraseñas. fields: password1: required: msg: Por favor, introduce una contraseña password2: required: msg: Por favor, vuelve a introducir la contraseña sfCompareValidator: check: password1 compare_error: Las 2 contraseñas son diferentes El parámetro check contiene el nombre del campo cuyo valor debe coincidir con el valor del campo actual para considerarse válido. Por defecto el validador comprueba que los dos parámetros sean iguales. Se puede utili- zar otra comparación indicándola en el parámetro operator. Los operadores disponibles son >, >=, <, <=, == y !=. 10.3.6.7. Validador Propel para valores únicos sfPropelUniqueValidator comprueba que el valor de un parámetro de la petición no exis- te en la base de datos. Se trata de un validador realmente útil para las columnas que de- ben ser índices únicos. fields: nombre: sfPropelUniqueValidator: class: Usuario column: login unique_error: Ese login ya existe. Por favor, seleccione otro login. En este ejemplo, el validador busca en la base de datos los registros correspondientes a la clase Usuario y comprueba si alguna fila tiene en su columna login el mismo valor que el parámetro que se pasa al validador. 10.3.6.8. Validador de archivos sfFileValidator permite restringir el tipo (mediante un array de mime-types) y el ta- maño de los archivos subidos por el usuario. fields: image: required: msg: Por favor, sube un archivo de imagen file: True sfFileValidator: mime_types: - 'image/jpeg' - 'image/png' - 'image/x-png' www.librosweb.es 218
  • 219. Symfony, la guía definitiva Capítulo 10. Formularios - 'image/pjpeg' mime_types_error: Solo se permiten los formatos PNG y JPEG max_size: 512000 max_size_error: El tamaño máximo es de 512Kb El atributo file debe valer True para ese campo y el formulario de la plantilla debe decla- rarse de tipo multipart. 10.3.6.9. Validador de callback sfCallbackValidator delega la validación en un método o función externa. El método que se invoca debe devolver true o false como resultado de la validación. fields: numero_cuenta: sfCallbackValidator: callback: is_numeric invalid_error: Por favor, introduce un número. numero_tarjeta_credito: sfCallbackValidator: callback: [misUtilidades, validarTarjetaCredito] invalid_error: Por favor, introduce un número correcto de tarjeta de crédito. El método o función que se llama recibe como primer argumento el valor que se debe comprobar. Se trata de un método muy útil cuando se quieren reutilizar los métodos o funciones existentes en vez de tener que volver a crear un código similar para la validación. SUGERENCIA También es posible crear validadores propios, como se describe más adelante en la sección “Cre- ando validadores propios”. 10.3.7. Validadores con nombre Si se utilizan de forma constante las mismas opciones para un validador, se pueden agrupar bajo un validador con nombre. En el ejemplo del formulario de contacto, el cam- po email requiere las mismas opciones en sfStringValidator que el campo name. De esta forma, es posible crear un validador con nombre miStringValidator para evitar tener que repetir las mismas opciones. Para ello, se añade una etiqueta miStringValidator bajo la clave validators:, y se indica la class y los param del validador que se quiere utilizar. Después, este validador ya se puede utilizar como cualquier otro validador indicando su nombre en la sección fields, como se muestra en el listado 10-28. Listado 10-28 - Reutilizando validadores con nombre en un archivo de valida- ción, en validate/enviar.yml validators: miStringValidator: class: sfStringValidator param: min: 2 min_error: Este campo es demasiado corto (mínimo 2 caracteres) www.librosweb.es 219
  • 220. Symfony, la guía definitiva Capítulo 10. Formularios max: 100 max_error: Este campo es demasiado largo (mínimo 100 caracteres) fields: nombre: required: msg: El nombre no se puede dejar vacío miStringValidator: email: required: msg: El email no se puede dejar vacío miStringValidator: sfEmailValidator: email_error: La dirección de email no es válida 10.3.8. Restringiendo la validación a un método Por defecto, los validadores indicados en el archivo de validación se ejecutan cuando la acción se llama mediante un método POST. Se puede redefinir esta opción de forma glo- bal o campo a campo especificando otro valor en la clave methods, de forma que se pue- da utilizar una validación diferente para métodos diferentes, como muestra el listado 10-29. Listado 10-29 - Definiendo cuando se valida un campo, en validate/enviar.yml methods: [post] # Opción por defecto fields: nombre: required: msg: El nombre no se puede dejar vacío miStringValidator: email: methods: [post, get] # Redefine la opción global required: msg: El email no se puede dejar vacío miStringValidator: sfEmailValidator: email_error: La dirección de email no es válida 10.3.9. ¿Cuál es el aspecto de un archivo de validación? Hasta ahora solamente se han mostrado partes del archivo de validación. Cuando se jun- tan todas las partes, las reglas de validación se pueden definir de forma sencilla en el ar- chivo YAML. El listado 10-30 muestra el archivo de validación completo para el formulario de contacto, incluyendo todas las reglas definidas anteriormente. Listado 10-30 - Ejemplo de archivo de validación completo fillin: enabled: true validators: miStringValidator: class: sfStringValidator www.librosweb.es 220
  • 221. Symfony, la guía definitiva Capítulo 10. Formularios param: min: 2 min_error: Este campo es demasiado corto (mínimo 2 caracteres) max: 100 max_error: Este campo es demasiado largo (máximo 100 caracteres) fields: nombre: required: msg: El nombre no se puede dejar vacío miStringValidator: email: required: msg: El email no se puede dejar vacío myStringValidator: sfEmailValidator: email_error: La dirección de email no es válida edad: sfNumberValidator: nan_error: Por favor, introduce un número min: 0 min_error: "Aun no has nacido, ¿cómo vas a enviar un mensaje?" max: 120 max_error: "Abuela, ¿no es usted un poco mayor para navegar por Internet?" mensaje: required: msg: El mensaje no se puede dejar vacío 10.4. Validaciones complejas El archivo de validación es útil en la mayoría de los casos, aunque puede no ser suficien- te cuando la validación es muy compleja. En este caso, se puede utilizar el método vali- dateXXX() en la acción o se puede utilizar alguna de las soluciones que se presentan a continuación. 10.4.1. Creando un validador propio Los validadores son clases que heredan de la clase sfValidator. Si las clases de valida- ción que incluye Symfony no son suficientes, se puede crear otra clase fácilmente y si se guarda en cualquier directorio lib/ del proyecto, se cargará automáticamente. La sinta- xis es muy sencilla: cuando el validador se ejecuta, se llama al método execute(). El mé- todo initialize() se puede emplear para definir opciones por defecto. El método execute() recibe como primer argumento el valor que se debe comprobar y como segundo argumento, el mensaje de error que se debe mostrar cuando falla la vali- dación. Los dos parámetros se pasan por referencia, por lo que se pueden modificar los mensajes de error directamente en el propio método de validación. El método initialize() recibe el singleton del contexto y el array de parámetros del ar- chivo YAML. En primer lugar debe invocar el método initialize() de su clase padre sfValidator y después, debe establecer los valores por defecto. www.librosweb.es 221
  • 222. Symfony, la guía definitiva Capítulo 10. Formularios Todos los validadores disponen de un contenedor de parámetros accesible mediante $this->getParameterHolder(). Si por ejemplo se quiere definir un validador llamado sfSpamValidator para comprobar si una cadena de texto no es spam, se puede utilizar el código del listado 10-31 en un ar- chivo llamado sfSpamValidator.class.php. El validador comprueba si $valor contiene más de max_url veces la cadena de texto http. Listado 10-31 - Creando un validador propio, en lib/sfSpamValidator.class.php class sfSpamValidator extends sfValidator { public function execute (&$valor, &$error) { // Para max_url=2, la expresión regular es /http.*http/is $re = '/'.implode('.*', array_fill(0, $this->getParameter('max_url') + 1, 'http')).'/is'; if (preg_match($re, $valor)) { $error = $this->getParameter('spam_error'); return false; } return true; } public function initialize ($contexto, $parametros = null) { // Inicializar la clase padre parent::initialize($contexto); // Valores por defecto de los parámetros $this->setParameter('max_url', 2); $this->setParameter('spam_error', 'Esto es spam'); // Establecer los parámetros $this->getParameterHolder()->add($parametros); return true; } } Después de incluir el validador en cualquier directorio con carga automática de clases (y después de borrar la cache de Symfony) se puede utilizar en los archivos de validación de la forma que muestra el listado 10-32. Listado 10-32 - Utilizando un validador propio, en validate/enviar.yml fields: mensaje: required: msg: El mensaje no se puede dejar vacío sfSpamValidator: www.librosweb.es 222
  • 223. Symfony, la guía definitiva Capítulo 10. Formularios max_url: 3 spam_error: En este sitio web no nos gusta el spam 10.4.2. Utilizando la sintaxis de los arrays para los campos de formulario PHP permite utilizar la sintaxis de los arrays para los campos de formulario. Cuando se diseñan manualmente los formularios o cuando se utilizan los que genera automática- mente Propel (ver Capítulo 14) el código HTML resultante puede ser similar al del listado 10-33. Listado 10-33 - Formulario con sintaxis de array <label for="articulo_titulo">Titulo:</label> <input type="text" name="articulo[titulo]" id="articulo_titulo" value="Valor inicial" size="45" /> Si en un archivo de validación se utiliza el nombre del campo de formulario tal y como aparece en el formulario (con los corchetes) se producirá un error al procesar el archivo YAML. La solución consiste en reemplazar los corchetes [] por llaves {} en la sección fields, como muestra el listado 10-34. Symfony se encarga de la conversión de los nom- bres que se envían después a los validadores. Listado 10-34 - Archivo de validación para un formulario que utiliza la sintaxis de los arrays fields: articulo{titulo}: required: Yes 10.4.3. Ejecutando un validador en un campo vacío En ocasiones es necesario ejecutar un validador a un campo que no es obligatorio, es de- cir, en un campo que puede estar vacío. El caso más habitual es el de un formulario en el que el usuario puede (pero no es obligatorio) cambiar su contraseña. Si decide cambiar- la, debe escribir la nueva contraseña dos veces. El ejemplo se muestra en el listado 10-35. Listado 10-35 - Archivo de validación para un formulario con 2 campos de contraseña fields: password1: password2: sfCompareValidator: check: password1 compare_error: Las 2 contraseñas no coinciden La validación que se ejecuta es la siguiente: ▪ Si password1 == null y password2 == null: ▪ La comprobación required se cumple. ▪ Los validadores no se ejecutan. ▪ El formulario es válido. www.librosweb.es 223
  • 224. Symfony, la guía definitiva Capítulo 10. Formularios ▪ Si password2 == null y password1 no es null: ▪ La comprobación required se cumple. ▪ Los validadores no se ejecutan. ▪ El formulario es válido. El validador para password2 debería ejecutarse si password1 es not null. Afortunadamen- te, los validadores de Symfony permiten controlar este caso gracias al parámetro group. Cuando un campo de formulario pertenece a un grupo, su validador se ejecuta si el cam- po no está vacío y si alguno de los campos que pertenecen al grupo no está vacío. Así que si se modifica la configuración del proceso de validación por lo que se muestra en el listado 10-36, la validación se ejecuta correctamente. Listado 10-36 - Archivo de validación para un formulario con 2 campos de con- traseña y un grupo fields: password1: group: grupo_password password2: group: grupo_password sfCompareValidator: check: password1 compare_error: Las 2 contraseñas no coinciden El proceso de validación ahora se ejecuta de la siguiente manera: ▪ Si password1 == null y password2 == null: ▪ La comprobación required se cumple. ▪ Los validadores no se ejecutan. ▪ El formulario es válido. ▪ Si password1 == null and password2 == lo_que_sea: ▪ La comprobación required se cumple. ▪ password2 es not null, por lo que se ejecuta su validador y falla. ▪ Se muestra un mensaje de error para password2. ▪ If password1 == lo_que_sea and password2 == null: ▪ La comprobación required se cumple. ▪ password1 es not null, por lo que se ejecuta también el validador para password2 por pertenecer al mismo grupo y la validación falla. ▪ Se muestra un mensaje de error para password2. ▪ If password1 == lo_que_sea and password2 == lo_que_sea: ▪ La comprobación required se cumple. www.librosweb.es 224
  • 225. Symfony, la guía definitiva Capítulo 10. Formularios ▪ password2 es not null, por lo que se ejecuta su validador y no se produ- cen errores. ▪ El formulario es válido. 10.5. Resumen Incluir formularios en las plantillas es muy sencillo gracias a los helpers de formularios que incluye Symfony y a sus opciones avanzadas. Si se definen formularios para modifi- car las propiedades de un objeto, los helpers de formularios para objetos simplifican enormemente su desarrollo. Los archivos de validación, los helpers de validación y la op- ción de volver a mostrar los datos en un formulario, permiten reducir el esfuerzo necesa- rio para crear un control estricto de los formularios que sea robusto y a la vez fácil de utilizar por parte de los usuarios. Además, cualquier validación por muy compleja que sea se puede realizar escribiendo un validador propio o utilizando un método valida- teXXX() en la clase de la acción. www.librosweb.es 225
  • 226. Symfony, la guía definitiva Capítulo 11. Integración con Ajax Capítulo 11. Integración con Ajax Las aplicaciones de la denominada Web 2.0 incluyen numerosas interacciones en el lado del cliente, efectos visuales complejos y comunicaciones asíncronas con los servidores. Todo lo anterior se realiza con JavaScript, pero programarlo manualmente es una tarea tediosa y que requiere de mucho tiempo para corregir los posibles errores. Afortunada- mente, Symfony incluye una serie de helpers que automatizan muchos de los usos comu- nes de JavaScript en las plantillas. La mayoría de comportamientos en el lado del cliente se pueden programar sin necesidad de escribir ni una sola línea de JavaScript. Los pro- gramadores solo tienen que ocuparse del efecto que quieren incluir y Symfony se encar- ga de lidiar con la sintaxis necesaria y con las posibles incompatibilidades entre navegadores. En este capítulo se describen las herramientas proporcionadas por Symfony para facilitar la programación en el lado del cliente: ▪ Los helpers básicos de JavaScript producen etiquetas <script> válidas según los estándares XHTML, para actualizar elementos DOM (Document Object Model) o para ejecutar un script mediante un enlace. ▪ Prototype es una librería de JavaScript completamente integrada en Symfony y que simplifica el desarrollo de scripts mediante la definición de nuevas funciones y métodos de JavaScript. ▪ Los helpers de Ajax permiten al usuario actualizar partes de la página web pin- chando sobre un enlace, enviando un formulario o modificando un elemento de formulario. ▪ Todos estos helpers disponen de múltiples opciones que proporcionan una mayor flexibilidad, sobre todo mediante el uso de las funciones de tipo callback. ▪ Script.aculo.us es otra librería de JavaScript que también está integrada en Sym- fony y que añade efectos visuales dinámicos que permiten mejorar la interfaz y la experiencia de usuario. ▪ JSON (JavaScript Object Notation) es un estándar utilizado para que un script de cliente se comunique con un servidor. ▪ Las aplicaciones Symfony también permiten definir interacciones complejas en el lado del cliente, combinando todos los elementos anteriores. Mediante una sola línea de código PHP (la llamada al helper de Symfony) es posible incluir las opcio- nes de autocompletado, arrastrar y soltar, listas ordenables dinámicamente y texto editable. 11.1. Helpers básicos de JavaScript JavaScript siempre se había considerado como poco útil en el desarrollo de aplicaciones web profesionales debido a sus problemas de incompatibilidad entre distintos navegado- res. Hoy en día, se han resuelto la mayoría de incompatibilidades y se han creado li- brerías muy completas que permiten programar interacciones complejas de JavaScript www.librosweb.es 226
  • 227. Symfony, la guía definitiva Capítulo 11. Integración con Ajax sin necesidad de programar cientos de líneas de código y sin perder cientos de horas co- rrigiendo problemas. El avance más popular se llama Ajax, como se explica más adelante en la sección “Helpers de Ajax”. Sorprendentemente, en este capítulo casi no se incluye código JavaScript. La razón es que Symfony permite la programación de scripts del lado del cliente de forma diferente: encapsula y abstrae toda la lógica JavaScript en helpers, por lo que las plantillas no inclu- yen código JavaScript. Para el programador, añadir cierta lógica a un elemento de la pá- gina solo requiere de una línea de código PHP, pero la llamada a este helper produce có- digo JavaScript, cuya complejidad se puede comprobar al ver el código fuente de la pági- na generada como respuesta. Los helpers se encargan de resolver los problemas de in- compatibilidades entre navegadores por lo que la cantidad de código JavaScript que ge- neran puede ser muy importante. Por tanto, en este capítulo se muestra como realizar los efectos que normalmente se programan manualmente con JavaScript sin necesidad de utilizar JavaScript. Todos los helpers descritos se encuentran disponibles en las plantillas siempre que se de- clare de forma explícita el uso del helper llamado Javascript. <?php use_helper('Javascript') ?> Algunos de estos helpers generan código HTML y otros generan directamente código JavaScript. 11.1.1. JavaScript en las plantillas En XHTML, los bloques de código JavaScript deben encerrarse en secciones CDATA. Por eso es tedioso crear páginas que tienen muchos bloques de código JavaScript. Symfony incluye un helper llamado javascript_tag() y que transforma una cadena de texto en una etiqueta <script> válida según los estándares XHTML. El listado 11-1 muestra el uso de este helper. Listado 11-1 - Incluyendo JavaScript con el helper javascript_tag() <?php echo javascript_tag(" function mifuncion() { ... } ") ?> => <script type="text/javascript"> //<![CDATA[ function mifuncion() { ... } //]]> </script> El uso habitual de JavaScript, más que sus bloques de código, es la definición de enlaces que ejecutan un determinado script cuando se pincha en ellos. El helper link_to_funct- ion() se encarga exactamente de eso, como muestra el listado 11-2. www.librosweb.es 227
  • 228. Symfony, la guía definitiva Capítulo 11. Integración con Ajax Listado 11-2 - Ejecutando JavaScript mediante un enlace con el helper link_to_function() <?php echo link_to_function('¡Pínchame!', "alert('Me has pinchado')") ?> => <a href="#" onClick="alert('Me has pinchado'); return none;">¡Pínchame!</a> Como sucede con el helper link_to(), se pueden añadir opciones a la etiqueta <a> gene- rada mediante un tercer argumento de la función. NOTA De la misma forma que el helper link_to() tiene una función relacionada llamada button_to(), también es posible ejecutar un script al pulsar un botón (<input type=”button”>) utilizando el helper button_to_function(). Si se necesita una imagen pinchable, se puede llamar a link_- to_function(image_tag(’mi_imagen’), “alert(’Me has pinchado’)”). 11.1.2. Actualizando un elemento DOM Una de las tareas habituales de las interfaces dinámicas es la actualización de algunos elementos de la página. Normalmente se realiza como se muestra en el listado 11-3. Listado 11-3 - Actualizando un elemento con JavaScript <div id="indicador">Comienza el procesamiento de datos</div> <?php echo javascript_tag(" document.getElementById("indicador").innerHTML = "<strong>El procesamiento de datos ha concluido</strong>"; ") ?> Symfony incluye un helper que realiza esta tarea y que genera código JavaScript (no HTML). El helper se denomina update_element_function() y el listado 11-4 muestra su uso. Listado 11-4 - Actualizar un elemento mediante JavaScript con el helper update_element_function() <div id="indicador">Comienza el procesamiento de datos</div> <?php echo javascript_tag( update_element_function('indicador', array( 'content' => "<strong>El procesamiento de datos ha concluido</strong>", )) ) ?> A primera vista parece que este helper no es muy útil, ya que el código necesario es tan largo como el código JavaScript original. En realidad su ventaja es la facilidad de lectura del código. Si lo que se necesita es insertar el contenido antes o después de un elemen- to, eliminarlo en vez de actualizarlo o no hacer nada si no se cumple una condición, el código JavaScript resultante es muy complicado. Sin embargo, el helper update_ele- ment_function() permite mantener la facilidad de lectura del código de la plantilla, tal y como se muestra en el listado 11-5. Listado 11-5 - Opciones del helper update_element_function() // Insertar el contenido después del elemento 'indicador' update_element_function('indicador', array( www.librosweb.es 228
  • 229. Symfony, la guía definitiva Capítulo 11. Integración con Ajax 'position' => 'after', 'content' => "<strong>El procesamiento de datos ha concluido</strong>", )); // Eliminar el elemento anterior a 'indicador', solo si $condicion vale true update_element_function('indicador', array( 'action' => $condicion ? 'remove' : 'empty', 'position' => 'before', )) El helper permite que el código de las plantillas sea más fácil de entender que el código JavaScript, además de proporcionar una sintaxis unificada para efectos similares. Tam- bién esa es la razón por la que el nombre del helper es tan largo: su nombre es tan explícito que no hace falta añadir comentarios que lo expliquen. 11.1.3. Aplicaciones que se degradan correctamente La etiqueta <noscript> permite especificar cierto código HTML que muestran los navega- dores que no tienen soporte de JavaScript. Symfony complementa esta etiqueta con un helper que funciona de forma inversa: asegura que cierto código solo se ejecuta en los navegadores que soportan JavaScript. Los helpers if_javascript() y end_if_javas- cript() permiten crear aplicaciones que se degradan correctamente en los navegadores que no soportan JavaScript, como muestra el listado 11-6. Listado 11-6 - Uso del helper if_javascript() para que la aplicación se degrade correctamente <?php if_javascript(); ?> <p>Tienes activado JavaScript.</p> <?php end_if_javascript(); ?> <noscript> <p>No tienes activado JavaScript.</p> </noscript> NOTA No es necesario incluir instrucciones echo cuando se llama a los helpers if_javascript() y end_if_javascript(). 11.2. Prototype Prototype es una librería de JavaScript muy completa que amplía las posibilidades del lenguaje de programación, añade todas esas funciones que faltaban y con las que los programadores soñaban y ofrece nuevos mecanismos para la manipulación de los ele- mentos DOM. El sitio web del proyecto es http://prototypejs.org/. Los archivos de Prototype se incluyen con el framework Symfony y son accesibles en cualquier nuevo proyecto, en la carpeta web/sf/prototype/. Por tanto, se puede utilizar Prototype añadiendo el siguiente código a la acción: $directorioPrototype = sfConfig::get('sf_prototype_web_dir'); $this->getResponse()->addJavascript($directorioPrototype.'/js/prototype'); www.librosweb.es 229
  • 230. Symfony, la guía definitiva Capítulo 11. Integración con Ajax También se puede añadir con el siguiente cambio en el archivo view.yml: all: javascripts: [%SF_PROTOTYPE_WEB_DIR%/js/prototype] NOTA Como los helpers de Ajax de Symfony, que se describen en la siguiente sección, dependen de Pro- totype, la librería Prototype se incluye automáticamente cuando se utiliza cualquiera de ellos. Por tanto, no es necesario añadir los archivos JavaScript de Prototype a la respuesta si la plantilla hace uso de cualquier helper cuyo nombre acaba en _remote. Una vez que la librería Prototype se ha cargado, se pueden utilizar todas las funciones nuevas que añade al lenguaje JavaScript. El objetivo de este libro no es describir esas nuevas funciones, pero es fácil encontrar buena documentación de Prototype en la web, como por ejemplo: ▪ Particletree (http://particletree.com/features/quick-guide-to-prototype/ ) ▪ Sergio Pereira (http://www.sergiopereira.com/articles/prototype.js.html ) ▪ Script.aculo.us (http://wiki.script.aculo.us/scriptaculous/show/Prototype ) Una de las funciones que Prototype añade a JavaScript es la función dólar, $(). Básica- mente se trata de un atajo de la función document.getElementById(), pero tiene más po- sibilidades. El listado 7-11 muestra un ejemplo de su uso. Listado 11-7 - Uso de la función $() para obtener un elemento a partir de su ID con JavaScript nodo = $('elementoID'); // Es equivalente a... nodo = document.getElementById('elementoID'); // Puede obtener más de un elemento a la vez // En este caso, el resultado es un array de elementos DOM nodos = $('primerDiv', 'segundoDiv'); Prototype también incluye una función que no dispone JavaScript y que devuelve un arr- ay de todos los elementos DOM que tienen un valor del atributo class igual al indicado como argumento: nodos = document.getElementByClassName('miclass'); No obstante, no se suele utilizar la función anterior, ya que Prototype incluye una función mucho más poderosa llamada doble dólar, $$(). Esta función devuelve un array con to- dos los elementos DOM seleccionados mediante un selector de CSS. La función anterior es equivalente por tanto a la siguiente: nodos = $$('.miclass'); Gracias al poder de los selectores CSS, se pueden procesar los nodos DOM mediante su class, su id y mediante selectores avanzados como el descendiente (padre-hijo) y el re- lacional (anterior-siguiente), mucho más fácilmente que como se haría mediante Xpath. Incluso es posible combinar todos los selectores CSS para seleccionar los elementos DOM mediante esta función: www.librosweb.es 230
  • 231. Symfony, la guía definitiva Capítulo 11. Integración con Ajax nodos = $$('body div#principal ul li.ultimo img > span.leyenda'); Un último ejemplo de las mejoras en la sintaxis de JavaScript proporcionadas por Pro- totype es el iterador de arrays llamado each. Permite un código tan conciso como PHP y con la posibilidad añadida de definir funciones anónimas y closures de JavaScript. Se tra- ta de un truco muy útil si se programa JavaScript manualmente. var verduras = ['Zanahorias', 'Lechuga', 'Ajo']; verduras.each(function(comida) { alert('Me encanta ' + comida); }); Como programar JavaScript con Prototype es mucho más divertido que hacerlo sin su ayuda y como Prototype es parte de Symfony, es conveniente dedicar el tiempo necesar- io para leer su documentación antes de continuar. 11.3. Helpers de Ajax ¿Qué sucede si se quiere actualizar un elemento de la página no con JavaScript como en el listado 11-5, sino mediante un script de PHP que se encuentra en el servidor? De esta forma, sería posible modificar parte de la página en función de una respuesta del servi- dor. El helper remote_function() realiza exactamente esa tarea, como se demuestra en el listado 11-8. Listado 11-8 - Uso del helper remote_function() <div id="mizona"></div> <?php echo javascript_tag( remote_function(array( 'update' => 'mizona', 'url' => 'mimodulo/miaccion', )) ) ?> NOTA El parámetro url puede contener una URI interna (modulo/accion?clave1=valor1&...) o el nombre de una regla del sistema de enrutamiento, al igual que sucede con el helper url_for(). Cuando se ejecuta, el script anterior actualiza el contenido del elemento cuyo id es igual a mizona con la respuesta de la acción mimodulo/miaccion. Este tipo de interacción se lla- ma Ajax, y es el núcleo de las aplicaciones web más interactivas. La versión en inglés de la Wikipedia (http://en.wikipedia.org/wiki/AJAX) lo describe de la siguiente manera: Ajax permite que las páginas web respondan de forma más rápida mediante el intercam- bio en segundo plano de pequeñas cantidades de datos con el servidor, por lo que no es necesario recargar la página entera cada vez que el usuario realiza un cambio. El objetivo es aumentar la interactividad, la rapidez y la usabilidad de la página. Ajax depende de XMLHttpRequest, un objeto JavaScript cuyo comportamiento es similar a un frame oculto, cuyo contenido se puede actualizar realizando una petición al servidor y se puede utilizar para manipular el resto de la página web. Se trata de un objeto a muy bajo nivel, por lo que los navegadores lo tratan de forma diferente y el resultado es que se necesitan muchas líneas de código para realizar peticiones Ajax a mano. Afortunada- mente, Prototype encapsula todo el código necesario para trabajar con Ajax y proporcio- na un objeto Ajax mucho más simple y que también utiliza Symfony. Este es el motivo www.librosweb.es 231
  • 232. Symfony, la guía definitiva Capítulo 11. Integración con Ajax por el que la librería Prototype se carga automáticamente cuando se utiliza un helper de Ajax en la plantilla. SUGERENCIA Los helpers de Ajax no funcionan si la URL de la acción remota no pertenece al mismo dominio que la página web que la llama. Se trata de una restricción por motivos de seguridad que imponen los navegadores y que no puede saltarse. Las interacciones de Ajax están formadas por 3 partes: el elemento que la invoca (un en- lace, un formulario, un botón, un contador de tiempo o cualquier otro elemento que el usuario manipula e invoca la acción), la acción del servidor y una zona de la página en la que mostrar la respuesta de la acción. Se pueden crear interacciones más complejas si por ejemplo la acción remota devuelve datos que se procesan en una función JavaScript en el navegador del cliente. Symfony incluye numerosos helpers para insertar interaccio- nes Ajax en las plantillas y todos contienen la palabra remote en su nombre. Además, to- dos comparten la misma sintaxis, un array asociativo con todos los parámetros de Ajax. Debe tenerse en cuenta que los helpers de Ajax generan código HTML, no código JavaScript. ¿Qué sucede con las acciones para Ajax? Las acciones que se invocan de forma remota no dejan de ser acciones normales y corrientes. Se les aplica el sistema de enrutamiento, determinan la vista que deben generar en función del valor que devuelven, pasan variables a sus plantillas y pueden modificar el modelo como cualquier otra acción. Sin embargo, cuando se invocan mediante Ajax, las acciones devuelven el valor true a la siguiente función: $esAjax = $this->getRequest()->isXmlHttpRequest(); Symfony es capaz de darse cuenta de que una acción se está ejecutando en un contexto Ajax y puede adaptar la respuesta de forma adecuada. Por tanto, y por defecto, las acciones Ajax no inclu- yen la barra de depuración de aplicaciones ni siquiera en el entorno de desarrollo. Además, no apli- can el proceso de decoración (es decir, sus plantillas no se insertan por defecto en el layout corres- pondiente). Si se necesita decorar la vista de una acción Ajax, se debe indicar explícitamente la op- ción has_layout: true para su vista en el archivo view.yml. Como el tiempo de respuesta es crucial en las interacciones Ajax, si la respuesta es sencilla, es una buena idea no crear la vista completa y devolver la respuesta directamente en forma de texto. Se puede utilizar por tanto el método renderText() en la acción para no utilizar la plantilla y mejo- rar el tiempo de respuesta de las peticiones Ajax. Novedad introducida en la versión en desarrollo: la mayoría de acciones Ajax finalizan con una plantilla que simplemente incluye un elemento parcial, porque el código de la respuesta Ajax ya se ha utilizado para mostrar la página inicial. Para evitar tener que crear una plantilla solo para una lí- nea de código, la acción puede utilizar el método renderPartial(). Este método se aprovecha de las ventajas de la reutilización de los elementos parciales, sus posibilidades de cache y la velocidad de ejecución del método renderText(). public function executeMiAccion() { www.librosweb.es 232
  • 233. Symfony, la guía definitiva Capítulo 11. Integración con Ajax // Código PHP de la acción return $this->renderPartial('mimodulo/miparcial'); } 11.3.1. Enlaces Ajax Los enlaces Ajax constituyen una de las partes más importantes de las interacciones Ajax realizadas en las aplicaciones de la Web 2.0. El helper link_to_remote() muestra un en- lace que llama a una función remota. La sintaxis es muy similar a link_to(), excepto que el segundo parámetro es el array asociativo con las opciones Ajax, como muestra el lista- do 11-9. Listado 11-9 - Enlace Ajax realizado con el helper link_to_remote() <div id="respuesta"></div> <?php echo link_to_remote('Borrar este post', array( 'update' => 'respuesta', 'url' => 'post/borrar?id='.$post->getId(), )) ?> En el ejemplo anterior, al pulsar sobre el enlace “Borrar este post” se realiza una llama- da en segundo plano a la acción post/borrar. La respuesta devuelta por el servidor se muestra automáticamente en el elemento de la página cuyo atributo id sea igual a resp- uesta. La figura 11-1 ilustra el proceso completo. Figura 11.1. Ejecutando una actualización remota mediante un enlace También es posible utilizar una imagen en vez de texto para mostrar el enlace, utilizar el nombre de una regla de enrutamiento en vez de modulo/accion y añadir opciones a la eti- queta <a> como tercer argumento, tal y como muestra el listado 11-10. Listado 11-10 - Opciones del helper link_to_remote() <div id="emails"></div> <?php echo link_to_remote(image_tag('refresh'), array( 'update' => 'emails', 'url' => '@listado_emails', ), array( 'class' => 'enlace_ajax', )) ?> 11.3.2. Formularios Ajax Los formularios web normalmente realizan una llamada a una acción que provoca que se deba recargar la página completa. El helper equivalente a link_to_function() para un formulario sería un helper que enviara los datos del formulario al servidor y que www.librosweb.es 233
  • 234. Symfony, la guía definitiva Capítulo 11. Integración con Ajax actualizara un elemento de la página con la respuesta del servidor. Eso es precisamente lo que hace el helper form_remote_tag(), y su sintaxis se muestra en el listado 11-11. Listado 11-11 - Formulario Ajax con el helper form_remote_tag() <div id="lista_elementos"></div> <?php echo form_remote_tag(array( 'update' => 'lista_elementos', 'url' => 'elemento/anadir', )) ?> <label for="elemento">Elemento:</label> <?php echo input_tag('elemento') ?> <?php echo submit_tag('Añadir') ?> </form> El helper form_remote_tag() crea una etiqueta <form> de apertura, como sucede con el helper form_tag(). El envío del formulario consiste en el envío en segundo plano de una petición de tipo POST a la acción elemento/anadir y con la variable elemento como pará- metro de la petición. La respuesta del servidor reemplaza los contenidos del elemento cuyo atributo id sea igual a lista_elementos, como se muestra en la figura 11-2. Los for- mularios Ajax se cierran con una etiqueta </form> de cierre de formularios. Figura 11.2. Ejecutando una actualización remota mediante un formulario ATENCIÓN Los formularios Ajax no pueden ser multipart, debido a una limitación del objeto XMLHttpReq- uest. En otras palabras, no es posible enviar archivos mediante formularios Ajax. Existen algunas técnicas para saltarse esta limitación, como por ejemplo utilizar un iframe oculto en vez del objeto XMLHttpRequest (se puede ver un ejemplo en http://www.air4web.com/files/upload/). Si es necesario incluir un formulario que sea normal y Ajax a la vez, lo mejor es definirlo como formulario normal y añadir, además del botón de envío tradicional, un segundo botón (<input type=”button” />) para enviar el formulario mediante Ajax. Symfony defi- ne este botón mediante el helper submit_to_remote(). De esta forma, es posible definir interacciones Ajax que se degradan correctamente en los navegadores que no las sopor- tan. El listado 11-12 muestra un ejemplo. Listado 11-12 - Formulario con envío de datos tradicional y Ajax <div id="lista_elementos"></div> <?php echo form_tag('@elemento_anadir_normal') ?> <label for="elemento">Elemento:</label> <?php echo input_tag('elemento') ?> <?php if_javascript(); ?> www.librosweb.es 234
  • 235. Symfony, la guía definitiva Capítulo 11. Integración con Ajax <?php echo submit_to_remote('envio_ajax', 'Anadir con Ajax', array( 'update' => 'lista_elementos', 'url' => '@elemento_anadir', )) ?> <?php end_if_javascript(); ?> <noscript> <?php echo submit_tag('Anadir') ?> </noscript> </form> Otro ejemplo en el que se podría utilizar la combinación de botones normales y botones Ajax es el de un formulario que edita un artículo o noticia. Podría incluir un botón realiza- do con Ajax para previsualizar los contenidos y un botón normal para publicar los conte- nidos directamente. NOTA Si el usuario envía el formulario pulsando la tecla Enter, el formulario se envía utilizando la acción definida en la etiqueta <form> principal, es decir, la acción normal y no la acción Ajax. Los formularios más modernos no solo se encargan de enviar sus datos cuando el usuario pulsa sobre el botón de envío, sino que también pueden reaccionar a los cambios produ- cidos por el usuario sobre alguno de sus campos. Symfony proporciona el helper obser- ve_field() para realizar esa tarea. El listado 11-13 muestra un ejemplo de uso de este helper para crear un sistema que sugiere valores a medida que el usuario escribe sobre un campo: cada carácter escrito en el campo elemento lanza una petición Ajax que actua- liza el valor del elemento sugerencias_elemento de la página. Listado 11-13 - Ejecutando una función remota cada vez que cambia el valor de un campo de formulario mediante observe_field() <?php echo form_tag('@elemento_anadir_normal') ?> <label for="elemento">Elemento:</label> <?php echo input_tag('elemento') ?> <div id="sugerencias_elemento"></div> <?php echo observe_field('elemento', array( 'update' => 'sugerencias_elemento', 'url' => '@elemento_escrito', )) ?> <?php echo submit_tag('Anadir') ?> </form> El par modulo/accion correspondiente a la regla @elemento_escrito se ejecuta cada vez que el usuario modifica el valor del campo de formulario que se está observando (en este caso, elemento) sin necesidad de enviar el formulario. La acción puede acceder a los ca- racteres escritos en cada momento por el usuario mediante el parámetro elemento de la petición. Si se necesita enviar otro valor en vez del contenido del campo de formulario que se está observando, se puede especificar en forma de expresión JavaScript en el parámetro with. Si por ejemplo es necesario que la acción disponga de un parámetro lla- mado param, se puede utilizar el helper observe_field() como muestra el listado 11-14. Listado 11-14 - Pasando parámetros personalizados a la acción remota con la opción with www.librosweb.es 235
  • 236. Symfony, la guía definitiva Capítulo 11. Integración con Ajax <?php echo observe_field('elemento', array( 'update' => 'sugerencias_elemento', 'url' => '@elemento_escrito', 'with' => "'param=' + value", )) ?> Este helper no genera un elemento HTML, sino que añade un comportamiento (del inglés, “behavior”) al elemento que se pasa como parámetro. Más adelante en este capítulo se describen más ejemplos de helpers de JavaScript que añaden comportamientos. Si se quieren observar todos los campos de un formulario, se puede utilizar el helper ob- serve_form(), que llama a una función remota cada vez que se modifica uno de los cam- pos del formulario. 11.3.3. Ejecución periódica de funciones remotas Por último, el helper periodically_call_remote() permite crear una interacción de Ajax que se repite cada pocos segundos. No está asociado con ningún elemento HTML de la página, sino que se ejecuta de forma transparente en segundo plano como una especie de comportamiento de la página entera. Se puede utilizar para seguir la posición del pun- tero del ratón, autoguardar el contenido de un área de texto grande, etc. El listado 11-15 muestra un ejemplo de uso de este helper. Listado 11-15 - Ejecutando periódicamente una función remota mediante periodically_call_remote() <div id="notificacion"></div> <?php echo periodically_call_remote(array( 'frequency' => 60, 'update' => 'notificacion', 'url' => '@observa', 'with' => "'param=' + $F('micontenido')", )) ?> Si no se especifica el número de segundos (mediante el parámetro frequency) que se es- peran después de cada repetición, se tiene en cuenta el valor por defecto que son 10 se- gundos. El parámetro with se evalúa con JavaScript, así que se puede utilizar cualquier función de Prototype, como por ejemplo la función $F(). 11.4. Parámetros para la ejecución remota Todos los helpers de Ajax descritos anteriormente pueden utilizar otros parámetros, además de los parámetros update y url. El array asociativo con los parámetros de Ajax puede modificar el comportamiento de la ejecución remota y del procesamiento de las respuestas. 11.4.1. Actualizar elementos diferentes en función del estado de la respuesta Si la ejecución remota no devuelve un resultado, los helpers pueden actualizar otro ele- mento distinto al elemento que se actualizaría en caso de una respuesta satisfactoria. www.librosweb.es 236
  • 237. Symfony, la guía definitiva Capítulo 11. Integración con Ajax Para conseguirlo, solo es necesario indicar como valor del parámetro update un array asociativo que establezca los diferentes elementos que se actualizan en caso de respues- ta correcta (success) y respuesta incorrecta (failure). Se trata de una técnica eficaz cuando una página contiene muchas interacciones de Ajax y una única zona de notifica- ción de errores. El listado 11-16 muestra el uso de esta técnica. Listado 11-16 - Actualización condicional en función de la respuesta <div id="error"></div> <div id="respuesta"></div> <p>¡Hola Mundo!</p> <?php echo link_to_remote('Borrar este artículo', array( 'update' => array('success' => 'respuesta', 'failure' => 'error'), 'url' => 'articulo/borrar?id='.$articulo->getId(), )) ?> SUGERENCIA Solo las respuestas de servidor cuyo código de estado HTTP sea de tipo error (500, 404 y todos los códigos diferentes de 2XX) provocan la actualización del elemento preparado para las respuestas erroneas. Las acciones que devuelven el valor sfView::ERROR no se consideran como erróneas. De esta forma, si se requiere que una acción de tipo Ajax devuelva una respuesta errónea, se debe ejecutar $this->getResponse()->setStatusCode(404) con cualquier código HTTP de error. 11.4.2. Actualizar un elemento según su posición Al igual que sucede con el helper update_element_function(), se puede especificar el ele- mento a actualizar de forma relativa respecto de otro elemento mediante el parámetro position. El listado 11-17 muestra un ejemplo. Listado 11-17 - Uso del parámetro position para modificar el lugar donde se muestra la respuesta <div id="respuesta"></div> <p>¡Hola Mundo!</p> <?php echo link_to_remote('Borrar este artículo', array( 'update' => 'respuesta', 'url' => 'articulo/borrar?id='.$articulo->getId(), 'position' => 'after', )) ?> En esta ocasión, la respuesta de la petición Ajax se muestra después (after) del elemen- to cuyo atributo id es igual a respuesta; es decir, se muestra después del <div> y antes del <p>. De esta forma, se pueden realizar varias peticiones Ajax y ver como se acumulan todas las respuestas después del elemento que se actualiza. El parámetro position puede tomar uno de los siguientes valores: ▪ before: antes del elemento ▪ after: después del elemento ▪ top: antes que cualquier otro contenido del elemento www.librosweb.es 237
  • 238. Symfony, la guía definitiva Capítulo 11. Integración con Ajax ▪ bottom: después de todos los contenidos del elemento 11.4.3. Actualizar un elemento en función de una condición Las peticiones Ajax pueden tomar un parámetro adicional que permite que el usuario de su consentimiento antes de ejecutar la petición con el objeto XMLHttpRequest, como muestra el listado 11-18. Listado 11-18 - Uso del parámetro confirm para solicitar el consentimiento del usuario antes de realizar la petición remota <div id="respuesta"></div> <?php echo link_to_remote('Borrar este artículo', array( 'update' => 'respuesta', 'url' => 'articulo/borrar?id='.$articulo->getId(), 'confirm' => '¿Estás seguro?', )) ?> En este caso, se muestra al usuario un cuadro de diálogo de JavaScript con el mensaje “¿Estás seguro?” cuando pincha sobre el enlace. La acción articulo/borrar solo se ejecu- ta si el usuario da su consentimiento a esta petición pulsando sobre el botón de “Aceptar”. La ejecución de la petición remota también se puede condicionar a que se cumpla una condición JavaScript evaluada en el navegador del usuario, mediante el parámetro condi- tion, tal y como se muestra en el listado 11-19. Listado 11-19 - Ejecución de petición remota condicionada a que se cumpla una condición probada en el lado del cliente <div id="respuesta"></div> <?php echo link_to_remote('Borrar este artículo', array( 'update' => 'respuesta', 'url' => 'articulo/borrar?id='.$articulo->getId(), 'condition' => "$('IDelemento') == true", )) ?> 11.4.4. Determinando el método de una petición Ajax Las peticiones Ajax se realizan por defecto mediante un método POST. Si se quiere reali- zar una petición Ajax que no modifica los datos o si se quiere mostrar un formulario que incluye validación como resultado de una petición Ajax, se puede utilizar el método GET. La opción method modifica el método de la petición Ajax, como muestra el listado 11-20. Listado 11-20 - Modificando el método de una petición Ajax <div id="respuesta"></div> <?php echo link_to_remote('Borrar este artículo', array( 'update' => 'respuesta', 'url' => 'articulo/borrar?id='.$articulo->getId(), 'method' => 'get', )) ?> www.librosweb.es 238
  • 239. Symfony, la guía definitiva Capítulo 11. Integración con Ajax 11.4.5. Permitiendo la ejecución de un script Si la respuesta de una petición Ajax incluye código JavaScript (el código es la respuesta del servidor y se incluye en el elemento indicado por el parámetro update) por defecto no se ejecuta ese código. El motivo es el de reducir la posibilidad de ataques remotos y para permitir al programador autorizar la ejecución del código de la respuesta después de comprobar el contenido del código. Para permitir la ejecución de los scripts de la respuesta del servidor, se debe utilizar la opción script. El listado 11-21 muestra un ejemplo de una petición Ajax remota que au- toriza la ejecución del código JavaScript que forme parte de la respuesta. Listado 11-21 - Permitiendo la ejecución de un script en una respuesta Ajax <div id="respuesta"></div> // Si la respuesta de la acción articulo/borrar contiene código // JavaScript, se ejecuta en el navegador del usuario <?php echo link_to_remote('Borrar este artículo', array( 'update' => 'respuesta', 'url' => 'articulo/borrar?id='.$articulo->getId(), 'script' => 'true', )) ?> Si la plantilla remota contiene helpers de Ajax (como por ejemplo remote_function()), estas funciones PHP generan código JavaScript, que no se ejecuta a menos que se indiq- ue la opción script => true. NOTA Cuando se permite la ejecución de los scripts de la respuesta remota, el código fuente del código remoto no se puede ver ni siquiera con una herramienta para visualizar el código generado. Los scripts se ejecutan pero su código no se muestra. Se trata de un comportamiento poco habitual, pe- ro completamente normal. 11.4.6. Creando callbacks Una desventaja importante de las interacciones creadas con Ajax es que son invisibles al usuario hasta que se actualiza la zona preparada para las notificaciones. Por tanto, si se produce un error de servidor o la red está congestionada, los usuarios pueden pensar que su acción se ha realizado correctamente cuando en realidad aun no ha sido procesa- da. Este es el motivo por el que es muy importante notificar al usuario sobre los eventos que se producen a lo largo de una interacción creada con Ajax. Por defecto, cada petición remota es un proceso asíncrono durante el que se pueden eje- cutar varias funciones JavaScript de tipo callback (por ejemplo para indicar el progreso de la petición). Todas las funciones de callback tienen acceso directo al objeto request, que contiene a su vez el objeto XMLHttpRequest. Los callback que se pueden definir se co- rresponden con los eventos que se producen durante una interacción de Ajax: ▪ before: antes de que se inicie la petición ▪ after: justo después de que se inicie la petición y antes de que se cargue www.librosweb.es 239
  • 240. Symfony, la guía definitiva Capítulo 11. Integración con Ajax ▪ loading: cuando se está cargando la respuesta remota en el navegador ▪ loaded: cuando el navegador ha terminado de cargar la respuesta remota ▪ interactive: cuando el usuario puede interaccionar con la respuesta remota, in- cluso si no se ha terminado de cargar ▪ success: cuando XMLHttpRequest está completo y el código HTTP de estado co- rresponde al rango 2XX ▪ failure: cuando XMLHttpRequest está completo y el código HTTP de estado no co- rresponde al rango 2XX ▪ 404: cuando la petición devuelve un error de tipo 404 ▪ complete: cuando XMLHttpRequest está completo (se ejecuta después de success o failure, si alguno de los 2 está definido) El ejemplo más habitual es el de mostrar un indicador de tipo Cargando... mientras la petición remota se está ejecutando y ocultarlo cuando se recibe la respuesta. Para incluir este comportamiento, solo es necesario añadir los parámetros loading y complete a la petición Ajax, tal y como muestra el listado 11-22. Listado 11-22 - Uso de callbacks en Ajax para mostrar y ocultar un indicador de actividad <div id="respuesta"></div> <div id="indicador">Cargando...</div> <?php echo link_to_remote('Borrar este artículo', array( 'update' => 'respuesta', 'url' => 'articulo/borrar?id='.$articulo->getId(), 'loading' => "Element.show('indicador')", 'complete' => "Element.hide('indicador')", )) ?> Los métodos show(), hide() y el objeto Element son otras de las utilidades proporciona- das por la librería Prototype. 11.5. Creando efectos visuales Symfony integra los efectos visuales de la librería script.aculo.us, para poder incluir efec- tos más avanzados que simplemente mostrar y ocultar elementos <div> en las páginas. La mejor documentación sobre la sintaxis que se puede emplear en los efectos se enc- uentra en el wiki de la librería en http://script.aculo.us/. Básicamente, la librería se en- carga de proporcionar objetos y funciones JavaScript que manipulan el DOM de la página para conseguir efectos visuales complejos. El listado 11-23 incluye algunos ejemplos. Co- mo el resultado es una animación o efecto visual de ciertas partes de la página, es reco- mendable que pruebes los efectos para entender bien en qué consiste cada efecto. El sit- io web de script.aculo.us incluye una galería donde se pueden ver sus efectos visuales. Listado 11-23 - Efectos visuales en JavaScript con Script.aculo.us // Resalta el elemento 'mi_elemento' Effect.Highlight('mi_elemento', { startcolor:'#ff99ff', endcolor:'#999999' }) www.librosweb.es 240
  • 241. Symfony, la guía definitiva Capítulo 11. Integración con Ajax // Oculta un elemento Effect.BlindDown('mi_elemento'); // Hace desaparecer un elemento Effect.Fade('mi_elemento', { transition: Effect.Transitions.wobble }) Symfony encapsula el objeto Effect de JavaScript en un helper llamado visual_effect(), que forma parte del helper Javascript. El código generado es JavaScript y puede utilizar- se en un enlace normal, como muestra el listado 11-24. Listado 11-24 - Efectos visuales en las plantillas con el helper visual_effect() <div id="div_oculto" style="display:none">¡Aquí estaba!</div> <?php echo link_to_function( 'Mostrar el DIV oculto', visual_effect('appear', 'div_oculto') ) ?> // Equivalente a llamar a Effect.Appear('div_oculto') El helper visual_effects() también se puede utilizar en los callbacks de Ajax, como en el listado 11-22, que muestra un indicador de actividad de forma más elegante que en el listado 11-22. El elemento indicador aparece de forma progresiva cuando comienza la petición Ajax y se desaparece también progresivamente cuando se recibe la respuesta del servidor. Además, el elemento respuesta se resalta después de ser actualizado por la petición remota, de forma que esa parte de la página capte la atención del usuario. Listado 11-25 - Efectos visuales en los callbacks de Ajax <div id="respuesta"></div> <div id="indicador" style="display: none">Cargando...</div> <?php echo link_to_remote('Borrar este artículo', array( 'update' => 'respuesta', 'url' => 'articulo/borrar?id='.$articulo->getId(), 'loading' => visual_effect('appear', 'indicator'), 'complete' => visual_effect('fade', 'indicator'). visual_effect('highlight', 'feedback'), )) ?> Los efectos visuales se pueden combinar de forma muy sencilla concatenando sus llama- das (mediante el .) dentro de un callback. 11.6. JSON JSON (JavaScript Object Notation) es un formato sencillo para intercambiar datos. Con- siste básicamente en un array asociativo de JavaScript (ver ejemplo en el listado 11-26) que se utilizar para incluir información del objeto. JSON ofrece 2 grandes ventajas para las interacciones Ajax: es muy fácil de leer en JavaScript y puede reducir el tamaño en bytes de la respuesta del servidor. Listado 11-26 - Ejemplo de objeto JSON en JavaScript var misDatosJson = {"menu": { "id": "archivo", "valor": "Archivo", www.librosweb.es 241
  • 242. Symfony, la guía definitiva Capítulo 11. Integración con Ajax "popup": { "menuitem": [ {"value": "Nuevo", "onclick": "CrearNuevoDocumento()"}, {"value": "Abrir", "onclick": "AbrirDocumento()"}, {"value": "Cerrar", "onclick": "CerrarDocumento()"} ] } }} El formato JSON es el más adecuado para la respuesta del servidor cuando la acción Ajax debe devolver una estructura de datos a la página que realizó la llamada de forma que se pueda procesar con JavaScript. Este mecanismo es útil por ejemplo cuando una sola peti- ción Ajax debe actualizar varios elementos en la página. En el listado 11-27 se muestra un ejemplo de página que contiene 2 elementos que de- ben ser actualizados. Un helper remoto solo puede actualizar uno de los elementos de la página (o titulo o nombre) pero no los 2 a la vez. Listado 11-27 - Plantilla de ejemplo para actualizaciones Ajax múltiples <h1 id="titulo">Carta normal</h1> <p>Estimado <span id="nombre">el_nombre</span>,</p> <p>Hemos recibido su email y le contestaremos en el menor plazo de tiempo.</p> <p>Reciba un saludo cordial,</p> Para actualizar los 2 elementos, la respuesta Ajax podría consistir en una única cabecera JSON cuyo contenido fuera el siguiente array: [["titulo", "Mi carta normal"], ["nombre", "Sr. Pérez"]] Mediante algunas pocas instrucciones de JavaScript se puede interpretar la respuesta del servidor y actualizar varios elementos de la página de forma seguida. El listado 11-28 muestra el código que se podría añadir a la plantilla del listado 11-27 para conseguir este efecto. Listado 11-28 - Actualizando más de un elemento mediante una respuesta remota <?php echo link_to_remote('Actualizar la carta', array( 'url' => 'publicaciones/actualizar', 'complete' => 'actualizaJSON(request, json)' )) ?> <?php echo javascript_tag(" function actualizaJSON(request, json) { var numeroElementos = json.length; for (var i = 0; i < numeroElementos; i++) { Element.update(json[i][0], json[i][1]); } } ") ?> El callback complete tiene acceso directo a la cabecera json de la respuesta y por tanto puede enviarlo a una función externa. La función actualizaJSON() recorre la cabecera www.librosweb.es 242
  • 243. Symfony, la guía definitiva Capítulo 11. Integración con Ajax JSON y para cada elemento del array actualiza el elemento cuyo atributo id coincide con el primer parámetro del array y muestra el contenido incluido en el segundo parámetro del array. El listado 11-29 muestra como devuelve la acción publicaciones/actualizar una resp- uesta de tipo JSON. Listado 11-29 - Acción de ejemplo devolviendo una cabecera JSON class publicacionesActions extends sfActions { public function executeActualizar() { $salida = '[["titulo", "Mi carta normal"], ["nombre", "Sr. Pérez"]]'; $this->getResponse()->setHttpHeader("X-JSON", '('.$salida.')'); return sfView::HEADER_ONLY; } El protocolo HTTP permite que la respuesta JSON se pueda enviar como una cabecera de la respuesta. Como la respuesta no tiene ningún contenido, la acción envía solo la cabe- cera de forma inmediata. De esta forma, se evita completamente la capa de la vista y es tan rápido como ->renderText() pero además con una respuesta más pequeña. ATENCIÓN Existe una limitación muy importante a la técnica mostrada en el listado 11-29: el tamaño máximo de las cabeceras HTTP. Aunque no existe un límite oficial, las cabeceras grandes pueden no trans- mitirse correctamente o no interpretarse bien en el navegador. De esta forma, si el array JSON es grande, la acción remota debería devolver una respuesta normal con los datos JSON incluídos co- mo un array de JavaScript. JSON se ha convertido en un estandar en el desarrollo de aplicaciones web. Los servicios web proponen la utilización de JSON en vez de XML para permitir la integración de servi- cios en el navegador del usuario en vez de en el servidor. El formato JSON es segura- mente la mejor opción para el intercambio de información entre el servidor y las funcio- nes JavaScript. SUGERENCIA Desde la versión 5.2 de PHP existen 2 funciones, json_encode() y json_decode(), que permiten convertir un array PHP en un array JSON y viceversa (http://www.php.net/manual/en/ref.j- son.php). Estas funciones facilitan la integración de los arrays JSON y de Ajax en general. 11.7. Interacciones complejas con Ajax Entre los helpers de Ajax de Symfony, también existen utilidades que permiten construir interacciones complejas con una sola llamada a una función. Estas utilidades permiten mejorar la experiencia de usuario añadiendo características propias de las aplicaciones de escritorio (arrastrar y soltar, autocompletar, edición directa de contenidos, etc.) sin nece- sidad de escribir código JavaScript. En las siguientes secciones se describen los helpers de las interacciones complejas mediante ejemplos sencillos. Los parámetros adicionales y otras configuraciones se pueden consultar en la documentación de script.aculo.us. www.librosweb.es 243
  • 244. Symfony, la guía definitiva Capítulo 11. Integración con Ajax ATENCIÓN Aunque es sencillo incluir interacciones complejas, lo más complicado es configurarlas de forma que el usuario las perciba como algo natural en la página. Por tanto, solo se deben utilizar cuando se está seguro de que va a mejorar la experiencia de usuario. No deberían incluirse cuando su efecto es el de confundir al usuario. 11.7.1. Autocompletar La interacción denominada “autocompletar” consiste en un cuadro de texto que muestra una lista de valores relacionados con los caracteres que teclea el usuario. Este efecto se puede conseguir con una única llamada al helper input_auto_complete_tag(), siempre que la acción remota devuelva una respuesta formateada como una lista de elementos HTML (<ul> y <li>) similar a la mostrada en el ejemplo 11-30. Listado 11-30 - Ejemplo de respuesta compatible con el helper de autocompletar <ul> <li>sugerencia 1</li> <li>sugerencia 2</li> ... </ul> El helper se puede incluir en cualquier plantilla de la misma forma que se incluiría cualqu- ier cuadro de texto, como se muestra en el ejemplo 11-31. Listado 11-31 - Uso del helper de autocompletar en una plantilla <?php echo form_tag('mimodulo/miaccion') ?> Buscar un autor en función de su nombre: <?php echo input_auto_complete_tag('autor', 'nombre por defecto', 'autor/autocompletar', array('autocomplete' => 'off'), array('use_style' => true) ) ?> <?php echo submit_tag('Buscar') ?> </form> Cada vez que el usuario teclee un carácter en el cuadro de texto autor, se realiza una lla- mada a la acción remota autor/autocompletar. El código de esa acción depende de cada caso y aplicación, pero debe obtener una lista de valores sugeridos en forma de lista de elementos HTML como la mostrada en el listado 11-30. El helper muestra la lista devuel- ta debajo del cuadro de texto autor y si el usuario pincha sobre un valor o lo selecciona mediante el teclado, el cuadro de texto se completa con ese valor, tal y como muestra la figura 11-3. Figura 11.3. Ejemplo de autocompletar www.librosweb.es 244
  • 245. Symfony, la guía definitiva Capítulo 11. Integración con Ajax El tercer argumento del helper input_auto_complete_tag() puede tomar uno de los sigu- ientes parámetros: ▪ use_style: aplica estilos CSS de forma automática a la lista que se muestra. ▪ frequency: frecuencia con la que se realizan peticiones remotas (por defecto son 0.4 segundos). ▪ tokens: permite autocompletar por partes. Si el valor de este parámetro es , y el usuario introduce pedro, juan a la acción solo se le pasa el valor juan (siempre se le pasa el último valor después de trocear el cuadro de texto según el carácter definido por tokens). NOTA El helper input_auto_complete_tag(), al igual que los que se muestran a continuación, también acepta las opciones habituales de los helpers remotos que se han descrito anteriormente en este capítulo. Una buena recomendación es la de utilizar los efectos visuales loading y complete para mejorar la experiencia de usuario. 11.7.2. Arrastrar y soltar En las aplicaciones de escritorio suele ser normal coger un elemento con el ratón, mover- lo y soltarlo en otro lugar. Sin embargo, en las aplicaciones web es mucho más raro de ver esta técnica, ya que es bastante difícil de programarla a mano con JavaScript. Afortu- nadamente, en Symfony se puede incluir esta técnica solo con una línea de código. El framework incluye 2 helpers, draggable_element() y drop_receiving_element(), que se encargan de modificar el comportamiento de los elementos; estos helpers “observan” a los elementos y les añaden nuevas habilidades. Se utilizan para declarar a los elementos como “arrastrable” o como “elemento en el que se pueden soltar los elementos arrastra- bles”. Un elemento arrastrable se activa cuando se pulsa con el ratón sobre el. Mientras no se suelte el ratón, el elemento se mueve siguiendo la posición del ratón. Los elemen- tos en los que se pueden soltar los elementos arrastrables llaman a una función remota cuando el elemento arrastrable se suelta sobre esa zona. El listado 11-32 muestra un ejemplo de esta interacción mediante un elemento que hace de carrito de la compra. Listado 11-32 - Elementos de arrastrar y soltar en un carrito de la compra <ul id="elementos"> <li id="elemento1" class="comida">Zanahoria</li> <?php echo draggable_element('elemento1', array('revert' => true)) ?> <li id="elemento2" class="comida">Manzana</li> <?php echo draggable_element('elemento2', array('revert' => true)) ?> <li id="elemento3" class="comida">Naranja</li> <?php echo draggable_element('elemento3', array('revert' => true)) ?> </ul> <div id="carrito"> <p>El carrito está vacío</p> <p>Arrastra y suelta elementos aquí para añadirlos al carrito</p> </div> www.librosweb.es 245
  • 246. Symfony, la guía definitiva Capítulo 11. Integración con Ajax <?php echo drop_receiving_element('carrito', array( 'url' => 'carrito/anadir', 'accept' => 'comida', 'update' => 'carrito', )) ?> Cada uno de los elementos de la lista se pueden coger con el ratón y moverlos por la ventana del navegador. Cuando se suelta el ratón, el elemento vuelve a su posición origi- nal. Si el elemento se suelta sobre el elemento cuyo atributo id es carrito, se realiza una llamada a la acción remota carrito/anadir. La acción puede determinar el elemento que se ha añadido mediante el parámetro de petición id. De esta forma, el listado 11-32 es una aproximación muy realista al proceso físico de compra de productos: se cogen los productos, se sueltan en el carrito y después se realiza el pago. SUGERENCIA En el lsitado 11-32, los helpers aparecen justo después del elemento que modifican, aunque no es obligatorio. Si se quiere, se pueden agrupar todos los helpers draggable_element() y drop_rec- eiving_element() al final de la plantilla. Lo único importante es el primer argumento que se pasa al helper y que indica el elemento al que se aplica. El helper draggable_element() acepta los siguientes parámetros: ▪ revert: si vale true, el elemento vuelve a su posición original cuando se suelta el ratón. También se puede indicar el nombre de una función que se ejecuta cuando finaliza el arrastre del elemento. ▪ ghosting: realiza una copia del elemento original y el usuario mueve la copia, quedando inmóvil el elemento original. ▪ snap: si vale false, el movimiento del elemento es libre. En otro caso, el elemen- to solo se puede desplazar de forma escalonada como si estuviera una gran reji- lla a la que se ajusta el elemento. El valor del desplazamiento horizontal (x) y vertical (y) del elemento se puede definir como xy, [x,y] o function(x,y){ re- turn [x,y] }. El helper drop_receiving_element() acepta los siguientes parámetros: ▪ accept: una cadena de texto o un array de cadenas de texto que representan a valores de clases CSS. Este elemento solo permitirá que se suelten sobre el los elementos cuyas clases CSS contengan al menos uno de los valores indicado. ▪ hoverclass: clase CSS que se añade al elemento cuando el usuario arrastra (sin soltarlo) un elemento sobre esta zona. 11.7.3. Listas ordenables Otra posibilidad que brindan los elementos arrastrables es la de ordenar una lista mov- iendo sus elementos con el ratón. El helper sortable_element() añade este comportam- iento a los elementos de la lista, como se muestra en el ejemplo del listado 11-33. Listado 11-33 - Ejemplo de lista ordenable www.librosweb.es 246
  • 247. Symfony, la guía definitiva Capítulo 11. Integración con Ajax <p>What do you like most?</p> <ul id="ordenar"> <li id="elemento_1" class="ordenable">Zanahorias</li> <li id="elemento_2" class="ordenable">Manzanas</li> <li id="elemento_3" class="ordenable">Naranjas</li> // A nadie le gustan las coles de Bruselas <li id="elemento_4">Coles de Bruselas</li> </ul> <div id="respuesta"></div> <?php echo sortable_element('ordenar', array( 'url' => 'elemento/ordenar', 'update' => 'respuesta', 'only' => 'ordenable', )) ?> Gracias a la magia del helper sortable_element(), la lista <ul> se transforma en una lista ordenable dinámicamente, de forma que sus elementos se pueden reordenar mediante la técnica de arrastras y soltar. Cada vez que el usuario mueve un elemento y lo suelta pa- ra reordenar la lista, se realiza una petición Ajax con los siguientes parámetros: POST /sf_sandbox/web/frontend_dev.php/elemento/ordenar HTTP/1.1 ordenar[]=1&ordenar[]=3&ordenar[]=2&_= La lista completa se pasa como un array con el formato ordenar[$rank]=$id, el $rank em- pieza en 0 y el $id es el valor que se indica después del guión bajo (_) en el valor del atributo id de cada elemento de la lista. El atributo id de la lista completa (ordenar en este caso) se utiliza para el nombre del array de parámetros que se pasan al servidor. El helper sortable_element() acepta los siguientes parámetros: ▪ only: una cadena de texto o un array de cadenas de texto que representan a va- lores de clases CSS. Solamente se podrán mover los elementos de la lista que tengan este valor en su atributo class. ▪ hoverclass: clase CSS que se añade a la lista cuando el usuario posiciona el pun- tero del ratón encima de ella. ▪ overlap: su valor debería ser horizontal si los elementos de la lista se muestran de forma horizontal y su valor debería ser vertical (que es el valor por defecto) cuando los elementos se muestran cada uno en una línea (como se muestran por defecto las listas en HTML). ▪ tag: si la lista reordenable no contiene elemento <li>, se debe indicar la etiqueta que define los elementos que se van a hacer reordenables (por ejemplo div o dl). 11.7.4. Edición directa de contenidos Cada vez más aplicaciones web permiten editar los contenidos de sus páginas sin necesi- dad de utilizar formularios que incluyen el contenido de la página. El funcionamiento de esta interacción es muy sencillo. Cuando el usuario pasa el ratón por encima de un bloq- ue de texto, este se resalta. Si el usuario pincha sobre el bloque, el texto se convierte en un control de formulario llamado área de texto (textarea) que muestra el texto original. www.librosweb.es 247
  • 248. Symfony, la guía definitiva Capítulo 11. Integración con Ajax Además, se muestra un botón para guardar los cambios. El usuario realiza los cambios en el texto original y pulsa sobre el botón de guardar los cambios. Una vez guardado, el área de texto desaparece y el texto modificado se vuelve a mostrar de forma normal. Con Symfony, toda esta interacción se puede realizar aplicando el helper input_in_pla- ce_editor_tag() al elemento. El listado 11-34 muestra el uso de este helper. Listado 11-34 - Ejemplo de texto editable <div id="modificame">Puedes modificar este texto</div> <?php echo input_in_place_editor_tag('modificame', 'mimodulo/miaccion', array( 'cols' => 40, 'rows' => 10, )) ?> Cuando el usuario pincha sobre el texto editable, se reemplaza por un cuadro de texto que contiene el texto original y que se puede modificar. Al guardar los cambios, se llama mediante Ajax a la acción mimodulo/miaccion con el contenido modificado como valor del parámetro value. El resultado de la acción actualiza el elemento editable. Se trata de una interacción muy rápida de incluir y muy poderosa. El helper input_in_place_editor_tag() acepta los siguientes parámetros: ▪ cols y rows: el tamaño (en filas y columnas) del área de texto que se muestra para editar el contenido original (si el valor de rows es mayor que 1, se muestra un <textarea>; en otro caso, se muestra un <input type=”text”>). ▪ loadTextURL: la URI de la acción que se llama para obtener el texto que se debe editar. Se trata de una opción útil cuando el contenido del elemento tiene un for- mato especial y se quiere que el usuario edite el texto sin ese formato aplicado. ▪ save_text y cancel_text: el texto del enlace para guardar los cambios (el valor por defecto es “ok”) y el del enlace para cancelar los cambios (el valor por defec- to es “cancel”). 11.8. Resumen Si estás cansado de escribir código JavaScript en las plantillas para incluir efectos en el navegador del usuario, los helpers de JavaScript de Symfony son una alternativa más sencilla. No solo automatizan los enlaces JavaScript tradicionales y la actualización de los elementos, sino que también permiten incluir interacciones Ajax de forma muy sencilla. Gracias a las mejoras que Prototype proporciona a la sintaxis de JavaScript y gracias a los efectos visuales de la librería script.aculo.us, hasta las interacciones más complejas se pueden realizar con unas pocas líneas de código. Y como en Symfony es igual de fácil hacer una página estática que una página completa- mente interactiva y dinámica, las aplicaciones web pueden incluir todas las interacciones tradicionales de las aplicaciones de escritorio. www.librosweb.es 248
  • 249. Symfony, la guía definitiva Capítulo 12. Uso de la cache Capítulo 12. Uso de la cache Una de las técnicas disponibles para mejorar el rendimiento de una aplicación consiste en almacenar trozos de código HTML o incluso páginas enteras para poder servirlas en futu- ras peticiones. Esta técnica se denomina “utilizar caches” y se pueden definir tanto en el lado del servidor como en el del cliente. Symfony incluye un sistema de cache en el servidor muy flexible. Con este sistema es muy sencillo guardar en un archivo una página entera, el resultado de una acción, un elemento parcial o un trozo de plantilla. La configuración del sistema de cache se realiza de forma intuitiva mediante archivos de tipo YAML. Cuando los datos se modifican, se pueden borrar partes de la cache de forma selectiva mediante la línea de comandos o mediante algunos métodos especiales en las acciones. Symfony también permite contro- lar la cache en el lado del cliente mediante las cabeceras de HTTP 1.1. En este capítulo se presentan todas estas técnicas y se dan pistas para determinar las mejoras que las ca- ches confieren a las aplicaciones. 12.1. Guardando la respuesta en la cache El principio básico de las caches de HTML es muy sencillo: parte o todo el código HTML que se envía al usuario como respuesta a su petición se puede reutilizar en peticiones si- milares. El código HTML se almacena en un directorio especial (el directorio cache/) don- de el controlador frontal lo busca antes de ejecutar la acción. Si se encuentra el código en la cache, se envía sin ejecutar la acción, por lo que se consigue un gran ahorro de tiempo de ejecución. Si no se encuentra el código, se ejecuta la acción y su respuesta (la vista) se guarda en el directorio de la cache para las futuras peticiones. Como todas las páginas pueden contener información dinámica, la cache HTML está deshabilitada por defecto. El administrador del sitio web debe activarla para mejorar el rendimiento de la aplicación. Symfony permite gestionar 3 tipos diferentes de cache HTML: ▪ Cache de una acción (con o sin layout) ▪ Cache de un elemento parcial, de un componente o de un slot de componentes ▪ Cache de un trozo de plantilla Los dos primeros tipos de cache se controlan mediante archivos YAML de configuración. La cache de trozos de plantillas se controla mediante llamadas a helpers dentro de las propias plantillas. 12.1.1. Opciones de la cache global La cache HTML se puede habilitar y deshabilitar (su valor por defecto) para cada aplica- ción de un proyecto y para cada entorno mediante la opción cache del archivo set- tings.yml. El listado 12-1 muestra como habilitar la cache. Listado 12-1 - Activando la cache, en miapp/config/settings.yml www.librosweb.es 249
  • 250. Symfony, la guía definitiva Capítulo 12. Uso de la cache dev: .settings: cache: on 12.1.2. Guardando una acción en la cache Las acciones que muestran información estática (que no depende de bases de datos ni de información guardada en la sesión) y las acciones que leen información de una base de datos pero no la modifican (acciones típicas del método GET) son el tipo de acción ideal para almacenar su resultado en la cache. La figura 12-1 muestra los elementos de la pá- gina que se guardan en la cache en este caso: o el resultado de la acción (su plantilla) o el resultado de la acción junto con el layout. Figura 12.1. Guardando una acción en la cache Si se dispone por ejemplo de una acción usuario/listado que devuelve un listado de to- dos los usuarios de un sitio web, a no ser que se modifique, añada o elimine un usuario (que se verá más adelante en la sección “Eliminar elementos de la cache”) la lista contie- ne siempre la misma información, por lo que esta acción es ideal para guardarla en la cache. La activación de la cache y las opciones para cada acción se definen en el archivo ca- che.yml del directorio config/ del módulo. El listado 12-2 muestra un ejemplo de este archivo. Listado 12-2 - Activando la cache de una acción, en miapp/modules/usuario/config/ cache.yml listado: enabled: on with_layout: false # Valor por defecto lifetime: 86400 # Valor por defecto La anterior configuración activa la cache para la acción listado y el layout no se guarda junto con el resultado de la acción (que además, es el comportamiento por defecto). Por tanto, aunque exista en la cache el resultado de la acción, el layout completo (junto con sus elementos parciales y componentes) se sigue ejecutando. Si la opción with_layout vale true, en la cache se guarda el resultado de la acción junto con el layout, por lo que este último no se vuelve a ejecutar. Para probar las opciones de la cache, se accede con el navegador a la acción en el entor- no de desarrollo. www.librosweb.es 250
  • 251. Symfony, la guía definitiva Capítulo 12. Uso de la cache http://miapp.ejemplo.com/miapp_dev.php/usuario/listado Ahora se puede apreciar un borde que encierra la zona del área en la página. La primera vez, el área tiene una cabecera azul, lo que indica que no se ha obtenido de la cache. Si se recarga la página, el área de la acción muestra una cabecera amarilla, indicando que esta vez sí se ha obtenido directamente de la cache (resultando en una gran reducción en el tiempo de respuesta de la acción). Más adelante en este capítulo se detallan las for- mas de probar y monitorizar el funcionamiento de la cache. NOTA Los slots son parte de la plantilla, por lo que si se guarda el resultado de una acción en la cache, también se guarda el valor de los slots definidos en la plantilla de la acción. De esta forma, la cache funciona de forma nativa para los slots. El sistema de cache también funciona para las páginas que utilizan parámetros. El módu- lo usuario anterior podría disponer de una acción llamada ver y a la que se pasa como parámetro una variable llamada id para poder mostrar los detalles de un usuario. El lis- tado 12-3 muestra como modificar los cambios necesarios en el archivo cache.yml para habilitar la cache también en esta acción. Se puede organizar de forma más clara el archivo cache.yml reagrupando las opciones comunes a todas las acciones del módulo bajo la clave all:, como también muestra el listado 12-3. Listado 12-3 - Ejemplo de cache.yml completo, en miapp/modules/usuario/config/ cache.yml listado: enabled: on ver: enabled: on all: with_layout: false # Valor por defecto lifetime: 86400 # Valor por defecto Ahora, cada llamada a la acción usuario/ver que tenga un valor del parámetro id dife- rente, crea un nuevo archivo en la cache. De esta forma, la cache para la petición: http://miapp.ejemplo.com/usuario/ver/id/12 es completamente diferente de la cache de la petición: http://miapp.ejemplo.com/usuario/ver/id/25 ATENCIÓN Las acciones que se ejecutan mediante el método POST o que tienen parámetros GET no se guar- dan en la cache. La opción with_layout merece una explicación más detallada. Esta opción determina el ti- po de información que se guarda en la cache. Si vale true, solo se almacenan en la cache el resultado de la ejecución de la plantilla y las variables de la acción. Si la opción vale www.librosweb.es 251
  • 252. Symfony, la guía definitiva Capítulo 12. Uso de la cache false, se guarda el objeto response entero. Por tanto, la cache en la que se guarda el la- yout (valor true) es mucho más rápido que la cache sin el layout. Si es posible, es decir, si el layout no depende por ejemplo de datos de sesión, es conve- niente optar por la opción que guarda el layout en la cache. Desgraciadamente, el layout normalmente contiene elementos dinámicos (como por ejemplo el nombre del usuario que está conectado), por lo que la opción habitual es la de no almacenar el layout en la cache. No obstante, las páginas que no depende de cookies, los canales RSS, las venta- nas emergentes, etc. se pueden guardar en la cache incluyendo su layout. 12.1.3. Guardando un elemento parcial, un componente o un slot de componentes en la cache En el Capítulo 7 se explicó la forma de reutilizar trozos de código en varias plantillas me- diante el helper include_partial(). Guardar un elemento parcial en la cache es tan sen- cillo como hacerlo en una acción y se activa de la misma forma, tal y como muestra la fi- gura 12-2. Figura 12.2. Guardando un elemento parcial, un componente o un slot de componentes en la cache El listado 12-4 por ejemplo muestra los cambios necesarios en el archivo cache.yml para activar la cache en el elemento parcial _mi_parcial.php que pertenece al módulo usuario. La opción with_layout no tiene sentido en este caso. Listado 12-4 - Guardando un elemento parcial en la cache, en miapp/modules/us- uario/config/cache.yml _mi_parcial: enabled: on listado: enabled: on ... Ahora todas las plantillas que incluyen este elemento parcial no ejecutan su código PHP, sino que utilizan la versión almacenada en la cache. <?php include_partial('usuario/mi_parcial') ?> Al igual que sucede en las acciones, la información que se guarda en la cache depende de los parámetros que se pasan al elemento parcial. El sistema de cache almacena tantas versiones diferentes como valores diferentes de parámetros se pasen al elemento parcial. www.librosweb.es 252
  • 253. Symfony, la guía definitiva Capítulo 12. Uso de la cache <?php include_partial('usuario/mi_otro_parcial', array('parametro' => 'valor')) ?> SUGERENCIA Guardar la acción en la cache es más avanzado que guardar elementos parciales, ya que cuando una acción se encuentra en la cache, la plantilla ni siquiera se ejecuta; si la plantilla incluye elemen- tos parciales, no se realizan las llamadas a esos elementos parciales. Por tanto, guardar elementos parciales en la cache solo es útil cuando no se está guardando en la cache la acción que se ejecuta o para los elementos parciales incluidos en el layout. Recordando lo que se explicó en el Capítulo 7: un componente es una pequeña acción que utiliza como vista un elemento parcial y un slot de componentes es un componente para el que la acción varía en función de las acciones que se ejecuten. Estos dos elemen- tos son similares a los elementos parciales, por lo que el funcionamiento de su cache es muy parecido. Si el layout global incluye un componente llamado dia mediante include_- component(’general/dia’) para mostrar la fecha, el archivo cache.yml del módulo gene- ral debería activar la cache de ese componente de la siguiente forma: _dia: enabled: on Cuando se guarda un componente o un elemento parcial en la cache, se debe decidir si se almacena solo una versión para todas las plantillas o una versión para cada plantilla. Por defecto, los componentes se guardan independientemente de la plantilla que lo inclu- ye. No obstante, los componentes contextuales, como por ejemplo los componentes que muestran una zona lateral diferente en cada acción, deben almacenarse tantas veces co- mo el número de plantillas diferentes que los incluyan. El sistema de cache se encarga automáticamente de este último caso, siempre que se establezca el valor true a la opción contextual: _dia: contextual: true enabled: on NOTA Los componentes globales (los que se guardan en el directorio templates/ de la aplicación) tam- bién se pueden guardar en la cache, siempre que se configuren sus opciones de cache en el archi- vo cache.yml de la aplicación. 12.1.4. Guardando un fragmento de plantilla en la cache Guardar en la cache el resultado completo de una acción solamente es posible para algu- nas acciones. Para el resto de acciones, las que actualizan información y las que mues- tran en la plantilla información que depende de la sesión, todavía es posible mejorar su rendimiento mediante la cache, pero de forma muy diferente. Symfony incluye un tercer tipo de cache, que se utiliza para los fragmentos de las plantillas y que se activa directa- mente en la propia plantilla, como se muestra en la figura 12-3. www.librosweb.es 253
  • 254. Symfony, la guía definitiva Capítulo 12. Uso de la cache Figura 12.3. Guardando un fragmento de plantilla en la cache Si por ejemplo se dispone de un listado de usuarios que muestra un enlace al último us- uario que se ha accedido, esta última información es dinámica. El helper cache() define las partes de la plantilla que se pueden guardar en la cache. El listado 12-5 muestra los detalles sobre su sintaxis. Listado 12-5 - Uso del helper cache(), en miapp/modules/usuario/templates/ listadoSuccess.php <!-- Código que se ejecuta cada vez --> <?php echo link_to('Último usuario accedido', 'usuario/ ver?id='.$id_ultimo_usuario_accedido) ?> <!-- Código guardado en la cache --> <?php if (!cache('usuarios')): ?> <?php foreach ($usuarios as $usuario): ?> <?php echo $usuario->getNombre() ?> <?php endforeach; ?> <?php cache_save() ?> <?php endif; ?> Así es como funciona esta cache: ▪ Si se encuentra en la cache una versión del fragmento llamado ‘usuarios’, se uti- liza para reemplazar todo el código existente entre <?php if (!cache(’usuar- ios’)): ?> y <?php endif; ?>. ▪ Si no se encuentra, se ejecuta el código definido entre esas 2 líneas y el resulta- do se guarda en la cache identificado con el nombre indicando en la llamada al helper cache(). Todo el código que no se incluye entre esas dos líneas, se ejecuta siempre y por tanto nunca se guarda en la cache. ATENCIÓN La acción (listado en este ejemplo) no puede tener activada la cache, ya que en ese caso, no se ejecutaría la plantilla y se ignoraría por completo la declaración de la cache de los fragmentos. La mejora en la velocidad de la aplicación cuando se utiliza esta cache no es tan signifi- cativa como cuando se guarda en la cache la acción entera, ya que en este caso siempre se ejecuta la acción, la plantilla se procesa al menos de forma parcial y siempre se utiliza el layout para decorar la plantilla. www.librosweb.es 254
  • 255. Symfony, la guía definitiva Capítulo 12. Uso de la cache Se pueden guardar otros fragmentos de la misma plantilla en la cache; sin embargo, en este caso se debe indicar un nombre único a cada fragmento, de forma que el sistema de cache de Symfony pueda encontrarlos cuando sea necesario. Como sucede con las acciones y los componentes, los fragmentos que se guardan en la cache pueden tener definido un tiempo de vida en segundos como segundo argumento de la llamada al helper cache(). <?php if (!cache('usuarios', 43200)): ?> Si no se indica explícitamente en el helper, se utiliza el valor por defecto para el tiempo de vida de la cache (que son 86400 segundos, equivalentes a 1 día). SUGERENCIA Otra forma de hacer que una acción se pueda guardar en la cache es pasar las variables que modi- fican su comportamiento en el patrón del sistema de enrutamiento de la acción. Si la página princi- pal muestra el nombre del usuario que está conectado, no se puede cachear la página a menos que la URL contenga el nombre del usuario. Otro caso es el de las aplicaciones multi-idioma: si se quiete activar la cache para una página que tiene varias traducciones, el código del idioma debería incluirse dentro del patrón de la URL. Aunque este truco aumenta el número de páginas que se guardan en la cache, puede ser muy útil para acelerar las aplicaciones que son muy interactivas. 12.1.5. Configuración dinámica de la cache El archivo cache.yml es uno de los métodos disponibles para definir las opciones de la ca- che, pero tiene el inconveniente de que no se puede modificar de forma dinámica. No obstante, como sucede habitualmente en Symfony, se puede utilizar código PHP en vez de archivos YAML, por lo que se puede configurar de forma dinámica la cache. ¿Para qué puede ser útil modificar dinámicamente las opciones de la cache? Un ejemplo práctico puede ser el de una página que es diferente para los usuarios autenticados y pa- ra los usuarios anónimos, aunque la URL sea la misma. Si se dispone de una página crea- da por la acción articulo/ver y que contiene un sistema de puntuación para los artículos, el sistema de puntuación podría estar deshabilitado para los usuarios anónimos. Para es- te tipo de usuarios, se muestra el formulario para registrarse cuando pinchan en el siste- ma de puntuación. Esta versión de la página se puede guardar tranquilamente en la ca- che. Por otra parte, los usuarios autenticados que pinchan sobre el sistema de puntua- ción, generan una petición POST que se emplea para calcular la nueva puntuación del artículo. En esta ocasión, la cache se debería deshabilitar para que Symfony cree la pági- na de forma dinámica. El sitio adecuado para definir las opciones dinámicas de la cache es en un filtro que se ejecute antes de sfCacheFilter. De hecho, todo el sistema de cache es un filtro de Sym- fony, como también lo son la barra de depuración de aplicaciones y las opciones de segu- ridad. Para habilitar la cache en la acción articulo/ver solo cuando el usuario no está au- tenticado, se crea el archivo conditionalCacheFilter en el directorio lib/ de la aplica- ción, tal y como se muestra en el listado 12-6. Listado 12-6 - Configurando la cache mediante PHP, en miapp/lib/ conditionalCacheFilter.class.php www.librosweb.es 255
  • 256. Symfony, la guía definitiva Capítulo 12. Uso de la cache class conditionalCacheFilter extends sfFilter { public function execute($filterChain) { $contexto = $this->getContext(); if (!$contexto->getUser()->isAuthenticated()) { foreach ($this->getParameter('pages') as $page) { $contexto->getViewCacheManager()->addCache($page['module'], $page['action'],array('lifeTime' => 86400)); } } // Ejecutar el siguiente filtro $filterChain->execute(); } } Este filtro se debe registrar en el archivo filters.yml antes de sfCacheFilter, como se muestra en el listado 12-7. Listado 12-7 - Registrando un filtro propio, en miapp/config/filters.yml ... security: ~ conditionalCache: class: conditionalCacheFilter param: pages: - { module: articulo, action: ver } cache: ~ ... Para que la cache condicional pueda utilizarse, solo es necesario borrar la cache de Sym- fony para que se autocargue la clase del nuevo filtro. La cache solo se habilitará para las páginas definidas en el parámetro pages y solo para los usuarios que no están autenticados. El método addCache() del objeto sfViewCacheManager requiere como parámetros el nom- bre de un módulo, el nombre de una acción y un array asociativo con las mismas opcio- nes que se definen en el archivo cache.yml. Si por ejemplo se necesita guardar en la ca- che la acción articulo/ver con el layout y con un tiempo de vida de 300 segundos, se puede utilizar el siguiente código: $contexto->getViewCacheManager()->addCache('articulo', 'ver', array( 'withLayout' => true, 'lifeTime' => 3600, )); Almacenamiento alternativo para la cache Por defecto, la cache de Symfony guarda sus datos en archivos almacenados en el disco duro del servidor. No obstante, existen métodos alternativos como almacenar los contenidos en la memoria www.librosweb.es 256
  • 257. Symfony, la guía definitiva Capítulo 12. Uso de la cache (utilizando memcache por ejemplo) o en una base de datos (útil si se quiere compartir la cache en- tre varios servidores o si se quiere poder borrar rápidamente la cache). En cualquier caso, es muy sencillo modificar el modo de almacenamiento de la cache de Symfony porque la clase PHP que utiliza el gestor de la cache está definida en el archivo factories.yml. La clase sfFileCache es la factoría que emplea por defecto la cache: view_cache: class: sfFileCache param: automaticCleaningFactor: 0 cacheDir: %SF_TEMPLATE_CACHE_DIR% Se puede reemplazar el valor de la opción class con una clase propia de almacenamiento de la cache o con una de las alternativas disponibles en Symfony (por ejemplo sfSQLiteCache). Los parámetros definidos en la clave param se pasan al método initialize() de la clase utilizada en forma de array asociativo. Cualquier clase definida para controlar el almacenamiento de la cache debe implementar todos los métodos de la clase abstracta sfCache. La documentación de la API (http://www.symfony-project.com/api/symfony.html ) tiene más información sobre este tema. 12.1.6. Uso de la cache super rápida Todas las páginas guardadas en la cache que se han explicado anteriormente implican la ejecución de algo de código PHP. En este tipo de páginas, Symfony carga toda la configu- ración, crea la respuesta, etc. Si se está completamente seguro de que una página no va a cambiar durante un periodo de tiempo, se puede saltar completamente Symfony si se guarda en la carpeta web/ el código HTML completo de la página. Este funcionamiento es posible gracias a las opciones del módulo mod_rewrite de Apache, siempre que la regla de enrutamiento defina un patrón que no termine en ningún sufijo o en .html. Para guardar las páginas completas en la cache, se puede acceder manualmente a todas las páginas mediante la siguiente instrucción ejecutada en la línea de comandos: > curl http://miapp.ejemplo.com/usuario/listado.html > web/usuario/listado.html Una vez ejecutado el anterior comando, cada vez que se realice una petición a la acción usuario/listado, Apache encuentra la página listado.html y la sirve directamente sin llegar a ejecutar Symfony. Aunque la desventaja es que no se puede controlar mediante Symfony las opciones de esa cache (tiempo de vida, borrado automático, etc.) la gran ventaja es el increíble aumento del rendimiento de la aplicación. Una forma más cómoda de generar estas páginas estáticas es la de utilizar el plugin sfSuperCache, que automatiza todo el proceso, permite definir el tiempo de vida de la ca- che e incluso permite el borrado de las páginas guardadas en la cache. El Capítulo 17 in- cluye más información sobre los plugins. Otras técnicas para mejorar el rendimiento Además de la cache de HTML, Symfony dispone de otros dos mecanismos de cache, que son com- pletamente automáticos y transparentes para el programador. En el entorno de producción, la confi- guración y las traducciones de las plantillas se guardan automáticamente en la cache en los direc- torios miproyecto/cache/config/ y miproyecto/cache/i18n/. www.librosweb.es 257
  • 258. Symfony, la guía definitiva Capítulo 12. Uso de la cache Los aceleradores PHP (eAccelerator, APC, XCache, etc.), también llamados módulos que guardan los opcodes en la cache, mejoran el rendimiento de los scripts PHP al guardar en una cache la ver- sión compilada de los scripts, por lo que se elimina el procesamiento y compilación de los scripts cada vez que se ejecutan. Las clases de Propel contienen muchísimo código PHP, por lo que son las que más se benefician de esta técnica. Todos estos aceleradores son compatibles con Symfony y pueden fácilmente triplicar el rendimiento de cualquier aplicación. Se recomienda su uso en los servidores de producción de las aplicaciones utilizadas por muchos usuarios. Con un acelerador PHP, se pueden almacenar datos en la memoria mediante la clase sfPro- cessCache, para no tener que realizar el mismo procesamiento en cada petición. Además, si se qu- iere almacenar el resultado de una función que consume una gran cantidad de CPU para su reutili- zación posterior, es posible utilizar el objeto sfFunctionCache. El Capítulo 18 muestra los detalles sobre estos dos mecanismos. 12.2. Eliminando elementos de la cache Si se modifican los scripts o los datos de la aplicación, la información de la cache estará desfasada. Para evitar incoherencias y posibles errores, se pueden eliminar partes de la cache de varias formas en función de las necesidades de cada caso. 12.2.1. Borrando toda la cache La tarea clear-cache del comando symfony se emplea para borrar la cache (la cache de HTML, de configuración y de internacionalización). Para borrar solo una parte de la cache, se pueden pasar parámetros, tal y como se muestra en el listado 12-8. Este comando so- lo se puede ejecutar desde el directorio raíz del proyecto. Listado 12-8 - Borrando la cache // Borrar toda la cache > symfony clear-cache // Atajo para borrar toda la cache > symfony cc // Borrar solo la cache de la aplicación miapp > symfony clear-cache miapp // Borrar solo la cache HTML de la aplicación miapp > symfony clear-cache miapp template // Borrar solo la cache de configuración de la aplicación miapp > symfony clear-cache miapp config 12.2.2. Borrando partes de la cache Cuando se modifican los datos de la base de datos, debería borrarse la cache de las acc- iones que tienen relación con los datos modificados. Aunque se podría borrar la cache en- tera, en este caso se borraría también la cache de todas las acciones que no tienen rela- ción con los datos modificados. Por este motivo, Symfony proporciona el método remo- ve() del objeto sfViewCacheManager. El argumento que se le pasa es una URI interna (tal www.librosweb.es 258
  • 259. Symfony, la guía definitiva Capítulo 12. Uso de la cache y como se utilizan por ejemplo en la función link_to()) y se elimina la cache de la acción relacionada con esa URI. Si se dispone de una acción llamada modificar en el módulo usuario, esta acción modifi- ca el valor de los datos de los objetos Usuario. Las páginas de las acciones listado y ver de este módulo que se guardan en la cache deberían borrarse, ya que en otro caso, se mostrarían datos desfasados. Para borrar estas páginas de la cache, se utiliza el método remove() tal y como muestra el listado 12-9. Listado 12-9 - Borrando la cache de una acción, en modules/usuario/actions/ actions.class.php public function executeModificar() { // Modificar un usuario $id_usuario = $this->getRequestParameter('id'); $usuario = UsuarioPeer::retrieveByPk($id_usuario); $this->foward404Unless($usuario); $usuario->setNombre($this->getRequestParameter('nombre')); ... $usuario->save(); // Borrar la cache de las acciones relacionadas con este usuario $cacheManager = $this->getContext()->getViewCacheManager(); $cacheManager->remove('usuario/listado'); $cacheManager->remove('usuario/ver?id='.$id_usuario); ... } Eliminar de la cache los elementos parciales, los componentes y los slots de componentes es un poco más complicado. Como se les puede pasar cualquier tipo de parámetro (inclu- so objetos), es casi imposible identificar la versión guardada en la cache en cada caso. Como la explicación es idéntica para los 3 tipos de elementos, solo se va a explicar el proceso para los elementos parciales. Symfony identifica los elementos parciales almace- nados en la cache mediante un prefijo especial (sf_cache_partial), el nombre del módu- lo, el nombre del elemento parcial y una clave única o hash generada a partir de todos los parámetros utilizados en la llamada a la función: // Un elemento parcial que se llama así <?php include_partial('usuario/mi_parcial', array('user' => $user) ?> // Se identifica en la cache de la siguiente manera /sf_cache_partial/usuario/_mi_parcial/sf_cache_key/bf41dd9c84d59f3574a5da244626dcc8 En teoría, es posible eliminar un elemento parcial guardado en la cache mediante el mé- todo remove() siempre que se conozca el valor de todos loa parámetros utilizados en ese elemento, aunque en la práctica es casi imposible conseguirlo. Afortunadamente, si se añade un parámetro denominado sf_cache_key en la llamada del helper include_part- ial(), se puede definir un identificador fácil de recordar para ese elemento parcial. De esta forma, y como muestra el listado 12-10, es fácil borrar un elemento parcial: Listado 12-10 - Borrando elementos parciales de la cache www.librosweb.es 259
  • 260. Symfony, la guía definitiva Capítulo 12. Uso de la cache <?php include_partial('usuario/mi_parcial', array( 'user' => $user, 'sf_cache_key' => $user->getId() ) ?> // Se identifica en la cache de la siguiente forma /sf_cache_partial/usuario/_mi_parcial/sf_cache_key/12 // Se puede borrar _mi_parcial para un usuario específico $cacheManager->remove('@sf_cache_partial?module=usuario&action=_mi_parcial&sf_cache_key='.$user->get Este método no se puede utilizar para borrar todas las versiones de un elemento parcial guardadas en la cache. Más adelante, en la sección “Borrando la cache a mano” se deta- lla como conseguirlo. El método remove() también se emplea para borrar fragmentos de plantillas. El nombre que identifica a cada fragmento en la cache se compone del perfijo sf_cache_partial, el nombre del módulo, el nombre de la acción y el valor de sf_cache_key (el identificador ú- nico utilizado en la llamada al helper cache()). El listado 12-11 muestra un ejemplo. Listado 12-11 - Borrando fragmentos de plantilla en la cache <!-- Código guardado en la cache --> <?php if (!cache('usuarios')): ?> ... // Lo que sea... <?php cache_save() ?> <?php endif; ?> // Se identifica en la cache de la siguiente forma /sf_cache_partial/usuario/listado/sf_cache_key/usuarios // Se puede borrar con el siguiente método $cacheManager->remove('@sf_cache_partial?module=usuario&action=listado&sf_cache_key=usuarios'); El borrado selectivo de la cache es realmente complicado La parte más complicada del borrado de la cache es la de determinar que acciones se ven afecta- das por la modificación de los datos. Imagina que dispones de una aplicación con un módulo llamado publicacion y las acciones lis- tado y ver, además de estar relacionada con un autor (representado por la clase Usuario). Si se modifican los datos de un Usuario, se verán afectadas todas las publicaciones de ese autor y el lis- tado de las publicaciones. Por tanto, en la acción modificar del módulo usuario se debería añadir lo siguiente: $c = new Criteria(); $c->add(PublicacionPeer::AUTOR_ID, $this->getRequestParameter('id')); $publicaciones = PublicacionPeer::doSelect($c); $cacheManager = sfContext::getInstance()->getViewCacheManager(); foreach ($publicaciones as $publicacion) { $cacheManager->remove('publicacion/ver?id='.$publicacion->getId()); } $cacheManager->remove('publicacion/listado'); www.librosweb.es 260
  • 261. Symfony, la guía definitiva Capítulo 12. Uso de la cache Si se utiliza la cache HTML, es necesario disponer de una visión clara de las dependencias y relac- iones entre el modelo y las acciones, de forma que no se produzcan errores por no comprender completamente esas relaciones. Debe tenerse en cuenta que todas las acciones que modifican el modelo seguramente deben incluir una serie de llamadas al método remove() si se utiliza la cache HTML. Cuando la situación sea realmente complicada, siempre se puede borrar la cache entera cada vez que se actualiza la base de datos. 12.2.3. Estructura del directorio de la cache El directorio cache/ de cada aplicación tiene la siguiente estructura: cache/ # sf_root_cache_dir [nombre_aplicacion]/ # sf_base_cache_dir [nombre_entorno]/ # sf_cache_dir config/ # sf_config_cache_dir i18n/ # sf_i18n_cache_dir modules/ # sf_module_cache_dir template/ # sf_template_cache_dir [nombre_servidor]/ all/ Las plantillas se guardan en la cache bajo el directorio [nombre_servidor] (sustituyendo los puntos por guiones bajos para mantener la compatibilidad con algunos sistemas de fi- cheros) y siguiendo una estructura relacionada con la URL. Por ejemplo, la plantilla de la siguiente página: http://www.miapp.com/usuario/ver/id/12 se guarda en el siguiente directorio de la cache: cache/miapp/prod/template/www_miapp_com/all/usuario/ver/id/12.cache El código no debería incluir las rutas de los archivos escritas manualmente. En su lugar, se deben utilizar las constantes definidas para las rutas. Para obtener por ejemplo la ruta absoluta del directorio template/ para la aplicación y entorno actuales, se emplea sfConfig::get(’sf_template_cache_dir’). Conocer la estructura de directorios es muy útil cuando se tienen que borrar manualmen- te partes de la cache. 12.2.4. Borrado manual de la cache El borrado de la cache entre diferentes aplicaciones suele ser problemático. Si por ejem- plo un administrador modifica los datos de la tabla usuario en la aplicación backend (la aplicación de gestión), se deberían borrar de la cache todas las acciones que dependen de ese usuario en la aplicación frontend (la aplicación pública). Como el método remove() utiliza URI internas, no se puede utilizar para borrar la cache de otras aplicaciones, ya que cada aplicación siempre se encuentra aislada de las demás y no tiene acceso a las reglas de enrutamiento del resto de aplicaciones. www.librosweb.es 261
  • 262. Symfony, la guía definitiva Capítulo 12. Uso de la cache La solución consiste en borrar manualmente los archivos del directorio cache/. Si la apli- cación backend quiere borrar la cache de la acción usuario/ver de la aplicación frontend para el usuario cuyo id vale 12, se podría utilizar el siguiente código: $sf_root_cache_dir = sfConfig::get('sf_root_cache_dir'); $cache_dir = $sf_root_cache_dir.'/frontend/prod/template/www_miapp_com/all'; unlink($cache_dir.'/usuario/ver/id/12.cache'); Este método de borrado no es muy convincente, ya que el comando anterior solo borra la cache del entorno actual y obliga a escribir el nombre del entorno y el nombre del servi- dor en la ruta del archivo. Para evitar estas molestias, se puede utilizar el método sfTo- olkit::clearGlob(). Este método acepta como parámetro un patrón de nombre de fiche- ro en el que se pueden incluir comodines. El siguiente ejemplo borra de la cache los mis- mos archivos que el ejemplo anterior sin necesidad de especificar ni el entorno ni el nom- bre del servidor: $cache_dir = $sf_root_cache_dir.'/frontend/*/template/*/all'; sfToolkit::clearGlob($cache_dir.'/usuario/ver/id/12.cache'); Este método también es muy práctico cuando se quieren borrar todas las páginas de una acción independientemente de los parámetros. Si la aplicación dispone de varios idiomas, es posible que el código del idioma aparezca en la URL. El enlace al perfil de un usuario podría tener el siguiente aspecto: http://www.miapp.com/en/usuario/ver/id/12 Para eliminar de la cache el perfil de un usuario cuyo id vale 12 independientemente del idioma, se debe ejecutar la siguiente instrucción: sfToolkit::clearGlob($cache_dir.'/*/usuario/ver/id/12.cache'); 12.3. Probando y monitorizando la cache La cache de HTML puede provocar incoherencias en los datos mostrados si no se gestiona correctamente. Cada vez que se activa la cache para un elemento, se debe probar y mo- nitorizar la mejora obtenida en el rendimiento de su ejecución. 12.3.1. Creando un entorno de ejecución intermedio El sistema de cache es propenso a crear errores en el entorno de producción que no se pueden detectar en el entorno de desarrollo, ya que en este último entorno la cache HTML está deshabilitada por defecto. Si se habilita la cache de HTML para algunas accio- nes, se debería crear un nuevo entorno de ejecución llamado staging en este capítulo y con las mismas opciones que el entorno prod (por lo tanto con la cache activada) pero con la opción web_debug activada (valor on). Para crear el nuevo entorno, se deben añadir las líneas mostradas en el listado 12-12 al archivo settings.yml de la aplicación. Listado 12-12 - Opciones del entorno staging, en miapp/config/settings.yml staging: .settings: www.librosweb.es 262
  • 263. Symfony, la guía definitiva Capítulo 12. Uso de la cache web_debug: on cache: on Además, se debe crear un nuevo controlador frontal copiando el de producción (que se- guramente se llamará miproyecto/web/index.php) en un archivo llamado miapp_sta- ging.php. En este archivo copiado es necesario modificar el valor de SF_ENVIRONMENT y SF_DEBUG, tal y como se muestra a continuación: define('SF_ENVIRONMENT', 'staging'); define('SF_DEBUG', true); Y solo con esos cambios ya se dispone de un nuevo entorno de ejecución. Para probarlo, se añade el nombre del controlador frontal a la URL después del nombre de dominio: http://miapp.ejemplo.com/miapp_staging.php/usuario/listado SUGERENCIA En vez de copiar un controlador frontal existente, es posible crear un nuevo controlador frontal med- iante la línea de comandos de Symfony. Para crear un entorno llamado staging en la aplicación miapp llamado miapp_staging.php y con la opción SF_DEBUG igual a true, se puede ejecutar el si- guiente comando: symfony init-controller miapp staging miapp_staging true. 12.3.2. Monitorizando el rendimiento El Capítulo 16 describe en detalle la barra de depuración de aplicaciones y sus conteni- dos. No obstante, como esa barra también contiene información relacionada con los ele- mentos guardados en la cache, se incluye ahora una breve descripción de sus caracterís- ticas relacionadas con la cache. Cuando se accede a una página que contiene elementos susceptibles de estar en la cache (acciones, elementos parciales, fragmentos, etc.) la barra de depuración web (que apare- ce en la esquina superior izquierda) muestra un botón para ignorar la cache (una flecha curvada verde), como se puede ver en la figura 12-4. Este botón se emplea para recar- gar la página y forzar a que se procesen todos los elementos que estaban en la cache. Se debe tener en cuenta que este botón no borra la cache. El último número que se muestra en la derecha de la barra es el tiempo que ha durado la ejecución de la petición. Si se habilita la cache en una página, este número debería ser muy inferior al recargar la página, ya que Symfony utilizará los datos de la cache en vez de volver a ejecutar por completo los scripts. Este indicador se puede utilizar para moni- torizar fácilmente las mejoras introducidas por la cache. Figura 12.4. Barra de depuración web en las páginas que utilizan la cache La barra de depuración también muestra el número de consultas de base de datos que se han ejecutado para la petición, el detalle del tiempo de ejecución de cada categoría (se muestra al pulsar sobre el tiempo de ejecución total). Monitorizando esta información es sencillo medir las mejoras en el rendimiento que son debidas a la cache. www.librosweb.es 263
  • 264. Symfony, la guía definitiva Capítulo 12. Uso de la cache 12.3.3. Pruebas de rendimiento (benchmarking) La depuración de las aplicaciones reduce notablemente la velocidad de ejecución de la aplicación, ya que se genera mucha información para que esté disponible en la barra de depuración web. De esta forma, el tiempo total de ejecución que se muestra cuando se accede a la aplicación en el entorno staging no es representativo del tiempo que se em- pleará en producción, donde la depuración está deshabilitada. Para obtener información sobre el tiempo de ejecución de cada petición, deberían utili- zarse herramientas para realizar pruebas de rendimiento, como Apache Bench o JMeter. Estas herramientas permiten realizar pruebas de carga y calculan dos parámetros muy importantes: el tiempo de carga medio de una página y la capacidad máxima del servi- dor. El tiempo medio de carga es esencial para monitorizar las mejoras de rendimiento introducidas por la activación de la cache. 12.3.4. Identificando elementos de la cache Cuando la barra de depuración web está activada, los elementos de la página que se enc- uentran en la cache se identifican mediante un recuadro rojo, además de que cada uno dispone de una caja de información sobre la cache en la esquina superior izquierda del elemento, como muestra la figura 12-5. La caja muestra un fondo azul si el elemento se ha ejecutado y un fondo de color amarillo si se ha obtenido directamente de la cache. Al pulsar sobre el enlace de información de la cache se muestra el identificador del elemen- to en la cache, su tiempo de vida y el tiempo que ha transcurrido dede su última modifi- cación. Esta información es útil para resolver problemas con elementos fuera de contex- to, para ver cuando se crearon los elementos y para visualizar las partes de la plantilla que se pueden guardar en la cache. Figura 12.5. Identificación de los elementos de la página que se guardan en la cache 12.4. HTTP 1.1 y la cache del lado del cliente El protocolo HTTP 1.1 define una serie de cabeceras que se pueden utilizar para acelerar una aplicación controlando la cache del navegador del usuario. La especificación del protocolo HTTP 1.1 publicada por el W3C (World Wide Web Consort- ium) define todas las cabeceras con gran detalle (http://www.w3.org/Protocols/rfc2616/ rfc2616-sec14.html). Si una acción tiene habilitada la cache y utiliza la opción with_layout, entonces puede hacer uso de los mecanismos que se describen en las siguientes secciones. Aunque algunos de los navegadores de los usuarios no soporten HTTP 1.1, no existe ningún riesgo en utilizar las opciones de cache de HTTP 1.1. Los navegadores que reciben cabeceras que no entienden, simplemente las ignoran, por lo que se aconseja utilizar los mecanismos de cache de HTTP 1.1. www.librosweb.es 264
  • 265. Symfony, la guía definitiva Capítulo 12. Uso de la cache Además, las cabeceras de HTTP 1.1 también las interpretan los servidores proxy y servi- dores cache. De esta forma, aunque el navegador del usuario no soporte HTTP 1.1, pue- de haber un proxy en la ruta de la petición que pueda aprovechar esas características. 12.4.1. Uso de la cabecera ETag para evitar el envío de contenidos no modificados Cuando se habilita la característica de ETag, el servidor web añade a la respuesta una ca- becera especial que contiene una firma de la respuesta enviada. ETag: "1A2Z3E4R5T6Y7U" El navegador del usuario almacena esta firma y la envía junto con la petición la próxima vez que el usuario acceda a la misma página. Si la firma demuestra que la página no se ha modificado desde la primera petición, el servidor no envía de nuevo la página de resp- uesta. En su lugar, envía una cabecera de tipo 304: Not modified. Esta técnica ahorra tiempo de CPU (si se está utilizando la compresión de contenidos) y ancho de banda (ya que la página no se vuelve a enviar) en el servidor, y tiempo de carga (porque la página no se envía de nuevo) en el cliente. En resumen, las páginas que se guardan en la cache con la cabecera ETag son todavía más rápidas de cargar que las páginas que están en la cache y no tienen ETag. Symfony permite activar la característica ETag para toda la aplicación en el archivo set- tings.yml. El valor por defecto de la opción ETag se muestra a continuación: all: .settings: etag: on En las acciones que se guardan en la cache junto con el layout, la respuesta se obtiene directamente del directorio cache/, por lo que el proceso es todavía más rápido. 12.4.2. Añadiendo la cabecera Last-Modified para evitar el envío de contenidos todavía válidos Cuando el servidor envía la respuesta al navegador, puede añadir una cabecera especial que indica cuando se modificaron por última vez los datos contenidos en la página: Last-Modified: Sat, 23 Nov 2006 13:27:31 GMT Los navegadores interpretan esta cabecera y la próxima vez que solicitan la misma pági- na, añaden una cabecera If-Modified apropiada: If-Modified-Since: Sat, 23 Nov 2006 13:27:31 GMT El servidor entonces puede comparar el valor enviado por el cliente y el valor devuelto por la aplicación. Si coinciden, el servidor devuelve una cabecera304: Not modified, aho- rrando ancho de banda y tiempo de CPU, al igual que sucedía con la cabecera ETag. Symfony permite establecer la cabecera Last-Modified de la misma forma que se esta- blece cualquier otra cabecera. En una acción se puede añadir de la siguiente manera: $this->getResponse()->setHttpHeader('Last-Modified', $this->getResponse()->getDate($timestamp)); www.librosweb.es 265
  • 266. Symfony, la guía definitiva Capítulo 12. Uso de la cache La fecha puede ser la fecha actual o la fecha de la última actualización de los datos de la página, obtenida a partir de la base de datos o del sistema de archivos. El método getDa- te() del objeto sfResponse convierte un timestamp en una fecha formateada según el estándar requerido por la cabecera Last-Modified (RFC1123). 12.4.3. Añadiendo cabeceras Vary para permitir varias versiones de la página en la cache Otra de las cabeceras de HTTP 1.1 es Vary, que define los parámetros de los que depen- de una página y que utilizan los navegadores y los servidores proxy para organizar la ca- che de las páginas. Si por ejemplo el contenido de una página depende de las cookies, se puede utilizar la siguiente cabecera Vary: Vary: Cookie En la mayoría de ocasiones, es difícil habilitar la cache para las acciones porque la página puede variar en función de las cookies, el idioma del usuario o cualquier otro parámetro. Si no es un inconveniente aumentar el tamaño de la cache, se puede utilizar en este caso la cabecera Vary. Además, se puede emplear esta cabecera para toda la aplicación o solo para algunas acciones, definiéndolo en el archivo de configuración cache.yml o mediante el método disponible en sfResponse, como se muestra a continuación: $this->getResponse()->addVaryHttpHeader('Cookie'); $this->getResponse()->addVaryHttpHeader('User-Agent'); $this->getResponse()->addVaryHttpHeader('Accept-Language'); Symfony guarda en la cache versiones diferentes de la página en función de cada uno de estos parámetros. Aunque el tamaño de la cache aumenta, la ventaja es que cuando el servidor recibe una petición que coincide con estas cabeceras, la respuesta se obtiene di- rectamente de la cache en vez de tener que procesarla. Se trata de un mecanismo muy útil para mejorar el rendimiento de las páginas que solo varían en función de las cabece- ras de la petición. 12.4.4. Añadiendo la cabecera Cache-Control para permitir la cache en el lado del cliente Hasta ahora, aunque se hayan añadido las cabeceras, el navegador sigue enviando petic- iones al servidor a pesar de disponer de una versión de la página en su cache. Para evitar estas peticiones, se pueden añadir las cabeceras Cache-Control y Expires a la respuesta. PHP deshabilita por defecto estas cabeceras, pero Symfony puede saltarse este compor- tamiento para evitar las peticiones innecesarias al servidor. Como es habitual, esta opción se activa mediante un método del objeto sfResponse. En una acción se puede definir el tiempo máximo que una página debería permanecer en la cache (en segundos): $this->getResponse()->addCacheControlHttpHeader('max_age=60'); Además, se pueden especificar las condiciones bajo las cuales se guarda la página en la cache, de forma que la cache del proveedor no almacene por ejemplo datos privados (co- mo números de cuenta y contraseñas): www.librosweb.es 266
  • 267. Symfony, la guía definitiva Capítulo 12. Uso de la cache $this->getResponse()->addCacheControlHttpHeader('private=True'); Mediante el uso de las directivas HTTP de Cache-Control es posible controlar los diversos mecanismos de cache existentes entre el servidor y el navegador del cliente. La especifi- cación del W3C de Cache-Control contiene la explicación detallada de todas estas directivas. Symfony permite añadir otra cabecera llamada Expires: $this->getResponse()->setHttpHeader('Expires', $this->getResponse()->getDate($timestamp)); SUGERENCIA La consecuencia más importante de activar el mecanismo Cache-Control es que los logs del ser- vidor no muestran todas las peticiones realizadas por los usuarios, sino solamente las que recibe realmente el servidor. De esta forma, si mejora el rendimiento de un sitio web, su popularidad des- cenderá de forma aparente en las estadísticas de acceso al sitio. 12.5. Resumen El sistema de cache permite mejorar el rendimiento de la aplicación de forma variable en función del tipo de cache utilizado. La siguiente lista muestra los tipos de cache disponi- bles en Symfony ordenados de mayor a menor mejora en el rendimiento de la aplicación: ▪ Super cache ▪ Cache de una acción con layout ▪ Cache de una acción sin layout ▪ Cache de fragmentos de plantillas Además, tambien se pueden guardar en la cache los elementos parciales y los componentes. Si la modificación de los datos del modelo o de la sesión obliga a borrar la cache para mantener la coherencia de la información, se puede realizar un borrado muy selectivo para no penalizar el rendimiento, ya que es posible borrar solamente los elementos mo- dificados manteniendo todos los demás. Una recomendación muy importante es la de probar cuidadosamente todas las páginas para las que se ha habilitado la cache, ya que suele ser habitual que se produzcan erro- res por haber guardado en la cache elementos inadecuados o por no haber borrado de la cache los elementos modificados. Una buena técnica es la de crear un entorno intermedio llamado staging dedicado a probar la cache y las mejoras en el rendimiento de la aplicación. Por último, es posible exprimir al máximo algunas características del protocolo HTTP 1.1 gracias a las opciones que proporciona Symfony para controlar la cache y que permite aprovechar las ventajas de la cache en el navegador de los clientes, de forma que se au- mente aun más el rendimiento de la aplicación. www.librosweb.es 267
  • 268. Symfony, la guía definitiva Capítulo 13. Internacionalización y localización Capítulo 13. Internacionalización y localización Cuando se desarrollan aplicaciones con soporte para varios idiomas, es fácil que la tra- ducción de todos los contenidos, el soporte de los estándares de cada país y la traducción de la interfaz se conviertan en una pesadilla. Afortunadamente, Symfony automatiza de forma nativa todos los aspectos del proceso de internacionalización. Como la palabra “internacionalización” es demasiado larga, los programadores normal- mente se refieren a ella como i18n (18 es el número de letras que existen entre la letra “i” y la letra “n” de la palabra “internacionalización”). La “localización” normalmente se abrevia como l10n. Estas 2 palabras se refieren a 2 aspectos diferentes de las aplicacio- nes web multiidioma. Una aplicación internacionalizada dispone de varias versiones de un mismo contenido en diferentes idiomas o formatos. La interfaz de una aplicación web de correo electrónico, puede ofrecer por ejemplo el mismo servicio en diferentes idiomas, cambiando solamente la interfaz. Una aplicación localizada dispone de información diferente en función del país desde el que se accede. El caso más sencillo es el de los contenidos de un portal de noticias: si el usuario accede desde Estados Unidos, se muestran las últimas noticias de Estados Uni- dos, pero si el usuario accede desde Francia, se mostrarán las noticias de Francia. Por tanto, una aplicación con l10n no solo proporciona los contenidos traducidos, sino que to- do el contenido puede cambiar de una versión a otra. En cualquier caso, el soporte de i18n y l10n en una aplicación comprende los siguientes aspectos: ▪ Traducción de texto (interfaz, contenidos estáticos y contenido) ▪ Estándares y formatos (fechas, cantidades, números, etc.) ▪ Contenido localizado (varias versiones de un mismo objeto en función del país del usuario) En este capítulo se muestra la forma en la que Symfony trata cada uno de estos elemen- tos y la forma en la que se pueden desarrollar aplicaciones con i18n y l10n. 13.1. Cultura del usuario Todas las opciones relacionadas con i18n en Symfony se basan en un parámetro de la sesión de usuario llamado culture (cultura). La cultura está formada por la combinación del país e idioma del usuario y determina la forma en la que se muestra el texto y la in- formación que depende de la cultura. Como su valor se serializa en la sesión de usuario, la cultura se almacena de forma persistente entre páginas diferentes. 13.1.1. Indicando la cultura por defecto Por defecto, la cultura de los nuevos usuarios toma el valor de la opción default_culture. Se puede modificar su valor en el archivo de configuración i18n.yml, como se muestra en el listado 13-1. www.librosweb.es 268
  • 269. Symfony, la guía definitiva Capítulo 13. Internacionalización y localización Listado 13-1 - Indicando la cultura por defecto, en miapp/config/i18n.yml all: default_culture: fr_FR NOTA Durante el desarrollo de la aplicación, es posible que los cambios en el archivo i18n.yml no se re- flejen en la aplicación accedida mediante el navegador. La razón es que la sesión guarda la infor- mación de la cultura de las páginas anteriores. Para acceder a la aplicación con el nuevo valor de la cultura, se deben borrar las cookies del dominio de la aplicación o se debe reiniciar el navegador. La cultura debe indicar el país y el idioma ya que, por ejemplo, se puede disponer de una traducción al francés diferente para los usuarios de Francia, Bélgica y Canadá, como tam- bién se pueden ofrecer traducciones diferentes al español para los usuarios de España y México. El idioma se codifica mediante 2 caracteres en minúscula siguiendo el estándar ISO 639-1 (en para inglés, por ejemplo). El país se codifica en forma de 2 caracteres en mayúscula siguiendo el estándar ISO 3166-1 (GB para Reino Unido, por ejemplo). 13.1.2. Modificando la cultura de un usuario La cultura de un usuario se puede modificar mientras accede a la aplicación (por ejemplo cuando un usuario decide cambiar la versión en inglés por la versión en francés) o cuan- do el usuario accede a la aplicación y se utiliza el idioma que ha seleccionado en sus pre- ferencias. Por este motivo la clase sfUser ofrece métodos getter y setter para la cultura del usuario. El listado 13-2 muestra cómo utilizar estos métodos en la acción. Listado 13-2 - Modificando y obteniendo la cultura en una acción // Modificando la cultura $this->getUser()->setCulture('en_US'); // Obteniendo la cultura $cultura = $this->getUser()->getCulture(); => en_US La cultura en la URL Cuando se utilizan las opciones de localización e internacionalización de Symfony, parece que exis- ten varias versiones diferentes de una página para una misma URL, ya que la versión que se mues- tra depende de la sesión de usuario. Este comportamiento hace difícil guardar las páginas en la ca- che o que las páginas se indexen correctamente en los buscadores. Una solución es hacer que la cultura se muestre en todas las URL, de forma que las páginas tradu- cidas se muestran como si fueran URL diferentes. Para conseguirlo, se añade la opción :sf_cul- ture en todas las reglas del archivo routing.yml de la aplicación: pagina: url: /:sf_culture/:pagina requirements: { sf_culture: (?:fr|en|de) } params: ... articulo: url: /:sf_culture/:ano/:mes/:dia/:slug www.librosweb.es 269
  • 270. Symfony, la guía definitiva Capítulo 13. Internacionalización y localización requirements: { sf_culture: (?:fr|en|de) } params: ... Para no tener que añadir manualmente el parámetro de petición sf_culture en todas las llamadas a link_to(), Symfony añade automáticamente la cultura del usuario a los parámetros de enrutam- iento por defecto. También funciona de forma inversa, ya que Symfony modifica automáticamente la cultura del usuario si encuentra el parámetro sf_culture en la URL. 13.1.3. Determinando la cultura de forma automática En muchas aplicaciones, la cultura del usuario se define durante la primera petición, en función de las preferencias de su navegador. Los usuarios pueden definir en el navegador una serie de idiomas que están dispuestos a aceptar. Esta información se envía al servi- dor en cada petición, mediante la cabecera HTTP Accept-Language. En Symfony esta ca- becera se puede acceder a través del objeto sfRequest. Si por ejemplo se quiere obtener la lista de idiomas preferidos del usuario en una acción, se utiliza la siguiente instrucción: $idiomas = $this->getRequest()->getLanguages(); Aunque la cabecera HTTP es una cadena de texto, Symfony la procesa y la convierte au- tomáticamente en un array. Por tanto, el idioma preferido del usuario se puede obtener en el ejemplo anterior mediante $idiomas[0]. En la página principal de un sitio web y en un filtro utilizado en varias páginas, puede ser útil establecer automáticamente la cultura del usuario al idioma preferido del navegador del usuario. ATENCIÓN La cabecera HTTP Accept-Language no es una información muy fiable, ya que casi ningún usuario sabe cómo modificar su valor en el navegador. En la mayoría de los casos, el idioma preferido del navegador es el idioma de la propia interfaz del navegador y los usuarios no están disponibles en todos los idiomas. Si se decide establecer de forma automática el valor de la cultura según el idio- ma preferido del navegador, es una buena idea proporcionar una forma sencilla de seleccionar otro idioma. 13.2. Estándares y formatos Las partes internas de una aplicación web no deben preocuparse por las diferencias cul- turales entre países. Las bases de datos por ejemplo almacenan las fechas y cantidades siguiendo estándares internacionales. Pero cuando los datos se envían o se reciben del usuario, es necesario realizar una conversión. Los usuarios normales no entienden lo que es un timestamp y prefieren llamar a su idioma en su propio idioma (por ejemplo “Franç- ais” en vez de “French”). Así que se debe aprovechar la posibilidad de realizar estas con- versiones de forma automática en función de la cultura del usuario. 13.2.1. Mostrando datos según la cultura del usuario Una vez que se define la cultura del usuario, los helpers que dependen de la cultura muestran automáticamente los datos de forma correcta. El helper format_number() por www.librosweb.es 270
  • 271. Symfony, la guía definitiva Capítulo 13. Internacionalización y localización ejemplo, muestra un número en un formato familiar para el usuario, en función de su cultura, tal y como muestra el listado 13-3. Listado 13-3 - Mostrando un número según la cultura del usuario <?php use_helper('Number') ?> <?php $sf_user->setCulture('en_US') ?> <?php echo format_number(12000.10) ?> => '12,000.10' <?php $sf_user->setCulture('fr_FR') ?> <?php echo format_number(12000.10) ?> => '12 000,10' No es necesario indicar a los helpers la cultura de forma explícita. Los helpers la buscan automáticamente en el objeto sesión. El listado 13-4 muestra todos los helpers que tie- nen en cuenta la cultura para mostrar sus datos. Listado 13-4 - Helpers dependientes de la cultura <?php use_helper('Date') ?> <?php echo format_date(time()) ?> => '9/14/06' <?php echo format_datetime(time()) ?> => 'September 14, 2006 6:11:07 PM CEST' <?php use_helper('Number') ?> <?php echo format_number(12000.10) ?> => '12,000.10' <?php echo format_currency(1350, 'USD') ?> => '$1,350.00' <?php use_helper('I18N') ?> <?php echo format_country('US') ?> => 'United States' <?php format_language('en') ?> => 'English' <?php use_helper('Form') ?> <?php echo input_date_tag('fecha_nacimiento', mktime(0, 0, 0, 9, 14, 2006)) ?> => input type="text" name="fecha_nacimiento" id="fecha_nacimiento" value="9/14/06" size="11" /> <?php echo select_country_tag('pais', 'US') ?> => <select name="pais" id="pais"><option value="AF">Afghanistan</option> ... <option value="GB">United Kingdom</option> <option value="US" selected="selected">United States</option> www.librosweb.es 271
  • 272. Symfony, la guía definitiva Capítulo 13. Internacionalización y localización <option value="UM">United States Minor Outlying Islands</option> <option value="UY">Uruguay</option> ... </select> Los helpers de fechas aceptan un parámetro opcional para indicar su formato, de modo que se pueda mostrar una fecha independiente de la cultura del usuario, pero no debería utilizarse en las aplicaciones con soporte de i18n. 13.2.2. Obteniendo información en una aplicación localizada Si es necesario obtener información del usuario, se debería obligar al usuario, si es posi- ble, a introducir datos que ya estén internacionalizados. Esta técnica evita tener que adi- vinar el formato en el que ha introducido el usuario sus datos. Por ejemplo, es complica- do que un usuario introduzca una cantidad monetaria con la separación de los miles. Se pueden restringir las posibilidades del usuario ocultando los datos realmente enviados al servidor (como por ejemplo mediante select_country_tag()) o separando las partes de un dato complejo en varias partes individuales sencillas. No obstante, para datos como fechas esta técnica no siempre es posible. Los usuarios están acostumbrados a introducir las fechas en el formato propio de su país, por lo que se deben convertir a un formato internacional. Para ello se puede utilizar la clase sfI18N. El listado 13-5 muestra cómo utilizar esta clase. Listado 13-5 - Obteniendo una fecha a partir de un formato propio del usuario en una acción $fecha= $this->getRequestParameter('fecha_nacimiento'); $cultura_usuario = $this->getUser()->getCulture(); // Obtener un timestamp $timestamp = sfI18N::getTimestampForCulture($fecha, $cultura_usuario); // Obtener las partes de una fecha list($dia, $mes, $ano) = sfI18N::getDateForCulture($fecha, $cultura_usuario); 13.3. Información textual en la base de datos Una aplicación que soporta la localización ofrece diferentes contenidos en función de la cultura del usuario. Una tienda online podría ofrecer los mismos productos al mismo pre- cio en todo el mundo, pero con una descripción personalizada para cada país. De esta forma, la base de datos tiene que ser capaz de almacenar diferentes versiones de una misma información y el esquema de la base de datos debe diseñarse de una forma espe- cial, además de indicar la cultura cada vez que se manipulan los objetos del modelo. 13.3.1. Creando un esquema para una aplicación localizada Cada una de las tablas que contiene información localizada, se debe dividir en 2 partes: una tabla que no contiene ninguna información relativa a la i18n y otra tabla con todas las columnas relacionadas con la i18n. Las dos tablas tienen una relación de tipo “uno a www.librosweb.es 272
  • 273. Symfony, la guía definitiva Capítulo 13. Internacionalización y localización muchos”. De esta forma, es posible añadir más idiomas sin tener que modificar el mode- lo. Como ejemplo se va a considerar una tabla llamada Producto. En primer lugar, se crean las tablas en el archivo schema.yml, tal y como muestra el lista- do 13-6. Listado 13-6 - Esquema de ejemplo para datos i18n, en config/schema.yml mi_conexion: mi_producto: _attributes: { phpName: Producto, isI18N: true, i18nTable: mi_producto_i18n } id: { type: integer, required: true, primaryKey: true, autoincrement: true } precio: { type: float } mi_producto_i18n: _attributes: { phpName: ProductoI18n } id: { type: integer, required: true, primaryKey: true, foreignTable: mi_producto, foreignReference: id } culture: { isCulture: true, type: varchar, size: 7, required: true, primaryKey: true } nombre: { type: varchar, size: 50 } Lo más importante del listado anterior son los atributos isI18N y i18nTable de la primera tabla y la columna especial culture en la segunda. Todos estos atributos son mejoras de Propel creadas por Symfony. Symfony puede automatizar aun más este proceso. Si la tabla que contiene los datos in- ternacionalizados tiene el mismo nombre que la tabla principal seguido de un sufijo _i18n y ambas están relacionadas con una columna llamada id, se pueden omitir las columnas id y culture en la tabla _i18n y los atributos específicos para i18n en la tabla principal. Si se siguen estas convenciones, Symfony es capaz de inferir toda esta información. Así, para Symfony es equivalente el esquema del listado 13-7 al listado 13-6 mostrado anteriormente. Listado 13-7 - Versión abreviada del esquema de ejemplo para datos i18n, en config/schema.yml mi_conexion: mi_producto: _attributes: { phpName: Producto } id: precio: float mi_producto_i18n: _attributes: { phpName: ProductoI18n } nombre: varchar(50) 13.3.2. Usando los objetos i18n generados Una vez construido el modelo de objetos (mediante la llamada a symfony propel-build- model y el borrado de la cache mediante symfony cc después de cada modificación del ar- chivo schema.yml), se puede utilizar la clase Producto con soporte de i18n como si fuera una sola tabla, tal y como muestra el listado 13-8. www.librosweb.es 273
  • 274. Symfony, la guía definitiva Capítulo 13. Internacionalización y localización Listado 13-8 - Trabajando con objetos i18n $producto = ProductoPeer::retrieveByPk(1); $producto->setCulture('fr'); $producto->setNombre('Nom du produit'); $producto->save(); $producto->setCulture('en'); $producto->setNombre('Product name'); $producto->save(); echo $producto->getNombre(); => 'Product name' $producto->setCulture('fr'); echo $producto->getNombre(); => 'Nom du produit' Si no se quiere modificar la cultura cada vez que se utiliza un objeto i18n, es posible mo- dificar el método hydrate() en la clase del objeto. El listado 13-9 muestra un ejemplo. Listado 13-9 - Redefiniendo el método hydrate() para establecer la cultura, en miproyecto/lib/model/Producto.php public function hydrate(ResultSet $rs, $startcol = 1) { parent::hydrate($rs, $startcol); $this->setCulture(sfContext::getInstance()->getUser()->getCulture()); } Las consultas realizadas mediante los objetos peer se pueden restringir para que solo ob- tengan los objetos que disponen de una traducción para la cultura actual, mediante el método doSelectWithI18n en lugar del habitual doSelect, como muestra el listado 13-10. Además, crea los objetos i18n relacionados a la vez que los objetos normales, de forma que se reduce el número de consultas necesarias para obtener el contenido completo (el Capítulo 18 incluye más información sobre las ventajas de este método sobre el rendim- iento de la aplicación). Listado 13-10 - Obteniendo objetos con un Criteria de tipo i18n $c = new Criteria(); $c->add(ProductoPeer::PRECIO, 100, Criteria::LESS_THAN); $productos = ProductoPeer::doSelectWithI18n($c, $cultura); // El argumento $cultura es opcional // Si no se indica, se utiliza la cultura actual Así que no es necesario trabajar directamente con los objetos i18n, sino que se pasa la cultura al modelo (o se deja que el modelo la obtenga automáticamente) cada vez que se quiere realizar una consulta con un objeto normal. 13.4. Traducción de la interfaz La interfaz de usuario es otro de los elementos que se deben adaptar en las aplicaciones i18n. Las plantillas tienen que poder mostrar las etiquetas, los mensajes y la navegación en diferentes idiomas pero manteniendo la misma presentación. Symfony recomienda www.librosweb.es 274
  • 275. Symfony, la guía definitiva Capítulo 13. Internacionalización y localización que las plantillas se construyan con el lenguaje por defecto de la aplicación y que se defi- na la traducción de las frases en un archivo de diccionario. De esta forma, no es necesar- io modificar las plantillas cada vez que se añade, modifica o elimina una traducción. 13.4.1. Configurando la traducción Las plantillas no se traducen automáticamente, lo que significa que antes que nada, se debe activar la opción de traducción de las plantillas en el archivo settings.yml, como se muestra en el listado 13-11. Listado 13-11 - Activando la traducción de la interfaz, en miapp/config/ settings.yml all: .settings: i18n: on 13.4.2. Usando el helper de traducción En esta sección se va a considerar que se quiere construir un sitio web en inglés y en francés, siendo el inglés el idioma por defecto. Antes de empezar con la traducción del si- tio web, una de las plantillas del sitio podría ser similar a la del listado 13-12. Listado 13-12 - Plantilla con un único idioma Welcome to our website. Today's date is <?php echo format_date(date()) ?> Para que Symfony pueda traducir las frases de una plantilla, estas deben identificarse co- mo “texto traducible”. Para ello se ha definido el helper __() (2 guiones bajos seguidos), que es parte del grupo de helpers llamado I18N. De esa forma, todas las plantillas deben encerrar las frases que se van a traducir en llamadas a ese helper. El listado 13-12 por ejemplo se puede modificar para que tenga el aspecto del listado 13-13 (como se verá más adelante en la sección “Cómo realizar traducciones complejas”, existe una forma mejor para llamar al helper de traducción). Listado 13-13 - Plantilla preparada para múltiples idiomas <?php use_helper('I18N') ?> <?php echo __('Welcome to our website.') ?> <?php echo __("Today's date is ") ?> <?php echo format_date(date()) ?> SUGERENCIA Si la aplicación hace uso del grupo de helpers I18N en todas sus páginas, puede ser una buena idea incluirlo en la opción standard_helpers del archivo settings.yml, de forma que no sea ne- cesario incluir use_helper(’I18N’) en cada plantilla. 13.4.3. Utilizando un archivo de diccionario Cuando se invoca la función __(), Symfony busca la traducción del argumento que se le pasa en el diccionario correspondiente a la cultura del usuario. Si se encuentra una frase www.librosweb.es 275
  • 276. Symfony, la guía definitiva Capítulo 13. Internacionalización y localización equivalente, la función devuelve la traducción y se muestra en la respuesta. De esta for- ma, la traducción de la interfaz se basa en los archivos de diccionario. Los archivos de diccionario se crean siguiendo el formato XLIFF (XML Localization Inter- change File Format), sus nombres siguen el patrón messages.[codigo de idioma].xml y se guardan en el directorio i18n/ de la aplicación. XLIFF es un formato estándar basado en XML. Como se trata de un formato muy utiliza- do, se pueden emplear herramientas externas que facilitan la traducción del sitio web en- tero. Las empresas que se encargan de realizar traducciones manejan este tipo de archi- vos y saben cómo traducir un sitio web entero añadiendo un nuevo archivo XLIFF. SUGERENCIA Además del estándar XLIFF, Symfony también permite utilizar otros sistemas para guardar los dicc- ionarios: gettext, MySQL, SQLite y Creole. La documentación de la API contiene toda la informa- ción sobre la configuración de estos métodos alternativos. El listado 13-14 muestra un ejemplo de la sintaxis XLIFF necesaria para crear el archivo messages.fr.xml que traduce al francés los contenidos del listado 13-13. Listado 13-14 - Diccionario en formato XLIFF, en miapp/i18n/messages.fr.xml <?xml version="1.0" ?> <xliff version="1.0"> <file orginal="global" source-language="en_US" datatype="plaintext"> <body> <trans-unit id="1"> <source>Welcome to our website.</source> <target>Bienvenue sur notre site web.</target> </trans-unit> <trans-unit id="2"> <source>Today's date is </source> <target>La date d'aujourd'hui est </target> </trans-unit> </body> </file> </xliff> El atributo source-language siempre contiene el código ISO completo correspondiente a la cultura por defecto. Cada frase o elemento que se traduce, se indica en una etiqueta trans-unit con un atributo id único. Si en la aplicación se utiliza la cultura por defecto (en este ejemplo en_US), las frases no se traducen y por tanto se muestran directamente los argumentos indicados en las lla- madas a __(). El resultado del listado 13-13 es similar al listado 13-12. Sin embargo, si se modifica la cultura a fr_FR o fr_BE, se muestran las traducciones del archivo messa- ges.fr.xml, y el resultado es el que se muestra en el listado 13-15. Listado 13-15 - Una plantilla traducida Bienvenue sur notre site web. La date d'aujourd'hui est <?php echo format_date(date()) ?> www.librosweb.es 276
  • 277. Symfony, la guía definitiva Capítulo 13. Internacionalización y localización Si se necesita añadir una nueva traducción, solamente es preciso crear un nuevo archivo messages.XX.xml de traducción en el mismo directorio que el resto de traducciones. 13.4.4. Trabajando con diccionarios Si el archivo messages.XX.xml aumenta tanto de tamaño como para hacerlo difícil de ma- nejar, se pueden dividir sus contenidos en varios archivos de diccionarios ordenados por temas. De esta forma, es posible por ejemplo dividir el archivo messages.fr.xml en los si- guientes tres archivos dentro del directorio i18n/: ▪ navegacion.fr.xml ▪ terminos_de_servicio.fr.xml ▪ busqueda.fr.xml Siempre que una traducción no se encuentre en el archivo messages.XX.xml por defecto, se debe indicar como tercer argumento en la llamada al helper __() el archivo de diccio- nario que debe utilizarse. Para traducir una cadena de texto que se encuentra en el ar- chivo navegacion.fr.xml, se utilizaría la siguiente instrucción: <?php echo __('Welcome to our website', null, 'navegacion') ?> Otra forma de organizar los diccionarios es mediante su división en módulos. En vez de crear un solo archivo messages.XX.xml para toda la aplicación, se crea un archivo en cada directorio modules/[nombre_modulo]/i18n/. Así se consigue que los módulos sean más in- dependientes de la aplicación, lo que es necesario para reutilizarlos, como por ejemplo en los plugins (ver Capítulo 17). 13.4.5. Trabajando con otros elementos que requieren traducción Otros elementos también pueden requerir ser traducidos: ▪ Las imágenes, documentos y cualquier otro tipo de contenido estático pueden va- riar en función de la cultura del usuario. Un ejemplo típico es el de las imágenes que se utilizan para mostrar un contenido de texto con una tipografía muy espec- ial. En este caso, se pueden crear subdirectorios para cada una de las culturas disponibles (utilizando el valor culture para el nombre de cada subdirectorio): <?php echo image_tag($sf_user->getCulture().'/miTexto.gif') ?> ▪ Los mensajes de error de los archivos de validación se muestran automáticamen- te mediante __(), por lo que para traducirlos, solo es necesario añadirlos a los archivos de diccionario. ▪ Las páginas por defecto de Symfony (página no encontrada, error interno de ser- vidor, acceso restringido, etc.) están escritas en inglés y tienen que reescribirse para las aplicaciones i18n. Probablemente, la solución consiste en crear un mó- dulo default propio en la aplicación y utilizar __() en las plantillas. El Capítulo 19 explica cómo personalizar estas páginas. www.librosweb.es 277
  • 278. Symfony, la guía definitiva Capítulo 13. Internacionalización y localización 13.4.6. Cómo realizar traducciones complejas La traducción mediante __() requiere que se se le pase como argumento una frase com- pleta. Sin embargo, es muy común tener variables mezcladas con el texto en una frase. Aunque puede ser tentador intentar cortar las frases en varios trozos, el resultado es que las llamadas al helper pierden su significado. Afortunadamente, el helper __() dispone de una opción para reemplazar el valor de las variables y que permite crear diccionarios que conservan su significado y simplifican la traducción. Las etiquetas HTML también se pue- den incluir en la llamada al helper. El listado 13-16 muestra un ejemplo. Listado 13-16 - Traduciendo frases con etiquetas HTML y código PHP // Frases originales Welcome to all the <strong>new</strong> users.<br /> There are <?php echo count_logged() ?> persons logged. // Ejemplo malo de como traducir las frases anteriores <?php echo __('Welcome to all the') ?> <strong><?php echo __('new') ?></strong> <?php echo __('users') ?>.<br /> <?php echo __('There are') ?> <?php echo count_logged() ?> <?php echo __('persons logged') ?> // Ejemplo correcto para traducir las frases anteriores <?php echo __('Welcome to all the <strong>new</strong> users') ?> <br /> <?php echo __('There are %1% persons logged', array('%1%' => count_logged())) ?> En este ejemplo, el nombre que se utiliza para la sustitución es %1%, pero puede utilizarse cualquier nombre, ya que el reemplazo se realiza en el helper mediante la función strtr() de PHP. Otro de los problemas habituales de las traducciones es el uso del plural. En función del número de resultados, el texto cambia, pero no lo hace de la misma forma en todos los idiomas. La última frase del listado 13-16 por ejemplo no es correcta si count_logged() devuelve 0 o 1. Aunque es posible comprobar el valor devuelto por la función y seleccio- nar la frase adecuada mediante código PHP, esta forma de trabajar es bastante tediosa. Además, cada idioma tiene sus propias reglas gramaticales, por lo que intentar inferir el plural de las palabras puede ser muy complicado. Como se trata de un problema muy ha- bitual, Symfony incluye un helper llamado format_number_choice(). El listado 13-17 muestra cómo utilizar este helper. Listado 13-17 - Traduciendo las frases en función del valor de los parámetros <?php echo format_number_choice( '[0]Nobody is logged|[1]There is 1 person logged|(1,+Inf]There are%1% persons logged', array('%1%' => count_logged()), count_logged()) ?> El primer argumento está formado por las diferentes posibilidades de frases. El segundo parámetro es el patrón utilizado para reemplazar variables (como con el helper __()) y es opcional. El tercer argumento es el número utilizado para determinar la frase que se utiliza. www.librosweb.es 278
  • 279. Symfony, la guía definitiva Capítulo 13. Internacionalización y localización Las frases de las diferentes posibilidades se separan mediante el carácter | seguido de un array de valores, utilizando la siguiente sintaxis: ▪ [1,2]: Acepta valores entre 1 y 2, ambos incluidos. ▪ (1,2): Acepta valores entre 1 y 2, ambos excluidos. ▪ {1,2,3,4}: Solo se aceptan los valores definidos en este conjunto. ▪ [-Inf,0): Acepta valores mayores o iguales que -infinito y que son estrictamente menores que 0. Se puede utilizar cualquier combinación no vacía de paréntesis y corchetes. Para que la traducción funcione correctamente, el archivo XLIFF debe contener el mensa- je tal y como aparece en la llamada al helper format_number_choice(). El listado 13-18 muestra un ejemplo. Listado 13-18 - diccionario XLIFF para un argumento de format_number_choice() ... <trans-unit id="3"> <source>[0]Nobody is logged|[1]There is 1 person logged|(1,+Inf]There are%1% persons logged</source> <target>[0]Personne n'est connecté|[1]Une personne est connectée|(1,+Inf]Ily a %1% personnes en ligne</target> </trans-unit> ... Comentarios sobre los charset Si se trabaja con contenidos internacionalizados en las plantillas, es habitual encontrarse con pro- blemas de charsets. Si se emplea un charset propio de un idioma, se debe modificar cada vez que el usuario cambia su cultura. Además, las plantillas escritas en un determinado charset no mues- tran correctamente los caracteres pertenecientes a otro charset. Por este motivo, si se utiliza más de una cultura, es muy recomendable crear todas las plantillas con el charset UTF-8 y que el layout declare que su contenido es UTF-8. Si se utiliza siempre UTF- 8, es poco probable que se produzcan sorpresas desagradables. Las aplicaciones construidas con Symfony definen el charset utilizado de forma centralizada en el archivo settings.yml. Si se modifica su valor, se modifican todas las cabeceras content-type de todas las páginas de respuesta. all: .settings: charset: utf-8 13.4.7. Utilizando el helper de traducción fuera de una plantilla No todo el texto que se muestra en las páginas viene de las plantillas. Por este motivo, es habitual tener que utilizar el helper __() en otras partes de la aplicación: acciones, fil- tros, clases del modelo, etc. El listado 13-19 muestra cómo utilizar el helper en una ac- ción mediante la instancia del objeto I18N obtenida a través del singleton de contexto. Listado 13-19 - Utilizando __() en una acción www.librosweb.es 279
  • 280. Symfony, la guía definitiva Capítulo 13. Internacionalización y localización $this->getContext()->getI18N()->__($texto, $argumentos, 'mensajes'); 13.5. Resumen La internacionalización y localización de las aplicaciones web es una tarea sencilla si se trabaja con el concepto de la cultura del usuario. Los helpers utilizan la cultura para mos- trar la información en el formato correcto y el contenido localizado que se guardan en la base de datos se ve como si fuera parte de una única tabla. Para la traducción de las in- terfaces, el helper __() y los diccionarios XLIFF permiten obtener la máxima flexibilidad con el mínimo trabajo. www.librosweb.es 280
  • 281. Symfony, la guía definitiva Capítulo 14. Generadores Capítulo 14. Generadores Muchas aplicaciones web se reducen a una mera interfaz de acceso a la información al- macenada en una base de datos. Symfony automatiza la tarea repetitiva de crear módu- los para manipular datos mediante el uso de objetos Propel. Si el modelo de objetos está bien definido, es posible incluso generar de forma automática la parte de administración completa de un sitio web. En este capítulo se explican los 2 tipos de generadores au- tomáticos incluidos en Symfony: el scaffolding (literalmente se puede traducir como “an- damiaje”) y el generador de la parte de administración. Este último generador se basa en un archivo de configuración especial con su propia sintaxis, por lo que la mayor parte de este capítulo hace referencia a las posibilidades que ofrece el generador de la administración. 14.1. Generación de código en función del modelo En las aplicaciones web, las operaciones de acceso a los datos se pueden clasificar en una de las siguientes categorías: ▪ Insertar un registro (creation, en inglés) ▪ Obtener registros (retrieval, en inglés) ▪ Modificar un registro o alguna de sus columnas (modification, en inglés) ▪ Borrar un registro (deletion, en inglés) Como estas operaciones son tan comunes, se ha creado un acrónimo para referirse a to- das ellas: CRUD (por las iniciales de sus nombres en inglés). Muchas páginas se reducen a alguna de esas operaciones. En un foro por ejemplo, el listado de los últimos mensajes es una operación de obtener registros y responder a un mensaje se corresponde con la opción de insertar un registro. En muchas aplicaciones web se crean continuamente acciones y plantillas que realizan las operaciones CRUD para una determinada tabla de datos. En Symfony, el modelo contiene la información necesaria para poder generar de forma automática el código de las opera- ciones CRUD, de forma que se simplifica el desarrollo inicial de la aplicación y las interfa- ces de la parte de gestión de las aplicaciones. Las tareas de generación de código a partir del modelo de datos crean un módulo entero, y se pueden ejecutar mediante el siguiente comando de Symfony: > symfony <NOMBRE_TAREA> <NOMBRE_APLICACION> <NOMBRE_MODULO> <NOMBRE_CLASE> Las tareas de generación de código son propel-init-crud, propel-generate-crud y propel-init-admin. 14.1.1. Scaffolding y administración Durante la fase de desarrollo de una aplicación, se puede utilizar la generación de código para alguna de las siguientes tareas: www.librosweb.es 281
  • 282. Symfony, la guía definitiva Capítulo 14. Generadores ▪ El “scaffolding” es una estructura básica de acciones y plantillas para poder reali- zar las operaciones CRUD en una tabla de la base de datos. El código generado es mínimo, ya que solo es una guía para seguir desarrollando. Se trata de la base inicial que debe adaptarse para seguir los requerimientos de lógica y presenta- ción de la aplicación. El “scaffolding” se utiliza durante la fase de desarrollo de la aplicación para crear una acceso vía web a la base de datos, para construir un prototipo rápido o para realizar automáticamente el código básico de un módulo basado en una tabla de la base de datos. ▪ La “administración” es una interfaz avanzada para manipular los datos y que se emplea en la parte de gestión o administración de las aplicaciones. La principal diferencia con el “scaffolding” es que el programador no modifica el código gene- rado para la parte de administración. Mediante archivos de configuración y he- rencia de clases se puede personalizar y extender la parte de administración ge- nerada. La presentación de la interfaz es importante y por eso incluyen opciones como el filtrado, la paginación y la ordenación de datos. La parte de administra- ción generada automáticamente con Symfony tiene calidad suficiente como para entregarla al cliente formando parte de la aplicación que se le ha desarrollado. La línea de comandos de Symfony utiliza la palabra crud para referirse al “scaffolding” y admin para referirse a la parte de administración de la aplicación. 14.1.2. Generando e iniciando el código Symfony dispone de dos formas para generar el código: mediante herencia (init) o me- diante la generación automática de código (generate). Los módulos se pueden “iniciar”, esto es, crear una serie de clases vacías que heredan de las del framework. Este método enmascara el código PHP de las acciones y de las planti- llas para evitar que sean modificadas. Se trata de un método útil cuando la estructura de datos puede variar o cuando se necesita crear rápidamente un interfaz para el acceso a la base de datos. El código que se ejecuta durante la ejecución de la aplicación no se en- cuentra en la aplicación, sino en la cache. Las tareas de la línea de comandos utilizadas para este tipo de generación comienzan con propel-init-. El código de la acción generada está vacío. Si por ejemplo se inicia un módulo llamado articulo, el código de las acciones será el siguiente: class articuloActions extends autoarticuloActions { } Por otra parte, también es posible generar el código completo de las acciones y plantillas para que pueda ser modificado. El módulo resultante es por tanto, independiente de las clases del framework y no es posible adaptarlo utilizando exclusivamente archivos de configuración. Las tareas de la línea de comandos utilizadas para este tipo de generación comienzan con propel-generate-. Como el objetivo del “scaffolding” es generar la base para futuros desarrollos, es mejor generar el “scaffolding” y no solo iniciarlo. Por otra parte, la parte de administración es fácil de actualizar mediante un cambio en los archivos de configuración y sigue siendo www.librosweb.es 282
  • 283. Symfony, la guía definitiva Capítulo 14. Generadores usable aunque se modifique el modelo de datos. Esta es la razón por la que en las admi- nistraciones el código de “inicia” y no sólo se “genera”. 14.1.3. Modelo de datos de ejemplo A lo largo de este capítulo, los listados de código muestran las posibilidades de los gene- radores de Symfony mediante un ejemplo sencillo, similar al utilizado en el Capítulo 8. Se trata de la típica aplicación para crear un blog, que contiene las clases Article y Comment. El listado 14-1 muestra el esquema de datos y la figura 14-1 lo ilustra. Listado 14-1 - Archivo schema.yml de la aplicación de ejemplo propel: blog_article: _attributes: { phpName: Article } id: title: varchar(255) content: longvarchar created_at: blog_comment: _attributes: { phpName: Comment } id: article_id: author: varchar(255) content: longvarchar created_at: Modelo de datos de ejemplo La generación de código no impone ninguna regla o restricción a la creación del esque- ma. Symfony utiliza el esquema tal y como se ha definido, interpreta sus atributos y ge- nera el “scaffolding” o la parte de administración de la aplicación. SUGERENCIA Para aprovechar al máximo este capítulo, deberías hacer todos los ejemplos que se incluyen. Si se realizan todos los pasos descritos en los listados de código, se obtiene un mejor conocimiento de lo que genera Symfony y de lo que se puede llegar a hacer con el código generado. La recomenda- ción es que crees una estructura de datos como la descrita anteriormente para crear una base de datos con las tablas blog_article y blog_comment, rellenándolas con datos de prueba. 14.2. Scaffolding El scaffolding es muy útil cuando se empieza a desarrollar una aplicación. Con un solo co- mando, Symfony es capaz de crear un módulo entero basado en la descripción de una ta- bla de la base de datos. 14.2.1. Generando el scaffolding Para generar el scaffolding del módulo article basado en la clase Article del modelo, se utiliza el siguiente comando: > symfony propel-generate-crud miaplicacion article Article www.librosweb.es 283
  • 284. Symfony, la guía definitiva Capítulo 14. Generadores Symfony comprueba la definición de la clase Article en el archivo schema.yml y crea una serie de acciones y plantillas que guarda en el directorio miaplicacion/modules/article/ y que están basadas en esa definición. El módulo generado incluye 3 vistas. La vista list, que es la vista por defecto, muestra las filas de datos de la tabla blog_article cuando se accede a la aplicación mediante http://localhost/miaplicacion_dev.php/article , tal y como muestra la figura 14-2. Figura 14.1. Vista "list" del módulo "article" Si se pincha sobre el identificador de un artículo, se muestra la lista show. Todos los deta- lles de una fila de datos se muestran en una única página, como se ve en la figura 14-3. Figura 14.2. Vista "show" del módulo "article" Si se modifica un artículo pinchando sobre el enlace edit o si se crea un nuevo artículo mediante el enlace create en la vista list, se muestra la vista edit, que se puede ver en la figura 14-4. Mediante las opciones incluidas en este módulo, es posible crear nuevos artículos y bo- rrar o modificar los artículos existentes. El código generado es una buena base a partir de la cual empezar el desarrollo de la aplicación. El listado 14-2 muestra las acciones y plantillas generadas para el nuevo módulo. www.librosweb.es 284
  • 285. Symfony, la guía definitiva Capítulo 14. Generadores Figura 14.3. Vista "edit" del módulo "article" Listado 14-2 - Elementos generados para las operaciones CRUD, en miaplicacion/ modules/article/ // En actions/actions.class.php index // Redirige a la acción "list" list // Muestra un listado de todas las filas de la tabla show // Muestra todas las columnas de una fila edit // Muestra un formulario para modificar la columnas de una fila update // Acción que se llama en el formulario de la acción "edit" delete // Borra una fila create // Crea una nueva fila // En templates/ editSuccess.php // Formulario para modificar una fila (vista "edit") listSuccess.php // Listado de todas las filas (vista "list") showSuccess.php // Detalle de una fila (vista "show") El código de todas estas acciones y plantillas es bastante sencillo y explícito, por lo que en vez de mostrar todo el código para explicarlo, el listado 14-3 muestra un pequeño ex- tracto de la clase de las acciones. Listado 14-3 - Clase Action generada, en miaplicacion/modules/article/actions/ actions.class.php class articleActions extends sfActions { public function executeIndex() { return $this->forward('article', 'list'); } public function executeList() { $this->articles = ArticlePeer::doSelect(new Criteria()); } public function executeShow() { $this->article = ArticlePeer::retrieveByPk($this->getRequestParameter('id')); $this->forward404Unless($this->article); } ... www.librosweb.es 285
  • 286. Symfony, la guía definitiva Capítulo 14. Generadores Para obtener una aplicación básica, solamente es necesario modificar el código generado para ajustarlo a las necesidades de la aplicación y repetir la generación del código de las operaciones CRUD para el resto de tablas con las que se deba interactuar. Generar el scaffolding de una aplicación permite dar un gran impulso inicial a su desarrollo, por lo que es buena idea dejar que Symfony haga el trabajo sucio y el desarrollador se centre en el diseño de la interfaz y de otros detalles específicos. 14.2.2. Iniciando el scaffolding Además de generarlo, también es posible “iniciar” el scaffolding, que se utiliza sobre todo para comprobar que se puede acceder a los datos de la base de datos. Un scaffolding que solo ha sido iniciado es muy fácil de crear y muy fácil de borrar una vez que se ha com- probado que todo funciona correctamente. El siguiente comando inicia el scaffolding correspondiente al módulo article que maneja las filas de datos correspondientes a la clase Article del modelo: > symfony propel-init-crud miaplicacion article Article Una vez iniciado, se puede acceder a la vista list mediante la acción por defecto: http://localhost/miaplicacion_dev.php/article Las páginas resultantes son exactamente iguales que las que tiene un scaffolding com- pletamente generado. Estas páginas se pueden utilizar como una interfaz web sencilla para la base de datos. Si se accede al archivo actions.class.php creado para el módulo article, se comprueba que está vacío, ya que todo hereda de una clase generada automáticamente. Con las plantillas sucede lo mismo: no existe ningún archivo de plantilla en el directorio templa- tes/. El código utilizado en las acciones y plantillas que solamente han sido iniciadas es el mismo que para el scaffolding que se genera completamente, pero se guarda en la ca- che de la aplicación (miproyecto/cache/miaplicacion/prod/module/autoArticle/). Durante el desarrollo de la aplicación, los programadores inician los scaffolding para inte- ractuar con los datos, sin importar el aspecto de la interfaz. El objetivo del código gene- rado con este método no es el de ser modificado para ajustarse a los requisitos de la aplicación; un scaffolding que solamente ha sido iniciado se puede considerar como una alternativa sencilla a la aplicación PHPmyadmin para la gestión de la información de la base de datos. 14.3. Creando la parte de administración de las aplicaciones Symfony es capaz de generar módulos más avanzados para la parte de gestión o admi- nistración de las aplicaciones, también basados en las definiciones de las clases del mo- delo del archivo schema.yml. Se puede crear toda la parte de administración de la aplica- ción mediante módulos generados automáticamente. Los ejemplos de esta sección des- criben los módulos de administración creados para una aplicación llamada backend. El es- queleto de la aplicación se puede crear mediante la tarea init-app de Symfony: > symfony init-app backend www.librosweb.es 286
  • 287. Symfony, la guía definitiva Capítulo 14. Generadores Los módulos de administración interpretan el modelo con la ayuda de un archivo de con- figuración especial llamado generator.yml, que se puede modificar para extender los componentes generados automáticamente y para controlar el aspecto visual de los mó- dulos. Este tipo de módulos también disponen de los mecanismos habituales descritos en los capítulos anteriores (layout, validación, enrutamiento, configuración propia, carga au- tomática de clases, etc.). Incluso es posible redefinir las acciones y plantillas generadas para incluir características propias, aunque el archivo generator.yml es suficiente para realizar la mayoría de modificaciones, por lo que el código PHP solamente es necesario para las tareas muy específicas. 14.3.1. Iniciando un módulo de administración Symfony permite crear la parte de administración de una aplicación módulo a módulo. Los módulos se generan en base a objetos Propel mediante la tarea propel-init-admin, que utiliza una sintaxis similar a la que se utiliza para iniciar un scaffolding: > symfony propel-init-admin backend article Article Este comando es suficiente para crear un módulo llamado article en la aplicación bac- kend y basado en la definición de la clase Article, que además es accesible desde la dirección: http://localhost/backend.php/article El aspecto visual de los módulos generados automáticamente, que se muestra en las fi- guras 14-5 y 14-6, es suficiente para incluirlo tal cual en una aplicación comercial. Figura 14.4. Vista "list" del módulo "article" en la aplicación "backend" www.librosweb.es 287
  • 288. Symfony, la guía definitiva Capítulo 14. Generadores Figura 14.5. Vista "edit" del módulo "article" en la aplicación "backend" Las diferencias entre la interfaz de un scaffolding y la de una administración generada automáticamente pueden parecer insignificantes, pero las posibilidades de configuración de la administración permiten mejorar el aspecto por defecto con muchas características para las que no es necesario escribir ni una sola línea de código PHP. NOTA Los módulos de una administración solamente pueden ser iniciados y nunca generados. 14.3.2. Un vistazo al código generado El código del módulo de administración module, que se encuentra en el directorio apps/ backend/modules/article/, está completamente vacío porque solo ha sido iniciado. La mejor forma de comprobar el código generado para este módulo es acceder con el nave- gador a sus páginas y después comprobar los contenidos de la carpeta cache/. El listado 14-4 muestra todas las acciones y plantillas generadas que se encuentran en la cache. Listado 14-4 - Elementos de administración generados automáticamente, en ca- che/backend/ENV/modules/article/ // En actions/actions.class.php create // Redirige a "edit" delete // Borra una fila edit // Muestra un formulario para modificar la columnas de una fila // y procesa el envío del formulario index // Redirige a "list" list // Muestra un listado de todas las filas de la tabla save // Redirige a "edit" // En templates/ _edit_actions.php _edit_footer.php _edit_form.php www.librosweb.es 288
  • 289. Symfony, la guía definitiva Capítulo 14. Generadores _edit_header.php _edit_messages.php _filters.php _list.php _list_actions.php _list_footer.php _list_header.php _list_messages.php _list_td_actions.php _list_td_stacked.php _list_td_tabular.php _list_th_stacked.php _list_th_tabular.php editSuccess.php listSuccess.php Los módulos de administración generados automáticamente se componen básicamente de las vistas edit y list. Si se observa el código PHP, se encontrará un código muy mo- dular, fácil de leer y extensible. 14.3.3. Conceptos básicos del archivo de configuración generator.yml La principal diferencia entre el scaffolding y la parte de administración de la aplicación (a- demás de que los módulos de una administración no disponen de la acción show) es que la administración se basa en las opciones del archivo de configuración generator.yml. Las opciones de configuración por defecto para un módulo de administración recién creado llamado article se pueden ver en el archivo backend/modules/article/config/genera- tor.yml, reproducido en el listado 14-5. Listado 14-5 - Configuración por defecto para la generación de la administra- ción, en backend/modules/article/config/generator.yml generator: class: sfPropelAdminGenerator param: model_class: Article theme: default Esta configuración es suficiente para generar una administración básica. Todas las opcio- nes propias se añaden bajo la clave param, después de la línea theme (lo que significa que todas las líneas que se añadan al final del archivo generator.yml tienen que empezar al menos por 4 espacios en blanco, para que estén correctamente indentadas). El listado 14-6 muestra un archivo generator.yml típico. Listado 14-6 - Configuración completa típica para el generador generator: class: sfPropelAdminGenerator param: model_class: Article theme: default fields: author_id: { name: Article author } www.librosweb.es 289
  • 290. Symfony, la guía definitiva Capítulo 14. Generadores list: title: List of all articles display: [title, author_id, category_id] fields: published_on: { params: date_format='dd/MM/yy' } layout: stacked params: | %%is_published%%<strong>%%=title%%</strong><br /><em>by %%author%% in %%category%% (%%published_on%%)</em><p>%%content_summary%%</p> filters: [title, category_id, author_id, is_published] max_per_page: 2 edit: title: Editing article "%%title%%" display: "Post": [title, category_id, content] "Workflow": [author_id, is_published, created_on] fields: category_id: { params: disabled=true } is_published: { type: plain} created_on: { type: plain, params: date_format='dd/MM/yy' } author_id: { params: size=5 include_custom=>> Choose an author << } published_on: { credentials: } content: { params: rich=true tinymce_options=height:150 } Las siguientes secciones explican en detalle todas las opciones que se pueden utilizar en este archivo de configuración. 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 ten- ga 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 sinta- xis: 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/sfAdminGene- ratorRefCard.pdf. Puede ser de utilidad tener la chuleta a mano cuando se leen los ejem- plos 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ódu- lo comment, es necesario crearlo mediante la tarea propel-init-admin: > symfony propel-init-admin backend comment Comment www.librosweb.es 290
  • 291. Symfony, la guía definitiva Capítulo 14. Generadores Figura 14.6. 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 cam- pos 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 sche- ma.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 mensa- je 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 www.librosweb.es 291
  • 292. Symfony, la guía definitiva Capítulo 14. Generadores 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 cualq- uier 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 em- plea 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] www.librosweb.es 292
  • 293. Symfony, la guía definitiva Capítulo 14. Generadores 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. Figura 14.7. Columnas seleccionadas para la vista "list" del módulo "comment" Figura 14.8. Agrupando campos en la vista "edit" del módulo "comment" www.librosweb.es 293
  • 294. Symfony, la guía definitiva Capítulo 14. Generadores 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 utili- za 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. SUGERENCIA 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 corres- ponderse con alguna de las columnas definidas en el esquema. Si la clase relacionada in- cluye 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 pue- de 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 co- mentarios 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ódu- los 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. www.librosweb.es 294
  • 295. Symfony, la guía definitiva Capítulo 14. Generadores Figura 14.9. 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 getArti- cleLink() 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 utili- zando 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 pue- den utilizar como columnas, en modules/comment/config/generator.yml generator: class: sfPropelAdminGenerator param: model_class: Comment theme: default list: display: [id, article_link, content] www.librosweb.es 295
  • 296. Symfony, la guía definitiva Capítulo 14. Generadores Figura 14.10. 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 getArti- cleLink() 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 genera- tor.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 llama- do _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/com- ment/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ódu- lo construido para la clase llamada GrupoUsuario, el elemento parcial tiene acceso al ob- jeto actual mendiante la variable $grupo_usuario. www.librosweb.es 296
  • 297. Symfony, la guía definitiva Capítulo 14. Generadores El resultado de este ejemplo es idéntico al mostrado en la figura 14-11, salvo que en es- te caso se respeta la separación en capas. Si se acostumbra a separar el código en ca- pas, 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 pre- fijo ~ ... list: display: [id, ~article_link, created_at] En la plantilla que se genera, la configuración anterior resulta en una llamada al compo- nente 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ítu- lo 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 www.librosweb.es 297
  • 298. Symfony, la guía definitiva Capítulo 14. Generadores list: title: List of Articles ... edit: title: Body of article %%title%% display: [content] Figura 14.11. 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 nece- sario modificarlos (siempre que el modelo utilice nombres de clase explícitos). SUGERENCIA 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/com- ment/config/generator.yml edit: fields: ... article_id: { help: The current comment relates to this article } Figura 14.12. Mensaje de ayuda en la vista "edit" del módulo "comment" www.librosweb.es 298
  • 299. Symfony, la guía definitiva Capítulo 14. Generadores 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. Las plantillas de administración están preparadas para la internacionalización Todo el texto incluido en las plantillas generadas automáticamente está internacionalizado, es decir, todos los textos se muestran mediante llamadas al helper __(). De esta forma, es muy fácil traducir una aplicación de administración generada automáticamente incluyendo la traducción de todas las frases en un archivo XLIFF, en el directorio apps/miaplicacion/i18n/, tal y como se explica 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 pue- den 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 co- lumna id de la lista de comentarios no solo muestra la clave primaria de cada comentar- io, 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 con- tent. 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 www.librosweb.es 299
  • 300. Symfony, la guía definitiva Capítulo 14. Generadores list: display: [article_link, =content] Figura 14.13. 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 posi- ble mostrar de forma seguida todos los datos en una sola cadena de texto que ocupe to- da la anchura de la tabla. El aspecto con el que se muestran los datos se denomina “lay- out” 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] www.librosweb.es 300
  • 301. Symfony, la guía definitiva Capítulo 14. Generadores Figura 14.14. 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 fil- tros, 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ña- dir un método __toString() a la clase Article (este método puede devolver, por ejem- plo, 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] www.librosweb.es 301
  • 302. Symfony, la guía definitiva Capítulo 14. Generadores Figura 14.15. 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 fil- tro 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 fil- tro es una lista desplegable con los datos de la columna correspondiente en la ta- bla 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 ca- lendario) de forma que se pueda indicar un intervalo temporal. ▪ Para las columnas booleanas, el filtro muestra una lista desplegable con los valo- res 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 valo- res (open y closed), pero estos valores se almacenan directamente en cada fila de la ta- bla (no se utiliza una relación con otra tabla). Un filtro de Symfony en este campo mos- trará 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 desple- gable. 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', www.librosweb.es 302
  • 303. Symfony, la guía definitiva Capítulo 14. Generadores '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 so- lució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] Figura 14.16. 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 cabe- ceras 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 per- mite reordenar los datos de forma adecuada. www.librosweb.es 303
  • 304. Symfony, la guía definitiva Capítulo 14. Generadores Figura 14.17. 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 apun- te 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 colum- nas 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 pagina- ció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. www.librosweb.es 304
  • 305. Symfony, la guía definitiva Capítulo 14. Generadores Figura 14.18. 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 op- ció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úme- ro 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. Afortunada- mente, se puede indicar al paginador que utilice un método específico tipo doSelectJ- oinXXX() 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 ti- po de campo de formulario que se muestra. Después, genera un helper de tipo ob- ject_*_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, ...] www.librosweb.es 305
  • 306. Symfony, la guía definitiva Capítulo 14. Generadores 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 directa- mente 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 opc- iones 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 opcio- nes 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() genera- do. La opción params del campo article_id en el ejemplo anterior produce el siguiente código en la plantilla: www.librosweb.es 306
  • 307. Symfony, la guía definitiva Capítulo 14. Generadores <?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 manual- mente la actualización de la columna en función del valor enviado por el elemento parc- ial. Symfony puede procesar automáticamente los campos normales (los que se corres- ponden con columnas reales) pero no puede adivinar la forma de tratar los datos que uti- lizan 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 for- mulario debería mostrar un cuadro de texto vacío para la contraseña y así el usuario pue- de 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/act- ions/actions.class.php class userActions extends sfActions { protected function updateUserFromRequest() { // Procesar los datos del campo parcial $password = $this->getRequestParameter('newpassword'); www.librosweb.es 307
  • 308. Symfony, la guía definitiva Capítulo 14. Generadores 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 me- diante un validador. Los módulos generados automáticamente pueden hacer uso de este mecanis- mo 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 cam- pos, 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 adminis- traciones para iniciar el módulo de la clase Comment, la acción comment/edit muestra au- tomá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 el objeto 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 ar- ticle (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 es- tar 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. www.librosweb.es 308
  • 309. Symfony, la guía definitiva Capítulo 14. Generadores Figura 14.19. 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 (ele- mentos disponibles) a la segunda lista (elementos seleccionados). ▪ El tipo admin_select_list es una lista desplegable expandida que permite selecc- ionar más de 1 elemento cada vez. ▪ El tipo admin_check_list es una lista de elementos checkbox seleccionables. Figura 14.20. Tipos de campos especiales disponibles para la gestión de las relaciones muchos-a-muchos www.librosweb.es 309
  • 310. Symfony, la guía definitiva Capítulo 14. Generadores 14.4.7. Añadiendo nuevas interacciones Los módulos de administración permiten a los usuarios realizar las operaciones CRUD ha- bituales, 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ági- na 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 nue- vos elementos. Figura 14.21. 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 ac- ció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 www.librosweb.es 310
  • 311. Symfony, la guía definitiva Capítulo 14. Generadores elemento. Por tanto, la acción save_and_add es un atajo muy útil cuando se están añad- iendo 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 pulsa- do 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 inte- racciones por defecto. El generador de la administración es capaz de reconocer las accio- nes _edit, _delete, _create, _list, _save, _save_and_add y _create. Figura 14.22. Interacciones de la vista "edit" También es posible definir interacciones propias, para lo que se debe especificar un nom- bre 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 addcom- ment.png, tal y como se muestra en la figura 14-24. Al pinchar sobre ese botón, se ejecu- ta la acción addComment del módulo actual. La clave primaria del objeto relacionado se pa- sa automáticamente a los parámetros de la petición que se produce. www.librosweb.es 311
  • 312. Symfony, la guía definitiva Capítulo 14. Generadores Figura 14.23. 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 utili- za 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 em- bargo, como se explica en el Capítulo 10, complica un poco la configuración del valida- dor, 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 www.librosweb.es 312
  • 313. Symfony, la guía definitiva Capítulo 14. Generadores 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 genera- dos 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 res- tringir 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 pue- de 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 } 14.5. Modificando el aspecto de los módulos generados www.librosweb.es 313
  • 314. Symfony, la guía definitiva Capítulo 14. Generadores La presentación de los módulos generados se puede modificar completamente para inte- grarlo con cualquier otro estilo gráfico. Los cambios no solo se pueden realizar mediante una hoja de estilos, sino que es posible redefinir las plantillas por defecto. 14.5.1. Utilizando una hoja de estilos propia Como el código HTML generado tiene un contenido bien estructurado, es posible modifi- car fácilmente su aspecto. Mediante la opción css de la configuración del generador es posible definir la hoja de es- tilos alternativa que se utiliza en el módulo de administración, como se muestra en el lis- tado 14-38. Listado 14-38 - Utilizando una hoja de estilos propia en vez de la de por defecto generator: class: sfPropelAdminGenerator param: model_class: Comment theme: default css: mystylesheet Además, también es posible utilizar las opciones habituales del archivo view.yml del mó- dulo para redefinir los estulos utilizados en cada vista. 14.5.2. Creando una cabecera y un pie propios Las vistas list y edit incluyen por defecto elementos parciales para la cabecera y el pie de página. Como no existen por defecto elementos parciales en el directorio templates/ del módulo de administración, solamente es necesario crearlos con los siguientes nom- bres para que se incluyan de forma automática: _list_header.php _list_footer.php _edit_header.php _edit_footer.php Si se quiere añadir por ejemplo una cabecera propia en la vista article/edit, se crea un archivo llamado _edit_header.php como el que muestra el listado 14-39. No es necesario realizar más configuraciones para que se incluya automáticamente. Listado 14-39 - Ejemplo de elemento parcial para la cabecera de la vista edit, en modules/articles/templates/_edit_header.php <?php if ($article->getNbComments() > 0): ?> <h2>This article has <?php echo $article->getNbComments() ?> comments.</h2> <?php endif; ?> Debe tenerse en cuenta que un elemento parcial de la vista edit siempre tiene acceso al objeto al que hace referencia mediante una variable con el mismo nombre que la clase y que un elemento parcial de la vista list tiene acceso al paginador actual mediante la va- riable $pager. Utilizando parámetros propios en la llamada a las acciones de la administración www.librosweb.es 314
  • 315. Symfony, la guía definitiva Capítulo 14. Generadores Las acciones del módulo de administración pueden recibir parámetros propios mediante la opción query_string del helper link_to(). Por ejemplo, para extender el elemento parcial _edit_hea- der anterior con un enlace a los comentarios del artículo, se utiliza el siguiente código: <?php if ($article->getNbComments() > 0): ?> <h2>This article has <?php echo link_to($article->getNbComments().' comments', 'comment/list', array('query_string' => 'filter=filter&filters%5Barticle_id%5D='.$article->getId())) ?></h2> <?php endif; ?> El valor que se pasa a la opción query_string es una versión codificada del siguiente valor más fácil de leer: 'filter=filter&filters[article_id]='.$article->getId() Se filtran los comentarios que se muestran a solamente los que estén relacionados con $article. Si se utiliza la opción query_string, es posible especificar el orden en el que se ordenan los datos y los filtros utilizados para mostrar una vista de tipo list. Esta opción también es útil para las inte- racciones propias. 14.5.3. Modificando el tema Existen otros elementos parciales en el directorio templates/ del módulo que heredan del framework y que se pueden redefinir para adaptarse a las necesidades de cada proyecto. Las plantillas del generador están divididas en pequeñas partes que se pueden redefinir de forma independiente, al igual que se pueden modificar las acciones una a una. No obstante, si se quieren redefinir todos los elementos parciales para varios módulos, lo mejor es crear un tema que se pueda reutilizar. Un tema es un conjunto completo de plantillas y acciones que se pueden utilizar en un módulo de administración si así se indi- ca en el archivo generator.yml. En el tema por defecto, Symfony utiliza los archivos defi- nidos en $sf_symfony_data_dir/generator/sfPropelAdmin/default/. Los archivos de los temas tienen que guardarse en el directorio data/generator/sfPro- pelAdmin/[nombre_tema]/template/ del proyecto, y la mejor forma de crear un nuevo te- ma es copiando los archivos del tema por defecto (que se encuentran en el directorio $sf_symfony_data_dir/generator/sfPropelAdmin/default/template/). De esta forma, es fácil asegurarse de que el tema propio contiene todos los archivos requeridos: // Elementos parciales, en [nombre_tema]/template/templates/ _edit_actions.php _edit_footer.php _edit_form.php _edit_header.php _edit_messages.php _filters.php _list.php _list_actions.php _list_footer.php _list_header.php _list_messages.php _list_td_actions.php _list_td_stacked.php www.librosweb.es 315
  • 316. Symfony, la guía definitiva Capítulo 14. Generadores _list_td_tabular.php _list_th_stacked.php _list_th_tabular.php // Acciones, en [nombre_tema]/template/actions/actions.class.php processFilters() // Procesa los filtros de la petición addFiltersCriteria() // Añade un filtro al objeto Criteria processSort() addSortCriteria() Se debe tener en cuenta que los archivos de las plantillas son en realidad “plantillas de plantillas”, es decir, archivos PHP que se procesan mediante una herramienta especial para generar las plantillas en función de las opciones del generador (este proceso se co- noce como la fase de compilación). Como las plantillas generadas deben contener código PHP que se ejecuta cuando se accede a estas plantillas, los archivos que son “plantillas de plantillas” tienen que utilizar una sintaxis alternativa para que el código PHP final no se ejecute durante el proceso de compilación de las plantillas. El listado 14-40 muestra un trozo de una de las “plantillas de plantillas” de Symfony. Listado 14-40 - Sintaxis de las plantillas de plantillas <?php foreach ($this->getPrimaryKey() as $pk): ?> [?php echo object_input_hidden_tag($<?php echo $this->getSingularName() ?>,'get<?php echo $pk->getPhpName() ?>') ?] <?php endforeach; ?> En el listado anterior, el código PHP indicado mediante <? se ejecuta durante la compila- ción, mientras que el código indicado mediante [? se ejecuta solamente durante la ejecu- ción final de la plantilla generada. El generador de plantillas reemplaza las etiquetas [? en etiquetas <?, por lo que la plantilla resultante es la siguiente: <?php echo object_input_hidden_tag($article, 'getId') ?> Trabajar con las “plantillas de plantillas” es bastante complicado, por lo que el mejor con- sejo para crear un tema propio es comenzarlo a partir del tema default, modificarlo poco a poco y probar los cambios continuamente. SUGERENCIA También es posible encapsultar un tema completo para el generador en un plugin, con lo que el te- ma es más fácil de reutilizar y más fácil de instalar en diferentes aplicaciones. El Capítulo 17 incluye más información. Contruyendo tu propio generador Tanto el scaffolding como la administración utilizan una serie de componentes internos de Symfony que automatizan la creación de acciones y plantillas en la cache, el uso d etemas y el procesamien- to de las “plantillas de plantillas”. De esta forma, Symfony proporciona todas las herramientas para crear tu propio generador, que puede ser similar a los existentes o ser completamente diferente. La generación automática de un módulo se gestiona mediante el método generate() de la clase sfGeneratorManager. Por ejem- plo, para generar una administración, Symfony realiza internamente la siguiente llamada a este método: www.librosweb.es 316
  • 317. Symfony, la guía definitiva Capítulo 14. Generadores $generator_manager = new sfGeneratorManager(); $data = $generator_manager->generate('sfPropelAdminGenerator', $parameters); Si se quiere construir un generador propio, es conveniente mirar la documentación de la API de las clases sfGeneratorManager y sfGenerator, y utilizar las clases sfAdminGenerator y sfCRUDGe- nerator como ejemplo. 14.6. Resumen Para iniciar o generar automáticamente los módulos de una aplicación de gestión, lo prin- cipal es disponer de un esquema y un modelo de objetos bien definidos. El código PHP del scaffolding está pensado para ser modificado, pero los módulos de una administración generada automáticamente se modifican mediante los archivos de configuración. El archivo generator.yml es la clave de los módulos generados automáticamente. Permite modificar completamente el contenido, las opciones y el aspecto gráfico de las vistas list y edit. Sin utilizar ni una sola línea de código PHP y solamente mediante opciones en un archivo de configuración YAML es posible añadir títulos a los campos de formulario, men- sajes de ayuda, filtros, configurar la ordenación de los datos, definir el tamaño de los lis- tados, el tipo de campos empleados en los formularios, las relaciones con claves exter- nas, las interacciones propias y el uso de credenciales. Si el generador de las administraciones no permite incluir las características requeridas por el proyecto, se pueden utilizar elementos parciales y se pueden redefinir las acciones para conseguir la máxima flexibilidad. Además, se pueden reutilizar todas las adaptacio- nes realizadas al generador de administraciones mediante el uso de los temas. www.librosweb.es 317
  • 318. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales Capítulo 15. Pruebas unitarias y funcionales La automatización de pruebas (automated tests) es uno de los mayores avances en la programación desde la invención de la orientación a objetos. Concretamente en el desa- rrollo de las aplicaciones web, las pruebas aseguran la calidad de la aplicación incluso cuando el desarrollo de nuevas versiones es muy activo. En este capítulo se introducen todas las herramientas y utilidades que proporciona Symfony para automatizar las pruebas. 15.1. Automatización de pruebas Cualquier programador con experiencia en el desarrollo de aplicaciones web conoce de sobra el esfuerzo que supone probar correctamente la aplicación. Crear casos de prueba, ejecutarlos y analizar sus resultados es una tarea tediosa. Además, es habitual que los requisitos de la aplicación varíen constantemente, con el consiguiente aumento del nú- mero de versiones de la aplicación y la refactorización continua del código. En este con- texto, es muy probable que aparezcan nuevos errores. Este es el motivo por el que la automatización de pruebas es una recomendación, aunque no una obligación, útil para crear un entorno de desarrollo satisfactorio. Los conjuntos de casos de prueba garantizan que la aplicación hace lo que se supone que debe hacer. In- cluso cuando el código interno de la aplicación cambia constantemente, las pruebas auto- matizadas permiten garantizar que los cambios no introducen incompatibilidades en el funcionamiento de la aplicación. Además, este tipo de pruebas obligan a los programado- res a crear pruebas en un formato estandarizado y muy rígido que pueda ser procesado por un framework de pruebas. En ocasiones, las pruebas automatizadas pueden reemplazar la documentación técnica de la aplicación, ya que ilustran de forma clara el funcionamiento de la aplicación. Un buen conjunto de pruebas muestra la salida que produce la aplicación para una serie de entradas de prueba, por lo que es suficiente para entender el propósito de cada método. Symfony aplica este principio a su propio código. El código interno del framework se vali- da mediante la automatización de pruebas. Estas pruebas unitarias y funcionales no se incluyen en la distribución estándar de Symfony, pero se pueden descargar directamente desde el repositorio de Subversion y se pueden acceder online en http://trac.symfony- project.com/browser/trunk/test. 15.1.1. Pruebas unitarias y funcionales Las pruebas unitarias aseguran que un único componente de la aplicación produce una salida correcta para una determinada entrada. Este tipo de pruebas validan la forma en la que las funciones y métodos trabajan en cada caso particular. Las pruebas unitarias se encargan de un único caso cada vez, lo que significa que un único método puede necesi- tar varias pruebas unitarias si su funcionamiento varía en función del contexto. Las pruebas funcionales no solo validan la transformación de una entrada en una salida, sino que validan una característica completa. Un sistema de cache por ejemplo www.librosweb.es 318
  • 319. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales solamente puede ser validado por una prueba funcional, ya que comprende más de 1 so- lo paso: la primera vez que se solicita una página, se produce su código; la segunda vez, se obtiene directamente de la cache. De modo que las pruebas funcionales validan proce- sos y requieren de un escenario. En Symfony, se deberían crear pruebas funcionales para todas las acciones. Para las interacciones más complejas, estos 2 tipos de pruebas no son suficientes. Las in- teracciones de Ajax, por ejemplo, requieren de un navegador web que ejecute código Ja- vaScript, por lo que es necesaria una herramienta externa para la automatización de las pruebas. Además, los efectos visuales solamente pueden ser validados por una persona. Si las pruebas automatizadas van a validar una aplicación compleja, probablemente sea necesario el uso combinado de estos 3 tipos de pruebas. Como recomendación final, es aconsejable crear pruebas sencillas y fáciles de entender. NOTA Las pruebas automatizadas comparan un resultado con la salida esperada para ese método. En otras palabras, evalúan “asertos” (del inglés, “assertions”, que son expresiones del tipo $a == 2. El valor de salida de un aserto es true o false, lo que determina si la prueba tiene éxito o falla. La palabra “aserto” es de uso común en las técnicas de automatización de pruebas. 15.1.2. Desarrollo basado en pruebas La metodología conocida como TDD o “desarrollo basado en pruebas” (“test-driven deve- lopment”) establece que las pruebas se escriben antes que el código de la aplicación. Crear las pruebas antes que el código, ayuda a pensar y centrarse en el funcionamiento de un método antes de programarlo. Se trata de una buena práctica que también recom- iendan otras metodologías como XP (“Extreme Programming”). Además, es un hecho in- negable que si no se escriben las pruebas antes, se acaba sin escribirlas nunca. En el siguiente ejemplo se supone que se quiere desarrollar una función elimine los ca- racteres problemáticos de una cadena de texto. La función elimina todos los espacios en blanco del principio y del final de la cadena; además, reemplaza todos los caracteres que no son alfanuméricos por guiones bajos y transforma todas las mayúsculas en minúscu- las. En el desarrollo basado en pruebas, en primer lugar se piensa en todos los posibles casos de funcionamiento de este método y se elabora una serie de entradas de prueba junto con el resultado esperado para cada una, como se muestra en la tabla 15-1. Tabla 15-1 - Lista de casos de prueba para la función que elimina caracteres problemáticos Dato de entrada Resultado esperado ” valor “ “valor” “valor otrovalor” “valor_otrovalor” “-)valor:..=otrovalor?” “__valor____otrovalor_“ “OtroValor” “otrovalor” www.librosweb.es 319
  • 320. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales “¡Un valor y otro valor!” “_un_valor_y_otro_valor_” A continuación, se crearían las pruebas unitarias, se ejecutarían y todas fallarían. Des- pués, se escribe el código necesario para realizar correctamente el primer caso y se vuel- ven a pasar todas las pruebas. En esta ocasión, la primera prueba no fallaría. Así se seg- uiría desarrollando el código del método completo hasta que todas las pruebas se pasen correctamente. Una aplicación desarrollada con la metodología basada en pruebas, acaba teniendo tanto código para pruebas como código para aplicación. Por tanto, las pruebas deberían ser sencillas para no perder el tiempo arreglando los problemas con el código de las pruebas. NOTA Refactorizar el código de un método puede crear errores que antes no existían. Esta es otra razón por la que es una buena idea pasar todas las pruebas creadas antes de instalar una nueva versión de la aplicación en el servidor de producción. Esta técnica se conoce como “regression testing”. 15.1.3. El framework de pruebas Lime En el ámbito de PHP existen muchos frameworks para crear pruebas unitarias, siendo los más conocidos PhpUnit y SimpleTest. Symfony incluye su propio frameowrk llamado Li- me. Se basa en la librería Test::More de Perl y es compatible con TAP, lo que significa que los resultados de las pruebas se muestran con el formato definido en el “Test Anything Protocol”, creado para facilitar la lectura ed los resultados de las pruebas. Lime proporciona el soporte para las pruebas unitarias, es más eficiente que otros frame- works de pruebas de PHP y tiene las siguientes ventajas: ▪ Ejecuta los archivos de prueba en un entorno independiente para evitar interfe- rencias entre las diferentes pruebas. No todos los frameworks de pruebas garan- tizan un entorno de ejecución “limpio” para cada prueba. ▪ Las pruebas de Lime son fáciles de leer y sus resultados también lo son. En los sistemas operativos que lo soportan, los resultados de Lime utilizan diferentes colores para mostrar de forma clara la información más importante. ▪ Symfony utiliza Lime para sus propias pruebas y su “regression testing”, por lo que el código fuente de Symfony incluye muchos ejemplos reales de pruebas uni- tarias y funcionales. ▪ El núcleo de Lime se valida mediante pruebas unitarias. ▪ Está escrito con PHP, es muy rápido y está bien diseñado internamente. Consta úicamente de un archivo, llamado lime.php, y no tiene ninguna dependencia. Las pruebas que se muestran en las secciones siguientes utilizan la sintaxis de Lime, por lo que funcionan directamente en cualquier instalación de Symfony. www.librosweb.es 320
  • 321. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales NOTA Las pruebas unitarias y funcionales no están pensadas para lanzarlas en un servidor de produc- ción. Se trata de herramientas para el programador, por lo que solamente deberían ejecutarse en la máquina de desarrollo del programador y no en un servidor de producción. 15.2. Pruebas unitarias Las pruebas unitarias de Symfony son archivos PHP normales cuyo nombre termina en Test.php y que se encuentran en el directorio test/unit/ de la aplicación. Su sintaxis es sencilla y fácil de leer. 15.2.1. ¿Qué aspecto tienen las pruebas unitarias? El listado 15-1 muestra un conjunto típico de pruebas unitarias para la función strtolo- wer(). En primer lugar, se instancia el objeto lime_test (todavía no hace falta que te pre- ocupes de sus parámetros). Cada prueba unitaria consiste en una llamada a un método de la instancia de lime_test. El último parámetro de estos método siempre es una cade- na de texto opcional que se utiliza como resultado del método. Listado 15-1 - Archivo de ejemplo de prueba unitaria, en test/unit/ strtolowerTest.php <?php include(dirname(__FILE__).'/../bootstrap/unit.php'); require_once(dirname(__FILE__).'/../../lib/strtolower.php'); $t = new lime_test(7, new lime_output_color()); // strtolower() $t->diag('strtolower()'); $t->isa_ok(strtolower('Foo'), 'string', 'strtolower() returns a string'); $t->is(strtolower('FOO'), 'foo', 'strtolower() transforms the input to lowercase'); $t->is(strtolower('foo'), 'foo', 'strtolower() leaves lowercase characters unchanged'); $t->is(strtolower('12#?@~'), '12#?@~', 'strtolower() leaves non alphabetical characters unchanged'); $t->is(strtolower('FOO BAR'), 'foo bar', 'strtolower() leaves blanks alone'); $t->is(strtolower('FoO bAr'), 'foo bar', 'strtolower() deals with mixed case input'); $t->is(strtolower(''), 'foo', 'strtolower() transforms empty strings into foo'); Para ejecutar el conjunto de pruebas, se utiliza la tarea test-unit desde la línea de co- mandos. El resultado de esta tarea en la línea de comandos es muy explícito, lo que per- mite localizar fácilmente las pruebas que han fallado y las que se han ejecutado correcta- mente. El listado 15-2 muestra el resultado del ejemplo anterior. Listado 15-2 - Ejecutando una prueba unitaria desde la línea de comandos www.librosweb.es 321
  • 322. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales > symfony test-unit strtolower 1..7 # strtolower() ok 1 - strtolower() returns a string ok 2 - strtolower() transforms the input to lowercase ok 3 - strtolower() leaves lowercase characters unchanged ok 4 - strtolower() leaves non alphabetical characters unchanged ok 5 - strtolower() leaves blanks alone ok 6 - strtolower() deals with mixed case input not ok 7 - strtolower() transforms empty strings into foo # Failed test (.batchtest.php at line 21) # got: '' # expected: 'foo' # Looks like you failed 1 tests of 7. SUGERENCIA La instrucción include al principio del listado 15-1 es opcional, pero hace que el archivo de la prue- ba sea un script de PHP independiente, es decir, que se puede ejecutar sin utilizar la línea de co- mandos de Symfony, mediante php test/unit/strtolowerTest.php. 15.2.2. Métodos para las pruebas unitarias El objeto lime_test dispone de un gran número de métodos para las pruebas, como se muestra en la figura 15-2. Tabla 15-2 - Métodos del objeto lime_test para las pruebas unitarias Método Descripción diag($mensaje) Muestra un comentario, pero no ejecuta ninguna prueba ok($prueba, $mensaje) Si la condición que se indica es true, la prueba tiene éxito is($valor1, $valor2, $mensaje) Compara 2 valores y la prueba pasa si los 2 son iguales (==) isnt($valor1, $valor2, Compara 2 valores y la prueba pasa si no son iguales $mensaje) like($cadena, Prueba que una cadena cumpla con el patrón de una $expresionRegular, $mensaje) expresión regular unlike($cadena, Prueba que una cadena no cumpla con el patrón de una $expresionRegular, $mensaje) expresión regular cmp_ok($valor1, $operador, Compara 2 valores mediante el operador que se indica $valor2, $mensaje) isa_ok($variable, $tipo, Comprueba si la variable que se le pasa es del tipo que se $mensaje) indica isa_ok($objeto, $clase, Comprueba si el objeto que se le pasa es de la clase que se $mensaje) indica www.librosweb.es 322
  • 323. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales can_ok($objeto, $metodo, Comprueba si el objeto que se le pasa dispone del método $mensaje) que se indica is_deeply($array1, $array2, Comprueba que 2 arrays tengan los mismos valores $mensaje) Valida que un archivo existe y que ha sido incluido include_ok($archivo, $mensaje) correctamente Provoca que la prueba siempre falle (es útil para las fail() excepciones) Provoca que la prueba siempre se pase (es útil para las pass() excepciones) Cuenta como si fueran $numeroPruebas pruebas (es útil skip($mensaje, $numeroPruebas) para las pruebas condicionales) Cuenta como si fuera 1 prueba (es útil para las pruebas que todo() todavía no se han escrito) La sintaxis es tan clara que prácticamente se explica por sí sola. Casi todos los métodos permiten indicar un mensaje como último parámetro. Este mensaje es el que se muestra como resultado de la prueba cuando esta tiene éxito. La mejor manera de aprender a uti- lizar estos métodos es utilizarlos, así que es importante el código del listado 15-3, que utiliza todos los métodos. Listado 15-3 - Probando los métodos del objeto lime_test, en test/unit/ ejemploTest.php <?php include(dirname(__FILE__).'/../bootstrap/unit.php'); // Funciones y objetos vacíos para las pruenas class miObjeto { public function miMetodo() { } } function lanza_una_excepcion() { throw new Exception('excepción lanzada'); } // Inicializar el objeto de pruebas $t = new lime_test(16, new lime_output_color()); $t->diag('hola mundo'); $t->ok(1 == '1', 'el operador de igualdad ignora el tipo de la variable'); $t->is(1, '1', 'las cadenas se convierten en números para realizar la comparación'); $t->isnt(0, 1, '0 y 1 no son lo mismo'); www.librosweb.es 323
  • 324. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales $t->like('prueba01', '/pruebad+/', 'prueba01 sigue el patrón para numerar las pruebas'); $t->unlike('pruebas01', '/pruebad+/', 'pruebas01 no sigue el patrón'); $t->cmp_ok(1, '<', 2, '1 es inferior a 2'); $t->cmp_ok(1, '!==', true, '1 y true no son exactamente lo mismo'); $t->isa_ok('valor', 'string', ''valor' es una cadena de texto'); $t->isa_ok(new miObjeto(), 'miObjeto', 'new crea un objeto de la clase correcta'); $t->can_ok(new miObjeto(), 'miMetodo', 'los objetos de la clase miObjeto tienen un método llamado miMetood'); $array1 = array(1, 2, array(1 => 'valor', 'a' => '4')); $t->is_deeply($array1, array(1, 2, array(1 => 'valor', 'a' => '4')), 'el primer array y el segundo array son iguales'); $t->include_ok('./nombreArchivo.php', 'el archivo nombreArchivo.php ha sido incluido correctamente'); try { lanza_una_excepcion(); $t->fail('no debería ejecutarse ningún código después de lanzarse la excepción'); } catch (Exception $e) { $t->pass('la excepción ha sido capturada correctamente'); } if (!isset($variable)) { $t->skip('saltamos una prueba para mantener el contador de pruebas correcto para la condición', 1); } else { $t->ok($variable, 'valor'); } $t->todo('la última prueba que falta'); Las pruebas unitarias de Symfony incluyen muchos más ejemplos de uso de todos estos métodos. SUGERENCIA Puede que sea confuso el uso de is() en vez de ok() en el ejemplo anterior. La razón es que el mensaje de error que muestra is() es mucho más explícito, ya que muestra los 2 argumentos de la prueba, mientras que ok() simplemente dice que la prueba ha fallado. 15.2.3. Parámetros para las pruebas En la inicialización del objeto lime_test se indica como primer parámetro el número de pruebas que se van a ejecutar. Si el número de pruebas realmente realizadas no coincide con este valor, la salida producida por Lime muestra un aviso. El conjunto de pruebas del listado 15-3 producen la salida del listado 15-4. Como en la inicialización se indica que se deben ejecutar 16 pruebas y realmente solo se han realizado 15, en la salida se muestra un mensaje de aviso. www.librosweb.es 324
  • 325. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales Listado 15-4 - El contador de pruebas realizadas permite planificar las pruebas > symfony test-unit ejemplo 1..16 # hola mundo ok 1 - el operador de igualdad ignora el tipo de la variable ok 2 - las cadenas se convierten en números para realizar la comparación ok 3 - 0 y 1 no son lo mismo ok 4 - prueba01 sigue el patrón para numerar las pruebas ok 5 - pruebas01 no sigue el patrón ok 6 - 1 es inferior a 2 ok 7 - 1 y true no son exactamente lo mismo ok 8 - 'valor' es una cadena de texto ok 9 - new crea un objeto de la clase correcta ok 10 - los objetos de la clase miObjeto tienen un método llamado miMetood ok 11 - el primer array y el segundo array son iguales not ok 12 - el archivo nombreArchivo.php ha sido incluido correctamente # Failed test (.testunitejemploTest.php at line 35) # Tried to include './nombreArchivo.php' ok 13 - la excepción ha sido capturada correctamente ok 14 # SKIP saltamos una prueba para mantener el contador de pruebas correcto para la condición ok 15 # TODO la última prueba que falta # Looks like you planned 16 tests but only ran 15. # Looks like you failed 1 tests of 16. El método diag() no cuenta como una prueba. Se utiliza para mostrar mensajes, de for- ma que la salida por pantalla esté más organizada y sea más fácil de leer. Por otra parte, los métodos todo() y skip() cuentan como si fueran pruebas reales. La combinación pass()/fail() dentro de un bloque try/catch cuenta como una sola prueba. Una estrategia de pruebas bien planificada requiere que se indique el número esperado de pruebas. Indicar este número es una buena forma de validar los propios archivos de pruebas, sobre todo en los casos más complicados en los que algunas pruebas se ejecu- tan dentro de condiciones y/o excepciones. Además, si la prueba falla en cualquier punto, es muy fácil de verlo porque el número de pruebas realizadas no coincide con el número de pruebas esperadas. El segundo parámetro del constructor del objeto lime_test indica el objeto que se utiliza para mostrar los resultado. Se trata de un objeto que extiende la clase lime_output. La mayoría de las veces, como las pruebas se realizan en una interfaz de comandos, la sali- da se construye mediante el objeto lime_output_color, que muestra la salida coloreada en los sistemas que lo permiten. 15.2.4. La tarea test-unit La tarea test-unit, que se utiliza para ejecutar las pruebas unitarias desde la línea de comandos, admite como argumento una serie de nombres de pruebas o un patrón de nombre de archivos. El listado 15-5 muestra los detalles. Listado 15-5 - Ejecutando las pruebas unitarias www.librosweb.es 325
  • 326. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales // Estructura del directorio de pruebas test/ unit/ miFuncionalTest.php miSegundoFuncionalTest.php otro/ nombreTest.php > symfony test-unit miFuncional ## Ejecutar miFuncionalTest.php > symfony test-unit miFuncional miSegundoFuncional ## Ejecuta las 2 pruebas > symfony test-unit 'otro/*' ## Ejecuta nombreTest.php > symfony test-unit '*' ## Ejecuta todas las pruebas (de forma recursiva) 15.2.5. Stubs, Fixtures y carga automática de clases La carga automática de clases no funciona por defecto en las pruebas unitarias. Por tan- to, todas las clases que se utilizan en una prueba se deben definir en el propio archivo de la prueba o se deben incluir como una dependencia externa. Este es el motivo por el que muchos archivos de pruebas empiezan con un grupo de instrucciones include, como se muestra en el listado 15-6. Listado 15-6 - Incluyendo las clases de forma explícita en las pruebas unitarias <?php include(dirname(__FILE__).'/../bootstrap/unit.php'); include(dirname(__FILE__).'/../../config/config.php'); require_once($sf_symfony_lib_dir.'/util/sfToolkit.class.php'); $t = new lime_test(7, new lime_output_color()); // isPathAbsolute() $t->diag('isPathAbsolute()'); $t->is(sfToolkit::isPathAbsolute('/test'), true, 'isPathAbsolute() returns true if path is absolute'); $t->is(sfToolkit::isPathAbsolute('test'), true, 'isPathAbsolute() returns true if path is absolute'); $t->is(sfToolkit::isPathAbsolute('C:test'), true, 'isPathAbsolute() returns true if path is absolute'); $t->is(sfToolkit::isPathAbsolute('d:/test'), true, 'isPathAbsolute() returns true if path is absolute'); $t->is(sfToolkit::isPathAbsolute('test'), false, 'isPathAbsolute() returns false if path is relative'); $t->is(sfToolkit::isPathAbsolute('../test'), false, 'isPathAbsolute() returns false if path is relative'); $t->is(sfToolkit::isPathAbsolute('..test'), false, 'isPathAbsolute() returns false if path is relative'); En las pruebas unitarias, no solo se debe instanciar el objeto que se está probando, sino también el objeto del que depende. Como las pruebas unitarias deben ser autosuficien- tes, depender de otras clases puede provocar que más de una prueba falle si alguna cla- se no funciona correctamente. Además, crear objetos reales es una tarea costosa, tanto en número de líneas de código necesarias como en tiempo de ejecución. Debe tenerse en www.librosweb.es 326
  • 327. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales cuenta que la velocidad de ejecución es esencial para las pruebas unitarias, ya que los programadores en seguida se cansan de los procesos que son muy lentos. Si se incluyen muchos scripts en una prueba unitaria, lo más útil es utilizar un sistema sencillo de carga automática de clases. Para ello, la clase sfCore (que se debe incluir ma- nualmente) dispone del método initSimpleAutoload(), que utiliza como parámetro una ruta absoluta. Todas las clases que se encuentren bajo esa ruta, se cargarán automática- mente. Si por ejemplo se quieren cargar automáticamente todas las clases del directorio $sf_symfony_lib_dir/util/, se utilizan las siguientes instrucciones al principio del script de la prueba unitaria: require_once($sf_symfony_lib_dir.'/util/sfCore.class.php'); sfCore::initSimpleAutoload($sf_symfony_lib_dir.'/util'); SUGERENCIA Los objetos Propel generados automáticamente dependen de muchísimas clases, por lo que en cuento se quiere probar un objeto Propel es necesario utilizar la carga automática de clases. Además, para que funcione Propel es necesario incluir los archivos del directorio vendor/propel/ (por lo que la llamada a sfCore se transforma en sfCore::initSimpleAutoload(arr- ay(SF_ROOT_ DIR.’/lib/model’, $sf_symfony_lib_dir.’/vendor/propel’));) e incluir las clases internas de Propel en la ruta para incluir archivos, también llamada ‘include_path’ (se utiliza set_include_path($sf_symfony_lib_dir.’/ vendor’.PATH_SEPARATOR.SF_ROOT_DIR.PATH_SEPARATOR.get_include_path(). Otra técnica muy utilizada para evitar los problemas de la carga automática de clases es el uso de stubs o clases falsas. Un stub es una implementación alternativa de una clase en la que los métodos reales se sustituyen por datos simples especialmente preparados. De esta forma, se emula el comportamiento de la clase real y se reduce su tiempo de ejecución. Los casos típicos para utilizar stubs son las conexiones con bases de datos y las interfaces de los servicios web. En el listado 15-7, las pruebas unitarias para una API de un servicio de mapas utilizan la clase WebService. En vez de ejecutar el método fetch() real de la clase del servicio web, la prueba utiliza un stub que devuelve datos de prueba. Listado 15-7 - Utilizando stubs en las pruebas unitarias require_once(dirname(__FILE__).'/../../lib/WebService.class.php'); require_once(dirname(__FILE__).'/../../lib/MapAPI.class.php'); class testWebService extends WebService { public static function fetch() { return file_get_contents(dirname(__FILE__).'/fixtures/data/servicio_web_falso.xml'); } } $miMapa = new MapAPI(); $t = new lime_test(1, new lime_output_color()); www.librosweb.es 327
  • 328. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales $t->is($miMapa->getMapSize(testWebService::fetch(), 100)); Los datos de prueba pueden ser más complejos que una cadena de texto o la llamada a un método. Los datos de prueba complejos se suelen denominar “fixtures”. Para mejorar el código de las pruebas unitarias, es recomendable mantener los fixtures en archivos in- dependientes, sobre todo si se utilizan en más de una prueba. Además, Symfony es ca- paz de transformar un archivo YAML en un array mediante el método sfYAML::load(). De esta forma, en vez de escribir arrays PHP muy grandes, los datos para las pruebas se pueden guardar en archivos YAML, como en el listado 15-8. Listado 15-8 - Usando archivos para los “fixtures” de las pruebas unitarias // En fixtures.yml: - input: '/test' output: true comment: isPathAbsolute() returns true if path is absolute - input: 'test' output: true comment: isPathAbsolute() returns true if path is absolute - input: 'C:test' output: true comment: isPathAbsolute() returns true if path is absolute - input: 'd:/test' output: true comment: isPathAbsolute() returns true if path is absolute - input: 'test' output: false comment: isPathAbsolute() returns false if path is relative - input: '../test' output: false comment: isPathAbsolute() returns false if path is relative - input: '..test' output: false comment: isPathAbsolute() returns false if path is relative // En testTest.php <?php include(dirname(__FILE__).'/../bootstrap/unit.php'); include(dirname(__FILE__).'/../../config/config.php'); require_once($sf_symfony_lib_dir.'/util/sfToolkit.class.php'); require_once($sf_symfony_lib_dir.'/util/sfYaml.class.php'); $testCases = sfYaml::load(dirname(__FILE__).'/fixtures.yml'); $t = new lime_test(count($testCases), new lime_output_color()); // isPathAbsolute() www.librosweb.es 328
  • 329. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales $t->diag('isPathAbsolute()'); foreach ($testCases as $case) { $t->is(sfToolkit::isPathAbsolute($case['input']), $case['output'],$case['comment']); } 15.3. Pruebas funcionales Las pruebas funcionales validan partes de las aplicaciones. Estas pruebas simulan la na- vegación del usuario, realizan peticiones y comprueban los elementos de la respuesta, tal y como lo haría manualmente un usuario para validar que una determinada acción hace lo que se supone que tiene que hacer. En las pruebas funcionales, se ejecuta un escenar- io correspondiente a lo que se denomina un “caso de uso”. 15.3.1. ¿Cómo son las pruebas funcionales? Las pruebas funcionales se podrían realizar mediante un navegador en forma de texto y un montón de asertos definidos con expresiones regulares complejas, pero sería una pér- dida de tiempo muy grande. Symfony dispone de un objeto especial, llamado sfBrowser, que actua como un navegador que está accediendo a una aplicación Symfony, pero sin necesidad de utilizar un servidor web real (y sin la penalización de las conexiones HTTP). Este objeto permite el acceso directo a los objetos que forman cada petición (el objeto petición, el objeto sesión, el objeto contexto y el objeto respuesta). Symfony también dispone de una extensión de esta clase llamada sfTestBrowser, que está especialmente diseñada para las pruebas funcionales y que tiene todas las características de sfBrowser, además de algunos métodos muy útiles para los asertos. Una prueba funcional suele comenzar con la inicialización del objeto del navegador para pruebas. Este objeto permite realizar una petición a una acción de la aplicación y permite verificar que algunos elementos están presentes en la respuesta. Por ejemplo, cada vez que se genera el esqueleto de un módulo mediante las tareas init-module o propel-init-crud, Symfony crea una prueba funciona de prueba para este módulo. La prueba realiza una petición a la acción por defecto del módulo y comprueba el código de estado de la respuesta, el módulo y la acción calculados por el sistema de en- rutamiento y la presencia de una frase específica en el contenido de la respuesta. Si el módulo se llama foobar, el archivo foobarActionsTest.php generado es similar al del lis- tado 15-9. Listado 15-9 - Prueba funcional por defecto para un módulo nuevo, en tests/ functional/frontend/foobarActionsTest.php <?php include(dirname(__FILE__).'/../../bootstrap/functional.php'); // Create a new test browser $browser = new sfTestBrowser(); $browser->initialize(); $browser-> www.librosweb.es 329
  • 330. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales get('/foobar/index')-> isStatusCode(200)-> isRequestParameter('module', 'foobar')-> isRequestParameter('action', 'index')-> checkResponseElement('body', '!/This is a temporary page/') ; SUGERENCIA Todos los métodos del navegador de Symfony devuelven un objeto sfTestBrowser, por lo que se pueden encadenar las llamadas a los métodos para que los archivos de prueba sean más fáciles de leer. Esta estrategia se llama “interfaz fluida con el objeto”, ya que nada puede parar el flujo de lla- madas a los métodos del objeto. Las pruebas funcionales pueden contener varias peticiones y asertos más complejos, co- mo se mostrará en las próximas secciones. Para ejecutar una prueba funcional, se utiliza la tarea test-functional de la línea de co- mandos de Symfony, como se muestra en el listado 15-10. Los argumentos que se indi- can a la tarea son el nombre de la aplicación y el nombre de la prueba (omitiendo el sufi- jo Test.php). Listado 15-10 - Ejecutando una prueba funcional mediante la línea de comandos > symfony test-functional frontend foobarActions # get /comment/index ok 1 - status code is 200 ok 2 - request parameter module is foobar ok 3 - request parameter action is index not ok 4 - response selector body does not match regex /This is a temporary page/ # Looks like you failed 1 tests of 4. 1..4 Por defecto, las pruebas funcionales generadas automáticamente para un módulo nuevo no pasan correctamente todas las pruebas. El motivo es que en los módulos nuevos, la acción index redirige a una página de bienvenida (que pertenece al módulo default de Symfony) que contiene la frase “This is a temporary page”. Mientras no se modifique la acción index del módulo, las pruebas funcionales de este módulo no se pasarán correcta- mente, lo que garantiza que no se ejecuten correctamente todas las pruebas para un módulo que está sin terminar. NOTA En las pruebas funcionales, la carga automática de clases está activada, por lo que no se deben in- cluir los archivos manualmente. 15.3.2. Navegando con el objeto sfTestBrowser El navegador para pruebas permite realizar peticiones GET y POST. En ambos casos se utiliza una URI real como parámetro. El listado 15-11 muestra cómo crear peticiones con el objeto sfTestBrowser para simular peticiones reales. Listado 15-11 - Simulando peticiones con el objeto sfTestBrowser www.librosweb.es 330
  • 331. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales include(dirname(__FILE__).'/../../bootstrap/functional.php'); // Se crea un nuevo navegador de pruebas $b = new sfTestBrowser(); $b->initialize(); $b->get('/foobar/show/id/1'); // Petición GET $b->post('/foobar/show', array('id' => 1)); // Petición POST // Los métodos get() y post() son atajos del método call() $b->call('/foobar/show/id/1', 'get'); $b->call('/foobar/show', 'post', array('id' => 1)); // El método call() puede simular peticiones de cualquier método $b->call('/foobar/show/id/1', 'head'); $b->call('/foobar/add/id/1', 'put'); $b->call('/foobar/delete/id/1', 'delete'); Una navegación típica no sólo está formada por peticiones a determinadas acciones, sino que también incluye clicks sobre enlaces y botones. Como se muestra en el listado 15- 12, el objeto sfTestBrowser también es capaz de simular la acción de pinchar sobre estos elementos. Listado 15-12 - Simulando una navegación real con el objeto sfTestBrowser $b->get('/'); // Petición a la página principal $b->get('/foobar/show/id/1'); $b->back(); // Volver a la página anterior del historial $b->forward(); // Ir a la página siguiente del historial $b->reload(); // Recargar la página actual $b->click('go'); // Buscar un enlace o botón llamado 'go' y pincharlo El navegador para pruebas incluye un mecanismo para guardar todas las peticiones reali- zadas, por lo que los métodos back() y forward() funcionan de la misma manera que en un navegador real. SUGERENCIA El navegador de pruebas incluye sus propios mecanismos para gestionar las sesiones (sfTestStorage) y las cookies. Entre las interacciones que más se deben probar, las de los formularios son probable- mente las más necesarias. Symfony dispone de 3 formas de probar la introducción de da- tos en los formularios y su envío. Se puede crear una petición POST con los parámetros que se quieren enviar, se puede llamar al método click() con los parámetros del formu- lario en un array o se pueden rellenar los campos del formulario de uno en uno y des- pués pulsar sobre el botón de envío. En cualquiera de los 3 casos, la petición POST resul- tante es la misma. El listado 15-13 muestra un ejemplo. Listado 15-13 - Simulando el envío de un formulario con datos mediante el obje- to sfTe stBrowser // Plantilla de ejemplo en modules/foobar/templates/editSuccess.php <?php echo form_tag('foobar/update') ?> <?php echo input_hidden_tag('id', $sf_params->get('id')) ?> www.librosweb.es 331
  • 332. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales <?php echo input_tag('name', 'foo') ?> <?php echo submit_tag('go') ?> <?php echo textarea('text1', 'foo') ?> <?php echo textarea('text2', 'bar') ?> </form> // Prueba funcional de ejemplo para este formulario $b = new sfTestBrowser(); $b->initialize(); $b->get('/foobar/edit/id/1'); // Opción 1: petición POST $b->post('/foobar/update', array('id' => 1, 'name' => 'dummy', 'commit' => 'go')); // Opción 2: Pulsar sobre el botón de envío con parámetros $b->click('go', array('name' => 'dummy')); // Opción 3: Introducir los valores del formulario campo a campo y // presionar el botón de envío $b->setField('name', 'dummy')-> click('go'); NOTA En las opciones 2 y 3, los valores por defecto del formulario se incluyen automáticamente en su envío y no es necesario especificar el destino del formulario. Si una acción finaliza con una redirección (redirect()), el navegador para pruebas no si- gue automáticamente la redirección, sino que se debe seguir manualmente mediante fo- llowRedirect(), como se muestra en el listado 15-14. Listado 15-14 - El navegador para pruebas no sigue automáticamente las redirecciones // Acción de ejemplo en modules/foobar/actions/actions.class.php public function executeUpdate() { ... $this->redirect('foobar/show?id='.$this->getRequestParameter('id')); } // Prueba funcional de ejemplo para esta acción $b = new sfTestBrowser(); $b->initialize(); $b->get('/foobar/edit?id=1')-> click('go', array('name' => 'dummy'))-> isRedirected()-> // Check that request is redirected followRedirect(); // Manually follow the redirection Existe un último método muy útil para la navegación: restart(), que inicializa el historial de navegación, la sesión y las cookies, es decir, como si se reiniciara el navegador. Una vez realizada la primera petición, el objeto sfTestBrowser dispone de acceso directo a los objetos de la petición, del contexto y de la respuesta. De esta forma, se pueden www.librosweb.es 332
  • 333. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales probar muchas cosas diferentes, desde el contenido textual de las páginas a las cabece- ras de la respuesta, pasando por los parámetros de la petición y la configuración: $peticion = $b->getRequest(); $contexto = $b->getContext(); $respuesta = $b->getResponse(); El objeto sfBrowser Todos los métodos para realizar la navegación descritos en los listados 15-10 a 15-13, no solamen- te están disponibles para las pruebas, sino que se pueden acceder desde cualquier parte de la apli- cación mediante el objeto sfBrowser. La llamada que se debe realizar es la siguiente: // Crear un nuevo navegador $b = new sfBrowser(); $b->initialize(); $b->get('/foobar/show/id/1')-> setField('name', 'dummy')-> click('go'); $content = $b->getResponse()->getContent(); ... El objeto sfBrowser es muy útil para ejecutar scripts programados, como por ejemplo para navegar por una serie de páginas para generar la cache de cada página (el Capítulo 18 muestra un ejemplo detallado). 15.3.3. Utilizando asertos Como el objeto sfTestBrowser dispone de acceso directo a la respuesta y a otros compo- nentes de la petición, es posible realizar pruebas sobre estos componentes. Se podría crear un nuevo objeto lime_test para estas pruebas, pero por suerte, sfTestBrowser dis- pone de un método llamado test() que devuelve un objeto lime_test sobre el que se pueden invocar los métodos para asertos descritos anteriormentes. El listado 15-15 muestra cómo realizar asertos mediante sfTestBrowser. Listado 15-15 - El navegador para pruebas dispone del método test() para reali- zar pruebas $b = new sfTestBrowser(); $b->initialize(); $b->get('/foobar/edit/id/1'); $request = $b->getRequest(); $context = $b->getContext(); $response = $b->getResponse(); // Acceder a los métodos de lime_test mediante el método test() $b->test()->is($request->getParameter('id'), 1); $b->test()->is($response->getStatuscode(), 200); $b->test()->is($response->getHttpHeader('content-type'), 'text/html;charset=utf-8'); $b->test()->like($response->getContent(), '/edit/'); www.librosweb.es 333
  • 334. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales NOTA Los métodos getResponse(), getContext(), getRequest() y test() no devuelven un objeto sfTestBrowser, por lo que no se pueden encadenar después de ellos otras llamadas a los méto- dos de sfTestBrowser. Las cookies enviadas y recibidas se pueden probar fácilmente mediante los objetos de la petición y de la respuesta, como se muestra en el listado 15-16. Listado 15-16 - Probando las cookies con sfTestBrowser $b->test()->is($request->getCookie('foo'), 'bar'); // Cookie enviada $cookies = $response->getCookies(); $b->test()->is($cookies['foo'], 'foo=bar'); // Cookie recibida Si se utiliza el método test() para probar los elementos de la petición, se acaban escrib- iendo unas líneas de código demasiado largas. Afortunadamente, sfTestbrowser contiene una serie de métodos especiales que permiten mantener las pruebas funcionales cortas y fáciles de leer, además de que devuelven objetos sfTestBrowser. El listado 15-15 se podría reescribir por ejemplo de forma más sencilla como se muestra en el listado 15-17. Listado 15-17 - Realizando pruebas directamente con sfTestBrowser $b = new sfTestBrowser(); $b->initialize(); $b->get('/foobar/edit/id/1')-> isRequestParameter('id', 1)-> isStatusCode()-> isResponseHeader('content-type', 'text/html; charset=utf-8')-> responseContains('edit'); El código de estado 200 es el valor por defecto que espera el método isStatusCode(), por lo que, para comprobar si la respuesta es correcta, se puede realizar la llamada sin argumentos. Otra ventaja del uso de estos métodos especiales es que no es necesario especificar el texto que se muestra en la salida, como sí que era necesario en los métodos del objeto lime_test. Los mensajes se generan automáticamente en los métodos especiales, y la salida producida es clara y muy sencilla de entender. # get /foobar/edit/id/1 ok 1 - request parameter "id" is "1" ok 2 - status code is "200" ok 3 - response header "content-type" is "text/html" ok 4 - response contains "edit" 1..4 En la práctica, los métodos especiales del listado 15-17 cubren la mayor parte de las pr- uebas habituales, por lo que raramente es necesario utilizar el método test() del objeto sfTestBrowser. El listado 15-14 demuestra que sfTestBrowser no sigue directamente las redirecciones. La ventaja de este comportamiento es que se pueden probar las propias redirecciones. El listado 15-18 muestra cómo probar la respuesta del listado 15-14. www.librosweb.es 334
  • 335. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales Listado 15-18 - Probando las redirecciones con sfTestBrowser $b = new sfTestBrowser(); $b->initialize(); $b-> get('/foobar/edit/id/1')-> click('go', array('name' => 'dummy'))-> isStatusCode(200)-> isRequestParameter('module', 'foobar')-> isRequestParameter('action', 'update')-> isRedirected()-> // Comprobar que la respuseta es una redirección followRedirect()-> // Obligar manualmente a seguir la redirección isStatusCode(200)-> isRequestParameter('module', 'foobar')-> isRequestParameter('action', 'show'); 15.3.4. Utilizando los selectores CSS Muchas pruebas funcionales validan que una página sea correcta comprobando que un determinado texto se encuentre en el contenido de la respuesta. Utilizando el método responseContains() y las expresiones regulares, es posible comprobar que existe un de- terminado texto, los atributos de una etiqueta o sus valores. Pero si lo que se quiere pro- bar se encuentra en lo más profundo del árbol DOM del contenido de la respuesta, la so- lución de las expresiones regulares es demasiado compleja. Este es el motivo por el que el objeto sfTestBrowser dispone de un método llamado getResponseDom(). El método devuelve un objeto DOM de libXML2, que es mucho más fá- cil de procesar que el texto simple. El listado 15-19 muestra un ejemplo de uso de este método. Listado 15-19 - El navegador para pruebas devuelve el contenido de la respues- ta como un objeto DOM $b = new sfTestBrowser(); $b->initialize(); $b->get('/foobar/edit/id/1'); $dom = $b->getResponseDom(); $b->test()->is($dom->getElementsByTagName('input')->item(1)->getAttribute('type'),'text'); Sin embargo, procesar un documento HTML con los métodos DOM de PHP no es lo sufic- ientemente rápido y sencillo. Por su parte, los selectores utilizados en las hojas de estilos CSS son una forma aun más potente de obtener los elementos de un documento HTML. Symfony incluye una herramienta llamada sfDomCssSelector, cuyo constructor espera un documento DOM como argumento. Esta utilidad dispone de un método llamado getTexts() que devuelve un array de las cadenas de texto seleccionadas mediante un se- lector CSS, y otro método llamado getElements() que devuelve un array de elementos DOM. El listado 15-20 muestra un ejemplo. Listado 15-20 - El navegador para pruebas permite acceder al contenido de la respuesta mediante el objeto sfDomCssSelector www.librosweb.es 335
  • 336. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales $b = new sfTestBrowser(); $b->initialize(); $b->get('/foobar/edit/id/1'); $c = new sfDomCssSelector($b->getResponseDom()) $b->test()->is($c->getTexts('form input[type="hidden"][value="1"]'), array(''); $b->test()->is($c->getTexts('form textarea[name="text1"]'), array('foo')); $b->test()->is($c->getTexts('form input[type="submit"]'), array('')); Como es habitual, Symfony busca siempre la máxima brevedad y claridad en el código, por lo que se dispone de un método alternativo llamado checkResponseElement(). Utili- zando este método, el listado 15-20 se puede transformar en el listado 15-21. Listado 15-21 - El navegador para pruebas permite acceder a los elementos de la respuesta utilizando selectores de CSS $b = new sfTestBrowser(); $b->initialize(); $b->get('/foobar/edit/id/1')-> checkResponseElement('form input[type="hidden"][value="1"]', true)-> checkResponseElement('form textarea[name="text1"]', 'foo')-> checkResponseElement('form input[type="submit"]', 1); El comportamiento del método checkResponseElement() depende del tipo de dato del se- gundo argumento que se le pasa: ▪ Si es un valor booleano, comprueba si existe un elemento que cumpla con el se- lector CSS indicado. ▪ Si es un número entero, comprueba que el selector CSS indicado devuelva el nú- mero de elementos de este parámetro. ▪ Si es una expresión regular, comprueba que el primer elemento seleccionado me- diante el selector CSS cumpla el patrón de la expresión regular. ▪ Si es una expresión regular precedida de !, comprueba que el primer elemento seleccionado mediante el selector CSS no cumpla con el patrón de la expresión regular. ▪ En el resto de casos, compara el primer elemento seleccionado mediante el se- lector CSS y el valor del segundo argumento que se pasa en forma de cadena de texto. El método acepta además un tercer parámetro opcional en forma de array asociativo. De esta forma es posible no solo realizar la prueba sobre el primer elemento devuelto por el selector CSS (si es que devuelve varios elementos) sino sobre otro elemento que se enc- uentra en una posición determinada, tal y como muestra el listado 15-22. Listado 15-22 - Utilizando la opción de posición para comprobar un elemento que se encuentra en una posición determinada $b = new sfTestBrowser(); $b->initialize(); $b->get('/foobar/edit?id=1')-> checkResponseElement('form textarea', 'foo')-> checkResponseElement('form textarea', 'bar', array('position' => 1)); www.librosweb.es 336
  • 337. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales El array de opciones también se puede utilizar para realizar 2 pruebas a la vez. Se puede comprobar que existe un elemento que cumple un selector y al mismo tiempo comprobar cuantos elementos lo cumplen, como se muestra en el listado 15-23. Listado 15-23 - Utilizando la opción para contar el número de elementos que cumplen el selector CSS $b = new sfTestBrowser(); $b->initialize(); $b->get('/foobar/edit?id=1')-> checkResponseElement('form input', true, array('count' => 3)); La herramienta del selector es bastante potente, ya que acepta la mayor parte de los se- lectores de CSS 2.1. De esta forma, se pueden hacer selecciones tan complejas como las que se muestran en el listado 15-24. Listado 15-24 - Ejemplo de selectores CSS complejos que acepta checkResponseElement() $b->checkResponseElement('ul#list li a[href]', 'click me'); $b->checkResponseElement('ul > li', 'click me'); $b->checkResponseElement('ul + li', 'click me'); $b->checkResponseElement('h1, h2', 'click me'); $b->checkResponseElement('a[class$="foo"][href*="bar.html"]', 'my link'); 15.3.5. Trabajando en el entorno de pruebas El objeto sfTestBrowser utiliza un controlador frontal especial, que trabaja en el entorno test. El listado 15-25 muestra la configuración por defecto de este entorno. Listado 15-25 - Configuración por defecto del entorno test, en myapp/config/ settings.yml test: .settings: # E_ALL | E_STRICT & ~E_NOTICE = 2047 error_reporting: 2047 cache: off web_debug: off no_script_name: off etag: off En este entorno, la cache y la barra de depuración web están desactivadas. No obstante, la ejecución del código genera logs en un archivo distinto a los logs de los entornos dev y prod, por lo que se pueden observar de forma independiente (en miproyecto/log/miapli- cacion_test.log). Además, en este entorno las excepciones no detienen la ejecución de los scripts, de forma que se pueda ejecutar un conjunto completo de pruebas incluso cuando falla alguna prueba. También es posible definir una conexión específica con la ba- se de datos, por ejemplo para utilizar una base de datos que tenga datos de prueba. Antes de utilizar el objeto sfTestBrowser, es necesario inicializarlo. Si se necesita, es po- sible especificar el nombre del servidor para la aplicación y una dirección IP para el clien- te, por si la aplicación controla estos dos parámetros. El listado 15-26 muestra cómo configurar estos parámetros. www.librosweb.es 337
  • 338. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales Listado 15-26 - Indicar el hostname y la IP en el navegador para pruebas $b = new sfTestBrowser(); $b->initialize('miaplicacion.ejemplo.com', '123.456.789.123'); 15.3.6. La tarea test-functional La tarea test-functional puede ejecutar una o varias pruebas funcionales, dependiendo del número de argumentos indicados. La sintaxis que se utiliza es muy similar a la de la tarea test-unit, salvo que la tarea para pruebas funcionales requiere como primer argu- mento el nombre de una aplicación, tal y como muestra el listado 15-27. Listado 15-27 - Sintaxis de la tarea para pruebas funcionales // Estructura del directorio de pruebas test/ functional/ frontend/ miModuloActionsTest.php miEscenarioTest.php backend/ miOtroEscenarioTest.php ## Ejecutar todas las pruebas funcionales de una aplicacion recursivamente > symfony test-functional frontend ## Ejecutar la prueba funcional cuyo nombre se indica como parámetro > symfony test-functional frontend myScenario ## Ejecutar todas las pruebas funcionales cuyos nombres cumplan con el patrón indicado > symfony test-functional frontend my* 15.4. Recomendaciones sobre el nombre de las pruebas En esta sección se presentan algunas de las buenas prácticas para mantener bien organi- zadas las pruebas y facilitar su mantenimiento. Los consejos abarcan la organización de los archivos, de las pruebas unitarias y de las pruebas funcionales. En lo que respecta a la estructura de archivos, los archivos de las pruebas unitarias de- berían nombrarse según la clase que se supone que están probando y las pruebas funcio- nales deberían nombrarse en función del módulo o escenario que se supone que están probando. El listado 15-28 muestra un ejemplo de estas recomendaciones. Como el nú- mero de archivos en el directorio test/ puede crecer muy rápidamente, si no se siguen estas recomendaciones, es posible que sea muy difícil encontrar el archivo de una prueba determinada. Listado 15-28 - Ejemplo de recomendaciones sobre el nombre de los archivos test/ unit/ miFuncionTest.php miSegundaFuncionTest.php foo/ barTest.php functional/ frontend/ www.librosweb.es 338
  • 339. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales miModuloActionsTest.php miEscenarioTest.php backend/ miOtroEscenarioTest.php En las pruebas unitarias, una buena práctica consiste en agrupar las pruebas según la función o método y comenzar cada grupo de pruebas con una llamada al método diag(). Los mensajes de cada prueba unitaria deberían mostrar el nombre de la función o méto- do que se prueba, seguido de un verbo y una propiedad, de forma que el resultado que se muestra parezca una frase que describe una propiedad de un objeto. El listado 15-29 muestra un ejemplo. Listado 15-29 - Ejemplo de recomendaciones para las pruebas unitarias // srttolower() $t->diag('strtolower()'); $t->isa_ok(strtolower('Foo'), 'string', 'strtolower() devuelve una cadena de texto'); $t->is(strtolower('FOO'), 'foo', 'strtolower() transforma la entrada en minúsculas'); # strtolower() ok 1 - strtolower() devuelve una cadena de texto ok 2 - strtolower() transforma la entrada en minúsculas Las pruebas funcionales deberían agruparse por página y deberían comenzar con una pe- tición. El listado 15-30 muestra un ejemplo de esta práctica. Listado 15-30 - Ejemplo de recomendaciones para las pruebas funcionales $browser-> get('/foobar/index')-> isStatusCode(200)-> isRequestParameter('module', 'foobar')-> isRequestParameter('action', 'index')-> checkResponseElement('body', '/foobar/') ; # get /comment/index ok 1 - status code is 200 ok 2 - request parameter module is foobar ok 3 - request parameter action is index ok 4 - response selector body matches regex /foobar/ Si se sigue esta recomendación, el resultado de la prueba es lo suficientemente claro co- mo para utilizarlo como documentación técnica del proyecto, ya que puede hacer innece- saria la propia documentación de la aplicación. 15.5. Otras utilidades para pruebas Las herramientas que incluye Symfony para realizar pruebas unitarias y funcionales son suficientes para la mayoría de casos. No obstante, se muestran a continuación algunas técnicas adicionales para resolver problemas comunes con las pruebas automatizadas: ejecutar pruebas en un entorno independiente, acceder a la base de datos desde las pr- uebas, probar la cache y realizar pruebas de las interacciones en el lado del cliente. www.librosweb.es 339
  • 340. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales 15.5.1. Ejecutando las pruebas en grupos Las tareas test-unit y test-functional ejecutan una sola prueba o un conjunto de prue- bas. Sin embargo, si se ejecutan las tareas sin indicar ningún parámetro, se lanzan todas las pruebas unitarias y funcionales del directorio test/. Para evitar el riesgo de interefe- rencias de unas pruebas a otras, cada prueba se ejecuta en un entorno de ejecución in- dependiente. Además, cuando se ejecutan todas las pruebas, el resultado que se mues- tra no es el mismo que el que genera cada prueba de forma independiente, ya que en es- te caso la salida estaría formada por miles de líneas. Lo que se hace es generar una sali- da resumida especialmente preparada. Por este motivo, la ejecución de un gran número de pruebas utiliza un test harness, que es un framework de pruebas con algunas carac- terísticas especiales. El test harness depende de un componente del framework Lime lla- mado lime_harness. Como se muestra en el listado 15-31, la salida producida indica el estado de las pruebas archivo por archivo y al final se muestra un resumen de todas las pruebas que se han pasado y el número total de pruebas. Listado 15-31 - Ejecutando todas las pruebas mediante el test harness > symfony test-unit unit/miFuncionTest.php.................ok unit/miSegundaFuncionTest.php..........ok unit/foo/barTest.php...................not ok Failed Test Stat Total Fail List of Failed ------------------------------------------------------------------ unit/foo/barTest.php 0 2 2 62 63 Failed 1/3 test scripts, 66.66% okay. 2/53 subtests failed, 96.22% okay. Las pruebas se ejecutan de la misma forma que si se lanzaran una a una, solamente es la salida la que se resume para hacerla más útil. De hecho, la estadística final se centra en las pruebas que no han tenido éxito y ayuda a localizarlas. Incluso es posible lanzar todas las pruebas de cualquier tipo mediante la tarea test-all, que también hace uso del test harness, como se muestra en el listado 15-32. Una buena práctica consiste en ejecutar esta tarea antes de realizar el paso a producción del nuevo código, ya que asegura que no se ha introducido ningún nuevo error desde la versión anterior. Listado 15-32 - Ejecutando todas las pruebas de un proyecto > symfony test-all 15.5.2. Acceso a la base de datos Normalmente, las pruebas unitarias necesitan acceder a la base de datos. Cuando se lla- ma al método sfTestBrowser::get() por primera vez, se inicializa una conexión con la base de datos. No obstante, si se necesita acceder a la base de datos antes de utilizar sfTestBrowser, se debe inicializar el objeto sfDabataseManager a mano, como muestra el listado 15-33. Listado 15-33 - Inicializando la base de datos en una prueba www.librosweb.es 340
  • 341. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales $databaseManager = new sfDatabaseManager(); $databaseManager->initialize(); // Opcionalmente, se puede obtener la conexión con la base de datos $con = Propel::getConnection(); Antes de comenzar las pruebas, se suele cargar la base de datos con datos de prueba, también llamados fixtures. El objeto sfPropelData permite realizar esta carga. No sola- mente es posible utilizar este objeto para cargar datos a partir de un archivo (como con la tarea propel-load-data) sino que también es posible hacerlo desde un array, como muestra el listado 15-34. Listado 15-34 - Cargando datos en la base de datos desde una prueba $data = new sfPropelData(); // Cargar datos desde un archivo $data->loadData(sfConfig::get('sf_data_dir').'/fixtures/test_data.yml'); // Cargar datos desde un array $fixtures = array( 'Article' => array( 'article_1' => array( 'title' => 'foo title', 'body' => 'bar body', 'created_at' => time(), ), 'article_2' => array( 'title' => 'foo foo title', 'body' => 'bar bar body', 'created_at' => time(), ), ), ); $data->loadDataFromArray($fixtures); Una vez cargados los datos, se pueden utilizar los objetos Propel necesarios como en cualquier aplicación normal. Las pruebas unitarias deben incluir los archivos correspond- ientes a esos objetos (se puede utilizar el método sfCore::sfSimpleAutoloading() para automatizar la carga, como se explicó en la sección anterior “Stubs, Fixtures y carga au- tomática de clases”). Los objetos de Propel se cargan automáticamente en las pruebas funcionales. 15.5.3. Probando la cache Cuando se habilita la cache para una aplicación, las pruebas funcionales se encargan de verificar que las acciones guardadas en la cache se comportan como deberían. En primer lugar, se habilita la cache para el entorno de pruebas (en el archivo set- tings.yml). Una vez habilitada, se puede utilizar el método isCached() del objeto sfTestBrowser para comprobar si una página se ha obtenido directamente de la cache o ha sido generada en ese momento. El listado 15-35 muestra cómo utilizar este método. Listado 15-35 - Probando la cache con el método isCached() www.librosweb.es 341
  • 342. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales <?php include(dirname(__FILE__).'/../../bootstrap/functional.php'); // Create a new test browser $b = new sfTestBrowser(); $b->initialize(); $b->get('/mymodule'); $b->isCached(true); // Comprueba si la respuesta viene de la cache $b->isCached(true, true); // Comprueba si la respuesta de la cache incluye el layoutlayout $b->isCached(false); // Comprueba que la respuesta no venga de la cache NOTA No es necesario borrar la cache antes de realizar la prueba funcional, ya que el proceso de arranq- ue utilizado por la prueba se encarga de hacerlo automáticamente. 15.5.4. Probando las interacciones en el lado del cliente El principal inconveniente de las técnicas descritas anteriormente es que no pueden si- mular el comportamiento de JavaScript. Si se definen interacciones muy complejas, como por ejemplo interacciones con Ajax, es necesario reproducir de forma exacta los movim- ientos del ratón y las pulsaciones de teclado que realiza el usuario y ejecutar los scripts de JavaScript. Normalmente, estas pruebas se hacen a mano, pero cuestan mucho tiem- po y son propensas a cometer errores. La solución a estos problemas se llama Selenium (http://www.openqa.org/selenium/), que consiste en un framework de pruebas escrito completamente en JavaScript. Selenium permite realizar una serie de acciones en la página de la misma forma que las haría un usuario normal. La ventaja de Selenium sobre el objeto sfBrowser es que Selenium es capaz de ejecutar todo el código JavaScript de la página, incluidas las interacciones crea- das con Ajax. Symfony no incluye Selenium por defecto. Para instalarlo, se crea un directorio llamado selenium/ en el directorio web/ del proyecto y se descomprime el contenido del archivo descargado desde http://www.openqa.org/selenium-core/download.action. Como Selenium se basa en JavaScript y la mayoría de navegadores tienen unas restricciones de seguridad muy estrictas, es importante ejecutar Selenium desde el mismo servidor y el mismo puerto que el que utiliza la propia aplicación. SUGERENCIA Debe ponerse especial cuidado en no subir el directorio selenium/ al servidor de producción, ya que estaría disponible para cualquier usuario que acceda a la raíz del servidor desde un navegador web. Las pruebas de Selenium se escriben en HTML y se guardan en el directorio web/selen- ium/tests/. El listado 15-36 muestra un ejemplo de prueba funcional en la que se carga la página principal, se pulsa el enlace “pinchame” y se busca el texto “Hola Mundo” en el www.librosweb.es 342
  • 343. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales contenido de la respuesta. Para acceder a la aplicación en el entorno test, se debe utili- zar el controlador frontal llamado miaplicacion_test.php. Listado 15-36 - Un ejemplo de prueba de Selenium, en web/selenium/test/ testIndex.html <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta content="text/html; charset=UTF-8" http-equiv="content-type"> <title>Index tests</title> </head> <body> <table cellspacing="0"> <tbody> <tr><td colspan="3">First step</td></tr> <tr><td>open</td> <td>/miaplicacion_test.php/</td> <td>&nbsp;</td></tr> <tr><td>clickAndWait</td> <td>link=pinchame</td> <td>&nbsp;</td></tr> <tr><td>assertTextPresent</td> <td>Hola Mundo</td> <td>&nbsp;</td></tr> </tbody> </table> </body> </html> Cada caso de prueba consiste en una página HTML con una tabla de 3 columnas: coman- do, destino y valor. No obstante, no todos los comandos indican un valor. En caso de que no se utilice un valor, es recomendable incluir el valor &nbsp; en esa columna (para que la tabla se vea mejor). El sitio web de Selenium dispone de la lista completa de coman- dos que se pueden utilizar. También es necesario añadir esta prueba al conjunto completo de pruebas, insertando una nueva línea en la tabla del archivo TestSuite.html del mismo directorio. El listado 15-37 muestra cómo hacerlo. Listado 15-37 - Añadiendo un archivo de pruebas al conjunto de pruebas, en web/selenium/test/TestSuite.html ... <tr><td><a href='./testIndex.html'>Mi primera prueba</a></td></tr> ... Para ejecutar la prueba, solamente es necesario acceder a la página: http://miaplicacion.ejemplo.com/selenium/index.html Si se selecciona la “Main Test Suite” y se pulsa sobre el botón de ejecutar todas las prue- bas, el navegador reproducirá automáticamente todos los pasos que se han indicado. NOTA Como las pruebas de Selenium se ejecutan en el propio navegador, permiten descubrir las incon- sistencias entre navegadores. Si se construye la prueba para un solo navegador, se puede lanzar esa prueba sobre todos los navegadores y comprobar su funcionamiento. Como las pruebas de Selenium se crean con HTML, acaba siendo muy aburrido escribir todo ese código HTML. Afortunadamente, existe una extensión de Selenium para Firefox www.librosweb.es 343
  • 344. Symfony, la guía definitiva Capítulo 15. Pruebas unitarias y funcionales (http://seleniumrecorder.mozdev.org/) que permite grabar todos los movimientos y accio- nes realizadas sobre una página y guardarlos como una prueba. Mientras se graba una sesión de navegación, se pueden añadir pruebas de tipo asertos pulsando el botón dere- cho sobre la ventana del navegador y seleccionando la opción apropiada del menún “Ap- pend Selenium Command”. Una vez realizados todos los movimientos y añadidos todos los comandos, se pueden guardar en un archivo HTML para añadirlo al conjunto de pruebas. La extensión de Fire- fox incluso permite ejecutar las pruebas de Selenium que se han creado con la extensión. NOTA No debe olvidarse reinicializar los datos de prueba antes de lanzar cada prueba de Selenium. 15.6. Resumen La automatización de pruebas abarca tanto las pruebas unitarias (que validan métodos o funciones) como las pruebas funcionales (que validan características completas de la aplicación). Symfony utiliza el framework de pruebas Lime para las pruebas unitarias y proporciona la clase sfTestBrowser para las pruebas funcionales. En ambos casos, se dis- pone de métodos para realizar todo tipo de asertos, desde los más sencillos hasta los más complejos, como por ejemplo los que se realizan mediante los selectores de CSS. La línea de comandos de Symfony permite ejecutar las pruebas de una en una (mediante las tareas test-unit y test-functional) o en grupo (mediante la tarea test-all). En lo que respecta a los datos, las pruebas automatizadas utilizan stubs (clases falsas) y fixtu- res (datos de prueba complejos) y Symfony simplifica su uso desde las pruebas unitarias. Si se definen las suficientes pruebas unitarias como para probar la mayor parte de una aplicación (quizás aplicando la metodología TDD), es mucho más seguro refactorizar el código de la aplicación y añadir nuevas características. Incluso, en ocasiones, las pruebas pueden reducir el tiempo requerido para la documentación técnica del proyecto. www.librosweb.es 344
  • 345. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones Capítulo 16. Herramientas para la administración de aplicaciones Durante el desarrollo y la instalación de las aplicaciones, los programadores necesitan to- da la información posible para determinar si la aplicación está funcionando como debería. Normalmente, esta información se obtiene mediante los archivos de log y las herramien- tas de depuración o debug. Los frameworks como Symfony son el núcleo de las aplicacio- nes, por lo que es esencial que el propio framework disponga de las herramientas nece- sarias para asegurar un desarrollo eficiente de las aplicaciones. Durante la ejecución de una aplicación en el servidor de producción, el administrador de sistemas repite una serie de tareas, como la rotación de los logs, la actualización de las aplicaciones, etc. Por este motivo, un framework también debe proporcionar las herram- ientas necesarias para automatizar lo más posible estas tareas. En este capítulo se detallan las herramientas de gestión de aplicaciones que dispone Symfony para realizar todas las tareas anteriores. 16.1. Logs La única forma de comprender lo sucedido cuando falla la ejecución de una petición, con- siste en echar un vistazo a la traza generada por el proceso que se ejecuta. Afortunada- mente, y como se va a ver en esta sección, tanto PHP como Symfony guardan mucha in- formación de este tipo en archivos de log. 16.1.1. Logs de PHP PHP dispone de una directiva llamada error_reporting, que se define en el archivo de configuración php.ini, y que especifica los eventos de PHP que se guardan en el archivo de log. Symfony permite redefinir el valor de esta opción, tanto a nivel de aplicación co- mo de entorno, en el archivo settings.yml, tal y como se muestra en el listado 16-1. Listado 16-1 - Indicando el valor de la directiva error_reporting, en miaplicacion/ config/settings.yml prod: .settings: error_reporting: 257 dev: .settings: error_reporting: 4095 Los números que se indican son una forma abreviada de referirse a los distintos niveles de error (la documentación de PHP contiene toda la información relativa a estos niveles). Básicamente, el valor 4095 es la forma abreviada del valor E_ALL | E_STRICT, y el valor 257 se refiere a E_ERROR | E_USER_ERROR (que es el valor por defecto de cualquier nuevo entorno definido). www.librosweb.es 345
  • 346. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones Para no penalizar el rendimiento de la aplicación en el entorno de producción, el servidor solamente guarda en el archivo de log los errores críticos de PHP. No obstante, en el en- torno de desarrollo, se guardan en el log todos los tipos de eventos, de forma que el pro- gramador puede disponer de la máxima información para seguir la pista a los errores. El lugar en el que se guardan los archivos de log de PHP depende de la configuración del archivo php.ini. Si no se ha modificado su valor, PHP utiliza las herramientas de log del servidor web (como por ejemplo los logs de error del servidor Apache). En este caso, los archivos de log de PHP se encuentran en el directorio de logs del servidor web. 16.1.2. Logs de Symfony Además de los archivos de log creados por PHP, Symfony también guarda mucha infor- mación de sus propios eventos en otros archivos de log. Los archivos de log creados por Symfony se encuentran en el directorio miproyecto/log/. Symfony crea un archivo por cada aplicación y cada entorno. El archivo del entorno de desarrollo de una aplicación lla- mada miaplicacion sería miaplicacion_dev.log y el archivo de log del entorno de pro- ducción de la misma aplicación se llamaría miaplicacion_prod.log. Si se dispone de una aplicación Symfony ejecutándose, se puede observar que la sintaxis de los archivos de log generados es muy sencilla. Cada evento resulta en una nueva línea en el archivo de log de la aplicación. Cada línea incluye la fecha y hora a la que se ha producido, el tipo de evento, el objeto que ha sido procesado y otros detalles relevantes que dependen de cada tipo de evento y/o objeto procesado. El listado 16-2 muestra un ejemplo del contenido de un archivo de log de Symfony. Listado 16-2 - Contenido de un archivo de log de Symfony, en log/ miaplicacion_dev.php Nov 15 16:30:25 symfony [info ] {sfAction} call "barActions->executemessages()" Nov 15 16:30:25 symfony [debug] SELECT bd_message.ID, bd_message.SENDER_ID, bd_... Nov 15 16:30:25 symfony [info ] {sfCreole} executeQuery(): SELECT bd_message.ID... Nov 15 16:30:25 symfony [info ] {sfView} set slot "leftbar" (bar/index) Nov 15 16:30:25 symfony [info ] {sfView} set slot "messageblock" (bar/mes... Nov 15 16:30:25 symfony [info ] {sfView} execute view for template "messa... Nov 15 16:30:25 symfony [info ] {sfView} render "/home/production/miproyecto/... Nov 15 16:30:25 symfony [info ] {sfView} render to client Estos archivos de log contienen mucha información, como por ejemplo las consultas SQL enviadas a la base de datos, las plantillas que se han procesado, las llamadas realizadas entre objetos, etc. 16.1.2.1. Configuración del nivel de log de Symfony Symfony define ocho niveles diferentes para los mensajes de log: emerg, alert, crit, err, warning, notice, info y debug, que son los mismos niveles que define el paquete PEAR::Log (http://pear.php.net/package/Log/). El archivo de configuración logging.yml de cada aplicación permite definir el nivel de los mensajes que se guardan en el archivo de log, como se muestra en el listado 16-3. www.librosweb.es 346
  • 347. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones Listado 16-3 - Configuración por defecto de los archivos de log en Symfony, en miaplicacion/config/logging.yml prod: enabled: off level: err rotate: on purge: off dev: test: #all: # enabled: on # level: debug # rotate: off # period: 7 # history: 10 # purge: on Por defecto, en todos los entornos salvo en el de producción, se guardan en los archivos de log todos los mensajes (hasta el nivel menos importante, el nivel debug). En el entor- no de producción, no se utilizan por defecto los archivos de log. Además, en este mismo entorno, si se activan los logs asignando el valor on a la opción enabled, solamente se guardan los mensajes más importantes (de crit a emerg). En el archivo logging.yml se puede modificar el nivel de los mensajes guardados para ca- da entorno de ejecución, de forma que se limite el tipo de mensajes que se guardan en el archivo de log. Las opciones rotate, period, history y purge se describen más adelante en la sección “Borrando y rotando archivos de log”. SUGERENCIA Los valores de las opciones de log son accesibles durante la ejecución de la aplicación mediante el objeto sfConfig y el uso del prefijo sf_logging_. Para determinar si están habilitados los archivos de log, se utilizaría por ejemplo la siguiente llamada: sfConfig::get(’sf_ logging_enabled’). 16.1.2.2. Añadiendo un mensaje de log Además de los mensajes generados por Symfony, también es posible añadir mensajes propios en el archivo de log desde el código de la aplicación, utilizando alguna de las téc- nicas mostradas en el listado 16-4. Listado 16-4 - Añadiendo un mensaje de log propio // Desde la acción $this->logMessage($mensaje, $nivel); // Desde una plantilla <?php use_helper('Debug') ?> <?php log_message($mensaje, $nivel) ?> www.librosweb.es 347
  • 348. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones El valor de la opción $nivel puede ser uno de los valores definidos para los mensajes de log de Symfony. Además, para escribir un mensaje en el log desde cualquier punto de la aplicación, se pueden utilizar directamente los métodos de sfLogger, como se muestra en el listado 16- 5. Los métodos disponibles comparten el mismo nombre que los niveles de log definidos. Listado 16-5 - Añadiendo un mensaje de log propio desde cualquier punto de la aplicación if (sfConfig::get('sf_logging_enabled')) { sfContext::getInstance()->getLogger()->info($mensaje); } Personalizando el mecanismo de logs Solamente disponible en las últimas versiones de Symfony El mecanismo de log de Symfony es muy sencillo y es muy fácil de personalizar. El unico requisito es que las clases del nuevo mecanismo de log deben implementar la interfaz sfLoggerInterface, que define un método llamado log(). Symfony invoca el método log() con 2 parámetros: $mensaje (el mensaje que se quiere guardar en el log), $prioridad (el nivel del mensaje). La siguiente clase miPropioLog define un mecanismo de log muy sencillo que utiliza la función error_log de PHP: class miPropioLog implements sfLoggerInterface { public function log($mensaje, $prioridad) { error_log(sprintf('%s (%s)', $mensaje, sfLogger::getPriorityName($prioridad))); } } Para registrar una clase propia de log, se puede utilizar el método sfLogger::getInstance()->registerLogger(). Si por ejemplo se quiere utilizar PEAR::Log, se añade lo siguiente al archivo config.php de la aplicación: require_once('Log.php'); require_once('Log/error_log.php'); // Clase sencilla que implementa la interfaz del mecanismo de log // que se quiere utilizar con Symfony class Log_my_error_log extends Log_error_log implements sfLoggerInterface { } // Registrar la clase $log = Log::singleton('my_error_log', PEAR_LOG_TYPE_SYSTEM, 'symfony'); sfLogger::getInstance()->registerLogger($log); www.librosweb.es 348
  • 349. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones 16.1.2.3. Borrando y rotando archivos de log Periódicamente es necesario borrar los archivos del directorio log/ de las aplicaciones, ya que estos archivos suelen crecer en tamaño varios MB cada pocos días, aunque todo de- pende del tráfico de la aplicación. Symfony proporciona la tarea log-purge para este propósito, y se puede ejecutar de forma periódica manualmente o mediante una tarea programada. El siguiente comando borra los archivos de log de todas las aplicaciones y entornos para los que el archivo logging.yml especifique un valor on a la opción purge (q- ue es el valor por defecto): > symfony log-purge Para mejorar el rendimiento y la seguridad de la aplicación, suele ser habitual almacenar los archivos de log de Symfony en varios archivos pequeños en vez de en un solo archivo muy grande. La estrategia de almacenamiento ideal para los archivos de log es la de vac- iar y hacer una copia de seguridad cada poco tiempo del archivo de log principal y man- tener un número limitado de copias de seguridad. Esta estrategia se denomina rotación de archivos de log y se puede activar desde el archivo de configuración logging.yml. Si se establece por ejemplo un period de 7 días y un history (número de copias de seguri- dad) de 10, como se muestra en el listado 16-6, es posible trabajar con un archivo de log activo y tener otras 10 copias de seguridad, cada una con los mensajes de log de 7 días diferentes. Cuando transcurren otros 7 días, el archivo de log activo se transforma en una copia de seguridad y se borra el archivo de la copia de seguridad más antigua. Listado 16-6 - Configurando la rotación de logs, en miaplicacion/config/ logging.yml prod: rotate: on period: 7 ## Por defecto, los archivos se rotan cada 7 días history: 10 ## Se mantienen 10 archivos de log como copia de seguridad Para ejecutar la rotación de los logs, se debe utilizar periódicamente la tarea log-rotate. Esta tarea sólo borra los archivos para los que la opción rotate vale on. Se puede indicar una aplicación y un entorno específicos al utilizar esta tarea: > symfony log-rotate miaplicacion prod Las copias de seguridad de los archivos de log se almacenan en el directorio logs/his- tory/ y a su nombre se les añade un sufijo con la fecha completa en la que fueron guardados. 16.2. Depuración de aplicaciones No importa lo buenos que sean los programadores o lo bueno que sea Symfony, siempre se acaban cometiendo errores. Una de las claves para el desarrollo rápido de aplicaciones es la detección y comprensión de los errores producidos. Afortunadamente, Symfony pro- porciona varias herramientas para depurar las aplicaciones. www.librosweb.es 349
  • 350. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones 16.2.1. Modo debug de Symfony Symfony dispone de un modo llamado “debug” que facilita el desarrollo y la depuración de las aplicaciones. Cuando se activa este modo, ocurre lo siguiente: ▪ La configuración de la aplicación se comprueba en cada petición, por lo que cual- quier cambio en la configuración se aplica inmediatamente, sin necesidad de bo- rrar la cache de configuración. ▪ Los mensajes de error muestran la traza completa de ejecución de forma clara y útil, para que sea más fácil de encontrar el elemento que está fallando. ▪ Se muestran más herramientas de depuración (como por ejemplo, todas las con- sultas a la base de datos). ▪ También se activa el modo debug de Propel, por lo que cualquier error en la lla- mada a un objeto de Propel, muestra una lista completa de los errores produci- dos en toda la arquitectura Propel. Por otra parte, cuando se desactiva el modo debug, las peticiones se procesan de la sigu- iente forma: ▪ Los archivos de configuración YAML se procesan una sola vez y se transforman en archivos PHP que se almacenan en la carpeta cache/config/. Todas las petic- iones que se realizan después de la primera petición, no tienen en cuenta los ar- chivos YAML de configuración y utilizan en su lugar la configuración guardada en la cache. Por tanto, el procesamiento de cada petición es mucho más rápido. ▪ Para forzar a que se vuelva a procesar la configuración de la aplicación, es nece- sario borrar a mano la cache de configuración. ▪ Cualquier error que se produzca durante el procesamiento de la petición, devuel- ve una respuesta con el código de estado 500 (Error Interno del Servidor) y no se muestran los detalles internos del error. El modo debug se activa para cada aplicación en su controlador frontal. Este modo se controla mediante la constante SF_DEBUG, como se muestra en el listado 16-7. Listado 16-7 - Controlador frontal de ejemplo con el modo debug activado, en web/miaplicacion_dev.php <?php define('SF_ROOT_DIR', realpath(dirname(__FILE__).'/..')); define('SF_APP', 'miaplicacion'); define('SF_ENVIRONMENT', 'dev'); define('SF_DEBUG', true); require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.' sfContext::getInstance()->getController()->dispatch(); www.librosweb.es 350
  • 351. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones ATENCIÓN En el servidor de producción, no se debería activar el modo debug y no se debería guardar ningún controlador frontal con este modo activado. El modo debug no solo penaliza el rendimiento de la aplicación, sino que revela información interna de la aplicación. Aunque las herramientas de depu- ración nunca desvelan la información necesaria para conectarse con la base de datos, la traza ge- nerada en las excepciones está llena de información demasiado sensible y que puede ser aprove- chada por un usuario malintencionado. 16.2.2. Excepciones Symfony Cuando se produce una excepción y está activado el modo debug, Symfony muestra un mensaje de error muy útil que contiene toda la información necesaria para descubrir la causa del problema. Los mensajes que produce la excepción están escritos de forma clara y hacen referencia a la causa más probable del problema. Normalmente ofrecen posibles soluciones para arreglar el error y para la mayoría de problemas comunes, incluso se muestra un enlace a la página del sitio web de Symfony que contiene más información sobre la excepción. La página con el mensaje de la excepción muestra en qué parte del código PHP se ha producido el error y la lista completa de los métodos que se han invocado, como se muestra en la figura 16-1. De esta forma, es posible seguir la traza de ejecución hasta la primera llamada que causó el problema. También se muestran los argumentos que se pasan a cada método. NOTA Symfony se basa en las excepciones de PHP para notificar los errores, que es un método mucho mejor que el funcionamiento de las aplicaciones desarrolladas con PHP 4. Para notificar un error de tipo 404, se utiliza el método sfError404Exception. www.librosweb.es 351
  • 352. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones Figura 16.1. Mensaje mostrado por una excepción de una aplicación Symfony Mientras se desarrolla la aplicación, las excepciones Symfony son de gran utilidad para depurar el funcionamiento de las aplicaciones. 16.2.3. Extensión Xdebug La extensión Xdebug de PHP (http://xdebug.org/) permite ampliar la cantidad de informa- ción que el servidor web almacena en los archivos de log. Symfony es capaz de integrar los mensajes de Xdebug en sus propios mensajes de error, por lo que es una buena idea activar esta extensión cuando se están depurando las aplicaciones. La instalación de la extensión depende de la plataforma en la que se realiza, por lo que se debe consultar la información disponible en el sitio web de Xdebug. Una vez instalada, se activa manual- mente en el archivo de configuración php.ini. En los sistemas *nix, se activa añadiendo la siguiente línea: zend_extension="/usr/local/lib/php/extensions/no-debug-non-zts-20041030/xdebug.so" En los sistemas Windows, la activación de Xdebug se realiza mediante: extension=php_xdebug.dll www.librosweb.es 352
  • 353. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones El listado 16-8 muestra un ejemplo de la configuración de Xdebug, que también se debe añadir al archivo php.ini. Listado 16-8 - Configuración de ejemplo para Xdebug ;xdebug.profiler_enable=1 ;xdebug.profiler_output_dir="/tmp/xdebug" xdebug.auto_trace=1 ; enable tracing xdebug.trace_format=0 ;xdebug.show_mem_delta=0 ; memory difference ;xdebug.show_local_vars=1 ;xdebug.max_nesting_level=100 Por último, para activar la extensión Xdebug, se debe reiniciar el servidor web. ATENCIÓN No debe olvidarse desactivar la extensión Xdebug en el servidor de producción. Si no se desactiva, el rendimiento de la aplicación disminuye notablemente. 16.2.4. Barra de depuración web Los archivos de log guardan información muy útil, pero no siempre son fáciles de leer. La tarea más básica, que consiste en localizar las líneas del archivo de log correspondientes a una determinada petición, suele complicarse cuando existen varios usuarios simultáne- os en la aplicación y cuando el archivo de log es muy grande. En ese momento es cuando se hace necesaria la barra de depuración web. Esta barra de depuración se muestra como una caja semitransparente superpuesta sobre el contenido de la ventana del navegador y que aparece en la esquina superior derecha, como se ve en la figura 16-2. Esta barra permite acceder directamente a los eventos guardados en el log, a la configuración actual, las propiedades de los objetos de la peti- ción y de la respuesta, los detalles de las consultas realizadas a la base de datos y una tabla con los tiempos empleados en cada elemento de la petición. Figura 16.2. La barra de depuración web se muestra en la esquina superior derecha de la ventana del navegador El color de fondo de la barra de depuración web depende del máximo nivel de los mensa- jes de log producidos durante la petición. Si ningún mensaje pasa del nivel debug, la www.librosweb.es 353
  • 354. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones barra se muestra con color de fondo gris. Si al menos un mensaje alcanza el nivel err, la barra muestra un color de fondo rojo. NOTA No debe confundirse el modo debug y la barra de depuración web. La barra se puede mostrar inclu- so cuando el modo debug está desactivado, aunque en este caso, muestra mucha menos información. Para activar la barra de depuración web en una aplicación, se utiliza la opción web_debug del archivo de configuración settings.yml. En los entornos de ejecución prod y test, el valor por defecto de la opción web_debug es off, por lo que se debe activar manualmente si se necesita. En el entorno de ejecución dev, esta opción tiene un valor por defecto de on, tal y como muestra el listado 16-9. Listado 16-9 - Activando la barra de depuración web, en miaplicacion/config/ settings.yml dev: .settings: web_debug: on Cuando se muestra la barra de depuración web, ofrece mucha información: ▪ Si se pincha sobre el logotipo de Symfony, la barra se oculta. Cuando está mini- mizada, la barra no oculta los elementos de la página que se encuentran en la esquina superior derecha. ▪ Como muestra la figura 16-3, cuando se pincha sobre la opción vars & config, se muestran los detalles de la petición, de la respuesta, de las opciones de configu- ración, de las opciones globales y de las propiedades PHP. La línea superior resu- me el estado de las opciones de configuración más importantes, como el modo debug, la cache y la presencia/ausencia de un acelerador de PHP (su nombre aparece en rojo si está desactivado y en color verde si se encuentra activado). Figura 16.3. La sección "vars & config" muestra todas las variables y constantes de la petición www.librosweb.es 354
  • 355. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones ▪ Cuando la cache se encuentra activada, se muestra una flecha verde en la barra de depuración web. Si se pulsa sobre esta flecha, la página se vuelve a procesar entera, independientemente de si se encuentra almacenada en la cache (no obs- tante, la cache no se vacía al pulsar sobre esta flecha). ▪ Como muestra la figura 16-4, al pulsar sobre la sección logs & msgs, se muestran los mensajes de log para la petición actual. En función de la importancia de los eventos, las líneas se muestran en gris, amarillo o rojo. Mediante los enlaces que se muestran en forma de lista en la parte superior, es posible filtrar los mensajes de log en función de su categoría. Figura 16.4. La sección "logs & msgs" muestra los mensajes de log de la petición actual NOTA Cuando la acción es el resultado de una redirección, solamente se muestran los mensajes de log de la última petición, por lo que es imprescindible consultar los archivos de log completos para de- purar las aplicaciones. ▪ Si durante el procesamiento de la petición se han ejecutado consultas SQL, se muestra un icono de una base de datos en la barra de depuración web. Si se pul- sa sobre este icono, se muestra el detalle de las consultas realizadas, como se muestra en la figura 16-5. ▪ A la derecha del icono del reloj se muestra el tiempo total de procesamiento req- uerido por la petición. Como el modo debug y la propia barra de depuración www.librosweb.es 355
  • 356. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones consumen muchos recursos, el tiempo que se muestra es mucho más lento que la ejecución real de la petición. Por tanto, es más importante fijarse en las dife- rencias de tiempos producidas por los cambios introducidos que en el propio tiempo mostrado. Si se pulsa sobre el icono del reloj, se muestran los detalles del tiempo de procesamiento de cada categoría, tal y como se muestra en la figura 16-6. Symfony muestra el tiempo consumido en las diferentes partes que compo- nen el procesamiento de la petición. Como solamente tiene sentido optimizar el tiempo de procesamiento propio de la petición, no se muestra el tiempo consumi- do por el núcleo de Symfony. Esta es la razón por la que la suma de todos los tiempos individuales no es igual al tiempo total mostrado. ▪ Si se pulsa sobre la X roja a la derecha de la barra, se oculta la barra de depura- ción web. Figura 16.5. La sección de consultas a la base de datos muestra las consultas ejecutadas durante la petición actual www.librosweb.es 356
  • 357. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones Figura 16.6. El icono del reloj muestra el tiempo de ejecución dividido por categorías Creando tu propio contador de tiempo Symfony utiliza la clase sfTimer para calcular el tiempo empleado en la configuración, el modelo, la acción y la vista. Utilizando el mismo objeto, se puede calcular el tiempo empleado por un proce- so propio y mostrar el resultado junto con el resto de tiempos de la barra de depuración web. Se trata de algo muy útil cuando se está trabajando en la optimización del rendimiento de la aplicación. Para inicializar el control del tiempo para un fragmento de código, se utiliza el método getTimer(). Este método devuelve un objeto de tipo sfTimer y comienza a contar el tiempo. Para detener el avance del contador de tiempo, se invoca el método addTime(). En ese instante, se puede obtener el tiempo transcurrido mediante el método getElapsedTime() y se muestra automáticamente junto con el resto de tiempos en la barra de depuración web. // Inicializar el contador y empezar a contar el tiempo $contador = sfTimerManager::getTimer('miContador'); // Otras instrucciones y código ... // Detener el contador y sumar el tiempo transcurrido $contador->addTime(); // Obtener el resultado (y detener el contador si no estaba detenido) $tiempoTranscurrido = $contador->getElapsedTime(); La ventaja de asignar un nombre a cada contador, es que se puede utilizar varias veces para acu- mular diferentes tiempos. Si por ejemplo el contador miContador se utiliza en un método que se lla- ma 2 veces en cada petición, la segunda llamada al método getTimer(’miContador’) comienza a contar el tiempo desde donde se quedó la última vez que se llamó a addTime(), por lo que el tiem- po transcurrido se sumará al tiempo anterior. El método getCalls() del contador devuelve el www.librosweb.es 357
  • 358. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones número de veces que ha sido utilizado el contador desde que se inició la petición, y este dato tam- bién se muestra en la barra de depuración web. // Obtener el número de veces que ha sido utilizado el contador $llamadas = $contador->getCalls(); Si se utiliza Xdebug, los mensajes de log son mucho más completos. Se guarda en el log todos los archivos PHP y todas las funciones que han sido llamadas, y Symfony integra esta información con su propio log interno. Cada fila de la tabla de mensajes de log dispone de una flecha bidireccional que se puede pulsar para obtener más detalles sobre la petición relacionada. Si algo no va bien, el modo Xdebug es el que más información proporciona para averiguar la causa. NOTA La barra de depuración web no se incluye por defecto en las respuestas de tipo Ajax y en los docu- mentos cuyo Content-Type no es de tipo HTML. Para el resto de las páginas, se puede deshabili- tar la barra de depuración web manualmente desde la acción mediante la llamada a sfConfig::- set(’sf_web_debug’, false). 16.2.5. Depuración manual Aunque muchas veces es suficiente con acceder a los mensajes de log generados por el framework, en ocasiones es mejor poder generar mensajes de log propios. Symfony dis- pone de utilidades, que se pueden acceder desde las acciones y desde las plantillas, para crear trazas sobre los eventos y/o valores presentes durante la ejecución de la petición. Los mensajes de log propios aparecen en el archivo de log de Symfony y en la barra de depuración web, como cualquier otro mensaje de Symfony. (El listado 16-4 anterior muestra un ejemplo de la sintaxis de un mensaje de log propio). Los mensajes de log propios se pueden utilizar por ejemplo para comprobar el valor de una variable en una plantilla. El listado 16-10 muestra cómo utilizar la barra de depuración web desde una plantilla para obtener información para el programador (también se puede utilizar el mé- todo $this->logMessage() desde una acción). Listado 16-10 - Creando un mensaje de log propio para depurar la aplicación <?php use_helper('Debug') ?> ... <?php if ($problem): ?> <?php log_message('{sfAction} ha pasado por aquí', 'err') ?> ... <?php endif ?> Si se utiliza el nivel err, se garantiza que el evento sea claramente visible en la lista de mensajes, como se muestra en la figura 16-7. www.librosweb.es 358
  • 359. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones Figura 16.7. Mensaje de log propio en la sección "logs & msgs" de la barra de depuración web Si no se quiere añadir una línea al log, sino que solo se necesita mostrar un mensaje cor- to o un valor, se debería utilizar debug_message en vez de log_message. Este método de la acción (para el que también existe un helper con el mismo nombre) muestra un mensaje en la barra de depuración web, en la parte superior de la sección logs & msgs. El listado 16-11 muestra un ejemplo de uso de esta utilidad. Listado 16-11 - Mostrando un mensaje en la barra de depuración web // En una acción $this->debugMessage($mensaje); // En una plantilla <?php use_helper('Debug') ?> <?php debug_message($mensaje) ?> 16.3. Cargando datos en una base de datos Durante el desarrollo de una aplicación, uno de los problemas recurrentes es el de la car- ga inicial de datos en la base de datos. Algunos sistemas de bases de datos disponen de soluciones específicas para esta tarea, pero ninguna se puede utilizar junto en el ORM de Symfony. Gracias al uso de YAML y al objeto sfPropelData, Symfony puede transferir www.librosweb.es 359
  • 360. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones automáticamente los datos almacenados en un archivo de texto a una base de datos. Aunque puede parecer que crear el archivo de texto con los datos iniciales de la aplica- ción cuesta más tiempo que insertarlos directamente en la base de datos, a la larga se ahorra mucho tiempo. Se trata de una utilidad muy práctica para la carga automática de datos de prueba para la aplicación. 16.3.1. Sintaxis del archivo de datos Symfony es capaz de procesar todos los archivos que siguen una sintaxis YAML definida muy simple y que se encuentren en el directorio data/fixtures/. Los archivos de datos, también llamados “fixtures”, se organizan por clases y cada sección de clase utiliza una cabecera con el valor del nombre de la clase. Para cada clase, las filas de datos disponen de una etiqueta que las identifica de forma única y una serie de pares nombre_campo: va- lor. El listado 16-12 muestra un ejemplo de un archivo preparado para cargar sus datos en una base de datos. Listado 16-12 - Archivo de datos de ejemplo, en data/fixtures/import_data.yml Article: ## Crea filas de datos en la tabla blog_article first_post: ## Etiqueta de la primera fila de datos title: My first memories content: | For a long time I used to go to bed early. Sometimes, when I had put out my candle, my eyes would close so quickly that I had not even time to say "I am going to sleep." second_post: ## Etiqueta de la segunda fila de datos title: Things got worse content: | Sometimes he hoped that she would die, painlessly, in some accident, she who was out of doors in the streets, crossing busy thoroughfares, from morning to night. Symfony transforma el nombre indicado para las columnas, en métodos setter utilizando la conversión de tipo camelCase (la columna title se transforma en setTitle(), la co- lumna content se transforma en setContent(), etc.). La ventaja de esta transformación es que se puede definir, por ejemplo, una columna llamada password para la que no exis- te una columna en la tabla de la base de datos; solamente es necesario definir un méto- do llamado setPassword() en el objeto User y ya es posible asignar valores a otras co- lumnas de datos en función de este dato, como por ejemplo una columna que guarde la contraseña encriptada. No es necesario definir el valor de la columna de la clave primaria. Como es un campo cuyo valor se autoincrementa, la capa de base de datos es capaz de determinar su valor. A las columnas created_at tampoco es necesario asignarles un valor, ya que Symfony sabe que a las columnas que se llaman así, les debe asignar la fecha actual del sistema a la hora de crearlas. www.librosweb.es 360
  • 361. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones 16.3.2. Importando los datos La tarea propel-load-data importa los datos de un archivo YAML en una base de datos. Las opciones de conexión con la base de datos se obtienen del archivo de configuración databases.yml, por lo que es necesario indicar a la tarea el nombre de una aplicación. Además, es posible indicar como argumento el nombre de un entorno de ejecución (su valor por defecro es dev). > symfony propel-load-data frontend Al ejecutar este comando, se leen todos los archivos de datos YAML del directorio data/ fixtures y se insertan las filas de datos en la base de datos. Por defecto, se reemplaza todo el contenido existente en la base de datos, aunque si se utiliza un argumento opcio- nal llamado append, el comando no borra los datos existentes. > symfony propel-load-data frontend append También es posible especificar otro archivo de datos u otro directorio, indicando su valor como una ruta relativa respecto del directorio del proyecto. > symfony propel-load-data frontend data/misfixtures/miarchivo.yml 16.3.3. Usando tablas relacionadas Ahora ya es posible añadir filas de datos a una tabla, pero de esta forma no es posible añadir filas con claves externas que hacen relación a otra tabla. Como los archivos de da- tos no incluyen la clave primaria, se necesita un método alternativo para relacionar los diferentes registros de datos entre sí. Volviendo al ejemplo del Capítulo 8, donde la tabla blog_article está relacionada con la tabla blog_comment, de la forma que se muestra en la figura 16-8. Figura 16.8. Ejemplo de modelo relacional de una base de datos En esta situación es en la que se utilizan las etiquetas únicas de cada fila de datos. Para añadir un campo de tipo Comment al artículo llamado first_post, simplemente es necesar- io añadir las siguientes líneas del listado 16-13 al archivo de datos import_data.yml. Listado 16-13 - Añadiendo un registro relacionado con otra tabla, en data/fixtu- res/import_data.yml Comment: first_comment: article_id: first_post author: Anonymous content: Your prose is too verbose. Write shorter sentences. www.librosweb.es 361
  • 362. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones La tarea propel-load-data es capaz de reconocer la etiqueta que se asignó anteriormente al artículo en el archivo import_data.yml y es capaz de obtener la clave primaria del re- gistro de tipo Article correspondiente en la base de datos, para asignar ese valor al campo article_id. No es necesario trabajar con los valores de las columnas de tipo ID, solo es necesario enlazar las filas de datos mediante sus etiquetas, por lo que su funcio- namiento es realmente simple. La única restricción para las filas de datos enlazadas es que los objetos utilizados en una clave externa tienen que estar definidos anteriormente en el archivo; es decir, igual que si se tuvieran que definir uno a uno. Los archivos de datos se procesan desde el principio hasta el final y por tanto, el orden en el que se escriben las filas de datos es muy importante. Un solo archivo de datos puede contener la declaración de varias clases diferentes. Sin embargo, si se necesitan insertar muchos datos en muchas tablas diferentes, es posible que el archivo de datos sea demasiado largo como para manejarlo fácilmente. Como la tarea propel-load-data procesa todos los archivos que encuentra en el directorio fixtures/, es posible dividir el archivo de datos YAML en otros archivos más pequeños. Lo único que hay que tener en cuenta es que las claves externas obligan a definir un de- terminado orden al procesar los datos. Para asegurar que los archivos se procesan en el orden adecuado, se puede añadir un número como prefijo del nombre del archivo, de for- ma que se procesen en el orden establecido. 100_article_import_data.yml 200_comment_import_data.yml 300_rating_import_data.yml 16.4. Instalando aplicaciones Symfony dispone de comandos para sincronizar 2 versiones diferentes de un mismo sitio web. La utilidad de estos comandos es la de poder instalar una aplicación o sitio web des- de un servidor de desarrollo hasta un servidor de producción, desde donde los usuarios accederán a la aplicación pública. Este proceso también se conoce como el “deploy” de una aplicación, por lo que a veces se utiliza la palabra “deployar” (o “desplegar”) para re- ferirse a la instalación de una aplicación. 16.4.1. Preparando un proyecto para transferirlo con FTP La forma habitual de instalar las aplicaciones en los servidores de producción consiste en trasferir todos los archivos de la aplicación mediante FTP (o SFTP). Sin embargo, los pro- yectos desarrollados con Symfony utilizan las librerías internas de Symfony y, salvo que se desarrolle con el archivo de pruebas sandbox (lo que no se recomienda) o salvo que los directorios lib/ y data/ estén enlazados mediante svn:externals, estas librerías no se encuentran dentro de los directorios del proyecto. Independientemente de que se rea- lice una instalación PEAR o se utilicen enlaces simbólicos, trasladar la misma estructura de directorios al servidor de producción suele ser una tarea costosa y no muy sencilla. Por este motivo, Symfony dispone de una utilidad que congela los proyectos, es decir, copia todas las librerías de Symfony necesarias en los directorios data/, lib/ y web/ del www.librosweb.es 362
  • 363. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones proyecto. Una vez congelado, el proyecto se transforma en una aplicación independiente y completamente ejecutable por sí misma, tal y como el entorno de pruebas sandbox. > symfony freeze Una vez que un proyecto ha sido congelado, se puede transferir directamente el director- io raíz completo del proyecto al servidor de producción y funciona sin necesidad de PEAR, enlaces simbólicos o cualquier otro elemento. SUGERENCIA En un mismo servidor se pueden ejecutar simultáneamente varios proyectos congelados, cada uno con su propia, e incluso diferente, versión de Symfony. Para devolver un proyecto a su estado original, se utiliza la tarea unfreeze (descongelar). Esta tarea borra los directorios data/symfony/, lib/symfony/ y web/sf/. > symfony unfreeze Si antes de congelar el proyecto existían enlaces simbólicos, Symfony es capaz de reco- nocerlos y al descongelar el proyecto, vuelve a crear los enlaces simbólicos originales. 16.4.2. Usando rsync para transferir archivos incrementalmente Cuando se realiza el primer traspaso de la aplicación web, es útil transferir mediante FTP (o SFTP) el directorio raíz completo del proyecto, pero cuando se trata de actualizar una aplicación para la que solamente se han modificado unos pocos archivos, la solución me- diante FTP no es la ideal. Si se utiliza FTP, o se vuelve a transferir completo el proyecto, con el consiguiente gasto de tiempo y ancho de banda, o se accede manualmente a todos los directorios con archivos modificados y se suben de uno en uno. Este último método, no solo es costoso en tiempo, sino que es muy propenso a cometer errores. Además, el sitio web puede estar no disponible o puede mostrar muchos errores durante el traspaso de las modificaciones. La solución que propone Symfony es el uso de la herramienta de syncronización rsyn me- diante SSH. Rsync (http://samba.anu.edu.au/rsync/) es una utilidad de la línea de coman- dos que permite realizar una transferencia incremental de archivos de forma muy rápida, además de que es una herramienta de software libre. En una transferencia incremental, solamente se transfieren los datos modificados. Si un archivo no ha sido modificado des- de la última sincronización, no se vuelve a enviar al servidor. Si un archivo solamente tiene un cambio parcial, solamente se envían los cambios realizados. La principal ventaja de rsync es que las sincronizaciones requieren el envío de muy pocos datos y por tanto, son muy rápidas. Symfony utiliza SSH conjuntamente con rsync para hacer más segura la transferencia de datos. La mayoría de servicios de hosting soportan el uso de SSH para aportar más segu- ridad a la transferencia de archivos hasta sus servidores. El cliente SSH utilizado por Symfony utiliza las opciones de conexión del archivo config/ properties.ini. El listado 16-14 muestra un ejemplo de las opciones de conexión para un servidor de producción. Antes de realizar la sincronización de la aplicación, se deben www.librosweb.es 363
  • 364. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones establecer las opciones de conexión en este archivo. También es posible definir una op- ción llamada parameters para utilizar parámetros propios con rsync. Listado 16-14 - Opciones de conexión para la sincronización con un servidor, en miproyecto/config/properties.ini [symfony] name=miproyecto [production] host=miaplicacion.example.com port=22 user=myuser dir=/home/myaccount/miproyecto/ NOTA No debe confundirse el servidor de producción (que es el servidor definido en el archivo propert- ies.ini del proyecto) con el entorno de producción (el controlador frontal y la configuración que se utiliza en producción). Como la sincronización de rsync mediante SSH requiere de varios comandos, y la sincro- nización suele ocurrir muchas veces durante la vida de una aplicación, Symfony automa- tiza esta tarea mediante un único comando: > symfony sync production El comando anterior ejecuta el comando rsync en el modo de prueba; es decir, muestra los archivos que tienen que ser sincronizados, pero no los sincroniza realmente. Para rea- lizar la sincronización, se debe indicar explícitamente mediante la opción go. > symfony sync production go No debe olvidarse borrar la cache en el servidor de producción después de la sincronización. SUGERENCIA En ocasiones, se producen errores en el servidor de producción que no existían en el servidor de desarrollo. El 90% de las veces el problema reside en una diferencia en las versiones de las aplica- ciones (de PHP, del servidor web o de la base de datos) o en la configuración de la aplicación. Para evitar sorpresas desagradables, se debe definir la configuración de PHP del servidor de producción en un archivo llamado php.yml, para poder comprobar que el entorno de desarrollo aplica las mis- mas ocpiones. El Capítulo 19 incluye más información sobre este archivo de configuración. ¿Está terminada la aplicación? Antes de subir la aplicación al servidor de producción, es necesario asegurarse de que está lista para ser utilizada por los usuarios. Antes de instalar la aplicación en el servidor de producción, es recomendable comprobar que se ha completado lo siguiente: Las páginas de error deberían mostrar un aspecto integrado con el del resto de la aplicación. El Capítulo 19 explica cómo personalizar las páginas del error 500, del error 400 y las de las páginas relacionadas con la seguridad. La sección “Administrando una aplicación en producción” explica, www.librosweb.es 364
  • 365. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones más adelante en este capítulo, cómo personalizar las páginas que se muestran cuando la aplica- ción no está disponible. El módulo default se debe eliminar del array enabled_modules del archivo settings.yml, de modo que no se muestren por error páginas del propio Symfony. El mecanismo de gestión sesiones utiliza una cookie para el navegador del usuario y esta cookie se llama symfony. Antes de instalar la aplicación en producción, puede ser una buena idea cambiarle el nombre para no mostrar que la aplicación está desarrollada con Symfony. El Capítulo 6 explica cómo modificar el nombre de la cookie en el archivo factories.yml. Por defecto, el archivo robots.txt del directorio web/ está vacío. Normalmente, es una buena idea modificar este archivo para indicar a los buscadores las partes de la aplicación que pueden ac- ceder y las partes que deberían evitar. También se suele utilizar este archivo para excluir ciertas URL de la indexación realizada por los buscadores, como por ejemplo las URL que consumen mu- cho tiempo de proceso o las páginas que no interesa indexar. Los navegadores más modernos buscan un archivo llamado favicon.ico cuando el usuario acce- de por primera vez a la aplicación. Este archivo es el icono que representa a la aplicación en la ba- rra de direcciones y en la carpeta de favoritos. Además de que este icono ayuda a completar el as- pecto de la aplicación, evita que se produzcan muchos errores de tipo 404 cuando los navegadores lo solicitan y no se encuentra disponible. 16.4.3. Ignorando los archivos innecesarios Cuando se sincroniza un proyecto Symfony con un servidor de producción, algunos archi- vos y directorios no deberían transferirse: ▪ Todos los directorios del versionado del código (.svn/, CVS/, etc.) y su contenido, solamente es necesario para el desarrollo e integración de la aplicación. ▪ El controlador frontal del entorno de desarrollo no debería ser accesible por los usuarios finales. Las herramientas de depuración y de log disponibles en este controlador frontal penalizan el rendimiento de la aplicación y proporcionan mu- cha información sobre las variables internas utilizadas por las acciones. Siempre debería eliminarse este controlador frontal en la aplicación pública. ▪ Los directorios cache/ y log/ del proyecto no deben borrarse cada vez que se re- aliza una sincronización. Estos directorios también deberían ignorarse. Si se dis- pone de un directorio llamado stats/, también debería ignorarse. ▪ Los archivos subidos por los usuarios tampoco deberían transferirse. Una de las buenas prácticas recomendadas por Symfony es la de guardar los archivos subi- dos por los usuarios en el directorio web/uploads/. De esta forma, se pueden ex- cluir todos estos archivos simplemente ignorando un directorio durante el traspa- so de la aplicación. Para excluir los archivos en las sincronizaciones de rsync, se edita el archivo rsync_ex- clude.txt que se encuentra en el directorio miproyecto/config/. Cada fila de ese archivo debe contener el nombre de un archivo, el nombre de un directorio o un patrón con co- modines *. La estructura de archivos de Symfony está organizada de forma lógica y www.librosweb.es 365
  • 366. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones diseñada de forma que se minimice el número de archivos o directorios que se deben ex- cluir manualmente de la sincronización. El listado 16-15 muestra un ejemplo. Listado 16-15 - Ejemplo de exclusiones en una sincronización rsync, en mipro- yecto/config/rsync_exclude.txt .svn /cache/* /log/* /stats/* /web/uploads/* /web/miaplicacion_dev.php NOTA Los directorios cache/ y log/ no deben sincronizarse con el servidor de producción, pero sí que deben existir en el servidor de producción. Si la estructura de directorios y archivos del proyecto miproyecto/ no los contiene, deben crearse manualmente. 16.4.4. Administrando una aplicación en producción El comando más utilizado en los servidores de producción es clear-cache. Cada vez que se actualiza Symfony o el proyecto, se debe ejecutar esta tarea (por ejemplo después de ejecutar la tarea sync) y también cada vez que se modifica la configuración en producción. > symfony clear-cache SUGERENCIA Si en el servidor de producción no está disponible la línea de comandos de Symfony, se puede bo- rrar la cache manualmente borrando todos los contenidos del directorio cache/. También es posible deshabilitar temporalmente la aplicación, por ejemplo cuando se ne- cesita actualizar una librería o cuando se tiene que actualizar una gran cantidad de datos. > symfony disable NOMBRE_APLICACION NOMBRE_ENTORNO Por defecto, una aplicación deshabilitada muestra la acción default/unavailable, definida en el propio framework. En el archivo settings.yml se puede definir el módulo y la acción que se utiliza cuando una aplicación está deshabilitada. El listado 16-16 muestra un ejemplo. Listado 16-16 - Indicando la acción a ejecutar para una aplicación no disponi- ble, en miaplicacion/config/settings.yml all: .settings: unavailable_module: mimodulo unavailable_action: maintenance La tarea enable vuelve a habilitar la aplicación y borra su cache. > symfony enable NOMBRE_APLICACION NOMBRE_ENTORNO Mostrando una página de "no disponible" mientras se borra la cache www.librosweb.es 366
  • 367. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones Si se establece el valor on a la opción check_lock en el archivo settings.yml, Symfony bloquea el acceso a la aplicación mientras se borra la cache, y todas las peticiones recibidas mientras se borra la cache se redirigen a una página que muestra que la aplicación está temporalmente no dis- ponible. Se trata de una opción muy recomendable cuando el tamaño de la cache es muy grande y el tiempo empleado para borrarla es mayor que unos milisegundos y el tráfico de usuarios de la aplicación es elevado. Esta página que indica que la aplicación no está disponible no es la misma que la que se muestra cuando se ejecuta el comando symfony disable (ya que mientras se borra la cache, Symfony no funciona correctamente ). Se trata de una página guardada en el directorio $sf_symfony_data_- dir/web/errors/, pero se puede crear un archivo unavailable.php propio en el directorio web/ errors/, para que Symfony lo utilice en su lugar. La opción check_lock está deshabilitada por de- fecto porque tiene un pequeño impacto sobre el rendimiento de la aplicación. La tarea clear-controllers elimina todos los controladores frontales del directorio web/ que no sean los controladores frontales utilizados en el entorno de producción. Si no se incluyen los contro- ladores frontales de desarrollo en el archivo rsync_exclude.txt, este comando garantiza que no se sube al servidor una puerta trasera que revele información interna sobre la aplicación. symfony clear-controllers Los permisos de los archivos y directorios del proyecto pueden cambiarse si se realiza un checkout desde un repositorio de Subversion. La tarea fix-perms arregla los permisos de los directorios y cambia por ejemplo los permisos de log/ y cache/ a un valor de 0777 (estos directorios deben te- ner permiso de escritura para que el framework funcione correctamente). symfony fix-perms Accediendo a los comandos de Symfony en el servidor de producción Si el servidor de producción dispone de una instalación de Symfony realizada con PEAR, la línea de comandos de Symfony está disponible en todos los directorios y funciona igual que en el servi- dor de desarrollo. Sin embargo, en los proyectos congelados, es necesario añadir php antes del co- mando symfony para poder ejecutar las tareas: // Con Symfony instalado mediante PEAR > symfony [opciones] <TAREA> [parametros] // Con un proyecto Symfony congelado > php symfony [opciones] <TAREA> [parametros] 16.5. Resumen Mediante los archivos de log de PHP y los de Symfony, es posible monitorizar y depurar las aplicaciones fácilmente. Durante el desarrollo de la aplicación, el modo debug, las ex- cepciones y la barra de depuración web ayudan a localizar la causa de los problemas. Pa- ra facilitar la depuración de la aplicación, es posible incluso insertar mensajes propios en el archivo de log y en la barra de depuración web. La interfaz de línea de comandos dispone de muchas utilidades para facilitar la gestión y administración de las aplicaciones durante las fases de desarrollo y de producción. Las tareas para cargar de forma masiva datos en la base de datos, la congelación de los www.librosweb.es 367
  • 368. Symfony, la guía definitiva Capítulo 16. Herramientas para la administración de aplicaciones proyectos y la sincronización de aplicaciones entre servidores, son tareas que ahorran mucho tiempo. www.librosweb.es 368
  • 369. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony Capítulo 17. Personalizar Symfony Antes o después, algún proyecto deberá modificar el comportamiento de Symfony. Sea una modificación del comportamiento de una clase o sea una nueva característica que hay que añadir al framework, el momento en el que es necesario modificar Symfony lle- gará de forma inevitable, ya que todos los clientes para los que se desarrollan aplicacio- nes tienen requerimientos muy específicos que ningún framework puede predecir. De hecho, como esta situación es tan común, Symfony dispone de un mecanismo llama- do mixin para extender y modificar las clases existentes. Incluso es posible reemplazar las clases del núcleo de Symfony por clases propias, utilizando las opciones de las fac- torías utilizadas por Symfony (las factorías se basan en el patrón de diseño “factories”). Una vez realizadas las modificaciones, se pueden encapsular en forma de plugin para po- der reutilizarlas en otras aplicaciones o por parte de otros programadores de Symfony. 17.1. Mixins Una de las limitaciones actuales de PHP más molestas es que una clase no puede heredar de más de una clase. Además, tampoco se pueden añadir nuevos métodos a una clase ya existente y no se pueden redefinir los métodos existentes. Para paliar estas dos limitacio- nes y para hacer el framework realmente modificable, Symfony proporciona una clase llamada sfMixer. Su nombre viene del concepto de mixin utilizado en la programación or- ientada a objetos. Un mixin es un grupo de métodos o funciones que se juntan en una clase para que otras clases hereden de ella. 17.1.1. Comprendiendo la herencia múltiple La herencia múltiple es la cualidad por la que una clase hereda de varias clases a la vez, heredando todas sus propiedades y métodos. A continuación se utiliza el ejemplo de una clase llamada Story (historia, relato) y otra clase llamada Book (libro), cada una de las cuales tiene sus propios métodos y propiedades, que se muestran en el listado 17-1. Listado 17-1 - Dos clases de ejemplo class Story { protected $title = ''; protected $topic = ''; protected $characters = array(); public function __construct($title = '', $topic = '', $characters = array()) { $this->title = $title; $this->topic = $topic; $this->characters = $characters; } public function getSummary() { return $this->title.', a story about '.$this->topic; www.librosweb.es 369
  • 370. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony } } class Book { protected $isbn = 0; function setISBN($isbn = 0) { $this->isbn = $isbn; } public function getISBN() { return $this->isbn; } } Una clase llamada ShortStory (relato corto) hereda de Story, una clase ComputerBook (li- bro sobre informática) hereda de Book, y como es lógico, una clase llamada Novel (nove- la) debería heredar tanto de Story como de Book para aprovechar todos sus métodos. Desafortunadamente, PHP no permite realizar esta herencia múltiple. No es posible crear una declaración para la clase Novel como la que se muestra en el listado 17-2. Listado 17-2 - PHP no permite la herencia múltiple class Novel extends Story, Book { } $myNovel = new Novel(); $myNovel->getISBN(); Una posibilidad para este ejemplo es que la clase Novel implemente dos interfaces en vez de heredar de dos clases, pero esta solución implica que las clases padre no pueden con- tener código en los métodos que definen. 17.1.2. Clases de tipo mixing La clase sfMixer intenta solucionar este problema desde otro punto de vista, permitiendo heredar de una clase a posteriori , siempre que la clase esté diseñada de forma adecua- da. El proceso completo está formado por 2 pasos: ▪ Declarar que una clase puede heredar de otras ▪ Registrar las herencias realizadas (o mixins), después de la declaración de la clase El listado 17-3 muestra cómo implementar la clase Novel anterior mediante sfMixer. Listado 17-3 - sfMixer permite la herencia múltiple class Novel extends Story { public function __call($method, $arguments) { www.librosweb.es 370
  • 371. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony return sfMixer::callMixins(); } } sfMixer::register('Novel', array('Book', 'getISBN')); $miNovela = new Novel(); $miNovela->getISBN(); Una de las clases que se quieren heredar (en este caso, Story) se utiliza como clase pa- dre principal, de forma que la clase hereda de ella directamente mediante PHP. La clase Novel se declara que es extensible mediante el código del método __call(). El método de la otra clase de la que se quiere heredar (en este caso, Book) se añade posteriormente a la clase Novel mediante la llamada a sfMixer::register(). Las próximas secciones deta- llan el funcionamiento completo de este proceso. Cuando se invoca el método getISBN() de la clase Novel, el funcionamiento es idéntico a si la clase Novel hubiera heredado también de la otra clase (como en el listado 17-2), salvo que en este caso, el funcionamiento se debe a la magia del método __call() y a los métodos estáticos de la clase sfMixer. El método getISBN() se dice que ha sido mez- clado (en inglés, “mixed”, y de ahí el nombre mixin) en la clase Novel. Cuando utilizar los mixins El mecanismo de mixin que utiliza Symfony es muy útil en muchas situaciones. La simulación de una herencia múltiple descrita anteriormente es sólo una de sus posibilidades. Los mixins también se pueden utilizar para modificar un método después de haber sido declarado. Si se crea por ejemplo una librería gráfica, es probable que se defina un objeto de tipo Line para representar una línea. Su clase dispone de 4 atributos (las coordenadas de sus 2 extremos) y un método llamado draw() para dibujar la propia línea. Un objeto llamado ColoredLine debería dis- poner de las mismas propiedades y argumentos, pero debería añadir un atributo adicional llamado color para especificar su color. Además, el método draw() de ColoredLine debería ser diferente al de Line, para tener en consideración el color de la línea. Por otra parte, todas las funciones rela- tivas al color se podrían encapsular en una clase llamada ColoredElement. De esta forma, se podrían reutilizar los métodos del color para otros elementos gráficos (Dot, Polygon, etc). En este caso, la forma ideal de implementar la clase ColoredLine sería como heredera de la clase Line y con una mezcla (mixin) de métodos de la clase ColoredElement. El método draw() final es una mezcla del método original de Line y del método de ColoredElement. Los mixins también se pueden utilizar para añadir nuevos métodos a clases ya existentes. La clase sfActions, por ejemplo, la utiliza Symfony para manejar las acciones y se define en el propio fra- mework. Una de las limitaciones de PHP es que no se puede modificar la definición de sfActions después de su declaración inicial. Si una aplicación tiene un requerimiento muy específico (por ejemplo, redirigir una petición a un servicio web especial), PHP no permitiría redefinir los métodos de sfActions, pero el mecanismo de mixins de Symfony es una solución ideal en este caso. 17.1.3. Declarar que una clase se puede extender Para declarar que una clase puede ser extendida, se debe preparar su código de forma que la clase sfMixer pueda identicarla como tal. Para preparar la clase, se añaden lo que www.librosweb.es 371
  • 372. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony se denomina “hooks”, que en este caso consisten en llamadas al método sfMixer::- callMixins(). Muchas de las clases propias de Symfony ya incluyen estos hooks, entre otras, sfRequest, sfResponse, sfController, sfUser y sfAction. En función del grado hasta el que se quiere hacer extensible a una clase, los hooks se co- locan en diferentes partes de la clase.: ▪ Para permitir que se puedan añadir métodos a una clase, se inserta el hook en el método __call() y se devuelve su valor, como se muestra en el listado 17-4. Listado 17-4 - Permitiendo que se puedan añadir nuevos métodos a una clase class unaClase { public function __call($method, $arguments) { return sfMixer::callMixins(); } } ▪ Para permitir modificar la forma en la que funciona un método, se debe insertar el hook dentro de ese método, como muestra el listado 17-5. El código que aña- de la clase de tipo mixin se ejecuta en el mismo lugar en el que se encuentra el hook. Listado 17-5 - Permitiendo que se pueda modificar un método class otraClase { public function unMetodo() { echo "Haciendo cosas..."; sfMixer::callMixins(); } } En ocasiones es necesario insertar más de un hook en un método. En este caso, se debe asignar un nombre a cada hook, de forma que posteriormente se pueda definir el hook que se quiere utilizar, tal y como muestra el listado 17-6. Para crear un hook con nom- bre, se utiliza el mismo método callMixins(), pero con un argumento que indica el nom- bre del hook. Cuando se registra el mixin posteriormente, se utiliza este nombre para in- dicar en que lugar del método se debe ejecutar el código del mixin. Listado 17-6 - Si un método contiene más de un hook, se les debe asignar un nombre class otraClase { public function otroMetodo() { echo "Empezando..."; sfMixer::callMixins('comienzo'); echo "Haciendo cosas..."; sfMixer::callMixins('fin'); echo "Finalizado"; www.librosweb.es 372
  • 373. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony } } Como muestra el listado 17-7, se pueden combinar todas estas técnicas para crear clases con la habilidad de poder insertar y/o modificar sus métodos. Listado 17-7 - Extendiendo una clase de diversas formas a la vez class BicycleRider { protected $name = 'John'; public function getName() { return $this->name; } public function sprint($distance) { echo $this->name." sprints ".$distance." metersn"; sfMixer::callMixins(); // El método sprint() se puede extender } public function climb() { echo $this->name.' climbs'; sfMixer::callMixins('slope'); // El método climb() se puede extender aquí... echo $this->name.' gets to the top'; sfMixer::callMixins('top'); // ...y en este otro punto también } public function __call($method, $arguments) { return sfMixer::callMixins(); // La clase BicyleRider se puede extender } } ATENCIÓN sfMixer solamente puede extender las clases que lo han declarado de forma explícita. Por tanto, no se puede utilizar este mecanismo para modificar una clase que no lo ha indicado en su código. En otras palabras, es como si las clases que quieren utilizar los servicios de sfMixer debieran sus- cribirse antes a esos servicios. 17.1.4. Registrando las extensiones Para registrar una extensión en un hook previamente definido, se utiliza el método sfMi- xer::register(). El primer argumento de este método es el elemento que se va a exten- der y el segundo argumento es una función de PHP que representa el mixin que se va a incluir. El formato del primer argumento depende de lo que se quiere extender: ▪ Si se extiende una clase, se utiliza el nombre de la clase. www.librosweb.es 373
  • 374. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony ▪ Si se extiende un método con un hook sin nombre, se utiliza el patrón clase:metodo. ▪ Si se extiende un método con un hook que dispone de nombre, se utiliza el patrón clase:metodo:hook. El listado 17-8 ilustra estas normas extendiendo la clase que se definió en el listado 17-7. El objeto que se extiende se pasa automáticamente como el primer argumento a los mé- todos del mixin (salvo, evidentemente, si el método que se ha extendido es de tipo sta- tic). El método del mixin también puede acceder a los parámetros de la llamada al mé- todo original. Listado 17-8 - Registrando extensiones class Steroids { protected $brand = 'foobar'; public function partyAllNight($bicycleRider) { echo $bicycleRider->getName()." spends the night dancing.n"; echo "Thanks ".$brand."!n"; } public function breakRecord($bicycleRider, $distance) { echo "Nobody ever made ".$distance." meters that fast before!n"; } static function pass() { echo " and passes half the peloton.n"; } } sfMixer::register('BicycleRider', array('Steroids', 'partyAllNight')); sfMixer::register('BicycleRider:sprint', array('Steroids', 'breakRecord')); sfMixer::register('BicycleRider:climb:slope', array('Steroids', 'pass')); sfMixer::register('BicycleRider:climb:top', array('Steroids', 'pass')); $superRider = new BicycleRider(); $superRider->climb(); => John climbs and passes half the peloton => John gets to the top and passes half the peloton $superRider->sprint(2000); => John sprints 2000 meters => Nobody ever made 2000 meters that fast before! $superRider->partyAllNight(); => John spends the night dancing. => Thanks foobar! Le mecanismo de extensiones no solo permite añadir nuevos métodos. El método partyAllNight() anterior utiliza un atributo de la clase Steroids. Por tanto, cuando se www.librosweb.es 374
  • 375. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony extiende la clase BicycleRider con un método de la clase Steroids, en realidad se está creando una nueva instancia de la clase Steroids dentro del objeto BicycleRider. ATENCIÓN No se pueden añadir 2 métodos con el mismo nombre a una clase ya existente. La razón es que la llamada a callMixins() en los métodos __call() utiliza el nombre del método del mixin como una clave. Además, no se puede añadir un método a una clase que ya dispone de un método con el mismo nombre, ya que el mecanismo de mixin depende del método mágico __call(), por lo que en este caso, nunca se llamaría a este segundo método. El segundo argumento de la llamada al método register() debe ser cualquier elemento PHP que se pueda invocar, por lo que puede ser un array de clase::metodo, un array de objeto->metodo o incluso el nombre de una función. El listado 17-9 muestra algunos ejemplos: Listado 17-9 - Cualquier código PHP que se pueda invocar puede ser utilizado para registrar una extensión // Registrnado el método de una clase sfMixer::register('BicycleRider', array('Steroids', 'partyAllNight')); // Registrando el método de un objeto $mySteroids = new Steroids(); sfMixer::register('BicycleRider', array($mySteroids, 'partyAllNight')); // Registrando una función sfMixer::register('BicycleRider', 'die'); El mecanismo de extensión es dinámico, lo que significa que si ya se ha instanciado un objeto, puede tener acceso a las extensiones realizadas en su clase. El listado 17-10 muestra un ejemplo. Listado 17-10 - El mecanismo de extensión es dinámico y se puede utilizar in- cluso después de instanciar los objetos $simpleRider = new BicycleRider(); $simpleRider->sprint(500); => John sprints 500 meters sfMixer::register('BicycleRider:sprint', array('Steroids', 'breakRecord')); $simpleRider->sprint(500); => John sprints 500 meters => Nobody ever made 500 meters that fast before! 17.1.5. Extendiendo de forma más precisa La instrucción sfMixer::callMixins() en realidad es un atajo de algo mucho más com- plejo. Esta instrucción recorre todos los elementos de la lista de mixins que se han regis- trado y se van ejecutando uno a uno, pasandoles el objeto actual y los parámetros del método que se está ejecutando. En otras palabras, una llamada a la función sfMixer::- callMixins() se comporta más o menos como el código del listado 17-11. Listado 17-11 - callMixin() recorre todos los mixins registrados y los ejecuta www.librosweb.es 375
  • 376. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony foreach (sfMixer::getCallables($class.':'.$method.':'.$hookName) as $callable) { call_user_func_array($callable, $parameters); } Si se necesitan pasar otros parámetros o se quiere procesar de forma especial el valor devuelto, se puede recorrer la lista de mixins de forma explícita en lugar de utilizar sfMi- xer::callMixins(). El listado 17-12 muestra un ejemplo de un mixin más integrado en la propia clase. Listado 17-12 - Reemplazando callMixin() por un código propio class Income { protected $amount = 0; public function calculateTaxes($rate = 0) { $taxes = $this->amount * $rate; foreach (sfMixer::getCallables('Income:calculateTaxes') as $callable) { $taxes += call_user_func($callable, $this->amount, $rate); } return $taxes; } } class FixedTax { protected $minIncome = 10000; protected $taxAmount = 500; public function calculateTaxes($amount) { return ($amount > $this->minIncome) ? $this->taxAmount : 0; } } sfMixer::register('Income:calculateTaxes', array('FixedTax', 'calculateTaxes')); Behaviors de Propel Los behaviors de Propel, que se vieron en el capítulo 8, son un tipo especial de mixin, ya que ext- ienden los objetos generados por Propel. A continuación se muestra un ejemplo. Los objetos Propel correspondientes a las tablas de la base de datos disponen de un método llama- do delete(), que borra el registro correspondiente de la base de datos. Sin embargo, si se dispone de una clase llamada Factura, para la que no se deben borrar las filas de datos, se podría modifi- car el método delete() para mantener el registro en la base de datos y modificar el valor de una columna llamada is_deleted que indica si el registro ha sido borrado. Los métodos que se utilizan para obtener los registros (doSelect(), retrieveByPk()) solamente deberían tener en cuenta las filas de datos para las que la columna is_deleted vale false. Además, se debería añadir otro mé- todo llamado forceDelete(), que es el que borraría realmente de la base de datos. Todas las www.librosweb.es 376
  • 377. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony modificaciones anteriores se podrían encapsular en una nueva clase llamada por ejemplo Paran- oidBehavior. La clase Factura completa extiende la clase BaseFactura de Propel y tiene los mé- todos de ParanoidBehavior mediante un mixin. De esta forma, un behavior en realidad es un mixin realizado sobre un objeto Propel. Además, la palabra “behavior” en Symfony implica otra característica: el hecho de que el mixin se encapsula en forma de plugin. La clase ParanoidBehavior mencionada anteriormente se corresponde con un plugin real de Symfony, llamado sfPropelParanoidBehaviorPlugin. El wiki de Symfony (http://trac.symfony-project.com/wiki/sfPropelParanoidBehaviorPlugin) dispone de más deta- lles sobre la instalación y uso de este plugin. Un último comentario sobre los behaviors: para poder utilizarlos, los objetos generados por Propel deben contener un gran número de hooks. Como esto puede penalizar el rendimiento de la aplica- ción si no se utilizan los behaviors, por defecto los hooks no están activados. Para añadir los hooks y activar el soporte de los behaviors, se debe establecer la propiedad propel.builder.addBehav- iors a true en el archivo propel.ini y se debe volver a construir el modelo. 17.2. Factorías Una factoría consiste en la definición de una clase que realiza una determinada tarea. Symfony utiliza las factorias en su funcionamiento interno, como por ejemplo para los controladores y para las sesiones. Cuando el framework necesita por ejemplo crear un nuevo objeto para una petición, busca en la definición de la factoría el nombre de la clase que se debe utilizar para esta tarea. Como la definición por defecto de la factoría para las peticiones es sfWebRequest, Symfony crea un objeto de esta clase para tratar con las pe- ticiones. La principal ventaja de utilizar las definiciones de las factorías es que es muy sencillo modificar las características internas de Symfony: simplemente es necesario mo- dificar la definición de la factoría y Symfony utiliza la clase propia indicada en vez de la clase por defecto. Las definiciones para las factorías se guardan en el archivo de configuración factor- ies.yml. El listado 17-13 muestra el contenido por defecto de ese archivo. Cada defini- ción consta del nombre de una clase y opcionalmente, de una serie de parámetros. Por ejemplo, la factoría para el almacenamiento de la sesión (que se indica bajo la clave sto- rage:) utiliza un parámetro llamado session_name para establecer el nombre de la cookie que se crea para el lado del cliente, de forma que se puedan realizar sesiones persistentes. Listado 17-13 - Archivo por defecto para las factorias, en miaplicacion/config/ factories.yml cli: controller: class: sfConsoleController request: class: sfConsoleRequest test: storage: class: sfSessionTestStorage www.librosweb.es 377
  • 378. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony #all: # controller: # class: sfFrontWebController # # request: # class: sfWebRequest # # response: # class: sfWebResponse # # user: # class: myUser # # storage: # class: sfSessionStorage # param: # session_name: symfony # # view_cache: # class: sfFileCache # param: # automaticCleaningFactor: 0 # cacheDir: %SF_TEMPLATE_CACHE_DIR% La mejor forma de crear una nueva factoría consiste en crear una nueva clase que here- de de la clase por defecto y añadirle nuevos métodos. La factoría para las sesiones de usuario se establece a la clase myUser (localizada en miaplicacion/lib) y hereda de la clase sfUser. Se puede utilizar el mismo mecanismo para aprovechar las factorías ya existentes. El listado 17-14 muestra el ejemplo de una factoría para el objeto de la petición. Listado 17-14 - Redefiniendo factorías // Se crea la clase miRequest.class.php en un directorio para // el que esté activada la carga automática de clases, por ejemplo // miaplicacion/lib/ <?php class miRequest extends sfRequest { // El código de la nueva factoría } # Se declara en el archivo factories.yml que esta nueva # clase es la factoría para las peticiones all: request: class: miRequest 17.3. Utilizando componentes de otros frameworks Si se requiere utilizar una clase externa y no se copia esa clase en algún directorio lib/ de Symfony, la clase se encontrará en algún directorio en el que Symfony no la puede encontrar. En este caso, si se utiliza esta clase en el código, es necesario incluir www.librosweb.es 378
  • 379. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony manualmente una instrucción require, a menos que se utilicen las propiedades de Sym- fony para enlazar y permitir la carga automática de otros componentes externos. Symfony de momento no proporciona utilidades y herramientas para resolver cualquier tipo de problema. Si se necesita un generador de archivos PDF, una API para interactuar con los mapas de Google o una implementación en PHP del motor de búsqueda Lucene, es necesario hacer uso de algunas librerías del framework de Zend (http://framework.zend.com/). Si se quieren manipular imágenes directamente con PHP, co- nectarse con una cuenta POP3 para obtener los emails o diseñar una interfaz para la con- sola de comandos, seguramente se utilizarán los eZcomponents (http://ez.no/ezcompo- nents). Afortunadamente, si se utilizan las opciones correctas, se pueden utilizar directa- mente en Symfony todos los componentes de estas librerías externas. Lo primero que hay que hacer es declarar la ruta al directorio raíz de cada librería, a me- nos que se hayan instalado mediante PEAR. Esta configuración se realiza en el archivo settings.yml. .settings: zend_lib_dir: /usr/local/zend/library/ ez_lib_dir: /usr/local/ezcomponents/ A continuación, se configura la carga automática de clases para especificar las librerías que se deben utilizar cuando la carga automática falla en Symfony: .settings: autoloading_functions: - [sfZendFrameworkBridge, autoload] - [sfEzComponentsBridge, autoload] Esta opción es diferente a las reglas definidas en el archivo autoload.yml (el capítulo 19 contiene más información sobre este archivo). La opción autoloading_functions especifi- ca las clases utilizadas como enlace o puente, mientras que el archivo autoload.yml es- pecifica las rutas y reglas utilizadas para buscar las clases. A continuación se describe lo que sucede cuando se crea un nuevo objeto de una clase que no ha sido cargada: 1. La función de Symfony encargada de la carga automática de clases (sfCore::splAutoload()) busca la clase en las rutas especificadas en el archivo autoload.yml. 2. Si no se encuentra ninguna clase, se invocan uno a uno los métodos declarados en la opción sf_autoloading_functions hasta que uno de ellos devuelva el valor true. 3. sfZendFrameworkBridge::autoload() 4. sfEzComponentsBridge::autoload() 5. Si todos los métodos anteriores devuelven false, si se utiliza una versión de PHP 5.0.X Symfony lanza una excepción indicando que la clase no existe. Si se utiliza una versión de PHP 5.1 o superior, el propio PHP genera el error. De esta forma, los componentes de otros frameworks pueden aprovecharse también del mecanismo de carga automática de clases, por lo que es incluso más sencillo que www.librosweb.es 379
  • 380. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony utilizarlos dentro de los frameworks originales. El siguiente código muestra por ejemplo cómo utilizar el componente Zend_Search (que implementa el motor de búsqueda Lucene en PHP) desde el propio framework Zend: require_once 'Zend/Search/Lucene.php'; $doc = new Zend_Search_Lucene_Document(); $doc->addField(Zend_Search_Lucene_Field::Text('url', $docUrl)); ... Utilizando Symfony y el enlace con el framework Zend, es mucho más fácil utilizar este componente: $doc = new Zend_Search_Lucene_Document(); // The class is autoloaded $doc->addField(Zend_Search_Lucene_Field::Text('url', $docUrl)); ... Los enlaces o puentes disponibles se guardan en el directorio $sf_symfony_lib_dir/ad- don/bridge/. 17.4. Plugins En ocasiones, es necesario reutilizar una porción de código desarrollada para alguna apli- cación Symfony. Si se puede encapsular ese código en una clase, tan sólo es necesario guardar la clase en algún directorio lib/ para que otras aplicaciones puedan encontrarla. Sin embargo, si el código se encuentra desperdigado en varios archivos, como por ejem- plo un tema para el generador de administraciones o una serie de archivos JavaScript y helpers que permiten utilizar fácilmente un efecto visual complejo, es muy complicado copiar todo este código en una clase. Los plugins permiten agrupar todo el código diseminado por diferentes archivos y reutili- zar este código en otros proyectos. Los plugins permiten encapsular clases, filtros, mi- xins, helpers, archivos de configuración, tareas, módulos, esquemas y extensiones para el modelo, fixtures, archivos estáticos, etc. Los plugins son fáciles de instalar, de actuali- zar y de desinstalar. Se pueden distribuir en forma de archivo comprimido .tgz, un paq- uete PEAR o directamente desde el repositorio de código. La ventaja de los paquetes PEAR es que pueden controlar las dependencias, lo que simplifica su actualización. La for- ma en la que Symfony carga los plugins permite que los proyectos puedan utilizarlos co- mo si fueran parte del propio framework. Básicamente, un plugin es una extensión encapsulada para un proyecto Symfony. Los plugins permiten no solamente reutilizar código propio, sino que permiten aprovechar los desarrollos realizados por otros programadores y permiten añadir al núcleo de Symfony extensiones realizadas por otros desarrolladores. 17.4.1. Plugins disponibles para Symfony El sitio web del proyecto Symfony dispone de una página dedicada a los plugins de Sym- fony. La página se encuentra dentro del wiki de Symfony, en la dirección: http://trac.symfony-project.com/wiki/SymfonyPlugins Cada plugin que se muestra en ese listado, cuenta con su propia página con instrucciones para su instalación y toda la documentación necesaria. www.librosweb.es 380
  • 381. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony Algunos plugins están desarrollados por voluntarios de la comunidad Symfony y otros han sido desarrollados por los mismos creadores de Symfony. Entre estos últimos se enc- uentran los siguientes: ▪ sfFeedPlugin: automatiza la manipulación de los canales RSS y Atom. ▪ sfThumbnailPlugin: crea imágenes en miniatura, por ejemplo para las imágenes subidas por los usuarios. ▪ sfMediaLibraryPlugin: permite gestionar la subida de archivos multimedia, inclu- yendo una extensión para los editores avanzados de texto que permite incluir las imágenes denro de los textos creados. ▪ sfShoppingCartPlugin: permite gestionar un carrito de la compra. ▪ sfPagerNavigationPlugin: dispone de controles para paginar elementos de forma clásica y mediante Ajax, basados en el objeto sfPager. ▪ sfGuardPlugin: permite incluir autenticación, autorización y otras opciones de gestión de usuarios más avanzadas que las que proporciona por defecto Symfony. ▪ sfPrototypePlugin: permite incluir los archivos de prototype y script.aculo.us co- mo librerías de JavaScript independientes. ▪ sfSuperCachePlugin: crea versiones cacheadas de las páginas web en el director- io de la cache bajo el directorio web raíz del proyecto, de forma que el servidor web pueda servirlas lo más rápidamente posible. ▪ sfOptimizerPlugin: optimiza el código fuente de la aplicación para que se ejecute más rápidamente en el entorno de producción (el próximo capítulo muestra los detalles). ▪ sfErrorLoggerPlugin: guarda un registro de todos los errores de tipo 404 y 500 en una base de datos e incluye un módulo de administración para gestionar estos errores. ▪ sfSslRequirementPlugin: proporciona soporte para la encriptación SSL en las acciones. El wiki también contiene otros plugins utilizados para extender los objetos Propel, que también se suelen llamar behaviors. Entre otros, están disponibles los siguientes: ▪ sfPropelParanoidBehaviorPlugin: deshabilita el borrado de los objetos y lo reem- plaza por la actualización de una columna llamada deleted_at. ▪ sfPropelOptimisticLockBehaviorPlugin: implementa la estrategia optimistic loc- king para los objetos Propel. Se recomienda visitar de forma habitual el wiki de Symfony, ya que se añaden plugins constantemente y normalmente proporcionan utilidades muy empleadas en el desarrollo de aplicaciones web. www.librosweb.es 381
  • 382. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony Además del wiki de Symfony, también se pueden distribuir los plugins en forma de archi- vo para bajar, se puede crear un canal PEAR o se pueden almacenar en un repositorio público. 17.4.2. Instalando un plugin El proceso de instalación de los plugins varía en función del método utilizado para distrib- uirlo. Siempre es recomendable leer el archivo README incluido en el plugin o las instrucc- iones de instalación disponibles en la página de descarga del plugin. Además, siempre se debe borrar la cache de Symfony después de la instalación de un plugin. Los plugins se instalan en cada proyecto. Todos los métodos descritos en las siguientes secciones resultan en la copia de los archivos de cada plugin en el directorio miproyecto/ plugins/[NOMBRE PLUGIN]/. 17.4.2.1. Plugins PEAR Los plugins listados en el wiki de Symfony se distribuyen en forma de paquete PEAR aso- ciados con una página del wiki. Para instalar un plugin de este tipo, se utiliza la tarea plugin-install con la URL completa del plugin, tal y como muestra el listado 17-15. Listado 17-15 - Instalando un plugin del wiki de Symfony > cd miproyecto > php symfony plugin-install http://plugins.symfony-project.com/nombrePlugin > php symfony cc También es posible descargar los archivos del plugin e instalarlo desde un directorio del sistema. En este caso, se reemplaza la URL del plugin por la ruta absoluta hasta el archi- vo del paquete descargado, como se muestra en el listado 17-16. Listado 17-16 - Instalando un plugin mediante un paquete PEAR descargado > cd miproyecto > php symfony plugin-install /ruta/hasta/el/archivo/descargado/nombrePlugin.tgz > php symfony cc Algunos plugins disponen de su propio canal PEAR. En este caso, se pueden instalar med- iante la tarea plugin-install y el nombre del canal, como se muestra en el listado 17-17. Listado 17-17 - Instalando un plugin desde un canal PEAR > cd miproyecto > php symfony plugin-install nombreCanal/nombrePlugin > php symfony cc Estos tres tipos de instalaciones utilizan paquetes PEAR, por lo que se utiliza el término común “Plugins PEAR” para referirse a los plugins del wiki, de canales PEAR o de un paq- uete PEAR descargado. 17.4.2.2. Plugins de archivo Algunos plugins se distribuyen en forma de un archivo o un conjunto de archivos. Para instalarlos, simplemente se descomprimen los archivos en el directorio plugins/ del www.librosweb.es 382
  • 383. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony proyecto. Si el plugin contiene un subdirectorio llamado web/, se copia o se realiza un en- lace simbólico a este directorio desde el directorio web/ del proyecto, como se muestra en el listado 17-18. Por último, siempre se debe borrar la cache después de instalar el plugin. Listado 17-18 - Instalando un plugin desde un archivo > cd plugins > tar -zxpf miPlugin.tgz > cd .. > ln -sf plugins/miPlugin/web web/miPlugin > php symfony cc 17.4.2.3. Instalando plugins desde un repositorio de código En ocasiones, los plugins disponen de su propio repositorio de código para el versionado de su código fuente. Estos plugins se pueden instalar simplemente descargando el código desde el repositorio hasta el directorio plugins/, pero este método puede ser problemáti- co si el propio proyecto también utiliza el versionado de su código fuente. Un método alternativo consiste en declarar el plugin como una dependencia externa, de forma que cada vez que se actualice el código fuente del proyecto, también se actualice el código fuente del plugin. Los repositorios de tipo Subversion, guardan las dependenc- ias externas en la propiedad svn:externals. Como se muestra en el listado 17-19, se puede añadir un plugin simplemente editando esta propiedad y actualizando posterior- mente el código fuente del proyecto. Listado 17-19 - Instalando un plugin desde un repositorio de código > cd miproyecto > svn propedit svn:externals plugins nombrePlugin http://svn.ejemplo.com/nombrePlugin/trunk > svn up > php symfony cc NOTA Si el plugin contiene un directorio llamado web/, se debe crear un enlace simbólico de la misma for- ma que la explicada para los plugins de archivos. 17.4.2.4. Activando un módulo de plugin Algunos plugins contienen módulos enteros. La única diferencia entre los módulos de plu- gins y los módulos normales es que los de los plugins no se guardan en el directorio mi- proyecto/apps/miaplicacion/modules/ (para facilitar su actualización). Además, se deben activar en el archivo settings.yml, como se muestra en el listado 17-20. Listado 17-20 - Activando un módulo de plugin, en miaplicacion/config/ settings.yml all: .settings: enabled_modules: [default, sfMiPluginModule] www.librosweb.es 383
  • 384. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony Este funcionamiento se ha establecido para evitar las situaciones en las que los módulos de un plugin se puedan habilitar de forma errónea para una aplicación que no los requie- re, lo que podría provocar un agujero de seguridad. Si un plugin dispone de dos módulos llamados frontend y backend, se debería habilitar el módulo frontend solamente para la aplicación frontend y el módulo backend en la aplicación backend. Este es el motivo por el que los módulos de los plugins no se activan automáticamente. SUGERENCIA El módulo default es el único módulo activado por defecto. Realmente no es un módulo de plugin, ya que es del propio framework (se guarda en el directorio $sf_symfony_data_dir/modules/def- ault/). Este módulo se encarga de mostrar las páginas de bienvenida, las páginas de error 404 y las de los errores de seguridad por no haber proporcionado las credenciales adecuadas. Si no se quieren utilizar las páginas por defecto de Symfony, se puede eliminar este módulo de la opción enabled_modules. 17.4.2.5. Listando los plugins instalados Accediendo al directorio plugins/ del proyecto, se pueden observar los plugins instala- dos, pero la tarea plugin-list proporciona más información: el número de versión y el nombre del canal para cada plugin instalado (ver el listado 17-21). Listado 17-21 - Listando los plugins instalados > cd miproyecto > php symfony plugin-list Installed plugins: sfPrototypePlugin 1.0.0-stable # pear.symfony-project.com (symfony) sfSuperCachePlugin 1.0.0-stable # pear.symfony-project.com (symfony) sfThumbnail 1.1.0-stable # pear.symfony-project.com (symfony) 17.4.2.6. Actualizando y desinstalando plugins Los plugins PEAR se pueden desinstalar ejecutando la tarea plugin-uninstall desde el directorio raíz del proyecto, como muestra el listado 17-22. Para desinstalar el plugin, se debe indicar también el nombre del canal desde el que se instaló (se puede obtener el nombre del canal mediante la tarea plugin-list). Listado 17-22 - Desinstalando un plugin > cd miproyecto > php symfony plugin-uninstall pear.symfony-project.com/sfPrototypePlugin > php symfony cc SUGERENCIA Algunos canales disponen de un alias para su nombre. El canal pear.symfony-project.com por ejemplo, también se puede llamar symfony, por lo que se puede desinstalar el plugin sfPrototy- pePlugin del listado 17-22, simplemente ejecutando php symfony plugin-uninstall symfony/ sfPrototypePlugin. www.librosweb.es 384
  • 385. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony Para desinstalar un plugin de archivo o un plugin instalado desde un repositorio, se bo- rran manualmente los archivos del plugin que se encuentran en los directorios plugins/ y web/ y se borra la cache. Para actualizar un plugin, se puede utilizar la tarea plugin-upgrade (para los plugins PEAR) o se puede ejecutar directamente svn update (si el plugin se ha instalado desde un repositorio de código). Los plugins de archivo no se pueden actualizar de una forma tan sencilla. 17.4.3. Estructura de un plugin Los plugins se crean mediante el lenguaje PHP. Si se entiende la forma en la que se es- tructura una aplicación, es posible comprender la estructura de un plugin. 17.4.3.1. Estructura de archivos de un plugin El directorio de un plugin se organiza de forma muy similar al directorio de un proyecto. Los archivos de un plugin se deben organizar de forma adecuada para que Symfony pue- da cargarlos automáticamente cuando sea necesario. El listado 17-23 muestra la estruc- tura de archivos de un plugin. Listado 17-23 - Estructura de archivos de un plugin nombrePlugin/ config/ *schema.yml // Esquema de datos *schema.xml config.php // Configuración específica del plugin data/ generator/ sfPropelAdmin */ // Temas para el generador de administraciones template/ skeleton/ fixtures/ *.yml // Archivos de fixtures tasks/ *.php // Tareas de Pake lib/ *.php // Clases helper/ *.php // Helpers model/ *.php // Clases del modelo modules/ */ // Módulos actions/ actions.class.php config/ module.yml view.yml security.yml templates/ *.php www.librosweb.es 385
  • 386. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony validate/ *.yml web/ * // Archivos estáticos 17.4.3.2. Posibilidades de los plugins Los plugins pueden contener numerosos elementos. Su contenido se tiene en considera- ción durante la ejecución de la aplicación y cuando se ejecutan tareas mediante la línea de comandos. Sin embargo, para que los plugins funcionen correctamente, es necesario seguir una serie de convenciones: ▪ Los esquemas de bases de datos los detectan las tareas propel-. Cuando se eje- cuta la tarea propel-build-model para el proyecto, se reconstruye el modelo del proyecto y los modelos de todos los plugins que dispongan de un modelo. Los es- quemas de los plugins siempre deben contener un atributo package que siga la notación plugins.nombrePlugin. lib.model, como se muestra en el listado 17-24. Listado 17-24 - Ejemplo de declaración de un esquema de un plugin, en miPlu- gin/config/schema.yml propel: _attributes: { package: plugins.miPlugin.lib.model } mi_plugin_foobar: _attributes: { phpName: miPluginFoobar } id: name: { type: varchar, size: 255, index: unique } ... ▪ La configuración del plugin se incluye en el script de inicio del plugin (config.php). Este archivo se ejecuta después de las configuraciones de la aplica- ción y del proyecto, por lo que Symfony ya se ha iniciado cuando se procesa esta configuración. Se puede utilizar este archivo por ejemplo para añadir nuevos di- rectorios a la ruta de directorios incluidos por PHP o para extender las clases existentes con mixins. ▪ Los archivos de datos o fixtures del directorio data/fixtures/ del plugin se proce- san mediante la tarea propel-load-data. ▪ Las tareas del plugin están disponibles en la línea de comandos de Symfony tan pronto como se instala el plugin. Una buena práctica consiste en prefijar el nom- bre de la tarea con una palabra significativa, por ejemplo el nombre del plugin. Si se teclea simplemente symfony en la línea de comandos, se puede ver la lista completa de tareas disponibles, incluyendo las tareas proporcionadas por todos los plugins instalados. ▪ Las clases propias se cargan automáticamente de la misma forma que las clases que se guardan en las carpetas lib/ del proyecto. ▪ Cuando se realiza una llamada a use_helper() en las plantillas, se cargan au- tomáticamente los helpers de los plugins. Estos helpers deben encontrarse en un subdirectorio llamado helper/ dentro de cualquier directorio lib/ del plugin. www.librosweb.es 386
  • 387. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony ▪ Las clases del modelo en el directorio miplugin/lib/model/ se utilizan para espec- ializar las clases del modelo generadas por Propel (en miplugin/lib/model/om/ y miplugin/lib/model/map/). Todas estas clases también se cargan automática- mente. Las clases del modelo generado para un plugin no se pueden redefinir en los directorios del proyecto. ▪ Los módulos proporcionan nuevas acciones, siempre que se declaren en la opción enabled_modules de la aplicación. ▪ Los archivos estáticos (imágenes, scripts, hojas de estilos, etc.) se sirven como el resto de archivos estáticos del proyecto. Cuando se instala un plugin mediante la línea de comandos, Symfony crea un enlace simbólico al directorio web/ del pro- yecto si el sistema operativo lo permite, o copia el contenido del directorio web/ del módulo en el directorio web/ del proyecto. Si el plugin se instala mediante un archivo o mediante un repositorio de código, se debe copiar manualmente el di- rectorio web/ del plugin (como debería indicar el archivo README incluido en el plugin). 17.4.3.3. Configuración manual de plugins Algunas tareas no las puede realizar automáticamente el comando plugin-install, por lo que se deben realizar manualmente durante la instalación del plugin: ▪ El código de los plugins puede hacer uso de una configuración propia (por ejem- plo mediante sfConfig::get(’app_miplugin_opcion’)), pero no se pueden indicar los valores por defecto en un archivo de configuración app.yml dentro del directo- rio config/ del plugin. Para trabajar con valores por defecto, se utilizan los se- gundos argumentos opcionales en las llamadas a los métodos sfConfig::get(). Las opciones de configuración se pueden redefinir en el nivel de la aplicación (el listado 17-25 muestra un ejemplo). ▪ Las reglas de enrutamiento propias se deben añadir manualmente en el archivo routing.yml. ▪ Los filtros propios también se deben añadir manualmente al archivo filters.yml de la aplicación. ▪ Las factorías propias se deben añadir manualmente al archivo factories.yml de la aplicación. En general, todas las configuraciones que deben realizarse sobre los archivos de configu- ración de las aplicaciones, se tienen que añadir manualmente. Los plugins que requieran esta instalación manual, deberían indicarlo en el archivo README incluido. 17.4.3.4. Personalizando un plugin para una aplicación Cuando se necesita personalizar el funcionamiento de un plugin, nunca se debería modifi- car el código del directorio plugins/. Si se realizan cambios en ese directorio, se per- derían todos los cambios al actualizar el plugin. Los plugins disponen de opciones y la www.librosweb.es 387
  • 388. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony posibilidad de redefinir su funcionamiento, de forma que se puedan personalizar sus características. Los plugins que están bien diseñados disponen de opciones que se pueden modificar en el archivo app.yml de la aplicación, tal y como se muestra en el listado 17-25. Listado 17-25 - Personalizando un plugin que utiliza la configuración de la aplicación // Ejemplo de código del plugin $foo = sfConfig::get('app_mi_plugin_opcion', 'valor'); # Modificar el valor por defecto de 'opcion' en el archivo # app.yml de la aplicación all: mi_plugin: opcion: otrovalor Las opciones del módulo y sus valores por defecto normalmente se describen en el archi- vo README del plugin. Se pueden modificar los contenidos por defecto de un módulo del plugin creando un mó- dulo con el mismo nombre en la aplicación. Realmente no se redefine el comportamiento del módulo original, sino que se sustituye, ya que se utilizan los elementos del módulo de la aplicación y no los del plugin. Funciona correctamente si se crean plantillas y archivos de configuración con el mismo nombre que los del plugin. Por otra parte, si un plugin quiere ofrecer un módulo cuyo comportamiento se pueda re- definir, el archivo actions.class.php del módulo del plugin debe estar vacío y heredar de una clase que se cargue automáticamente, de forma que esta clase pueda ser heredada también por el actions.class.php del módulo de la aplicación. El listado 17-26 muestra un ejemplo. Listado 17-26 - Personalizando la acción de un plugin // En miPlugin/modules/mimodulo/lib/miPluginmimoduloActions.class.php class miPluginmimoduloActions extends sfActions { public function executeIndex() { // Instrucciones y código } } // En miPlugin/modules/mimodulo/actions/actions.class.php require_once dirname(__FILE__).'/../lib/miPluginmimoduloActions.class.php'; class mimoduloActions extends miPluginmimoduloActions { // Vacío } // En miaplicacion/modules/mimodulo/actions/actions.class.php class mimoduloActions extends miPluginmimoduloActions { www.librosweb.es 388
  • 389. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony public function executeIndex() { // Aquí se redefine el código del plugin } } 17.4.4. Cómo crear un plugin Solamente los plugins creados como paquetes PEAR se pueden instalar mediante la tarea plugin-install. Este tipo de plugins se pueden distribuir mediante el wiki de Symfony, mediante un canal PEAR o mediante la descarga de un archivo. Por tanto, si que quiere crear un plugin, es mejor publicarlo como paquete PEAR en vez de como archivo normal y corriente. Además, los plugins instalados mediante paquetes PEAR son más fáciles de actualizar, pueden declarar las dependencias que tienen y copian automáticamente los archivos estáticos en el directorio web/. 17.4.4.1. Organización de archivos Si se ha creado una nueva característica para Symfony, puede ser útil encapsularla en un plugin para poder reutilizarla en otros proyectos. El primer paso es el de organizar los ar- chivos de forma lógica para que los mecanismos de carga automática de Symfony pue- dan cargarlos cuando sea necesario. Para ello, se debe seguir la estructura de archivos mostrada en el listado 17-23. El listado 17-27 muestra un ejemplo de estructura de ar- chivos para un plugin llamado sfSamplePlugin. Listado 17-27 - Ejemplo de los archivos que se encapsulan en un plugin sfSamplePlugin/ README LICENSE config/ schema.yml data/ fixtures/ fixtures.yml tasks/ sfSampleTask.php lib/ model/ sfSampleFooBar.php sfSampleFooBarPeer.php validator/ sfSampleValidator.class.php modules/ sfSampleModule/ actions/ actions.class.php config/ security.yml lib/ BasesfSampleModuleActions.class.php templates/ indexSuccess.php www.librosweb.es 389
  • 390. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony web/ css/ sfSampleStyle.css images/ sfSampleImage.png Para la creación de los plugins, no es importante la localización del directorio del plugin (sfSamplePlugin/ en el caso del listado 17-27), ya que puede encontrarse en cualquier si- tio del sistema de archivos. SUGERENCIA Se aconseja ver la estructura de archivos de los plugins existentes antes de crear plugins propios, de forma que se puedan utilizar las mismas convenciones para el nombrado de archivos y la misma estructura de archivos. 17.4.4.2. Creando el archivo package.xml El siguiente paso en la creación del plugin es añadir un archivo llamado package.xml en el directorio raíz del plugin. El archivo package.xml sigue la misma sintaxis de PEAR. El lista- do 17-28 muestra el aspecto típico de un archivo package.xml de un plugin. Listado 17-28 - Ejemplo de archivo package.xml de un plugin de Symfony <?xml version="1.0" encoding="UTF-8"?> <package packagerversion="1.4.6" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/ tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/ package-2.0.xsd"> <name>sfSamplePlugin</name> <channel>pear.symfony-project.com</channel> <summary>symfony sample plugin</summary> <description>Just a sample plugin to illustrate PEAR packaging</description> <lead> <name>Fabien POTENCIER</name> <user>fabpot</user> <email>fabien.potencier@symfony-project.com</email> <active>yes</active> </lead> <date>2006-01-18</date> <time>15:54:35</time> <version> <release>1.0.0</release> <api>1.0.0</api> </version> <stability> <release>stable</release> <api>stable</api> </stability> <license uri="http://www.symfony-project.org/license">MIT license</license> <notes>-</notes> <contents> www.librosweb.es 390
  • 391. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony <dir name="/"> <file role="data" name="README" /> <file role="data" name="LICENSE" /> <dir name="config"> <!-- model --> <file role="data" name="schema.yml" /> </dir> <dir name="data"> <dir name="fixtures"> <!-- fixtures --> <file role="data" name="fixtures.yml" /> </dir> <dir name="tasks"> <!-- tasks --> <file role="data" name="sfSampleTask.php" /> </dir> </dir> <dir name="lib"> <dir name="model"> <!-- model classes --> <file role="data" name="sfSampleFooBar.php" /> <file role="data" name="sfSampleFooBarPeer.php" /> </dir> <dir name="validator"> <!-- validators --> <file role="data" name="sfSampleValidator.class.php" /> </dir> </dir> <dir name="modules"> <dir name="sfSampleModule"> <file role="data" name="actions/actions.class.php" /> <file role="data" name="config/security.yml" /> <file role="data" name="lib/BasesfSampleModuleActions.class.php" /> <file role="data" name="templates/indexSuccess.php" /> </dir> </dir> <dir name="web"> <dir name="css"> <!-- stylesheets --> <file role="data" name="sfSampleStyle.css" /> </dir> <dir name="images"> <!-- images --> <file role="data" name="sfSampleImage.png" /> </dir> </dir> </dir> </contents> <dependencies> <required> <php> <min>5.0.0</min> </php> <pearinstaller> <min>1.4.1</min> www.librosweb.es 391
  • 392. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony </pearinstaller> <package> <name>symfony</name> <channel>pear.symfony-project.com</channel> <min>1.0.0</min> <max>1.1.0</max> <exclude>1.1.0</exclude> </package> </required> </dependencies> <phprelease /> <changelog /> </package> Las partes más interesates del archivo anterior son las etiquetas <contents> y <dependen- cies>, que se describen a continuación. Como el resto de etiquetas no son específicas de Symfony, se puede consultar la documentación de PEAR (http://pear.php.net/manual/en/) para obtener más información sobre el formato de package.xml. 17.4.4.3. Contenidos La etiqueta <contents> se utiliza para describir la estructura de archivos de los plugins. Mediante esta etiqueta se dice a PEAR los archivos que debe copiar y el lugar en el que los debe copiar. La estructura de archivos se define mediante etiquetas <dir> y <file>. Todas las etiquetas de tipo <file> deben contener un atributo role=”data”. La sección <contents> del listado 17-28 describe la estructura de directorios exacta del listado 17-27. NOTA El uso de etiquetas <dir> no es obligatorio, ya que se pueden utilizar rutas relativas como valor de los atributos name de las etiquetas <file>. No obstante, se recomienda utilizarlas para que el archi- vo package.xml sea fácil de leer. 17.4.4.4. Dependencias de los plugins Los plugins están diseñados para funcionar con una serie de versiones de PHP, PEAR, Symfony, paquetes PEAR y otros plugins. La etiqueta <dependencies> declara todas estas dependencias y ayuda a PEAR a comprobar si se encuentran instalados todos los paque- tes requeridos, lanzando una excepción si alguno no está disponible. Siempre se deberían declarar las dependencias de PHP, PEAR y Symfony; al menos se deberían declarar las correspondientes a la instalación propia del autor del plugin, como requerimiento mínimo de instalación. Si no se sabe qué requerimientos establecer, se pueden indicar como requisitos PHP 5.0, PEAR 1.4 y Symfony 1.0. También es recomendable añadir un número correspondiente a la versión más avanzada de Symfony para la que el plugin funciona correctamente. De esta forma, se producirá un error al intentar utilizar un plugin con una versión muy avanzada de Symfony. Así, el au- tor del plugin se ve obligado a asegurar que el plugin funciona con las nuevas versiones de Symfony antes de lanzar una nueva versión del plugin. Siempre es mejor que se www.librosweb.es 392
  • 393. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony muestre un mensaje de error y se obligue a actualizar el plugin, que no simplemente de- jar que el plugin no funcione y no avise de ninguna manera. 17.4.4.5. Construyendo el plugin PEAR dispone de un comando (pear package) que construye un archivo comprimido de ti- po .tgz con los contenidos del paquete, siempre que se ejecute el comando desde un di- rectorio que contiene un archivo package.xml, tal y como muestra el listado 17-29: Listado 17-29 - Creando un paquete PEAR para el plugin > cd sfSamplePlugin > pear package Package sfSamplePlugin-1.0.0.tgz done Una vez construido el plugin, se puede comprobar que funciona correctamente instalan- dolo en el propio sistema, como se muestra en el listado 17-30. Listado 17-30 - Instalando el plugin > cp sfSamplePlugin-1.0.0.tgz /home/production/miproyecto/ > cd /home/production/miproyecto/ > php symfony plugin-install sfSamplePlugin-1.0.0.tgz Según la descripción de la etiqueta <contents>, los archivos del plugin se instalarán en diferentes directorios del proyecto. El listado 17-31 muestra donde acaban los archivos del plugin sfSamplePlugin después de su instalación. Listado 17-31 - Los archivos del plugin se instalan en los directorios plugins/ y web/ plugins/ sfSamplePlugin/ README LICENSE config/ schema.yml data/ fixtures/ fixtures.yml tasks/ sfSampleTask.php lib/ model/ sfSampleFooBar.php sfSampleFooBarPeer.php validator/ sfSampleValidator.class.php modules/ sfSampleModule/ actions/ actions.class.php config/ security.yml lib/ www.librosweb.es 393
  • 394. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony BasesfSampleModuleActions.class.php templates/ indexSuccess.php web/ sfSamplePlugin/ ## Copia o enlace simbólico, dependiendo del sistema operativo css/ sfSampleStyle.css images/ sfSampleImage.png Posteriormente, se comprueba si el plugin funciona correctamente dentro de la aplica- ción. Si todo funciona bien, el plugin ya está listo para ser utilizado en otros proyectos y para compartirlo con el resto de la comunidad de Symfony. 17.4.4.6. Distribuir un plugin desde el sitio web del proyecto Symfony La mejor forma de publicitar un plugin es distribuirlo desde el sitio web symfony- project.com. Cualquier plugin desarrollado por cualquier programador se puede distribuir desde este sitio web, siempre que se realicen los siguientes pasos: 1. El archivo README del plugin debe describir la instalación y uso del plugin y el ar- chivo LICENSE debe indicar el tipo de licencia de uso del plugin. El archivo README debe escribirse con el formato común de los wikis (http://trac.symfony-project.com/wiki/WikiFormatting ). 2. Se crea un paquete PEAR para el plugin mediante el comando pear package y se prueba su funcionamiento. El nombre del paquete PEAR debe seguir la notación sfSamplePlugin-1.0.0.tgz (1.0.0 es la versión del plugin). 3. Se crea una nueva página en el wiki de Symfony llamada sfSamplePlugin (es obligatorio utilizar el sufijo Plugin). En esta página, se describe el uso del plugin, la licencia, las dependencias y el proceso de instalación. Se pueden reutilizar los contenidos del archivo README del plugin. Se pueden comprobar las páginas de los plugins existentes para utilizarlas como ejemplo. 4. Se adjunta el paquete PEAR a la página del wiki (sfSamplePlugin-1.0.0.tgz). 5. Se añade la página del plugin ([wiki:sfSamplePlugin]) a la lista de plugins dispo- nibles, que también es una página del wiki y está disponible en (http://trac.symfony-project.com/wiki/SymfonyPlugins ). Si se siguen todos estos pasos, cualquier usuario puede instalar el plugin ejecutando el siguiente comando en el directorio de un proyecto Symfony: > php symfony plugin-install http://plugins.symfony-project.com/sfSamplePlugin 17.4.4.7. Convenciones sobre el nombre de los plugins Para mantener el directorio plugins/ limpio, todos los nombres de los plugins deberían seguir la notación camelCase y deben contener el sufijo Plugin, como por ejemplo carri- toCompraPlugin, feedPlugin, etc. Antes de elegir el nombre de un plugin, se debe com- probar que no exista otro plugin con el mismo nombre. www.librosweb.es 394
  • 395. Symfony, la guía definitiva Capítulo 17. Personalizar Symfony NOTA Los plugins relacionados con Propel deberían contener la palabra Propel en su nombre. Un plugin que por ejemplo se encargue de la autenticación mediante el uso de objetos Propel, podría llamar- se sfPropelAuth. Los plugins siempre deberían incluir un archivo LICENSE que desriba las condiciones de uso del plugin y la licencia seleccionada por su autor. También se debería incluir en el ar- chivo README información sobre los cambios producidos en cada versión, lo que realiza el plugin, las instrucciones sobre su intalación y configuración, etc. 17.5. Resumen Las clases de Symfony contienen hooks utilizados por sfMixer para permitir ser modifica- das a nivel de la aplicación. El mecanismo de mixins permite la herencia múltiple y la re- definición de métodos durante la ejecución de la aplicación, aunque las limitaciones de PHP no lo permitirían. De esta forma, es fácil extender las características de Symfony, in- cluso cuando se quieren reemplazar por completo las clases internas de Symfony, para lo que se dispone del mecanismo de factorías. Muchas de las extensiones que se pueden realizar ya existen en forma de plugins, que se pueden instalar, actualizar y desinstalar fácilmente desde la línea de comandos de Sym- fony. Crear un plugin es tan sencillo como crear un paquete de PEAR y permite reutilizar un mismo código en varias aplicaciones diferentes. El wiki de Symfony incluye muchos plugins y también es posible añadir plugins propios. Ahora que se sabe cómo hacerlo, los creadores de Symfony esperan que muchos progra- madores realicen mejoras a Symfony y las distribuyan a toda la comunidad de Symfony. www.librosweb.es 395
  • 396. Symfony, la guía definitiva Capítulo 18. Rendimiento Capítulo 18. Rendimiento Si una aplicación está pensada para ser utilizada por muchos usuarios, su optimización y su rendimiento son factores muy importantes a tener en cuenta durante su desarrollo. Como es lógico, el rendimiento siempre ha sido una de las máximas preocupaciones de los creadores de Symfony. Aunque la gran ventaja de reducir el tiempo de desarrollo de una aplicación gracias a Symfony conlleva una disminución de su rendimiento, los programadores de Symfony siempre han tenido presente los requerimientos de rendimiento habituales. De esta for- ma, se ha analizado cada clase y cada método para que sean lo más rápidos posible. La penalización mínima en el rendimiento de la aplicación (que se puede medir mostran- do simplemente un mensaje tipo “Hola Mundo” con y sin Symfony) es muy reducida. Por tanto, el framework es escalable y responde correctamente a las pruebas de carga, tam- bién llamadas pruebas de stress. La prueba definitiva de su buen rendimiento es que al- gunos sitios con muchísimo tráfico (varios millones de usuarios y muchas interacciones Ajax) utilizan Symfony y están muy satisfechos con su rendimiento. La lista de sitios web desarrollados con Symfony se puede obtener en el wiki del proyecto: http://trac.symfony- project.com/wiki/ApplicationsDevelopedWithSymfony . Evidentemente, los sitios web con millones de usuarios tienen los recursos necesarios pa- ra crear granjas de servidores y para mejorar el hardware de los servidores. No obstante, si no se dispone de este tipo de recursos, existen unos pequeños trucos que se pueden seguir para mejorar el rendimiento de las aplicaciones Symfony. En este capítulo se muestran algunas de las optimizaciones recomendadas para mejorar el rendimiento en todos los niveles del framework, aunque la mayoría están pensadas para usuarios avan- zados. Aunque alguna técnica ya se ha comentado en otros capítulos anteriores, siempre es conveniente reunir todas las técnicas en un único lugar. 18.1. Optimizando el servidor Una aplicación bien optimizada debería ejecutarse en un servidor que también estuviera muy optimizado. Para asegurar que no existe un cuello de botella en los elementos exter- nos a Symfony, se deberían conocer las técnicas básicas para optimizar los servidores. A continuación se muestran una serie de opciones que se deben comprobar para que el rendimiento del servidor no se vea penalizado. Si la opción magic_quotes_gpc del archivo php.ini tiene asignado un valor de on, el rendi- miento de la aplicación disminuye, ya que PHP añade mecanismos de escape a todas las comillas de los parámetros de la petición y Symfony después aplica los mecanismos in- versos, por lo que el único efecto de esta opción es una pérdida de rendimiento y posi- bles problemas en algunos sistemas. Si se tiene acceso a la configuración de PHP, se de- bería desactivar esta opción. Cuanto más reciente sea la versión de PHP que se utiliza, mayor será el rendimiento. La versión PHP 5.2 es más rápida que PHP 5.1, que a su vez es mucho más rápida que PHP www.librosweb.es 396
  • 397. Symfony, la guía definitiva Capítulo 18. Rendimiento 5.0. De forma que es una buena idea actualizar a la última versión de PHP para obtener una gran mejora en su rendimiento. El uso de un acelerador de PHP (como por ejemplo, APC, XCache, o eAccelerator) es casi obligatorio en un servidor de producción, ya que mejora el rendimiento de PHP en un 50% y no tiene ningún inconveniente. Para disfrutar de la auténtica velocidad de ejecu- ción de PHP, es necesario instalar algún acelerador. Por otra parte, se deben desactivar en el servidor de producción todas las herramientas de depuración, como las extensiones Xdebug y APD. NOTA Si te estás preguntando sobre la penalización en el rendimiento causada por el uso de la extensión mod_rewrite, su efecto es despreciable. Aunque es evidente que cargar por ejemplo una imagen mediante la reglas de reescritura de este módulo es más lento que cargar la imagen directamente, la penalización producida es muchas órdenes de magnitud inferior a la ejecución de cualquier sen- tencia PHP. Algunos programadores de Symfony también utilizan syck, que es una extensión de PHP que procesa archivos de tipo YAML, en vez de utilizar el procesamiento interno de Sym- fony. Aunque esta extensión es mucho más rápida, la cache que utiliza Symfony reduce en gran medida el tiempo de procesamiento de los archivos YAML, por lo que en un ser- vidor de producción, el beneficio que se obtiene es muy reducido, casi inexistente. Además, la extensión syck no es todavía lo suficientemente mandura y de hecho, puede provocar errores si se utiliza. No obstante, se puede instalar la extensión (http://whytheluckystiff.net/syck/) y Symfony la reconoce automáticamente. SUGERENCIA Cuando un solo servidor no es suficiente, se puede añadir otro servidor y hacer un balanceo de la carga entre ellos. Mientras que el directorio uploads/ sea compartido y se almacenen las sesiones en una base de datos, Symfony funciona igual de bien en una arquitectura de balanceo de carga. 18.2. Optimizando el modelo En Symfony, la capa del modelo tiene fama de ser el componente más lento. Si las prue- bas de rendimiento demuestran que se debe optimizar esta capa para una aplicación, a continuación se muestran las posibles mejoras que se pueden realizar. 18.2.1. Optimizando la integración de Propel Inicializar la capa del modelo (las clases internas de Propel) requiere cierto tiempo, ya que se deben cargar algunas clases y se deben construir algunos objetos. No obstante, por la forma en la que Symfony integra Propel, esta inicialización solamente se produce cuando una acción requiere realmente utilizar el modelo, por lo que si sucede, se realiza lo más tarde posible. Las clases Propel se inicializan solamente cuando un objeto del mo- delo generado se carga automáticamente. Por tanto, las páginas que no utilizan el mode- lo no se ven penalizadas por la capa del modelo. www.librosweb.es 397
  • 398. Symfony, la guía definitiva Capítulo 18. Rendimiento Si una aplicación no necesita la capa del modelo, se puede evitar la inicialización del ob- jeto sfDatabaseManager desactivando por completo la capa del modelo mediante la sigu- iente opción del archivo settings.yml: all: .settings: use_database: off Las clases generadas para el modelo (en lib/model/om/) ya están optimizadas porque se les han eliminado los comentarios y también se cargan de forma automática cuando es necesario. Utilizar el sistema de carga automática en vez de incluir las clases a mano, ga- rantiza que las clases se cargan solamente cuando son realmente necesarias. Por tanto, si una clase del modelo no se utiliza, el mecanismo de carga automática ahorra tiempo de ejecución, mientras que la alternativa de utilizar sentencias include de PHP no podría ahorrarlo. En lo que respecta a los comentarios, se utilizan para documentar el uso de los métodos generados, pero aumentan mucho el tamaño de los archivos, lo que disminuye el rendimiento en los sistemas con discos duros lentos. Como los métodos de las clases generadas tienen nombres muy explícitos, los comentarios se desactivan por defecto. Estas 2 mejoras son específicas de Symfony, pero se puede volver a las opciones por de- fecto de Propel cambiando estas 2 opciones en el archivo propel.ini, como se muestra a continuación: propel.builder.addIncludes = true # Añadir sentencias "include" en las clases generadas # en vez de utiliza la carga automática de clases propel.builder.addComments = true # Añadir comentarios a las clases generadas 18.2.2. Limitando el número de objetos que se procesan Cuando se utiliza un método de una clase peer para obtener los objetos, el resultado de la consulta pasa el proceso de “hidratación” (“hydrating” en inglés) en el que se crean los objetos y se cargan con los datos de las filas devueltas en el resultado de la consulta. Pa- ra obtener por ejemplo todas las filas de la tabla articulo mediante Propel, se ejecuta la siguiente instrucción: $articulos = ArticuloPeer::doSelect(new Criteria()); La variable $articulos resultante es un array con los objetos de tipo Article. Cada obje- to se crea e inicializa, lo que requiere cierta cantidad de tiempo. La consecuencia de este comportamiento es que, al contrario de lo que sucede con las consultas a la base de da- tos, la velocidad de ejecución de una consulta Propel es directamente proporcional al nú- mero de resultados que devuelve. De esta forma, los métodos del modelo deberían opti- mizarse para devolver solamente un número limitado de resultados. Si no se necesitan todos los resultados devueltos por Criteria, se deberían limitar mediante los métodos setLimit() y setOffset(). Si solamente se necesitan por ejemplo las filas de datos de la 10 a la 20 para una consulta determinada, se puede refinar el objeto Criteria como se muestra en el listado 18-1. Listado 18-1 - Limitando el número de resultados devueltos por Criteria www.librosweb.es 398
  • 399. Symfony, la guía definitiva Capítulo 18. Rendimiento $c = new Criteria(); $c->setOffset(10); // Posición de la primera fila que se obtiene $c->setLimit(10); // Número de filas devueltas $articulos = ArticuloPeer::doSelect($c); El código anterior se puede automatizar utilizando un paginador. El objeto sfPropelPager gestiona de forma automática los valores offset y limit para una consulta Propel, de forma que solamente se crean los objetos mostrados en cada página. La documentación del paginador (http://www.symfony-project.org/cookbook/trunk/pager) dispone de más infor- mación sobre esta clase. 18.2.3. Minimizando el número de consultas mediante Joins Mientras se desarrolla una aplicación, se debe controlar el número de consultas a la base de datos que realiza cada petición. La barra de depuración web muestra el número de consultas realizadas para cada página y al pulsar sobre el pequeño icono de una base de datos, se muestra el código SQL de las consultas realizadas. Si el número de consultas crece de forma desproporcionada, seguramente es necesario utilizar una Join. Antes de explicar los métodos para Joins, se muestra lo que sucede cuando se recorre un array de objetos y se utiliza un método getter de Propel para obtener los detalles de la clase relacionada, como se ve en el listado 18-2. Este ejemplo supone que el esquema describe una tabla llamada articulo con una clave externa relacionada con la tabla autor. Listado 18-2 - Obteniendo los detalles de una clase relacionada dentro de un bucle // En la acción $this->articulos = ArticuloPeer::doSelect(new Criteria()); // Consulta realizada en la base de datos por doSelect() SELECT articulo.id, articulo.titulo, articulo.autor_id, ... FROM articulo // En la plantilla <ul> <?php foreach ($articulos as $articulo): ?> <li><?php echo $articulo->getTitulo() ?>, escrito por <?php echo $articulo->getAutor()->getNombre() ?></li> <?php endforeach; ?> </ul> Si el array $articulos contiene 10 objetos, el método getAutor() se llama 10 veces, lo que implica una consulta con la base de datos cada vez que se tiene que crear un objeto de tipo Autor, como se muestra en el listado 18-3. Listado 18-3 - Los métodos getter de las claves externas, implican una consulta a la base de datos // En la plantilla $articulo->getAutor() // Consulta a la base de datos producida por getAutor() www.librosweb.es 399
  • 400. Symfony, la guía definitiva Capítulo 18. Rendimiento SELECT autor.id, autor.nombre, ... FROM autor WHERE autor.id = ? // ? es articulo.autor_id Por tanto, la página que genera el listado 18-2 implica 11 consultas a la base de datos: una consulta para construir el array de objetos Articulo y otras 10 consultas para obte- ner el objeto Autor asociado a cada objeto anterior. Evidentemente, se trata de un nú- mero de consultas muy grande para mostrar simplemente un listado de los artículos dis- ponibles y sus autores. Si se utiliza directamente SQL, es muy fácil reducir el número de consultas a solamente 1, obteniendo las columnas de la tabla articulo y las de la tabla autor mediante una úni- ca consulta. Esto es exactamente lo que hace el método doSelectJoinAutor() de la clase ArticuloPeer. Este método realiza una consulta más compleja que un simple doSelect(), y las columnas adicionales que están presentes en el resultado obtenido permiten a Pro- pel “hidratar” tanto los objetos de tipo Articulo como los objetos de tipo Autor. El código del listado 18-4 produce el mismo resultado que el del listado 18-2, pero solamente req- uiere 1 consulta con la base de datos en vez de 11 consultas, por lo que es mucho más rápido. Listado 18-4 - Obteniendo los detalles de los artículos y sus autores en la misma consulta // En la acción $this->articulos = ArticuloPeer::doSelectJoinAutor(new Criteria()); // Consulta a la base de datos realizada por doSelectJoinAutor() SELECT articulo.id, articulo.titulo, articulo.autor_id, ... autor.id, autor.name, ... FROM articulo, autor WHERE articulo.autor_id = autor.id // En la plantilla no hay cambios <ul> <?php foreach ($articulos as $articulo): ?> <li><?php echo $articulo->getTitulo() ?>, escrito por <?php echo $articulo->getAutor()->getNombre() ?></li> <?php endforeach; ?> </ul> No existen diferencias entre el resultado devuelto por doSelect() y el resultado devuelto por doSelectJoinXXX(); los dos métodos devuelven el mismo array de objetos (de tipo Articulo en este ejemplo). La diferencia se hace evidente cuando se utiliza un método getter asociado con una clave externa. En el caso del método doSelect(), se realiza una consulta a la base de datos y se crea un nuevo objeto con el resultado; en el caso del método doSelectJoinXXX(), el objeto asociado ya existe y no se realiza la consulta con la base de datos, por lo que el proceso es mucho más rápido. Por tanto, si se sabe de ante- mano que se van a utilizar los objetos relacionados, se debe utilizar el método doSe- lectJoinXXX() para reducir el número de consultas a la base de datos y por tanto, para mejorar el rendimiento de la página. www.librosweb.es 400
  • 401. Symfony, la guía definitiva Capítulo 18. Rendimiento El método doSelectJoinAutor() se genera automáticamente cuando se ejecuta la tarea propel-build-model, debido a la relación entre las tablas articulo y autor. Si existen otras claves externas en la tabla del artículo, por ejemplo una tabla de categorías, la cla- se BaseArticuloPeer generada contendría otros métodos Join, como se muestra en el lis- tado 18-5. Listado 18-5 - Ejemplo de métodos doSelect disponibles para una clase ArticuloPeer // Obtiene objetos "Articulo" doSelect() // Obtiene objetos "Articulo" y crea los objetos "Autor" relacionados doSelectJoinAutor() // Obtiene objetos "Articulo" y crea los objetos "Categoria" relacionados doSelectJoinCategoria() // Obtiene objetos "Articulo" y crea todos los objetos relacionados salvo "Autor" doSelectJoinAllExceptAutor() // Obtiene objetos "Articulo" y crea todos los objetos relacionados doSelectJoinAll() Las clases peer también disponen de métodos Join para doCount(). Las clases que sopor- tan la internacionalización (ver Capítulo 13) disponen de un método doSelectWithI18n(), que se comporta igual que los métodos Join, pero con los objetos de tipo i18n. Para des- cubrir todos los métodos de tipo Join generados para las clases del modelo, es conven- iente inspeccionar las clases peer generadas en el directorio lib/model/om/. Si no se enc- uentra el método Join necesario para una consulta (por ejemplo no se crean automática- mente los métodos Join para las relaciones muchos-a-muchos), se puede crear un méto- do propio que extienda el modelo. SUGERENCIA Evidentemente, la llamada al método doSelectJoinXXX() es un poco más lenta que la llamada a un método simple doSelect(), por lo que solamente mejora el rendimiento global de la página si se utilizan los objetos relacionados. 18.2.4. Evitar el uso de arrays temporales Cuando se utiliza Propel, los objetos creados ya contienen todos los datos, por lo que no es necesario crear un array temporal de datos para la plantilla. Los programadores que no están acostumbrados a trabajar con ORM suelen caer en este error. Estos programa- dores suelen preparar un array de cadenas de texto o de números para las plantillas, mientras que, en realidad, las plantillas pueden trabajar directamente con los arrays de objetos. Si la plantilla por ejemplo muestra la lista de títulos de todos los artículos de la base de datos, un programador que no está acostumbrado a trabajar de esta forma pue- de crear un código similar al del listado 18-6. www.librosweb.es 401
  • 402. Symfony, la guía definitiva Capítulo 18. Rendimiento Listado 18-6 - Crear un array temporal en la acción es inútil si ya se dispone de un array de objetos // En la acción $articulos = ArticuloPeer::doSelect(new Criteria()); $titulos = array(); foreach ($articulos as $articulo) { $titulos[] = $articulo->getTitulo(); } $this->titulos = $titulos; // En la plantilla <ul> <?php foreach ($titulos as $titulo): ?> <li><?php echo $titulo ?></li> <?php endforeach; ?> </ul> El problema del código anterior es que el proceso de creación de objetos del método doSelect() hace que crear el array $titulos sea inútil, ya que el mismo código se puede reescribir como muestra el listado 18-7. De esta forma, el tiempo que se pierde creando el array $titulos se puede aprovechar para mejorar el rendimiento de la aplicación. Listado 18-7 - Utilizando el array de objetos, no es necesario crear un array temporal // En la acción $this->articulos = ArticuloPeer::doSelect(new Criteria()); // En la plantilla <ul> <?php foreach ($articulos as $articulo): ?> <li><?php echo $articulo->getTitulo() ?></li> <?php endforeach; ?> </ul> Si realmente es necesario crear un array temporal porque se realiza cierto procesamiento con los objetos, la mejor solución es la de crear un nuevo método en la clase del modelo que devuelva directamente ese array. Si por ejemplo se necesita un array con los títulos de los artículos y el número de comentarios de cada artículo, la acción y la plantilla de- berían ser similares a las del listado 18-8. Listado 18-8 - Creando un método propio para preparar un array temporal // En la acción $this->articulos = ArticuloPeer::getArticuloTitulosConNumeroComentarios(); // En la plantilla <ul> <?php foreach ($articulos as $articulo): ?> <li><?php echo $articulo[0] ?> (<?php echo $articulo[1] ?> comentarios)</li> <?php endforeach; ?> </ul> www.librosweb.es 402
  • 403. Symfony, la guía definitiva Capítulo 18. Rendimiento Solamente falta crear un método getArticuloTitulosConNumeroComentarios() muy rápido en el modelo, que se puede crear saltándose por completo el ORM y todas las capas de abstracción de bases de datos. 18.2.5. Saltándose el ORM Cuando no se quieren utilizar los objetos completos, sino que solamente son necesarias algunas columnas de cada tabla (como en el ejemplo anterior) se pueden crear métodos específicos en el modelo que se salten por completo la capa del ORM. Se puede utilizar por ejemplo Creole para acceder directamente a la base de datos y devolver un array con un formato propio, como se muestra en el listado 18-9. Listado 18-9 - Accediendo directamente con Creole para optimizar los métodos del modelo, en lib/model/ArticuloPeer.php class ArticuloPeer extends BaseArticuloPeer { public static function getArticuloTitulosConNumeroComentarios() { $conexion = Propel::getConnection(); $consulta = 'SELECT %s as titulo, COUNT(%s) AS num_comentarios FROM %s LEFT JOIN %s ON %s = %s GROUP BY %s'; $consulta = sprintf($consulta, ArticuloPeer::TITULO, ComentarioPeer::ID, ArticuloPeer::TABLE_NAME, ComentarioPeer::TABLE_NAME, ArticuloPeer::ID, ComentarioPeer::ARTICULO_ID, ArticuloPeer::ID ); $sentencia = $conexion->prepareStatement($consulta); $resultset = $sentencia->executeQuery(); $resultados = array(); while ($resultset->next()) { $resultados[] = array($resultset->getString('titulo'), $resultset->getInt('num_comentarios')); } return $resultados; } } Si se crean muchos métodos de este tipo, se puede acabar creando un método específico para cada acción, perdiendo la ventaja de la separación en capas y la abstracción de la base de datos. SUGERENCIA Si Propel no es adecuado para la capa del modelo de algún proyecto, es mejor considerar el uso de otros ORM antes de escribir todas las consultas a mano. El plugin sfDoctrine proporciona una in- terfaz para el ORM PhpDoctrine. Además, se puede utilizar otra capa de abstracción de bases de datos en vez de Creole. Desde la versión PHP 5.1, se encuentra incluida en PHP la capa de abs- tracción PDO, que ofrece una alternativa mucho más rápida que Creole. www.librosweb.es 403
  • 404. Symfony, la guía definitiva Capítulo 18. Rendimiento 18.2.6. Optimizando la base de datos Existen numerosas técnicas para optimizar la base de datos y que pueden ser aplicadas independientemente de Symfony. En esta sección, se repasan brevemente algunas de las estrategias más utilizadas, aunque es necesario un buen conocimiento de motores de ba- ses de datos para optimizar la capa del modelo. SUGERENCIA Recuerda que la barra de depuración web muestra el tiempo de ejecución de cada consulta realiza- da por la página, por lo que cada cambio que se realice debería comprobarse para ver si realmente reduce el tiempo de ejecución. A menudo, las consultas a las bases de datos se realizan sobre columnas que no son cla- ves primarias. Para aumentar la velocidad de ejecución de esas consultas, se deben crear índices en el esquema de la base de datos. Para añadir un índice a una columna, se aña- de la propiedad index: true a la definición de la columna, tal y como muestra el listado 18-10. Listado 18-10 - Añadiendo un índice a una sola columna, en config/schema.yml propel: articulo: id: autor_id: titulo: { type: varchar(100), index: true } Se puede utilizar de forma alternativa el valor index: unique para definir un índice único en vez de un índice normal. El archivo schema.yml también permite definir índices sobre varias columnas (el Capítulo 8 contiene más información sobre la sintaxis de los índices). El uso de índices es muy recomendable, ya que es una buena form