TRAJOIN is an Application to Translate symfony documents Jointly.

home > 1.2/tutorial > whats-new.txt

[1] Edit ↑TOP

What's new in symfony 1.2?


[2] Edit ↑TOP

This tutorial is a quick technical introduction for symfony 1.2. It is for developers who have already worked with symfony 1.0 and 1.1 and who want to quickly learn new features of symfony 1.2.


[3] Edit ↑TOP

First, please note that symfony 1.2 is compatible with PHP 5.2.4 or later, whereas symfony 1.1 works with PHP 5.1 and symfony 1.0 with PHP 5.0.


[4] Edit ↑TOP

If you want to upgrade from 1.1 or 1.0, please read the UPGRADE file found in the symfony distribution. You will have there all the information needed to safely upgrade your projects to symfony 1.2.


[5] Edit ↑TOP

Propel


[6] Edit ↑TOP

Propel has been upgraded to version 1.3, which replaces support for Creole with PDO, and includes many new features including: object instance pooling, master-slave connections, native nested-set support, and better date handling.


[7] Edit ↑TOP

The databases.yml configuration file now uses the PDO syntax:


[8] Edit ↑TOP

dev:
  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

[9] Edit ↑TOP

The transaction api has change slightly: ->begin has been renamed ->beginTransaction() and ->rollback() has been renamed ->rollBack(). The ::doSelectRS method has been renamed to ::doSelectStmt.


[10] Edit ↑TOP

For more information on Propel 1.3, please read the complete documentation on their website.


[11] Edit ↑TOP

Doctrine


[12] Edit ↑TOP

Doctrine is a first-class citizen in symfony 1.2. It means that symfony 1.2 comes bundled with both the Propel plugin and the Doctrine one. All the built-in features are available for both Propel and Doctrine, so feel free to choose the ORM that suits you better.


[13] Edit ↑TOP

Request


[14] Edit ↑TOP

You can now simulate PUT and DELETE requests from a browser by using the POST method and adding a special sf_method parameter:


[15] Edit ↑TOP

<form action="#" method="POST">
  <input type="hidden" name="sf_method" value="PUT" />

  <!-- // ... -->
</form>

[16] Edit ↑TOP

The form_tag() helper has been updated to automatically generate the hidden tag for methods different from GET or POST. So, the opening form tag from the code above can be generated by using the form_tag() helper like this:


[17] Edit ↑TOP

<?php echo form_tag('#', array('method' => 'PUT')) ?>

[18] Edit ↑TOP

As the link_to() helper now embeds a CSRF token if CSRF protection is enabled, you can check the token when the link is clicked by using the checkCSRFProtection() method:


[19] Edit ↑TOP

public function executeDelete($request)
{
  $request->checkCSRFProtection();
}

[20] Edit ↑TOP

The checkCSRFProtection() throws an exception if the token is not present or not valid.


[21] Edit ↑TOP

Forms


[22] Edit ↑TOP

Nested forms


[23] Edit ↑TOP

One of the main limitation of Propel forms in symfony 1.1 was the inability to auto-save objects from embedded forms. This has been implemented in symfony 1.2, which means that when you save a Propel form, symfony will automatically save the main object and all the objects related to the embedded forms.


[24] Edit ↑TOP

New sfForm methods


[25] Edit ↑TOP

The sfForm::renderFormTag() method generates the opening form tag for the current form. It also adds the enctype attribute if the form needs to be multipart and adds a hidden tag if the form method is not GET or POST:


[26] Edit ↑TOP

<?php echo $form->renderFormTag(url_for('@article_update'), array('method' => 'PUT')) ?>

[27] Edit ↑TOP

The sfFormPropel class overrides renderFormTag() to automatically change the HTTP method based on the related object: if the object is new, the method will be POST, and if the object already exists, the method will be PUT.


[28] Edit ↑TOP

The sfForm::hasErrors() method returns true if the form has some errors and false otherwise. This methods returns false if the form is not bound. This method is quite useful in a template:


[29] Edit ↑TOP

<?php if ($form->hasErrors()): ?>
  The form has some errors you need to fix.
<?php endif; ?>

[30] Edit ↑TOP

The sfForm::renderUsing() method allows to render the form using a specific widget schema formatter. This allows to use a formatter directly within a template:


[31] Edit ↑TOP

// in a template, the form will be rendered using the "list" form formatter
echo $form->renderUsing('list');

[32] Edit ↑TOP

The sfForm::renderHiddenFields() method returns HTML for hidden widgets. This can be useful when a form's fields are rendered individually in a template:


[33] Edit ↑TOP

<form action="<?php echo url_for('@some_route') ?>">
  <?php echo $form->renderHiddenFields() ?>
  <ul>
    <?php echo $form['name']->renderRow() ?>
  </ul>
  <input type="submit" />
</form>

[34] Edit ↑TOP

The sfForm::getStylesheets() and sfForm::getJavaScripts() methods return the stylesheets and the JavaScripts files needed to render the form. These files can be defined in the form itself by overriding these two methods, or more commonly by each widget (see the Widget paragraph to learn how).


[35] Edit ↑TOP

In a template, you can use the include_javascripts_for_form() and include_stylesheets_for_form() helpers to render them as HTML. They both take a form object as an argument:


[36] Edit ↑TOP

<?php include_javascripts_for_form($form) ?>
<?php include_stylesheets_for_form($form) ?>

[37] Edit ↑TOP

sfForm implements the Iterator and the Countable interfaces to allow easier iterating on form fields in a template:


[38] Edit ↑TOP

<ul>
  <?php foreach ($form as $field): ?>
    <li><?php echo $field ?></li>
  <?php endforeach; ?>
</ul>

[39] Edit ↑TOP

Cleaned-up values pre-processing


[40] Edit ↑TOP

A way to pre-process the cleaned up values before they are used by Propel to update the object has been added.


[41] Edit ↑TOP

If you want to pre-process a value, you need to add an updateXXXColumn() method to your form where XXX is the PHP name of the Propel column.


[42] Edit ↑TOP

The method must return the processed value or false to remove the value from the array of cleaned up values:


[43] Edit ↑TOP

class UserForm extends sfFormPropel
{
  // ...

