Actualización de Symfony 1.1 a Symfony 1.2

(Traducción de la guía oficial de actualización de Symfony 1.1 a Symfony 1.2)

Este documento describe los cambios introducidos por Symfony 1.2 y detalla los pasos necesarios para actualizar los proyectos desarrollados con Symfony 1.1.

AVISO: Symfony 1.2 es compatible con las versiones de PHP superiores a 5.2.4, aunque también podría funcionar con PHP 5.2.0 o 5.2.3.

¿Cómo actualizar los proyectos?

Para actualizar un proyecto realizado con Symfony 1.1 de modo que sea compatible con Symfony 1.2, debes realizar los siguientes pasos:

1) Si no utilizas una herramienta de gestión del código fuente (SCM) tipo Subversion, Perforce o Visual Source Safe, deberías realizar una copia de seguridad de todo tu proyecto.

2) Entra en el directorio de tu proyecto y ejecuta la tarea project:upgrade1.2 para realizar la actualización automática:

$ php symfony project:upgrade1.2

Esta tarea se puede ejecutar varias veces sobre un mismo proyecto sin consecuencias negativas. La tarea de actualización tienes que ejecutarla en cada proyecto que quieras actualizar a la versión Symfony 1.2 beta o final.

3) El último paso consiste en reconstruir todas las clases del modelo y todos los formularios debido a los cambios que se describen más adelante:

$ php symfony propel:build-model
$ php symfony propel:build-forms
$ php symfony propel:build-filters

4) Borra la cache:

$ php symfony cc

Las siguientes secciones de este artículo describen los principales cambios de Symfony 1.2 que requieren una actualización automática o manual.

Propel

Propel se ha actualizado hasta la versión 1.3, lo que implica la sustitución de Creole por PDO.

Como Creole ya no se utiliza, las siguientes clases ya no existen:

Nombre de la clase Clase equivalente
sfCreoleDatabase sfPropelDatabase
sfDebugConnection DebugPDO
sfMessageSource_Creole sfMessageSource_PDO
sfCreoleSessionStorage sfPDOSessionStorage

La tarea propel:build-db también se ha eliminado porque Propel 1.3 todavía no proporciona esta funcionalidad.

El primer paso para actualizar Propel consiste en modificar el archivo de configuración databases.yml para utilizar la nueva sintaxis de PDO.

El contenido actual de tu archivo databases.yml será similar a:

all:
  propel:
    class:      sfPropelDatabase
    param:
      dsn:      mysql://username:password@localhost/ejemplo

Debes sustituir la configuración anterior por la siguiente:

dev:
  propel:
    param:
      classname: DebugPDO

test:
  propel:
    param:
      classname:  DebugPDO

all:
  propel:
    class: sfPropelDatabase
    param:
      dsn:        mysql:dbname=ejemplo;host=localhost
      username:   username
      password:   password
      encoding:   utf8
      persistent: true
      pooling:    true
      classname:  PropelPDO

A continuación, actualiza el archivo propel.ini con la nueva DSN en formato PDO y con las opciones de configuración actualizadas.

Localiza las siguientes líneas en tu archivo propel.ini:

[ini]
propel.database            = mysql
propel.database.createUrl  = mysql://username:password@localhost/
propel.database.url        = mysql://username:password@localhost/ejemplo

Sustituye las líneas anteriores por las siguientes:

[ini]
propel.database            = mysql
propel.database.driver     = mysql
propel.database.url        = mysql:dbname=ejemplo;host=localhost
propel.database.user       = username
propel.database.password   = password
propel.database.encoding   = utf8

Como la API interna de Propel ha cambiado bastante, debes volver a generar todas las clases del modelo:

$ php symfony propel:build-model

En la mayoría de los proyectos, los cambios anteriores son todo lo que hay que hacer para actualizar Propel. No obstante, si has personalizado las clases del modelo, es probable que tengas que actualizarlas para incluir los cambios en la API producidos por pasar de Creole a PDO. La tarea de actualización automática (project:upgrade1.2) intenta modificar las declaraciones de las funciones para cumplir con la interfaz Persistent añadiendo la información necesaria para el //type hinting// de PropelPDO en ->save($con = null) y ->delete($con = null).

