Las novedades de Symfony 1.3 y 1.4

Este tutorial es una rápida introducción técnica de Symfony 1.3 y 1.4. Está pensada para los programadores que ya conocen Symfony 1.2 y quieren aprender lo más rápido posible todas las novedades de Symfony 1.3 y 1.4.

En primer lugar, Symfony 1.3 es compatible con PHP 5.2.4 o superior. Si quieres actualizar un proyecto desarrollado con Symfony 1.2, puedes leer la guía de actualización donde se explica detalladamente cómo actualizar tus proyectos de forma segura a Symfony 1.3.

Mailer

Symfony 1.3/1.4 incluyen un nuevo mailer basado en la librería SwiftMailer 4.1. Enviar un email es tan sencillo como utilizar el método composeAndSend() en una acción:

$this->getMailer()->composeAndSend('remitente@ejemplo.com', 'destinatario@ejemplo.com', 'Asunto', 'Contenido');

Si necesitas más flexibilidad, puedes utilizar el método compose() y después enviar el email. El siguiente ejemplo muestra cómo añadir un archivo adjunto en el mensaje:

$mensaje = $this->getMailer()->
  compose('remitente@ejemplo.com', 'destinatario@ejemplo.com', 'Asunto', 'Contenido')->
  attach(Swift_Attachment::fromPath('/ruta/hasta/el/archivo.zip'))
;
$this->getMailer()->send($mensaje);

Como el nuevo mailer es muy potente, te recomendamos que leas su documentación para conocer todas sus posibilidades.

Seguridad

Cuando se crea una nueva aplicación con la tarea generate:app, las opciones de seguridad ahora están activadas por defecto:

  • escaping_strategy: su valor por defecto ahora es true (se puede desactivar con la opción --escaping-strategy).
  • csrf_secret: por defecto se genera un secreto aleatorio, por lo que la protección CSRF también se activa por defecto (se puede desactivar con la opción --csrf-secret). Te recomendamos encarecidamente que modifiques el secreto generado aleatoriamente. Para ello, modifica el archivo de configuración settings.yml o utiliza la opción --csrf-secret.

Widgets

Títulos por defecto

Cuando se genera automáticamente el título de un campo del formulario a partir de su nombre, ahora no se añade el sufijo _id:

  • nombre_completo => Nombre Completo (como siempre)
  • autor_id => Autor (antes se generaba “Autor id”)

sfWidgetFormInputText

La clase sfWidgetFormInput ahora es abstracta. Los campos de texto se crean mediante la clase sfWidgetFormInputText. Este cambio se ha realizado para facilitar la introspección de las clases de formulario.

Widgets de internacionalización

Se han añadido los siguientes widgets:

  • sfWidgetFormI18nChoiceLanguage
  • sfWidgetFormI18nChoiceCurrency
  • sfWidgetFormI18nChoiceCountry
  • sfWidgetFormI18nChoiceTimezone

Los tres primeros reemplazan respectivamente a los siguientes widgets declarados obsoletos: sfWidgetFormI18nSelectLanguage, sfWidgetFormI18nSelectCurrency y sfWidgetFormI18nSelectCountry.

Interfaz fluída

Los widgets ahora incluyen una interfaz fluída para los siguientes métodos:

  • sfWidgetForm: setDefault(), setLabel(), setIdFormat(), setHidden()
  • sfWidget: addRequiredOption(), addOption(), setOption(), setOptions(), setAttribute(), setAttributes()
  • sfWidgetFormSchema: setDefault(), setDefaults(), addFormFormatter(), setFormFormatterName(), setNameFormat(), setLabels(), setLabel(), setHelps(), setHelp(), setParent()
  • sfWidgetFormSchemaDecorator: addFormFormatter(), setFormFormatterName(), setNameFormat(), setLabels(), setHelps(), setHelp(), setParent(), setPositions()

Validadores

sfValidatorRegex

El validador sfValidatorRegex ahora incluye una opción llamada must_match. Si el valor de esta opción es false, la expresión regular no se debe cumplir para que el valor sea considerado como válido.