  protected function updateProfilePhotoColumn($value)
  {
    // if the user has not submitted a profile_photo,
    // remove the value from the values as we want
    // to keep the old one
    if (!$value)
    {
      return false;
    }

    // remove the old photo
    // save the photo on the disk

    // change the value to the new file name
    return $filename;
  }
}

[44] Edit ↑TOP

Validators


[45] Edit ↑TOP

sfValidatorSchemaCompare


[46] Edit ↑TOP

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:


[47] Edit ↑TOP

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

// symfony 1.2 only
$v = new sfValidatorSchemaCompare('left', '==', 'right');

[48] Edit ↑TOP

sfValidatorChoice


[49] Edit ↑TOP

sfValidatorChoice has now a multiple option to make it behave like a sfValidatorChoiceMany.


[50] Edit ↑TOP

sfValidatorPropelChoice has now a multiple option to make it behave like a sfValidatorPropelChoiceMany.


[51] Edit ↑TOP

sfValidatorDateRange


[52] Edit ↑TOP

symfony 1.2 comes with a new sfValidatorDateRange validators to validate a range of dates.


[53] Edit ↑TOP

sfValidatorFile


[54] Edit ↑TOP

The sfValidatedFile class has been made a bit more flexible when it saves the file.


[55] Edit ↑TOP

Its constructor takes a new argument, the path to use when saving the file.


[56] Edit ↑TOP

So, when saving a file, you can now:


[57] Edit ↑TOP
  • pass an absolute filename as before:

[58] Edit ↑TOP

    $file->save(sfConfig::get('sf_uploads_dir').'/filename.pdf');

[59] Edit ↑TOP
  • pass a relative path (symfony will make it absolute by prepending the path):

[60] Edit ↑TOP

    $file->save('filename.pdf');

[61] Edit ↑TOP
  • pass null. The filename will be auto-generated by the generateFilename() method:

[62] Edit ↑TOP

    $file->save();

[63] Edit ↑TOP

Moreover, the save() method returns the filename of the saved file (relative to the path argument).


[64] Edit ↑TOP

As the sfValidatedFile object is created by the sfValidatorFile validator, a new path option has been added to the latter and is simply passed to the sfValidatedFile constructor:


[65] Edit ↑TOP

$this->validatorSchema['file'] = new sfValidatorFile(array('path' => sfConfig::get('sf_uploads_dir')));

[66] Edit ↑TOP

The sfFormPropel takes advantages of these new enhancements to automate the saving of the files related to a Propel object, thanks to the new saveUploadedFile() method.


[67] Edit ↑TOP

So, if you have a Propel object with a file column, and if you pass a path when defining the file validator in your form, symfony will take care of everything for you:


[68] Edit ↑TOP
  • if no file is uploaded in the form
    • do nothing and do not change anything in the database

[69] Edit ↑TOP
  • if a file is uploaded in the form
    • remove the old file
    • save the new one
    • set the file column to the filename (relative to the given path)

[70] Edit ↑TOP

So, in the related action, you can now just use $this->form->save() to save the object and save the file automatically.


[71] Edit ↑TOP

By default, symfony will generate a unique file name by computing a hash and adding the guessed extension. If you want to change the filename, you can simply create a generateXXXFilename() method in your object where XXX is the PHP name of the column. The method is given the sfValidatedFile object:


[72] Edit ↑TOP

public function generateFileFilename(sfValidatedFile $file)
{
  return $this->getId().$file->getExtension($file->getOriginalExtension());
}

[73] Edit ↑TOP

You can of course also create a updateXXXColumn() as we have seen before to override this behavior and manage the file uploading process by yourself.


[74] Edit ↑TOP

sfValidatorI18nChoiceCountry and sfValidatorI18nChoiceLanguage


[75] Edit ↑TOP

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.


[76] Edit ↑TOP

Setting default values for required and invalid error codes related messages


[77] Edit ↑TOP

Two new methods have been added to the sfValidatorBase class allowing to define default standard messages for required and invalid error codes:


[78] Edit ↑TOP

sfValidatorBase::setRequiredMessage('This field is required');
sfValidatorBase::setInvalidMessage('The value provided for this field is invalid');

[79] Edit ↑TOP

Widgets


[80] Edit ↑TOP

Widget options


[81] Edit ↑TOP

All widgets have a new default option. This option sets the default value for the widget:


[82] Edit ↑TOP

$widget = new sfWidgetFormInput(array('default' => 'default value'));
$widget->setDefault('default value');
echo $widget->getDefault();

[83] Edit ↑TOP

You can also set default values for widget schemas:


[84] Edit ↑TOP

$widget = new sfWidgetFormSchema(...);
$widget->setDefaults(array('name' => 'default value'));
$widget->setDefault('name', 'default value');
var_export($widget->getDefaults());

[85] Edit ↑TOP

These defaults are taken into account in the forms when you don't override them with form defaults.


[86] Edit ↑TOP

All widgets have a new label option. This option sets the label for the widget when used in a widget schema context:


[87] Edit ↑TOP

$widget = new sfWidgetFormInput(array('label' => 'Enter your name here'));
$widget->setLabel('Enter your name here');
echo $widget->getLabel();

[88] Edit ↑TOP

Widgets JavaScripts and stylesheets


[89] Edit ↑TOP

Widgets can declare the JavaScripts and stylesheets they need to be rendered with the getJavaScripts() and getStylesheets() methods:


[90] Edit ↑TOP

class MyWidget extends sfWidgetForm
{
  public function getJavaScripts()
  {
    return array('/path/to/a/file.js');
  }

  public function getStylesheets()
  {
    return array('/path/to/a/file.css' => 'all', '/path/to/a/file.css' => 'screen,print');
  }
}

[91] Edit ↑TOP

The getJavaScripts() method must return an array of JavaScript files. The getStylesheets() method must return an array with files as keys and media as values.


[92] Edit ↑TOP

New widgets


[93] Edit ↑TOP

symfony 1.2 comes with some new widgets:


[94] Edit ↑TOP
  • sfWidgetFormDateRange
  • sfWidgetFormFilterInput
  • sfWidgetFormFilterDate
  • sfWidgetFormI18nSelectCurrency
  • sfWidgetFormSelectCheckbox

[95] Edit ↑TOP

sfWidgetFormChoice family