Modifica todas las instancias de:

public function save($con = null)
public function delete($con = null)

Para añadir el //type hinting// de PropelPDO:

public function save(PropelPDO $con = null)
public function delete(PropelPDO $con = null)

La API de las transacciones ha cambiado ligeramente, ya que ->begin ahora se llama ->beginTransaction() y ->rollback() ahora se llama ->rollBack(). El siguiente código muestra las diferencias:

Creole:

$con->begin();
try {
  /* db logic */
  $con->commit();
} catch (SQLException $sqle) {
  $con->rollback();
  throw $sqle;
}

PDO:

$con->beginTransaction();
try {
  /* db logic */
  $con->commit();
} catch (PDOException $sqle) {
  $con->rollBack();
  throw $sqle;
}

El método ::doSelectRS ahora se llama ::doSelectStmt. El siguiente código muestra las diferencias:

Creole:

// cómo hidratar manualmente los objetos
$rs = AuthorPeer::doSelectRS(new Criteria());
while($rs->next()) {
  $a = new Author();
  $a->hydrate($rs);
}

// cómo crear un array de una sola columna
$rs = AuthorPeer::doSelectRS(new Criteria());
$names = array();
while($rs->next()) {
  $names[] = $rs->getString(2);
}

$con = Propel::getConnection(SomeTablePeer::DATABASE_NAME);
$stmt = $con->prepareStatement("SELECT * FROM some_table WHERE name = ?");
$stmt->setString(1, $name);
$rs = $stmt->executeQuery();
while($rs->next()) {
   print "Name: " . $rs->getString("name") . "\n";
}

PDO:

// cómo hidratar manualmente los objetos
$stmt = AuthorPeer::doSelectStmt(new Criteria());
while($row = $stmt->fetch(PDO::FETCH_NUM)) {
  $a = new Author();
  $a->hydrate($row);
}

// cómo crear un array de una sola columna
$stmt = AuthorPeer::doSelectStmt(new Criteria());
$names = array();
while($res = $stmt->fetchColumn(1)) {
  $names[] = $res;
}

$con = Propel::getConnection(SomeTablePeer::DATABASE_NAME);
$stmt = $con->prepare("SELECT * FROM some_table WHERE name = ?");
$stmt->bindValue(1, $name);
$stmt->execute();
while($row = $stmt->fetch()) {
   print "Name: " . $row['name'] . "\n";
}

La documentación oficial de Propel incluye más información sobre los cambios introducidos por Propel 1.3.

Además, todos los archivos de Propel se han pasado de lib/propel a lib. La tarea de actualización modifica el archivo de configuración propel.ini para tener en cuenta todos estos cambios.

Peticiones

Las opciones de configuración path_info_array, path_info_key y relative_url_root se han movido del archivo settings.yml al archivo factories.yml (se han colocado dentro de la sección param de la configuración de la factoría request).

Este cambio ha permitido eliminar la dependencia entre sfRequest y sfConfig.

Estas tres opciones de la petición ahora se pasan a su constructor como cuarto argumento. Los formatos también se pasan como una opción, en vez de pasarlos como atributos.

Las constantes de sfRequest referidas a los métodos de la petición ahora son cadenas de texto en vez de valores numéricos y el método sfRequest::NONE se ha eliminado:

Constante Valor anterior Nuevo valor
GET 2 GET
POST 4 POST
PUT 5 PUT
DELETE 6 DELETE
HEAD 7 HEAD
NONE 1 -

Los métodos getMethod() y getMethodName() ahora devuelven el mismo valor, por lo que getMethodName() se ha declarado obsoleto.

Se ha eliminado el método sfAction::getMethodNames() y su código relacionado en sfValidationExecutionFilter del plugin sfCompat10Plugin. Este método se declaró como obsoleto en Symfony 1.1 y no era muy útil en Symfony 1.0.