La opción pattern de sfValidatorRegex ahora puede ser cualquier instancia de sfCallable que devuelva na expresión regular cuando se le invoque.

sfValidatorUrl

El validador sfValidatorUrl dispone de una nueva opción llamada protocols que permite especificar los protocolos permitidos:

$validador = new sfValidatorUrl(array('protocols' => array('http', 'https')));

Por defecto se permiten los siguientes protocolos:

  • http
  • https
  • ftp
  • ftps

sfValidatorSchemaCompare

La clase sfValidatorSchemaCompare incluye dos nuevos comparadores:

  • IDENTICAL, que es equivalente a ===
  • NOT_IDENTICAL, que es equivalente a !==

sfValidatorChoice, sfValidatorPropelChoice, sfValidatorDoctrineChoice

Los validadores sfValidatorChoice, sfValidatorPropelChoice y sfValidatorDoctrineChoice tienen dos nuevas opciones que sólo se activan si la opción multiple vale true:

  • min, el mínimo número de valores que deben ser seleccionados
  • max, el máximo número de valores que deben ser seleccionados

Validadores de internacionalización

Se ha incluido un nuevo validador:

  • sfValidatorI18nTimezone

Mensajes de error por defecto

Ahora se puede definir globalmente un mensaje de error por defecto utilizando el método sfForm::setDefaultMessage():

sfValidatorBase::setDefaultMessage('required', 'Este campo es obligatorio.');

El código anterior redefine el mensaje que muestran por defecto todos los validadores (que en este caso sería “Required.”). Los mensajes por defecto se deben definir antes de crear el primer validador, por lo que puedes definirlos por ejemplo en la clase de configuración.

Nota

Los métodos setRequiredMessage() y setInvalidMessage() se han declarado obsoletos, por lo que internamente llaman al nuevo método setDefaultMessage().

Cuando Symfony muestra un error, el mensaje utilizado se determina de la siguiente manera:

  • Symfony comprueba si al validador se le pasó un mensaje cuando se creo (mediante el segundo argumento del constructor del validador)
  • Si no existe el mensaje anterior, comprueba si se ha definido un mensaje por defecto con el método setDefaultMessage()
  • Si no existe el mensaje anterior, se muestra el mensaje por defecto definido por el propio validador (cuando se ha utilizado el método addMessage() para añadir el mensaje)

Interfaz fluída

Los validadores ahora incluyen una interfaz fluída para los siguientes métodos:

  • sfValidatorSchema: setPreValidator(), setPostValidator()
  • sfValidatorErrorSchema: addError(), addErrors()
  • sfValidatorBase: addMessage(), setMessage(), setMessages(), addOption(), setOption(), setOptions(), addRequiredOption()

sfValidatorFile

Si la opción file_uploads se ha deshabilitado en el archivo de configuración php.ini, se lanza una excepción al crear una instancia de sfValidatorFile.

Formularios

sfForm::useFields()

El nuevo método sfForm::useFields() elimina todos los campos del formulario salvo los que se hayan indicado como argumento del método y salvo los campos de tipo oculto. En algunas ocasiones es más fácil indicar de forma explícita los campos que quieres mostrar en un formulario en vez de quitar todos los campos que no quieres mostrar. Si se añaden por ejemplo nuevos campos a un formulario base, no se mostrarán en el formulario hasta que se añadan de forma explícita (este comportamiento es como si el formulario fuese un modelo en el que se añade una nueva columna en la tabla asociada).

class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    $this->useFields(array('title', 'content'));
  }
}

El array de campos de formulario también se utiliza para determinar el orden en el que se muestran. Si quieres deshabilitar esta ordenación automática, puedes pasar un valor falsecomo segundo argumento del método useFields().

sfForm::getEmbeddedForm($nombre)

Ahora se puede acceder a un formulario embebido concreto utilizando el método ->getEmbeddedForm().

sfForm::renderHiddenFields()