[96] Edit ↑TOP

There is also a new choice widget family:


[97] Edit ↑TOP
  • sfWidgetFormChoice
  • sfWidgetFormChoiceMany
  • sfWidgetFormPropelChoice
  • sfWidgetFormPropelChoiceMany

[98] Edit ↑TOP

By default, these widgets behave like their Select counterpart. But they are a wrapper on top of the sfWidgetFormSelect, sfWidgetFormSelectRadio, and sfWidgetFormSelectCheckbox widgets. They use one of these three widgets for the rendering. To change the default widget, you have some options:


[99] Edit ↑TOP
  • expanded:
    • if false, then the widget will be a select tag
    • if true and multiple is false, then the widget will be a list of radio tags
    • if true and multiple is true, then the widget will be a list of checkbox tags
  • renderer_options: When creating the widget (select, radio, checkbox), this is the options you want to pass to the renderer widget
  • renderer_class: The class to use instead of the default one
  • renderer: You can also pass a widget object directly (this overrides the previous options).

[100] Edit ↑TOP

Here are some example:


[101] Edit ↑TOP

$widget = new sfWidgetFormPropelSelect(array('model' => 'Article'));

// is equivalent to
$widget = new sfWidgetFormPropelChoice(array('model' => 'Article'));

// change the rendering to use a radio list
$widget->setOption('expanded', true);

// create a multiple select
$widget = new sfWidgetFormPropelChoiceMany(array('model' => 'Article'));

// change the rendering to use a checkbox list
$widget->setOption('expanded', true);

[102] Edit ↑TOP

These new widgets are now used by default for the Propel generated forms.


[103] Edit ↑TOP

Response


[104] Edit ↑TOP

sfWebResponse::getCharset()


[105] Edit ↑TOP

The sfWebResponse::getCharset() returns the current response charset. This charset is automatically updated if you change it by setting the content type.


[106] Edit ↑TOP

sfWebResponse::getStylesheets() and sfWebResponse::getJavascripts()


[107] Edit ↑TOP

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:


[108] Edit ↑TOP

$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(),
)

[109] Edit ↑TOP

The sfWebResponse::ALL is also now the default value for the position argument.


[110] Edit ↑TOP

Prototype and Scriptaculous


[111] Edit ↑TOP

In symfony, 1.2 the bundled Prototype and Scriptaculous libraries and their associated helpers (JavascriptHelper) have been moved to a core plugin.


[112] Edit ↑TOP

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 are 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.


[113] Edit ↑TOP

In addition some very basic javascript helpers that are reusable by any JS framework, have been extracted to a JavascriptBaseHelper which stays in core.


[114] Edit ↑TOP

As a new addition, javascript_tag() now can behave as slot(). This allows such usage


[115] Edit ↑TOP

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

[116] Edit ↑TOP

Tests


[117] Edit ↑TOP

sfBrowser::info()


[118] Edit ↑TOP

When you write a lot of functional tests for a given module, it is sometimes useful to have some visual information about what it is being done. As of symfony 1.2, the info() method outputs some text to help categorize your tests:


[119] Edit ↑TOP

$browser->
  info('First scenario')->
  // ... some tests
  info('Second scenario')->
  // ... some more tests
;

[120] Edit ↑TOP

Debugging tests


[121] Edit ↑TOP

When a problem occurs in a functional test, the HTML transfered to the browser help to diagnose the cause. As of symfony 1.2, this is quite easy to display the generated HTML without having to interrupt the fluent interface style:


[122] Edit ↑TOP

$browser->
  get('/a_uri_with_an_error')->
  with('response')->debug()->
  // some tests that won't be executed
;

[123] Edit ↑TOP

The debug() method will output the response headers and content and will interrupt the flow of the browser.


[124] Edit ↑TOP

Testers


[125] Edit ↑TOP

The sfTestFunctionalBase class now delegates the actual tests to sfTester classes. symfony 1.2 has several built-in tester classes:


[126] Edit ↑TOP
  • request: sfTesterRequest
  • response: sfTesterResponse
  • user: sfTesterUser
  • view_cache: sfTesterViewCache

[127] Edit ↑TOP

All the old methods from the test browser have been moved to one of the tester class:


[128] Edit ↑TOP
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

[129] Edit ↑TOP

The tester classes also comes with new test methods:


[130] Edit ↑TOP
tester class new method name
sfTesterRequest hasCookie
sfTesterRequest isCookie
sfTesterRequest isMethod
sfTesterUser isAuthenticated
sfTesterUser hasCredential
sfTesterUser isAttribute
sfTesterUser isFlash

[131] Edit ↑TOP

Here is how to use the new testers:


[132] Edit ↑TOP

$browser->
  get('/')->
  with('request')->isParameter('module', 'foo')->
  with('request')->isParameter('module', 'bar')
;

[133] Edit ↑TOP

The call to the with() method returns the tester object on which you can call any of its methods.


[134] Edit ↑TOP

If you want to call several methods on the same tester object, you can put them in a 'block':


[135] Edit ↑TOP

$browser->
  get('/')->

  with('request')->begin()->
    isParameter('module', 'foo')->
    isParameter('module', 'bar')->
    isMethod('get')->
    isFormat('html')->
  end()->

  with('response')->begin()->
    isStatusCode(200)->
    checkElement('body', 'foo')->
    isHeader('Content-Type', 'text/html; charset: UTF-8')->
    isRedirected(false)->
  end()
;

[136] Edit ↑TOP

You can add your own tester by creating a class that inherits from sfTester and register it:


[137] Edit ↑TOP

$browser->setTester('my_tester', 'myTesterClassName');

[138] Edit ↑TOP

You can also extends the existing tester classes by overriding the default class names:


[139] Edit ↑TOP

$browser->setTester('request', 'myTesterRequest');

[140] Edit ↑TOP

To improve testing pages with forms, we added a select() and deselect() method to the test browser. This currently supports selecting and deselecting checkboxes and selecting radiobuttons, which will cause the other radiobuttons of the same name to be deselected.


[141] Edit ↑TOP

$browser->
  select('aRadioId')->
  select('aCheckboxId')->
  deselect('anotherCheckbox');

[142] Edit ↑TOP

Cookies


[143] Edit ↑TOP