Validadores

Los valores de la constante sfValidatorSchemaCompare se han modificado. Aunque no es necesario ningún cambio en tu código, esta modificación permite utilizar atajos muy interesantes. Los siguientes dos ejemplos son equivalentes:

// symfony 1.1 y 1.2
$v = new sfValidatorSchemaCompare('left', sfValidatorSchemaCompare::EQUAL, 'right');

// sólo en symfony 1.2
$v = new sfValidatorSchemaCompare('left', '==', 'right');

En Symfony 1.1, los validadores sfValidatorI18nChoiceCountry y sfValidatorI18nChoiceLanguage requerían una opción llamada culture. Como estos validadores no utilizan la cultura, esta opción culture se ha declarado obsoleta. No obstante, la opción no se ha eliminado para mantener la compatibilidad con las versiones anteriores de Symfony.

Además, se han añadido dos nuevos métodos a la clase sfValidatorBase que permiten establecer los mensajes por defecto de los errores required y invalid:

sfValidatorBase::setRequiredMessage('este dato es obligatorio');
sfValidatorBase::setInvalidMessage('este dato no es válido');

Forms

En Symfony 1.1, el archivo BaseFormPropel se generaba en un directorio erróneo (lib/form/base/). Por tanto, es necesario que lo muevas al directorio lib/form/.

Widgets

Los formularios Propel generados automáticamente ahora utilizan por defecto el widget sfWidgetFormChoice en vez de sfWidgetFormSelect. Para utilizar estos widgets avanzados, es necesario que generes de nuevo todos los formularios:

$ php symfony propel:build-forms

Respuesta

La factoría response dispone de una nueva opción llamada send_http_headers. Esta opción vale true por defecto, salvo en el entorno de ejecución test, donde PHP no debe enviar las cabeceras (en Symfony 1.1 este mismo comportamiento se conseguía mediante la opción sf_test).

Este cambio ha permitido eliminar la dependencia entre sfResponse y sfConfig.

Los métodos getStylesheets() y getJavascripts() ahora devuelven todas las hojas de estilos y todos los archivos de JavaScript ordenados por posición si se pasa el valor sfWebResponse::ALL como primer argumento:

$respuesta = new sfWebResponse(new sfEventDispatcher());
$respuesta->addStylesheet('foo.css');
$respuesta->addStylesheet('bar.css', 'first');

var_export($respuesta->getStylesheets());

// muestra
array(
  'bar.css' => array(),
  'foo.css' => array(),
)

El valor sfWebResponse::ALL ahora también es el valor por defecto para el argumento de la posición. Como en Symfony 1.1 el valor por defecto es una cadena de texto vacía, los métodos sólo devuelven los archivos registrados para la posición por defecto, lo que no es muy intuitivo.

En Symfony 1.1 se pueden obtener todos los archivos pasando como posición el valor ALL. Este comportamiento todavía está disponible utilizando el valor sfWebResponse::RAW:

var_export($respuesta->getStylesheets(sfWebResponse::RAW));

// muestra
array(
  'first' =>
    array(
      'bar.css' => array (),
    ),
  '' =>
    array(
    'foo.css' => array(),
    ),
  'last' => array(),
)

Además, ahora todas las posiciones (first, (posición vacía) y last) están disponibles como constantes:

sfWebResponse::FIRST  === 'first'
sfWebResponse::MIDDLE === ''
sfWebResponse::LAST   === 'last'

Los métodos removeStylesheet() y removeJavascript() ahora sólo requieren un argumento, que es el archivo que se debe eliminar de la respuesta. El archivo se elimina en todas las posiciones disponibles. En Symfony 1.1 la posición se indicaba como segundo argumento.

Prototype y Scriptaculous

