Upgrading Projects from 1.1 to 1.2 ================================== This document describes the changes made in symfony 1.2 and what need to be done to upgrade your symfony 1.1 projects. If you want more detailed information on what has been changed/added in symfony 1.2, you can read the [What's new?](http://www.symfony-project.org/tutorial/1_2/whats-new) tutorial. >**CAUTION** >symfony 1.2 is compatible with PHP 5.2.4 or later. >It might also work with PHP 5.2.0 to 5.2.3 but there is no guarantee. How to upgrade? --------------- To upgrade a project: * Check that all plugins used by your project are compatible with symfony 1.2 * If you don't use a SCM tool, please make a backup of your project. * Upgrade symfony to 1.2 * Launch the `project:upgrade1.2` task from your project directory to perform an automatic upgrade: $ php symfony project:upgrade1.2 This task can be launched several times without any side effect. Each time you upgrade to a new symfony 1.2 beta / RC or the final symfony 1.2, you have to launch this task. * Upgrade the plugins to their 1.2 version * You need to rebuild your models and forms due to some changes described below (read below how to upgrade to Propel 1.3 first): $ php symfony propel:build-model $ php symfony propel:build-forms $ php symfony propel:build-filters * Clear the cache: $ php symfony cc The remaining sections explain the main changes made in symfony 1.2 that need some kind of upgrade (automatic or not). Upgrade to 1.2 final -------------------- If you have upgraded your 1.1 project before 1.2 final, you need to check the following things: * If you have generated CRUD modules with the `--non-verbose-templates` options, you need to remove the extra `$form->renderHiddenFields()` statement in the `_form.php` template. If not, you can have erroneous "CSRF attack detected" error messages. * For all generated CRUD modules, and if you have enabled CSRF protection, you need to insert `$request->checkCSRFProtection()` at the beginning of the generated `executeDelete()` method to be protected from CSRF attacks. Propel ------ Propel has been upgraded to version 1.3, which replaces support for Creole with PDO. Due to the removal of Creole, the following classes are removed: *class name* | *equivalent* ------------------------ | ------------------------------- `sfCreoleDatabase` | `sfPropelDatabase` `sfDebugConnection` | `DebugPDO` `sfMessageSource_Creole` | `sfMessageSource_PDO` `sfCreoleSessionStorage` | `sfPDOSessionStorage` The `propel:build-db` task has been removed as this functionality is not yet provided by Propel 1.3. The first step to upgrading is changing from Creole to PDO syntax in the database configuration from the project `databases.yml` file. Locate the following: [yml] all: propel: class: sfPropelDatabase param: dsn: mysql://username:password@localhost/example Replace with the following: [yml] dev: propel: param: classname: DebugPDO test: propel: param: classname: DebugPDO all: propel: class: sfPropelDatabase param: dsn: mysql:dbname=example;host=localhost username: username password: password encoding: utf8 persistent: true pooling: true classname: PropelPDO Next, you must also upgrade the `propel.ini` with the PDO format DSN and updated configuration options. Locate the following: [ini] propel.database = mysql propel.database.createUrl = mysql://username:password@localhost/ propel.database.url = mysql://username:password@localhost/example Replace with the following: [ini] propel.database = mysql propel.database.driver = mysql propel.database.url = mysql:dbname=example;host=localhost propel.database.user = username propel.database.password = password propel.database.encoding = utf8 Since the underlying api has changed quite a bit, you need to rebuild the object model: $ php symfony propel:build-model In most cases, this will be all that is required. If you have customized object model classes, you may need to manually upgrade for the changes in API from Creole to PDO. The upgrade task will attempt to change method signatures to match the `Persistent` interface, by adding type hinting for PropelPDO in `->save($con = null)` and `->delete($con = null)`. Change instances of: [php] public function save($con = null) public function delete($con = null) To add PropelPDO type hint: [php] public function save(PropelPDO $con = null) public function delete(PropelPDO $con = null) The transaction api has change slightly: `->begin` has been renamed `->beginTransaction()` and `->rollback()` has been renamed `->rollBack()`. Here are the differences: `Creole`: [php] $con->begin(); try { /* db logic */ $con->commit(); } catch (SQLException $sqle) { $con->rollback(); throw $sqle; } `PDO`: [php] $con->beginTransaction(); try { /* db logic */ $con->commit(); } catch (PDOException $sqle) { $con->rollBack(); throw $sqle; } The `::doSelectRS` method has been renamed to `::doSelectStmt`. Here are the differences: `Creole`: [php] // example of how to manually hydrate objects $rs = AuthorPeer::doSelectRS(new Criteria()); while($rs->next()) { $a = new Author(); $a->hydrate($rs); } // example of how to create array of single column $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`: [php] // example of how to manually hydrate objects $stmt = AuthorPeer::doSelectStmt(new Criteria()); while($row = $stmt->fetch(PDO::FETCH_NUM)) { $a = new Author(); $a->hydrate($row); } // example of how to create array of single column $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"; } The `Clob` and `Lob` classes from Creole are not used anymore. It means you need to change your code when using these objects: [php] // Propel 1.1 $object->getClobColumn()->getContents(); // Propel 1.2 $object->getClobColumn(); See http://propel.phpdb.org/trac/wiki/Users/Documentation/1.3/Upgrading for more details on upgrading. All the Propel library have been moved from `lib/propel` to `lib`. The upgrade task upgrades the `propel.ini` file to reflect these changes. Request ------- The `path_info_array`, `path_info_key`, and `relative_url_root` settings have been moved from `settings.yml` to `factories.yml` (in the `param` section of the `request` factory configuration). This change removes the dependency between `sfRequest` and `sfConfig`. These three request options are now passed to the request constructor as a fourth argument. The formats are also passed as an option, instead of being passed as an attribute. The request method constants from `sfRequest` values have changed from integers to strings and the `sfRequest::NONE` method has been removed: **Constant** | **Old value** | **New value** ------------ | ------------- | ------------- GET | 2 | GET POST | 4 | POST PUT | 5 | PUT DELETE | 6 | DELETE HEAD | 7 | HEAD NONE | 1 | - The `getMethod()` and `getMethodName()` methods now returns the same value, so `getMethodName()` is deprecated. The `sfAction::getMethodNames()` and the corresponding code in `sfValidationExecutionFilter` from `sfCompat10Plugin` have been removed. This method was deprecated in 1.1 and was not really useable in 1.0. Validators ---------- The `sfValidatorSchemaCompare` constant values have been changed. No change to your code need to be done, but now, you can use nice shortcuts. The following two examples are equivalent: [php] // symfony 1.1 and 1.2 $v = new sfValidatorSchemaCompare('left', sfValidatorSchemaCompare::EQUAL, 'right'); // symfony 1.2 only $v = new sfValidatorSchemaCompare('left', '==', 'right'); The `sfValidatorI18nChoiceCountry` and `sfValidatorI18nChoiceLanguage` validators had a required `culture` option in symfony 1.1. As the culture is not used in those validators, the `culture` option is now deprecated. It is still there to maintain backward compatibility but you don't need to provide it anymore. Forms ----- In symfony 1.1, the `BaseFormPropel` was generated in the wrong place (under the `lib/form/base/` directory). You need to move it to the `lib/form/` directory. Widgets ------- The new `sfWidgetFormChoice` widgets are now used by default by the Propel generated forms instead of `sfWidgetFormSelect`. To take advantage of this more powerful widgets, you will need to rebuilt your forms: $ php symfony propel:build-forms Response -------- There is a new setting for the `response` factory: `send_http_headers`. This setting is `true` by default, except for the `test` environment where the headers must not be sent by PHP (the same goal was achieved by using the `sf_test` setting in symfony 1.1). This change removes the dependency between `sfResponse` and `sfConfig`. The `getStylesheets()` and `getJavascripts()` methods can now return all the stylesheets and javascripts ordered by position if you pass `sfWebResponse::ALL` as their first argument: [php] $response = new sfWebResponse(new sfEventDispatcher()); $response->addStylesheet('foo.css'); $response->addStylesheet('bar.css', 'first'); var_export($response->getStylesheets()); // outputs array( 'bar.css' => array(), 'foo.css' => array(), ) The `sfWebResponse::ALL` is also now the default value for the position argument. In symfony 1.1, as the default value is the empty string, the methods only return the files registered for the default position by default, which is not very intuitive. In symfony 1.1, you was able to get all the files by passing the `'ALL'` string as the position, and this behavior is still available by passing `sfWebResponse::RAW`: [php] var_export($response->getStylesheets(sfWebResponse::RAW)); // outputs array( 'first' => array( 'bar.css' => array (), ), '' => array( 'foo.css' => array(), ), 'last' => array(), ) All the positions (first, '', and last) are now also available as constants: [php] sfWebResponse::FIRST === 'first' sfWebResponse::MIDDLE === '' sfWebResponse::LAST === 'last' The `removeStylesheet()` and `removeJavascript()` methods now only take one argument, the file to remove from the response. It will remove the file in all the available positions. In symfony 1.1, they take the position as a second argument. Prototype and Scriptaculous --------------------------- symfony continues to decouple its bundled software. In 1.2 the bundled Prototype and Scriptaculous libraries and helpers (`JavascriptHelper`) have been moved to a core-plugin. Core plugins behave like any plugin but are shipped with symfony. This will make the JavaScript and CSS files of the new `sfProtoculousPlugin` (the more or less unofficial name of the often featured duo) behave like real plugin assets. They will be now in `web/sfProtoculousPlugin` rather than in `web/sf` (as it has been in 1.0 and 1.1). The `prototype_web_dir` setting will also now point to the new directory. In addition some very basic javascript helpers that are reusable by any JS framework, have been extracted to a `JavascriptBaseHelper` which stays in core. As a new addition, `javascript_tag()` now can behave as `slot()`. This allows such usage [php] alert('All is good') Assets from built-in plugins ---------------------------- Some built-in plugins come with some stylesheet and JavaScript files. To make them accessible to your project, you need to run the `plugin:publish-assets` task: $ php symfony plugin:publish-assets The `project:upgrade1.2` task do this for you. Browser ------- The `sfBrowser` and `sfTestBrowser` classes have been refactored in four classes: * `sfBrowserBase`: The base browser class. It knows nothing about symfony, except classes from the symfony platform. * `sfBrowser`: It inherits from `sfBrowserBase` and implements the methods specific to symfony. * `sfTestFunctionalBase`: The base functional test class. It implements test methods that are independant from symfony. * `sfTestFunctional`: It inherits from `sfTestFunctionalBase` and implements the test methods specific to symfony. * `sfTestBrowser`: A BC class which is the same as the `sfTestFunctional` class with a constructor signature compatible with symfony 1.1 The idea behind the refactor is that the `sfTestFunctional` class is a test class, not a browser. So, it takes a browser and a test object as its arguments: [php] $testBrowser = new sfTestBrowser('localhost'); $tester = new sfTestFunctional(new sfBrowser('localhost'), new lime_test()); The `sfTestFunctional` class acts as a proxy for the browser class which means that all methods from the browser are accessible directly from the functional tester object. This refactor must not introduce backward incompatibility with symfony 1.1. The browser classes now add the `HTTP_REFERER` header for each request. Tests ----- ### Testers The `sfTestFunctionalBase` class now delegates the actual tests to `sfTester` classes. symfony 1.2 has several built-in tester classes: * `request`: `sfTesterRequest` * `response`: `sfTesterResponse` * `user`: `sfTesterUser` * `view_cache`: `sfTesterViewCache` All the old methods from the test browser have been moved to one of the tester class: *method name* | *tester class* | *new method name* ---------------------- | ------------------- | ----------------- `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` The tester classes also comes with new test methods: *tester class* | *new method name* ------------------- | ----------------- `sfTesterRequest` | `hasCookie` `sfTesterRequest` | `isCookie` `sfTesterRequest` | `isMethod` | `sfTesterUser` | `isAuthenticated` `sfTesterUser` | `hasCredential` `sfTesterUser` | `isAttribute` `sfTesterUser` | `isFlash` Even if the old methods are deprecated, they do not send any warning and will not be removed to maintain backward compatibility with symfony 1.0 and 1.1. ### Links When you simulate a click on a button or on a link, you give the name to the `click()` method. But you don't have the possibility to differentiate two different links or buttons with the same name. As of symfony 1.2, the `click()` method takes a third argument to pass some options. You can pass a `position` option to change the link you want to click on: [php] $b-> click('/', array(), array('position' => 1))-> // ... ; By default, symfony clicks on the first link it finds in the page. You can also pass a `method` option to change the method of the link or the form you are clicking on: [php] $b-> click('/delete', array(), array('method' => 'delete'))-> // ... ; This is very useful when a link is converted to a dynamic form generated with JavaScript. Actions ------- By default, when you use the `redirectIf()` or `redirectUnless()` methods in your actions, symfony automatically changes the response HTTP status code to 302. These two methods now have an additional optional argument to change this default status code: [php] $this->redirectIf($condition, '@homepage', 301); $this->redirectUnless($condition, '@homepage', 301); The `redirect()` method already have this feature. Components ---------- If you have the output escaper enabled, there was a bug in symfony 1.0 and 1.1. When you passed some variables from an action to a component, these were escaped in the component. So, in some circumstances, you had to unescaped them: [php] public function executeInAComponent($request) { $this->foo = $this->foo->getRawValue(); } As of symfony 1.2, this has been fixed and all variables are now unescaped by default in a component method. For the above example, you will need to remove the `getRawValue()` step as the framework does this automatically for you. sfParameterHolder ----------------- The `has()` method of `sfParameterHolder` has been changed to be more semantically correct. It now returns `true` even if the value is `null`: [php] $ph = new sfParameterHolder(); $ph->set('foo', 'bar'); $ph->set('bar', null); $ph->has('foo') === true; $ph->has('bar') === true; // returns false under symfony 1.0 or 1.1 The `sfParameterHolder::has()` method is used by the `hasParameter()` and `hasAttribute()` methods available for a large number of core classes. Tasks ----- The Propel tasks relying on Phing now output a clear error message if the embed Phing task fails. Some CLI tasks takes an application name as an argument because they need to connect to the database. We need an application because the configuration can be customized on an application basis and all the symfony configuration system is based on the application level. For these tasks, this argument has been removed in favor of an optional "application" option. If you don't provide the "application" option, symfony will take the database configuration from the project `databases.yml` file. The following task signatures have been changed accordingly: * `propel:build-all-load` * `propel:data-dump` * `propel:data-load` >**Note** >This is possible because `sfDatabaseManager` now takes a project configuration or an application configuration. For the curious one, this works because `sfDatabaseConfigHandler` now returns a static or a dynamic configuration based on an array of configuration files (see the `execute()` and `evaluate()` methods). The `propel:insert-sql` task removes all the data from the database. As it destroys information, it now asks the user to confirm the execution of the task. The same goes for the `propel:build-all` and `propel:build-all-load` tasks, as they call the `propel:insert-sql` task. If you want to use these tasks in a batch and want to avoid the confirmation question, pass the `no-confirmation` option: $ php symfony propel:insert-sql --no-confirmation Before symfony 1.2, the `propel:insert-sql` task was the only task to read its database configuration information from `propel.ini`. As of symfony 1.2, it reads its information from `databases.yml`. So, if you use several different connections in your model, the task will take those into account. Thanks to this new feature, you can now use the `--connection` option if you want to only load SQL statements for a given connection: $ php symfony propel:insert-sql --connection=propel You can also use the `--env` and the `--application` options to select a specific configuration to use: $ php symfony propel:insert-sql --env=prod --application=frontend The `propel:generate-crud` has been renamed to `propel:generate-module`. The old task name is still available as an alias. The `non-atomic-actions` option of `propel:generate-module` has been removed and some new options have been added: * singular: The singular name for the actions and templates * plural: The plural name for the actions and templates * route-prefix: The route prefix to use * with-propel-route: Whether the related routes are Propel aware To ease the debugging, the `propel:build-model`, `propel:build-all`, and `propel:build-all-load` tasks do not remove the generated XML schemas anymore if you pass the `--trace` option. URL helpers ----------- The old `post` option of `link_to` is still valid but deprecated: [php] // is deprecated true)) ?> // and equivalent to 'post')) ?> Using `link_to()` for non-symfony links is no longer possible in favour of the new functionality. In fact, `link_to()` was never intended for cases like this: [php] // does no longer work "alert('js')"); ?> // use this instead Link The `url_for()` and `link_to()` helpers support new signatures. Instead of an internal URI, they can now also take the route name and an array of parameters: [php] echo url_for('@article', array('id' => 1)); echo link_to('Link to article', '@article', array('id' => 1)); The old behavior still works without changing anything to your code. Image helper ------------ In symfony 1.0 and 1.1 the `image_tag` helper would generate the `alt` attribute of the img-tag from the filename. This now only happens if `sf_compat_10` is on. The new behaviour eases finding of unset alt attributes using a (x)html validator. As a bonus, there is now a `alt_title` option that will set alt and title attribute to the same value, which is useful for cross browser tooltips. View ---- You can override `sfViewCacheManager::generateCacheKey()` by defining a `sf_cache_namespace_callable` setting. As of symfony 1.2, the callable is now called with an additional argument, the view cache manager instance. Configuration ------------- Before symfony 1.2, all the plugins installed under the `plugins` directory, and all built-in plugins were automatically loaded. As of symfony 1.2, you need to enable the plugins you want to use in your projects. You can do this in your `ProjectConfiguration` class. Here is how to enable the Doctrine plugin and disable the Propel one: [php] public function setup() { $this->enablePlugins('sfDoctrinePlugin'); $this->disablePlugins('sfPropelPlugin'); } You can add several plugins in one call by passing an array of plugin names: [php] public function setup() { $this->enablePlugins(array('sfDoctrinePlugin', 'sfGuardPlugin')); } You can also change the order in which plugins are loaded by using the `setPlugins` method: [php] public function setup() { $this->setPlugins(array('sfDoctrinePlugin', 'sfCompat10Plugin')); } The `orm` setting is deprecated in `settings.yml` as it is now automatically set when the ORM plugin is loaded. The `compat_10` setting is also deprecated in `settings.yml` as it is now automatically set when the `sfCompat10Plugin` is loaded. So, to enable the 1.0 compatibility plugin, you need to enable it in your configuration: [php] public function setup() { $this->enablePlugins('sfCompat10Plugin'); } By default, symfony only enables the Propel plugin. You can also enable all installed plugins: [php] public function setup() { $this->enableAllPluginsExcept('sfDoctrinePlugin'); } The previous example allows you to enable all plugins except the Doctrine one. If you upgrade from 1.0 or 1.1, this line will make symfony behave like in symfony 1.0 and 1.1. The `sfLoader` class is deprecated as the `getHelperDirs()` and `loadHelpers()` methods are now part of the `sfApplicationConfiguration` class. The `sfLoader` methods now generate a deprecated log message and then call the new methods from the current active configuration. ### Plugin configuration Plugins now have the option of providing a plugin configuration class. These plugin configuration classes setup autoloading for the plugin, and are instantiated in `sfProjectConfiguration`. This means symfony 1.2 tasks no longer require an application argument for plugin classes to be used. This allows plugins to connect to `command.*` events, something that was not previously possible. I18N ---- Internally, the `sfCultureInfo` class is now only used as a singleton. Even if it is still possible to bypass the `getInstance()` method and instantiate a new object directly, it is deprecated. By using the singleton, the performance are better as we only instantiate one culture info object per request and it also means that you can now override some culture information globally in your configuration classes. The `sfCultureInfo::getCountries()`, `sfCultureInfo::getCurrencies()`, and `sfCultureInfo::getLanguages()` methods now take an optional argument which allows to restrict the return value: [php] // will only return the FR and ES countries in english $countries = sfCultureInfo::getInstance('en')->getCountries(array('FR', 'ES')); The `sfCultureInfo::getCurrencies()` method now returns an array of currency names. In previous symfony versions, it returned an array with the symbol and the name. To get the old behavior, just pass `true` as the second argument: [php] $currencies = sfCultureInfo::getInstance('en')->getCurrencies(null, true); To get the translation of a single country, language, or currency, you can now use the `getCountry()`, `getCurrency()`, and `getLanguage()` methods from the `sfCultureInfo` class. Exception Templates ------------------- symfony now respects the current request format when rendering any uncaught exceptions. You can customize each format's output by adding a template to your project or application `config/error` directory. For example, an uncaught exception during an XML request could render `config/error/exception.xml.php` when your application is in debug mode, or `config/error/error.xml.php` when your application is not in debug mode. If you had customized the 500 error template in your project, you will need to manually move it to the new directory: * For symfony 1.1: from `config/error500.php` to `config/error/error.html.php` * For symfony 1.0: from `web/errors/error500.php` to `config/error/error.html.php`