Cookies are now extensively supported in the sfBrowser and sfTestBrowser classes.


[144] Edit ↑TOP

You can manage cookies between requests by using the setCookie(), removeCookie(), and clearCookies() methods of the sfBrowser class:


[145] Edit ↑TOP

$b->
  setCookie('foo', 'bar')->
  removeCookie('bar')->
  get('/')->
  // ...

  clearCookies()->
  get('/')->
  // ...

[146] Edit ↑TOP

You can also test cookies with the hasCookie() and isCookie() methods from the sfTesterRequest class:


[147] Edit ↑TOP

$b->
  get('/')->
  with('request')->begin()->
    hasCookie('foo')->
    hasCookie('foobar', false)->
    isCookie('foo', 'bar')->
    isCookie('foo', '/a/')->
    isCookie('foo', '/!z/')->
  end()
;

[148] Edit ↑TOP

The hasCookie() method takes a Boolean as its second argument to be able to test the fact that a cookie is not set.


[149] Edit ↑TOP

The second argument of the isCookie() method behaves as the second argument of the checkElementResponse method. It can be a string, a regular expression, or a negative regular expression.


[150] Edit ↑TOP

The browser class also automatically expires cookies as per the expire value of each cookie.


[151] Edit ↑TOP

Request


[152] Edit ↑TOP

You can now test the HTTP method used by the request in your functional tests using the isMethod() method from the request tester:


[153] Edit ↑TOP

$b->
  setField('login', 'johndoe')->
  click('Submit')->
  with('request')->isMethod('post');

[154] Edit ↑TOP

Links


[155] Edit ↑TOP

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.


[156] Edit ↑TOP

As of symfony 1.2, the click() method takes a third argument to pass some options.


[157] Edit ↑TOP

You can pass a position option to change the link you want to click on:


[158] Edit ↑TOP

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

[159] Edit ↑TOP

By default, symfony clicks on the first link it finds in the page.


[160] Edit ↑TOP

You can also pass a method option to change the method of the link or the form you are clicking on:


[161] Edit ↑TOP

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

[162] Edit ↑TOP

This is very useful when a link is converted to a dynamic form generated with JavaScript. You can also simulate the CSRF token generated by the link_to() helper by passing a _with_csrf option:


[163] Edit ↑TOP

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

[164] Edit ↑TOP

Forms


[165] Edit ↑TOP

If you use the new form framework, you can now test the errors generated by the submitted form:


[166] Edit ↑TOP

$browser->
  click('save', array(...))->
  with('form')->begin()->
    hasErrors()->
    isError('name', 'Required.')->
    isError('name', '/Required/')->
    isError('name', '!/Invalid/')->
    isError('name')->
    isError('name', 1)->
  end()
;

[167] Edit ↑TOP

You can also debug a form with the debug() method:


[168] Edit ↑TOP

$browser->
  click('save', array(...))->
  with('form')->debug()->
  // some tests that won't be executed
;

[169] Edit ↑TOP

It will output the submitted values and all the errors if any.


[170] Edit ↑TOP

Propel


[171] Edit ↑TOP

The Propel plugin comes with a propel tester. This tester is not registered by default:


[172] Edit ↑TOP

$browser->setTester('propel', 'sfTesterPropel');

[173] Edit ↑TOP

This tester provides a check() method to test a Propel model:


[174] Edit ↑TOP

$browser->
  post('...', array(...))->
  with('propel')->begin()->
    check('Article', array(), 3)->
    check('Article', array('title' => 'new title'))->
    check('Article', array('title' => 'new title'))->
    check('Article', array('title' => 'new%'))->
    check('Article', array('title' => '!new%'), 2)->
    check('Article', array('title' => 'title'), false)->
    check('Article', array('title' => '!title'))->
  end()
;

[175] Edit ↑TOP

It takes a model class name as its first argument, a Criteria object or an array of conditions as the second one. The third one can be one of:


[176] Edit ↑TOP
  • true: To check that the Criteria returns at least one object
  • false: To check that the Criteria returns no object
  • an integer to check the number of returned objects

[177] Edit ↑TOP

Coverage


[178] Edit ↑TOP

There is a new test:coverage task that output the code coverage for some given tests:


[179] Edit ↑TOP

./symfony test:coverage test/unit/model/ArticleTest.php lib/model/Article.php

[180] Edit ↑TOP

The first argument is a test file or a test directory. The second one is the file or directory you want to cover.


[181] Edit ↑TOP

If you want to know which lines are not covered, simply add the --detailed option:


[182] Edit ↑TOP

./symfony test:coverage --detailed test/unit/model/ArticleTest.php lib/model/Article.php

[183] Edit ↑TOP

Loggers


[184] Edit ↑TOP

symfony 1.2 comes with a new built-in logger: sfVarLogger. This logger class logs all the messages as an array for later use. It's up to you to get the logs and do something with them:


[185] Edit ↑TOP

$log = new sfVarLogger();
$log->log('foo');

var_export($log->getLogs());

// outputs
array(
  0 => array(
    'priority'      => 6,
    'priority_name' => 'info',
    'time'          => 1219385295,
    'message'       => 'foo',
    'type'          => 'sfOther',
    'debug_stack'   => array()
  )
)

[186] Edit ↑TOP

Each log is an associative array with the following keys: priority, priority_name, time, message, type, and debug_stack.


[187] Edit ↑TOP

The debug_stack attribute is only set if you turns the xdebug_logging option to true. It then contains the stack trace returned by xdebug as an array.


[188] Edit ↑TOP

The sfVarLogger is also the base class for the sfWebDebugLogger class. So, the xdebugLogging option of sfWebDebugLogger has been renamed to xdebug_logging.


[189] Edit ↑TOP

Web Debug Toolbar


[190] Edit ↑TOP

The web debug toolbar is now customizable. The toolbar is composed of panels. A panel is an object that extends sfWebDebugPanel and provides all the needed information to display if on the toolbar.


[191] Edit ↑TOP

By default, the following panels are automatically registered:


[192] Edit ↑TOP
name class name
symfony_version sfWebDebugPanelSymfonyVersion
cache sfWebDebugPanelCache
config sfWebDebugPanelConfig
logs sfWebDebugPanelLogs
time sfWebDebugPanelTimer
memory sfWebDebugPanelMemory
db sfWebDebugPanelPropel