Symfony continúa eliminando sus dependencias con el software externo que incluye. En Symfony 1.2, las librerías Prototype y Scriptaculous y sus helpers asociados (JavascriptHelper) se incluyen en forma de plugin incluido por defecto, también llamado core plugin. Estos plugins incluidos por defecto se comportan como cualquier otro plugin de Symfony, siendo su única diferencia que no hay que descargarlos. De esta forma, los archivos CSS y JavaScript del plugin sfProtoculousPlugin (así es como se ha decidido llamar a la combinación de Prototype y Scriptaculous) se comportan igual que los archivos de cualquier otro plugin. Por tanto, estos archivos ahora se encuentran en web/sfProtoculousPlugin en vez de web/sf (como sucedía en Symfony 1.0 y 1.1). La opción prototype_web_dir ahora también apunta a ese directorio.

Además, se ha creado el grupo de helpers JavascriptBaseHelper, que incluye unos helpers de JavaScript muy básicos que funcionan con cualquier framework de JavaScript y que por tanto, se incluye dentro del núcleo del framework.

Por último, javascript_tag() ahora se comporta como un slot(), lo que permite crear código como el siguiente:

<?php javascript_tag() ?>
alert('All is good')
<?php end_javascript_tag() ?>

Nota: los plugins normalmente se instalan mediante una tarea y esta tarea invoca la creación de un enlace simbólico en el directorio web. Como este enlace también se requiere para los plugins incluidos por defecto, debes crear este enlace manualmente mediante la tarea:

symfony plugin:publish-assets --only-core

Aunque todavía no está terminado, se está trabajando para que la tarea anterior se ejecute automáticamente cuando se crea un proyecto y cuando se realiza la actualización de un proyecto.

Navegador

Las clases sfBrowser y sfTestBrowser se han refactorizado en cuatro clases:

  • sfBrowserBase: la clase base del navegador. No está directamente relacionada con Symfony, aunque utiliza algunas clases de lo que se conoce como “plataforma Symfony”.
  • sfBrowser: hereda de sfBrowserBase e incluye los métodos específicos de Symfony.
  • sfTestFunctionalBase: la clase base de las pruebas funcionales. Implementa métodos para pruebas independientes de Symfony.
  • sfTestFunctional: hereda de sfTestFunctionalBase e implementa los métodos para pruebas específicas de Symfony.
  • sfTestBrowser: una clase que sólo existe para mantener la compatibilidad con las versiones anteriores de Symfony. Esta clase es la misma que sfTestFunctional, pero tiene un constructor compatible con el que se utilizaba en Symfony 1.1

El motivo por el que se han refactorizado las clases es que ahora sfTestFunctional es una clase sólo para pruebas, no un navegador. Por tanto, esta clase utiliza como argumentos un navegador y un objeto para pruebas.

$testBrowser = new sfTestBrowser('localhost');
$tester = new sfTestFunctional(new sfBrowser('localhost'), new lime_test());

La clase sfTestFunctional actúa como un intermediario de la clase del navegador, lo que significa que todos los métodos del navegador son accesibles desde el objeto utilizado para la prueba funcional.

Esta refactorización no provoca ninguna incompatibilidad con Symfony 1.1.

Las clases del navegador ahora añaden la cabecera HTTP_REFERER en todas las peticiones.

Pruebas

Testers

La clase sfTestFunctionalBase ahora delega la ejecución de las pruebas a las clases de tipo sfTester, de las que Symfony 1.2 incluye las siguientes:

  • request: sfTesterRequest
  • response: sfTesterResponse
  • user: sfTesterUser
  • view_cache: sfTesterViewCache

Todos los métodos disponibles anteriormente en el navegador de pruebas se han trasladado a alguna clase de tipo tester:

Nombre del método Clase tipo tester Nombre del método nuevo
isRequestParameter sfTesterRequest isParameter
isRequestFormat sfTesterRequest isFormat
isStatusCode sfTesterResponse isStatusCode
responseContains sfTesterResponse contains
isResponseHeader sfTesterResponse isHeader
checkResponseElement sfTesterResponse checkElement
isUserCulture sfTesterUser isCulture
isCached sfTesterViewCache isCached
isUriCached sfTesterViewCache isUriCached

Las clases tester también disponen de los siguientes métodos nuevos:

Clase tester Nombre del nuevo método
sfTesterRequest hasCookie
sfTesterRequest isCookie
sfTesterRequest isMethod
sfTesterUser isAuthenticated
sfTesterUser hasCredential
sfTesterUser isAttribute
sfTesterUser isFlash

Aunque los métodos viejos se han declarado obsoletos, no muestran ningún mensaje de aviso y no se han eliminado para mantener la compatibilidad con las versiones 1.0 y 1.1 de Symfony.

Enlaces

Cuando se simula la pulsación de un botón o de un enlace, se utiliza su nombre con el método click(). El problema es que no se puede diferenciar dos enlaces o dos botones con el mismo nombre.

A partir de la versión 1.2 de Symfony, el método click() acepta un tercer argumento con el que se pueden pasar algunas opciones.

Se puede pasar por ejemplo una opción llamada position para seleccionar la posición del enlace que se quiere pinchar:

$b->
  click('/', array(), array('position' => 1))->
  // ...
;

Por defecto Symfony siempre pincha sobre el primer enlace que encuentra en la página.

También se puede pasar una opción llamada method para modificar el método del enlace o formulario que se está pulsando:

$b->
  click('/delete', array(), array('method' => 'delete'))->
  // ...
;

Esta última opción es muy útil cuando un enlace se convierte mediante JavaScript en un formulario generado dinámicamente.

Acciones

Por defecto, cuando se utilizan los métodos redirectIf() o redirectUnless() en las acciones, Symfony modifica automáticamente el código de estado de HTTP a 302.

En Symfony 1.2 estos dos métodos disponen de un argumento opcional que permite cambiar este código de estado de HTTP::

$this->redirectIf($condition, '@homepage', 301);
$this->redirectUnless($condition, '@homepage', 301);

El método redirect() ya disponía de esta característica.

sfParameterHolder

El método has() de sfParameterHolder se ha modificado para ser más correcto semánticamente. Ahora este método devuelve true incluso cuando el valor es null:

$ph = new sfParameterHolder();
$ph->set('variable1', 'valor');
$ph->set('variable2', null);

$ph->has('variable1') === true;
$ph->has('variable2') === true; // en Symfony 1.0 y 1.1 devuelve false

Recuerda que el método sfParameterHolder::has() lo utilizan hasParameter() y hasAttribute(), que a su vez están disponibles en numerosas clases del núcleo de Symfony.

Tareas

Las tareas Propel que utilizan Phing ahora muestran un mensaje de error muy visible cuando la tarea de Phing produce un error.

Algunas tareas toman como argumento el nombre de la aplicación, ya que requieren conectarse con la base de datos. El nombre de la aplicación es imprescindible porque la configuración se puede redefinir en cada aplicación y todo el sistema de configuración de Symfony se basa en el nivel de la aplicación.

Este argumento se ha eliminado en todas esas tareas y ahora se utiliza una opción llamada application. Si no indicas la opción application, Symfony utiliza la configuración de la base de datos existente en el archivo databases.yml del proyecto.

La declaración de las siguientes tareas se ha modificado para cumplir con todo lo anterior:

  • propel:build-all-load
  • propel:data-dump
  • propel:data-load

Nota: este comportamiento es posible porque sfDatabaseManager ahora utiliza la configuración de un proyecto o de una aplicación. Para los más técnicos y curiosos, este funcionamiento es debido a que sfDatabaseConfigHandler ahora devuelve una configuración estática o dinámica en función de un array de archivos de configuración (ver los métodos execute() y evaluate()).

Como ya se sabe, la tarea propel:insert-sql elimina todos los datos existentes en la base de datos utilizada. Como es una tarea que destruye información, ahora solicita al usuario la confirmación de si realmente quiere ejecutar la tarea. Esta confirmación también la solicitan las tareas propel:build-all y propel:build-all-load, ya que las dos invocan a la tarea propel:insert-sql.

Si vas a utilizar la tarea en un script, puedes evitar esta confirmación utilizando la opción no-confirmation:

$ php symfony propel:insert-sql --no-confirmation

En las versiones anteriores a Symfony 1.2, la tarea propel:insert-sql era la única que obtenía la configuración de la base de datos en el archivo propel.ini. Desde Symfony 1.2, esta tarea obtiene la configuración en el archivo databases.yml. De esta forma, si utilizas diferentes conexiones en tu modelo, esta tarea las tiene en cuenta. Además, gracias a este nuevo comportamiento puedes utilizar la opción --connection si sólo quieres cargar las sentencias SQL de una conexión concreta:

$ php symfony propel:insert-sql --connection=propel

También puedes utilizar las opciones --env y --application para seleccionar la configuración concreta que se utiliza:

$ php symfony propel:insert-sql --env=prod --application=frontend

La tarea propel:generate-crud ha cambiado su nombre por propel:generate-module, aunque el nombre anterior todavía se puede utilizar en forma de alias.

Además, la opción non-atomic-actions de la tarea propel:generate-module se ha eliminado y se han añadido las siguientes nuevas opciones:

  • singular: el nombre en singular de las acciones y plantillas
  • plural: el nombre en plural de las acciones y plantillas
  • route-prefix: el prefijo que utilizan las rutas
  • with-propel-route: si se deben generar rutas de Propel

Para facilitar la depuración, las tareas propel:build-model, propel:build-all y propel:build-all-load no eliminan los esquemas XML si se les pasa la opción --trace.

Helpers para URL

La opción post del helper link_to se ha declarado obsoleta, aunque todavía se puede utilizar:

// la siguiente instrucción es obsoleta
<?php echo link_to('@some_route', array('post' => true)) ?>

// la siguiente instrucción es equivalente a la anterior
<?php echo link_to('@some_route', array('method' => 'post')) ?>

Los helpers url_for() y link_to() también se pueden utilizar de otra forma. Además de la URI interna completa, ahora también pueden tomar como argumentos el nombre de la ruta y un array de parámetros:

echo url_for('@article', array('id' => 1));
echo link_to('Link to article', '@article', array('id' => 1));

La forma anterior de utilizar estos dos helpers todavía sigue funcionando, por lo que no debes realizar ningún cambio en tu código.

Helper de las imágenes

En Symfony 1.0 y 1.1, el helper image_tag genera el atributo alt de la etiqueta <img /> a partir del nombre del archivo de la imagen. En Symfony 1.2, este comportamiento sólo se produce si está activada la compatibilidad con las versiones anteriores (sf_compat_10 vale on). De esta forma, ahora es mucho más fácil utilizar un validador XHTML para encontrar los atributos alt vacíos. Además, existe una opción llamada alt_title que establece los atributos alt y title al mismo valor, lo que es útil para mostrar mensajes de tipo tooltip que funcionan en cualquier navegador.

Vista

Ahora se puede redefinir el método sfViewCacheManager::generateCacheKey() si se utiliza la opción sf_cache_namespace_callable. En Symfony 1.2, el elemento ejecutable se invoca con un argumento adicional que es la instancia del gestor de la caché de la vista.

Configuración

En las versiones anteriores a Symfony 1.2, se cargaban todos los plugins instalados en el directorio plugins y todos los plugins incluidos por defecto en el framework.

A partir de Symfony 1.2, es obligatorio habilitar los plugins que se quieren utilizar en cada proyecto. La activación se puede realizar en la clase ProjectConfiguration. A continuación se muestra cómo activar el plugin Doctrine y cómo desactivar el plugin de Propel:

public function setup()
{
  $this->enablePlugins('sfDoctrinePlugin');
  $this->disablePlugins('sfPropelPlugin');
}

También se pueden activar varios plugins simultáneamente pasando un array con el nombre de todos los plugins:

public function setup()
{
  $this->enablePlugins(array('sfDoctrinePlugin', 'sfGuardPlugin'));
}

También se puede modificar el orden en el que se cargan los plugins mediante el método setPlugins:

public function setup()
{
  $this->setPlugins(array('sfDoctrinePlugin', 'sfCompat10Plugin'));
}

La opción orm del archivo settings.yml se ha declarado obsoleta y ahora se establece de forma automática cuando se carga el plugin correspondiente a un ORM.

De la misma forma, la opción compat_10 del archivo settings.yml también se ha declarado obsoleta y también se establece de forma automática cuando se carga el plugin sfCompat10Plugin.

Por tanto, para activar la compatibilidad con Symfony 1.0, tienes que activarla en la configuración del proyecto:

public function setup()
{
  $this->enablePlugins('sfCompat10Plugin');
}

Symfony sólo activa por defecto el plugin Propel.

Una forma más cómoda de activar todos los plugins instalados consiste en utilizar el método enableAllPluginsExcept:

public function setup()
{
  $this->enableAllPluginsExcept('sfDoctrinePlugin');
}

El ejemplo anterior activa todos los plugins instalados menos el plugin de Doctrine. Por tanto, si estás actualizándote desde Symfony 1.0 o 1.1, el código anterior hace que Symfony se comporte igual que en las versiones 1.0 y 1.1.

La clase sfLoader se ha declarado obsoleta porque los métodos getHelperDirs() y loadHelpers() ahora son parte de la clase sfApplicationConfiguration. Si sigues utilizando los métodos de sfLoader, se genera un mensaje en el log indicando que han sido declarados obsoletos y se invocan automáticamente los métodos establecidos por la configuración actual.

Configuración de los plugins

Los plugins ahora pueden incluir una clase de configuración. Estas clases de configuración de los plugins establecen la carga automática de clases de los plugins y se instancian en sfProjectConfiguration. Esto significa que las tareas de Symfony 1.2 ya no requieren el nombre de la aplicación para las clases de los plugins. Además, esto permite que los plugins puedan acceder a los eventos de tipo command.*, lo que no era posible hasta el momento.

Internacionalización

La clase sfCultureInfo ahora sólo se utiliza internamente como un singleton. Como es posible saltarse el método getInstance() e instanciar un nuevo objeto directamente, se ha declarado como obsoleto. El rendimiento mejora ligeramente por utilizar un singleton, ya que en cada petición sólo se instancia un objeto con la información de la cultura. Además, ahora también es posible redefinir globalmente parte de la información de la cultura desde las clases de configuración.

Los métodos sfCultureInfo::getCountries(), sfCultureInfo::getCurrencies() y sfCultureInfo::getLanguages() ahora aceptan un tercer argumento que permite restringir el valor devuelto:

// sólo devuelve el nombre de Francia y España en inglés
$paises = sfCultureInfo::getInstance('en')->getCountries(array('FR', 'ES'));

El método sfCultureInfo::getCurrencies() ahora devuelve un array con el nombre de las divisas. En las versiones anteriores de Symfony, este método devolvía un array con el símbolo y el nombre de cada divisa. Para mantener el comportamiento anterior, puedes pasar el valor true como segundo argumento del método:

$divisas = sfCultureInfo::getInstance('en')->getCurrencies(null, true);

Para obtener la traducción del nombre de un único país, idioma o moneda, se pueden utilizar respectivamente los métodos getCountry(), getCurrency() y getLanguage() de la clase sfCultureInfo.

Plantillas de error para las excepciones

Symfony ahora tiene en cuenta el formato de la petición cuando muestra el mensaje de error de alguna excepción. Se puede personalizar cada formato añadiendo una plantilla en el directorio config/error del proyecto o de la aplicación.

Si por ejemplo se produce un error durante una petición XML, se muestra la plantilla config/error/exception.xml.php cuando la aplicación se ejecuta en el entorno de desarrollo y config/error/error.xml.php cuando la aplicación está en producción.

Si ya tenías creadas las plantillas personalizadas para el error 500, tienes que cambiarlas de directorio manualmente:

  • En Symfony 1.1: cambiala de config/error500.php a config/error/error.html.php
  • En symfony 1.0: cambiala de web/errors/error500.php a config/error/error.html.php