El método ->renderHiddenFields() ahora obtiene todos los campos ocultos de los formularios embebidos. También se ha definido un argumento para evitar la recursión por todos los formularios, lo que es útil cuando utilizas un formateador propio para mostrar los formularios embebebidos.

// muestra todos los campos ocultos, incluyendo los de los formularios embebidos
echo $form->renderHiddenFields();
 
// muestra los campos ocultos sin aplicar la recursión
echo $form->renderHiddenFields(false);

sfFormSymfony

La nueva clase sfFormSymfony permite la notificación de eventos por parte de los formularios de Symfony. Para obtener el despachador de eventos (“event dispatcher”) dentro de una clase de formulario, puedes utilizar self::$dispatcher. Symfony notifica los siguientes eventos de formulario:

  • form.post_configure: este evento se notifica después de configurar cada formulario
  • form.filter_values: este evento se notifica cuando se combinan los valores y los arrays de archivos enviados por los usuarios justo antes de asociar los datos con el formulario
  • form.validation_error: este evento se notifica siempre que falla la validación del formulario
  • form.method_not_found: este evento se notifica siempre que se invoca un método desconocido

BaseForm

Todos los proyectos nuevos de Symfony 1.3 y 1.4 incluyen una clase BaseForm que se puede utilizar para extender el componente Form añadiéndole funcionalidades específicas para el proyecto. Los formularios generados automáticamente por sfDoctrinePlugin y sfPropelPlugin también heredan de esta clase. Si creas clases de formulario a mano, deberían heredar de BaseForm en lugar de sfForm.

sfForm::doBind()

La limpieza de los parámetros enviados por el usuario ahora se realiza en un método aislado y fácil de acceder por parte del programador llamado ->doBind(), que recibe el array de parámetros y archivos del método ->bind().

sfForm(Doctrine|Propel)::doUpdateObject()

Las clases de formulario de Doctrine y Propel ahora incluyen un método llamado ->doUpdateObject(), que recibe de ->updateObject() un array con los valores que ha procesado ->processValues().

sfForm::enableLocalCSRFProtection() y sfForm::disableLocalCSRFProtection()

La protección CSRF ahora se puede configurar fácilmente desde el método configure() de las clases de formulario gracias a los nuevos métodos sfForm::enableLocalCSRFProtection() y sfForm::disableLocalCSRFProtection().

Si quieres deshabilitar la protección CSRF en un formulario, añade la siguiente línea en su método configure():

$this->disableLocalCSRFProtection();

Invocar al método disableLocalCSRFProtection() deshabilita la protección CSRF, incluso aunque después se pase un secreto CSRF al crear la instancia del formulario.

Interfaz fluída

Algunos métodos de sfForm ahora implementan una interfaz fluída: addCSRFProtection(), setValidators(), setValidator(), setValidatorSchema(), setWidgets(), setWidget(), setWidgetSchema(), setOption(), setDefault() y setDefaults().

Cargadores automáticos de clases

Los cargadores automáticos de clases de Symfony ahora no distinguen mayúsculas de minúsculas, tal y como sucede en el propio código PHP.

sfAutoloadAgain (EN PRUEBAS)

Se ha añadido un nuevo cargador automático de clases que sólo está disponible en el modo debug. La nueva clase sfAutoloadAgain recarga el autoloader normal de Symfony y busca la clase específica en el sistema de archivos. El resultado final es que ya no tienes que ejecutar el comando symfony cc después de añadir una nueva clase en el proyecto.

Pruebas unitarias y funcionales

Acelerando las pruebas

Si dispones de una gran cantidad de pruebas, resulta muy lento ejecutar todas ellas cada vez que haces un cambio en la aplicación, sobre todo cuando fallan algunas pruebas. El motivo es que cada vez que arregles una prueba que había fallado, tienes que volver a ejecutar todas las pruebas para asegurarte de que no has roto nada nuevo. No obstante, mientras no se arreglen las pruebas que fallan, no tiene sentido volver a ejecutar todas las demás pruebas. Por ello, la tarea test:all dispone de una opción llamada --only-failed (-f es el atajo) que obliga a ejecutar solamente las pruebas que fallaron la última vez:

$ php symfony test:all --only-failed

La primera vez se ejecutan todas las pruebas, pero en las siguientes veces sólo se ejecutan las pruebas que fallaron la última vez. A medida que arregles el código de la aplicación, algunas pruebas se corregirán y por tanto ya no se volverán a ejecutar. Cuando se ejecuten correctamente todas las pruebas que fallaban, ya puedes volver a ejecutar de nuevo todo el conjunto de pruebas unitarias y funcionales.

Pruebas funcionales

Cuando una petición provoca una excepción, el método debug() del tester de la respuesta muestra la excepción en forma de texto y no mediante su formato HTML habitual, por lo que se facilita la depuración de las aplicaciones.

sfTesterResponse dispone de un nuevo método llamado matches() que realiza una comprobación sobre todo el contenido de la respuesta utilizando una expresión regular. Se trata de una gran ayuda cuando la respuesta no es de tipo XML en las que no se muy útil el método checkElement(). Este nuevo método también reemplaza al viejo y limitado método contains():

$browser->with('response')->begin()->
  matches('/I have \d+ apples/')->    // como argumento se indica una expresión regular
  matches('!/I have \d+ apples/')->   // si se añade ! como prefijo, la expresión regular no se debe cumplir
  matches('!/I have \d+ apples/i')->  // también se pueden utilizar modificadores de expresiones regulares
end();

Salida XML compatible con JUnit

Las tareas de pruebas ahora disponen de una opción llamada --xml con la que pueden generar su salida en un formato XML compatible con JUnit:

$ php symfony test:all --xml=log.xml

Depuración sencilla

Para facilitar la depuración cuando se produce un error en un conjunto de pruebas, se puede hacer uso de una opción llamada --trace para que muestre detalladamente todos los errores producidos:

$ php symfony test:all -t

Uso del color en Lime

A partir de Symfony 1.3/1.4, la herramienta Lime detecta correctamente los entornos en los que puede mostrar sus mensajes con colores, por lo que prácticamente siempre puedes omitir el segundo argumento del constructor de lime_test:

$t = new lime_test(1);

sfTesterResponse::checkForm()

El tester de la respuesta ahora incluye un método para comprobar fácilmente que todos los campos del formulario se incluyen en el contenido de la respuesta:

$browser->with('response')->begin()->
  checkForm('ArticleForm')->
end();

Si lo prefieres también puedes pasar el objeto del formulario:

$browser->with('response')->begin()->
  checkForm($browser->getArticleForm())->
end();

Si la respuesta incluye varios formularios, también tienes la opción de indicar un formulario específico mediante un selector de CSS:

$browser->with('response')->begin()->
  checkForm('ArticleForm', '#articleForm')->
end();

sfTesterResponse::isValid()

Si quieres comprobar si la respuesta obtenida es un documento XML bien formado, puedes hacer uso del método ->isValid() del tester:

$browser->with('response')->begin()->
  isValid()->
end();

También puedes validar la respuesta según su tipo de documento o DTD pasando el valor true como argumento:

$browser->with('response')->begin()->
  isValid(true)->
end();

Por otra parte, si dispones de un esquema XSD o RelaxNG para validar la respuesta, debes indicar su ruta como argumento de este método:

$browser->with('response')->begin()->
  isValid('/ruta/hasta/schema.xsd')->
end();

Evento context.load_factories

Las pruebas funcionales ahora pueden definir listeners para el evento context.load_factories, algo que no era posible en las anteriores versiones de Symfony.

$browser->addListener('context.load_factories', array($browser, 'listenForNewContext'));

Método click() mejorado

El método ->click() ahora acepta cualquier selector CSS, haciendo que sea mucho más fácil seleccionar un elemento de forma semántica.

$browser
  ->get('/login')
  ->click('form[action$="/login"] input[type="submit"]')
;