[193] Edit ↑TOP

You can customize the web debug toolbar by listening to the debug.web.load_panels event. The listener can then add new panels, remove existing ones, or even replace some.


[194] Edit ↑TOP

The sfWebDebugPanelLogs panel filters the logs to be displayed by notifying the debug.web.filter_logs event. For example, the sfWebDebugPanelPropel and sfWebDebugPanelTimer connect to this event to remove all Propel related logs and timer logs from the logs panel.


[195] Edit ↑TOP

The total time displayed on the web debug toolbar is now computed with $_SERVER['REQUEST_TIME'] (we used to compute it with sfConfig::get('sf_time_start'), which does not exist anymore). This means that the time displayed will be much larger than in symfony 1.0 and 1.1.


[196] Edit ↑TOP

Tasks


[197] Edit ↑TOP

The Propel tasks relying on Phing now output a clear error message if the embed Phing task fails.


[198] Edit ↑TOP

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.


[199] Edit ↑TOP

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.


[200] Edit ↑TOP

The following task signatures have been changed accordingly:


[201] Edit ↑TOP
  • propel:build-all-load
  • propel:data-dump
  • propel:data-load

[202] Edit ↑TOP
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).


[203] Edit ↑TOP

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.


[204] Edit ↑TOP

propel:insert-sql


[205] Edit ↑TOP

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.


[206] Edit ↑TOP

If you want to use these tasks in a batch and want to avoid the confirmation question, pass the no-confirmation option:


[207] Edit ↑TOP
$ php symfony propel:insert-sql --no-confirmation

[208] Edit ↑TOP

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:


[209] Edit ↑TOP
$ php symfony propel:insert-sql --connection=propel

[210] Edit ↑TOP

You can also use the --env and the --application options to select a specific configuration to use:


[211] Edit ↑TOP
$ php symfony propel:insert-sql --env=prod --application=frontend

[212] Edit ↑TOP

New methods available for your tasks


[213] Edit ↑TOP

The sfTask base class now provides three new methods to use in your tasks:


[214] Edit ↑TOP
  • logBlock(): Logs a message in a styled block (default styles are: INFO, COMMENT, QUESTION, and ERROR)
  • ask(): Asks a question to the user and returns the given answer
  • askConfirmation(): Asks a confirmation to the user and return true if the user confirmed, false otherwise

[215] Edit ↑TOP

propel:generate-module


[216] Edit ↑TOP

The propel:generate-crud has been renamed to propel:generate-module. The old task name is still available as an alias.


[217] Edit ↑TOP

The non-atomic-actions option of propel:generate-module has been removed and some new options have been added:


[218] Edit ↑TOP
  • 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

[219] Edit ↑TOP

propel:generate-module-for-route


[220] Edit ↑TOP

The new propel:generate-module-for-route generates a module based on a route definition:


[221] Edit ↑TOP

php symfony propel:generate-module-for-route frontend articles

[222] Edit ↑TOP

app:routes


[223] Edit ↑TOP

As symfony now can auto-generate routes (see below), the app:routes task displays the list of current routes for an application:


[224] Edit ↑TOP

php symfony app:routes frontend

[225] Edit ↑TOP

If you want to get some details about a route, just add the route name:


[226] Edit ↑TOP

php symfony app:routes frontend articles_update

[227] Edit ↑TOP

plugin:install-assets


[228] Edit ↑TOP

As plugins are normally installed via a task and this invokes the creation of the symlink in the web directory this is required for core plugins as well.


[229] Edit ↑TOP

To do this manually you need to invoke:


[230] Edit ↑TOP

symfony plugin:publish-assets --only-core


[231] Edit ↑TOP

The target is that the project creation and upgrade task do this for you.


[232] Edit ↑TOP

Routing


[233] Edit ↑TOP

Routing options


[234] Edit ↑TOP

The routing takes two new options:


[235] Edit ↑TOP
  • generate_shortest_url: Whether to generate the shortest URL possible
  • extra_parameters_as_query_string: Whether to generate extra parameters as a query string

[236] Edit ↑TOP

By default, they are set to false in the default factories.yml configuration file to keep backward compatibility with symfony 1.0 and 1.1.


[237] Edit ↑TOP

You can also enable or disable these options on a route basis:


[238] Edit ↑TOP

articles:
  url:     /articles/:page
  param:   { module: article, action: list, page: 1 }
  options: { generate_shortest_url: true }

[239] Edit ↑TOP

This route will generate the shortest URL possible. So, if you pass a page of 1, the generated URL will be /articles:


[240] Edit ↑TOP

echo url_for('@articles?page=1');
// /articles
// would have been /articles/1 in symfony 1.1

echo url_for('@articles?page=2');
// /articles/2

[241] Edit ↑TOP

Here is an example for extra_parameters_as_query_string:


[242] Edit ↑TOP

articles:
  url:     /articles
  options: { extra_parameters_as_query_string: true }

[243] Edit ↑TOP

This route will accept extra parameters and add them as a query string:


[244] Edit ↑TOP

echo url_for('@articles?page=1');
// /articles?page=1
// would not have matched the route in symfony 1.1

echo url_for('@articles?page=2');
// /articles?page=2

[245] Edit ↑TOP

sfRoute


[246] Edit ↑TOP

The sfPatternRouting class now stores its routes as an array of sfRoute objects instead of a flat associative array.


[247] Edit ↑TOP

By default, symfony uses the built-in sfRoute object, but you can change it by specifying a class entry in your routing.yml configuration file:


[248] Edit ↑TOP

foo_bar:
  url:   /foo/:bar
  class: myRoute
  param: { module: foo, action: bar }

[249] Edit ↑TOP

All the routing logic is contained in the sfRoute class, which means you can override the parsing and the generation logic with your own.


[250] Edit ↑TOP
The sfRoute class is much more modular than the old sfPatternRouting class to allow easier customization of the default behavior. The \"compilation\" phase has been refactored into smaller methods, the code has been simplified, and it is based on a \"real\" tokenizer.


[251] Edit ↑TOP

If you connect your routes with PHP code, you must now pass a sfRoute instance as the second argument for the connect(), preprendRoute(), appendRoute(), and insertRouteBefore() methods:


[252] Edit ↑TOP

$route = new myRoute('/foo/:bar', array('module' => 'foo', 'action' => 'bar'));
$routing->connect('foo_bar', $route);

[253] Edit ↑TOP

When a request comes in, the matching route object is stored as an attribute of the request and you can get it from an action via the getRoute() method:


[254] Edit ↑TOP

public function executeIndex()
{
  $route = $this->getRoute();
}

[255] Edit ↑TOP

The sfRoute constructor takes an array of options as its last argument to allow the customization of each route. In the routing.yml configuration file, use to options key:


[256] Edit ↑TOP

article:
  url:     /article/:id-:slug
  options: { segment_separators: [/, ., -] }

[257] Edit ↑TOP

sfRequestRoute


[258] Edit ↑TOP

Symfony has another built-in route, sfRequestRoute, which can enforce the HTTP method for your routes:


[259] Edit ↑TOP

article:
  url:          /article/:id
  requirements: { sf_method: get }
  class:        sfRequestRoute

[260] Edit ↑TOP

If you don't pass a sf_method requirement, symfony will enforces a get, or a head request.


[261] Edit ↑TOP

This is made possible because the parse() method of sfRouting now takes a context as a second argument. When the request calls the routing, it passes the following context:


[262] Edit ↑TOP
  • method: The HTTP method name
  • format: The request format
  • host: The hostname
  • is_secure: Whether the request was called with HTTPS or not
  • request_uri: The full request URI
  • prefix: The prefix to add to each generated route

[263] Edit ↑TOP

With the previous routing configuration, the article route will only match requests with a get HTTP method. If you define several routes with the same url but different method requirements, you can pass sf_method as a parameter when you generate a route:


[264] Edit ↑TOP

<?php echo link_to('Great article', '@article?id=1&sf_method=get')) ?>

[265] Edit ↑TOP

sfObjectRoute


[266] Edit ↑TOP

symfony 1.2 comes with another route class that extends sfRequestRoute, sfObjectRoute.


[267] Edit ↑TOP

sfObjectRoute binds a route to a PHP object. A sfObjectRoute will call some methods on your PHP class to get the object, or a collection of objects, related to the route.


[268] Edit ↑TOP

article:
  url:     /article/:id
  class:   sfObjectRoute
  options: { model: Article, type: object, method: getById }

[269] Edit ↑TOP

When an incoming URL matches the route, the sfObjectRoute::getObject() method will get the related object by calling the Article::getById() method.


[270] Edit ↑TOP

The same goes for a collection of objects:


[271] Edit ↑TOP

articles:
  url:     /articles/newest
  class:   sfObjectRoute
  options: { model: Article, type: list, method: getNewest }

[272] Edit ↑TOP

sfPropelRoute


[273] Edit ↑TOP

sfPropelRoute extends sfObjectRoute to bind a route to a Propel model.


[274] Edit ↑TOP

In your actions, you can retrieve the related object or collection of objects by using the sfObjectRoute::getObject() and sfObjectRoute::getObjects() methods:


[275] Edit ↑TOP

public function executeList($request)
{
  $this->articles = $this->getRoute()->getObjects();
}

public function executeShow($request)
{
  $this->article = $this->getRoute()->getObject();
}

[276] Edit ↑TOP

Here is an example for an Article Propel object:


[277] Edit ↑TOP

article:
  url:     /article/:id
  param:   { module: article, action: show }
  class:   sfPropelRoute
  options: { model: Article, type: object }

articles:
  url:     /articles
  param:   { module: article, action: list }
  class:   sfPropelRoute
  options: { model: Article, type: list, method: getPublishedArticleCriteria }

[278] Edit ↑TOP

If you don't define a method, sfPropelRoute will retrieve the object by building a Criteria object based on the available route variables.


[279] Edit ↑TOP

The sfPropelRoute has two main advantages over sfRoute:


[280] Edit ↑TOP
  • When a request comes in and the route matches the URL, the matching sfPropelRoute object will be available in your actions, and the related Article object will be available by calling the getObject() method. Moreover, if the object does not exist in the database, it will automatically redirect the user to a 404 error page. This means less boiler-plate code in your actions.

[281] Edit ↑TOP
  • When you generate a link for this route, you can use the new url_for() signature and pass the article object directly for the parameters argument:

    
    <?php echo url_for('article', $article) ?>
    

    If you have to pass extra parameters (to enforce a HTTP method for example), you can use an array like this:

    
    <?php echo url_for('article', array('sf_subject' => $article, 'sf_method' => 'get')) ?>
    

    And of course, you can still use the full parameters:

    
    <?php echo url_for('article', array('id' => $article->getId(), 'slug' => $article->getSlug())) ?>
    

    Or use the internal URI:

    
    <?php echo url_for('@article?id='.$article->getId().'&slug='.$article->getSlug()) ?>
    

    The sfPropelRoute converts the article object to an array of parameters automatically.


[282] Edit ↑TOP

sfPropelRoute does not only work with the primary key. It can also work with any column. In the following example, let's add the slug column to the route pattern:


[283] Edit ↑TOP

article:
  url:     /article/:id/:slug
  param:   { module: article, action: show }
  class:   sfPropelRoute
  options: { model: Article, type: object }

[284] Edit ↑TOP

But sometimes, you want to put in the pattern some information that does not exist in the database. In this case, you can pass a method option:


[285] Edit ↑TOP

post:
  url:     /post/:id/:year/:month/:day/:slug
  param:   { module: article, action: show }
  class:   sfPropelRoute
  options: { model: Article, type: object, method: getObjectForRoute }

[286] Edit ↑TOP

The getObjectForRoute() receives an array of parameters as its first argument and must return an Article object:


[287] Edit ↑TOP

class ArticlePeer extends BaseArticlePeer
{
  static public function getObjectForRoute($parameters)
  {
    $criteria = new Criteria();
    $criteria->add(self::ID, $parameters['id']);

    return self::doSelectOne($criteria);
  }
}

[288] Edit ↑TOP

sfPropelRoute also provide a setListCriteria() method to override the route parsed parameters. This is especially useful when you want to refine the objects returned by the route, based on some other parameters, like a filter for example:


[289] Edit ↑TOP

public function executeList($request)
{
  $this->filters = new ArticleFormFilter();
  if ($request->isMethod('post'))
  {
    $this->filters->bind($request->getParameter('article_filters'));
    if ($this->filters->isValid())
    {
      $this->getRoute()->setListCriteria($this->filters->getCriteria());
    }
  }

  $this->articles = $this->getRoute()->getObjects();
}

[290] Edit ↑TOP

sfRouteCollection, sfObjectRouteCollection, and sfPropelRouteCollection


[291] Edit ↑TOP

symfony also comes with "collection" route classes.


[292] Edit ↑TOP

These classes (sfRouteCollection, sfObjectRouteCollection, sfPropelRouteCollection) generate standard routes based on a definition:


[293] Edit ↑TOP

articles:
  class:   sfPropelRouteCollection
  options: { model: Article, module: article }

[294] Edit ↑TOP

By default, this route will generate seven routes:


[295] Edit ↑TOP
  • list: articles GET /articles.:sf_format
  • new: articles_new GET /articles/new.:sf_format
  • create: articles_create POST /articles.:sf_format
  • edit: articles_edit GET /articles/:id/edit.:sf_format
  • update: articles_update PUT /articles/:id.:sf_format
  • delete: articles_delete DELETE /articles/:id.:sf_format

[296] Edit ↑TOP

You can customize the generation with several options:


[297] Edit ↑TOP
  • model: The model class name
  • actions: The list of actions to generate (from the 7 listed above)
  • module: The module name
  • prefix_path: The prefix path to add for every route
  • column: The column name for the primary key (id by default)
  • with_show: Whether to add the show method or not
  • segment_names: The segment names for new and edit
  • model_methods: The methods to use to get a collection or an object
  • requirements: The default requirements (by default, the id requirement is \d+)
  • route_class: The route class to use (by default sfObjectRoute for sfObjectRouteCollection and sfPropelRoute for sfPropelRouteCollection)
  • collection_actions: A list of actions to add as collection actions
  • object_actions: A list of actions to add as object actions

[298] Edit ↑TOP

Here is another example with some options:


[299] Edit ↑TOP

articles:
  class:   sfPropelRouteCollection
  options:
    model:              Article
    module:             article
    prefix_path:        /foo
    methods:            [ list, edit, update ]
    segment_names:      { list: nouveau, edit: edition }
    collection_actions: { filter: post }
    object_actions:     { publish: put }

[300] Edit ↑TOP

URL helpers


[301] Edit ↑TOP

link_to() and url_for()


[302] Edit ↑TOP

If you want to create a link to a resource that must be submitted with the POST, PUT, or DELETE HTTP method, the link_to() helper can convert a link to a form if you pass the method option:


[303] Edit ↑TOP

<?php echo link_to('@article_delete?id=1', array('method' => 'delete')) ?>

[304] Edit ↑TOP

For POST, DELETE, and PUT methods, the link_to() helper also embeds a CSRF token if CSRF protection is enabled in settings.yml.


[305] Edit ↑TOP

The old post option is still valid but deprecated:


[306] Edit ↑TOP

// is deprecated
<?php echo link_to('@some_route', array('post' => true)) ?>

// and equivalent to
<?php echo link_to('@some_route', array('method' => 'post')) ?>

[307] Edit ↑TOP

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:


[308] Edit ↑TOP

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

[309] Edit ↑TOP

The old behavior still works without changing anything to your code.


[310] Edit ↑TOP

Configuration


[311] Edit ↑TOP

Before symfony 1.2, all the plugins installed under the plugins directory, and all built-in plugins were automatically loaded.


[312] Edit ↑TOP

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:


[313] Edit ↑TOP

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

[314] Edit ↑TOP

You can add several plugins in one call by passing an array of plugin names:


[315] Edit ↑TOP

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

[316] Edit ↑TOP

You can also change the order in which plugins are loaded by using the setPlugins method:


[317] Edit ↑TOP

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


[318] Edit ↑TOP

To enable the 1.0 compatibility plugin, you need to enable it in your configuration:


[319] Edit ↑TOP

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

[320] Edit ↑TOP

By default, symfony only enables the Propel plugin.


[321] Edit ↑TOP

You can also enable all installed plugins:


[322] Edit ↑TOP

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

[323] Edit ↑TOP

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.


[324] Edit ↑TOP

Plugin configuration


[325] Edit ↑TOP

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.


[326] Edit ↑TOP

Filters


[327] Edit ↑TOP

symfony is now able to generate filters based on your model thanks to the propel:build-filters task:


[328] Edit ↑TOP
$ php symfony propel:build-filters

[329] Edit ↑TOP

This task generates filter form classes in lib/filter/ by default.


[330] Edit ↑TOP

To filter an Article model, here is the code you need in your actions:


[331] Edit ↑TOP

$this->filters = new ArticleFormFilter();
if ($request->isMethod('post'))
{
  $this->filters->bind($request->getParameter('article_filters'));
  if ($this->filters->isValid())
  {
    $this->articles = ArticlePeer::doSelect($this->filters->getCriteria());
  }
}

[332] Edit ↑TOP

And here is the related template code:


[333] Edit ↑TOP

<form action="<?php echo url_for('@articles_filter') ?>" method="post">
  <table>
    <?php echo $filters ?>
    <tr>
      <td colspan="2">
        &nbsp;<a href="<?php echo url_for('@articles') ?>">Reset</a>
        <input type="submit" value="Filter" />
      </td>
    </tr>
  </table>
</form>

[334] Edit ↑TOP

If you want to persist the filters between requests, here is a small working example:


[335] Edit ↑TOP

public function executeList($request)
{
  $this->filters = new ArticleFormFilter($this->getUser()->getAttribute('article_filters'));
  if ($request->isMethod('post'))
  {
    $this->filters->bind($request->getParameter('article_filters'));
    if ($this->filters->isValid())
    {
      $this->getUser()->setAttribute('article_filters', $this->filters->getValues());
    }
  }

  $criteria = $this->filters->buildCriteria($this->getUser()->getAttribute('article_filters'));
  $this->articles = ArticlePeer::doSelect($criteria);
}

[336] Edit ↑TOP

Exception Templates


[337] Edit ↑TOP

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.


[338] Edit ↑TOP

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.


[339] Edit ↑TOP

If you had customized the 500 error template in your project, you will need to manually move it to the new directory:


[340] Edit ↑TOP
  • 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

[341] Edit ↑TOP

Smaller improvements


[342] Edit ↑TOP

symfony 1.2 also comes with a lot of improvements here and there. Here are some of them.


[343] Edit ↑TOP

Action


[344] Edit ↑TOP

In an action, you can now generate a URL by using the routing object directly by calling generateUrl():


[345] Edit ↑TOP

public function executeIndex()
{
  $this->redirect($this->generateUrl('homepage'));
}

[346] Edit ↑TOP

The generateUrl() method takes a route name, an array of parameters, and whether to generate an absolute URL as its constructor arguments.


[347] Edit ↑TOP

By default, when you use the redirectIf() or redirectUnless() methods in your actions, symfony automatically changes the response HTTP status code to 302.


[348] Edit ↑TOP

These two methods now have an additional optional argument to change this default status code:


[349] Edit ↑TOP

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

[350] Edit ↑TOP

The redirect() method already have this feature.


[351] Edit ↑TOP

YAML


[352] Edit ↑TOP

The YAML parser now supports full merge key (see http://yaml.org/type/merge.html for more examples).


[353] Edit ↑TOP

$yaml = new sfYamlParser();

var_export($yaml->parse(<<<EOF
default_param: &default_param
  datasource: propel
  phptype:    mysql
  hostspec:   localhost
  database:   db
  username:   user
  password:   pass

param:
  <<: *default_param
  username:   myuser
  password:   mypass
EOF
));

// outputs
array(
  'default_param' => array(
    'datasource' => 'propel',
    'phptype'    => 'mysql',
    'hostspec'   => 'localhost',
    'database'   => 'db',
    'username'   => 'user',
    'password'   => 'pass',
  )

  'param' => array(
    'datasource' => 'propel',
    'phptype'    => 'mysql',
    'hostspec'   => 'localhost',
    'database'   => 'db',
    'username'   => 'myuser',
    'password'   => 'mypass',
  )
)

[354] Edit ↑TOP

sfParameterHolder


[355] Edit ↑TOP

The has() method of sfParameterHolder has been changed to be more semantically correct.


[356] Edit ↑TOP

It now returns true even if the value is null:


[357] Edit ↑TOP

$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

[358] Edit ↑TOP

The sfParameterHolder::has() method is used by the hasParameter() and hasAttribute() methods available for a large number of core classes.


[359] Edit ↑TOP

image_tag() helper


[360] Edit ↑TOP

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.


[361] Edit ↑TOP

View Configuration


[362] Edit ↑TOP

As of symfony 1.2, it is now possible to change the View class used by symfony to render the partials by setting the partial_view_class in module.yml. The class must extend sfPartialView. Similar to the view_class setting, symfony will automatically appends PartialView to the partial_view_class value:


[363] Edit ↑TOP

# module.yml
all:
  partial_view_class: my

[364] Edit ↑TOP

Factories


[365] Edit ↑TOP

The sfViewCacheManager class is now configurable in factories.yml:


[366] Edit ↑TOP

view_cache_manager:
  class: sfViewCacheManager

[367] Edit ↑TOP

View


[368] Edit ↑TOP

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.


[369] Edit ↑TOP

Events


[370] Edit ↑TOP

symfony 1.2 comes with new events:


[371] Edit ↑TOP
  • user.change_authentication: notified when a user authentication status changes. The event takes the authenticated flag as an argument after the change occurred.
  • debug.web.load_panels (cf. web debug toolbar paragraph)
  • debug.web.filter_logs (cf. web debug toolbar paragraph)

[372] Edit ↑TOP

I18N


[373] Edit ↑TOP

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.


[374] Edit ↑TOP

The sfCultureInfo::getCountries(), sfCultureInfo::getCurrencies(), and sfCultureInfo::getLanguages() methods now take an optional argument which allows to restrict the return value:


[375] Edit ↑TOP

// will only return the FR and ES countries in english
$countries = sfCultureInfo::getInstance('en')->getCountries(array('FR', 'ES'));

[376] Edit ↑TOP

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:


[377] Edit ↑TOP

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

[378] Edit ↑TOP

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.


[379] Edit ↑TOP

Include Path Management


[380] Edit ↑TOP

symfony 1.2 adds the static method sfToolkit::addIncludePath() for easily managing the PHP include_path setting. This method accepts two parameters: a single path or array of paths, and a position string (either "front" or "back"). The second parameter defaults to "front".


[381] Edit ↑TOP

// an example from sfPropelPlugin
sfToolkit::addIncludePath(array(
  sfConfig::get('sf_root_dir'),
  sfConfig::get('sf_symfony_lib_dir'),
  realpath(dirname(__FILE__).'/../lib/vendor'),
));

[382] Edit ↑TOP

Admin Generator


[383] Edit ↑TOP

The admin generator has been rewritten for symfony 1.2. It is based on the form framework. To generate an admin module, use the propel:generate-admin task:


[384] Edit ↑TOP
$ php symfony propel:generate-admin backend Article

[385] Edit ↑TOP

By default, the task adds a REST route for your module. You can also create a route by yourself and pass it as an argument instead of the model class name:


[386] Edit ↑TOP
$ php symfony propel:generate-admin backend article

[387] Edit ↑TOP

The configuration is done via the generator.yml. All the configuration is now done under the config key:


[388] Edit ↑TOP

generator:
  class: sfPropelGenerator
  param:
    model_class:           DemoCategory
    theme:                 admin
    non_verbose_templates: true
    with_show:             false
    singular:              ~
    plural:                ~
    route_prefix:          categories
    with_propel_route:     1

    config:
      actions: ~
      fields:  ~
      list:    ~
      filter:  ~
      form:    ~
      edit:    ~
      new:     ~

[389] Edit ↑TOP

You can now have different configuration for the edit and new form, and they both inherit from the form configuration.


[390] Edit ↑TOP

The name entry has been renamed label.


[391] Edit ↑TOP

The old admin generator has been kept for backward compatibility, so you can still use it if you want by running the propel:init-admin task.


Comments

Menu

Documentation



Latest Histories

Untranslated