moved management of the locale from the Session class to the Request class

The locale management does not require sessions anymore.

In the Symfony2 spirit, the locale should be part of your URLs. If this is the case
(via the special _locale request attribute), Symfony will store it in the request
(getLocale()).

This feature is now also configurable/replaceable at will as everything is now managed
by the new LocaleListener event listener.

How to upgrade:

The default locale configuration has been moved from session to the main configuration:

Before:

framework:
    session:
        default_locale: en

After:

framework:
    default_locale: en

Whenever you want to get the current locale, call getLocale() on the request (was on the
session before).
This commit is contained in:
Fabien Potencier 2011-10-05 11:09:51 +02:00
parent 8b55541aee
commit 74bc699b27
26 changed files with 264 additions and 152 deletions

View File

@ -59,6 +59,7 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
### HttpFoundation
* [BC BREAK] moved management of the locale from the Session class to the Request class
* added a generic access to the PHP built-in filter mechanism: ParameterBag::filter()
* made FileBinaryMimeTypeGuesser command configurable
* added Request::getUser() and Request::getPassword()

View File

@ -3,11 +3,40 @@ UPGRADE FROM 2.0 to 2.1
* assets_base_urls and base_urls merging strategy has changed
Unlike most configuration blocks, successive values for
``assets_base_urls`` will overwrite each other instead of being merged.
This behavior was chosen because developers will typically define base
URL's for each environment. Given that most projects tend to inherit
configurations (e.g. ``config_test.yml`` imports ``config_dev.yml``)
and/or share a common base configuration (i.e. ``config.yml``), merging
could yield a set of base URL's for multiple environments.
Unlike most configuration blocks, successive values for
``assets_base_urls`` will overwrite each other instead of being merged.
This behavior was chosen because developers will typically define base
URL's for each environment. Given that most projects tend to inherit
configurations (e.g. ``config_test.yml`` imports ``config_dev.yml``)
and/or share a common base configuration (i.e. ``config.yml``), merging
could yield a set of base URL's for multiple environments.
* moved management of the locale from the Session class to the Request class
Configuring the default locale:
Before:
framework:
session:
default_locale: fr
After:
framework:
default_locale: fr
Retrieving the locale from a Twig template:
Before: {{ app.request.session.locale }}
After: {{ app.request.locale }}
Retrieving the locale from a PHP template:
Before: $view['session']->getLocale()
After: $view['request']->getLocale()
Retrieving the locale from PHP code:
Before: $session->getLocale()
After: $request->getLocale()

View File

@ -51,6 +51,7 @@ class Configuration implements ConfigurationInterface
->scalarNode('secret')->isRequired()->end()
->scalarNode('ide')->defaultNull()->end()
->booleanNode('test')->end()
->scalarNode('default_locale')->defaultValue('en')->end()
->end()
;
@ -161,7 +162,6 @@ class Configuration implements ConfigurationInterface
->canBeUnset()
->children()
->booleanNode('auto_start')->defaultFalse()->end()
->scalarNode('default_locale')->defaultValue('en')->end()
->scalarNode('storage_id')->defaultValue('session.storage.native')->end()
->scalarNode('name')->end()
->scalarNode('lifetime')->end()

View File

@ -65,6 +65,8 @@ class FrameworkExtension extends Extension
$container->setParameter('kernel.trust_proxy_headers', $config['trust_proxy_headers']);
$container->setParameter('kernel.default_locale', $config['default_locale']);
if (!empty($config['test'])) {
$loader->load('test.xml');
}
@ -280,7 +282,6 @@ class FrameworkExtension extends Extension
// session
$container->getDefinition('session_listener')->addArgument($config['auto_start']);
$container->setParameter('session.default_locale', $config['default_locale']);
// session storage
$container->setAlias('session.storage', $config['storage_id']);

View File

@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\EventListener;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Routing\RouterInterface;
/**
* Initializes the locale based on the current request.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class LocaleListener
{
private $router;
private $defaultLocale;
public function __construct($defaultLocale = 'en', RouterInterface $router = null)
{
$this->defaultLocale = $defaultLocale;
$this->router = $router;
}
public function onEarlyKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if ($request->hasPreviousSession()) {
$request->setDefaultLocale($request->getSession()->get('_locale', $this->defaultLocale));
} else {
$request->setDefaultLocale($this->defaultLocale);
}
}
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
$request = $event->getRequest();
if ($locale = $request->attributes->get('_locale')) {
$request->setLocale($locale);
if ($request->hasPreviousSession()) {
$request->getSession()->set('_locale', $request->getLocale());
}
}
if (null !== $this->router) {
$this->router->getContext()->setParameter('_locale', $request->getLocale());
}
}
}

View File

@ -88,19 +88,6 @@ class RouterListener
throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e);
}
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
$context = $this->router->getContext();
$session = $request->getSession();
if ($locale = $request->attributes->get('_locale')) {
if ($session) {
$session->setLocale($locale);
}
$context->setParameter('_locale', $locale);
} elseif ($session) {
$context->setParameter('_locale', $session->getLocale());
}
}
}
private function parametersToString(array $parameters)

View File

@ -25,6 +25,7 @@
<xsd:attribute name="trust-proxy-headers" type="xsd:string" />
<xsd:attribute name="ide" type="xsd:string" />
<xsd:attribute name="secret" type="xsd:string" />
<xsd:attribute name="default-locale" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="form">
@ -72,7 +73,6 @@
<xsd:complexType name="session">
<xsd:attribute name="storage-id" type="xsd:string" />
<xsd:attribute name="default-locale" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="lifetime" type="xsd:integer" />
<xsd:attribute name="path" type="xsd:string" />

View File

@ -14,7 +14,6 @@
<services>
<service id="session" class="%session.class%">
<argument type="service" id="session.storage" />
<argument>%session.default_locale%</argument>
</service>
<service id="session.storage.native" class="%session.storage.native.class%" public="false">

View File

@ -37,7 +37,6 @@
<argument key="debug">%kernel.debug%</argument>
<argument key="charset">%kernel.charset%</argument>
</argument>
<argument type="service" id="session" on-invalid="ignore" />
</service>
<service id="translator" class="%translator.identity.class%">

View File

@ -8,6 +8,7 @@
<parameter key="controller_resolver.class">Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver</parameter>
<parameter key="controller_name_converter.class">Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser</parameter>
<parameter key="response_listener.class">Symfony\Component\HttpKernel\EventListener\ResponseListener</parameter>
<parameter key="locale_listener.class">Symfony\Bundle\FrameworkBundle\EventListener\LocaleListener</parameter>
</parameters>
<services>
@ -27,5 +28,12 @@
<tag name="kernel.event_listener" event="kernel.response" method="onKernelResponse" />
<argument>%kernel.charset%</argument>
</service>
<service id="locale_listener" class="%locale_listener.class%">
<tag name="kernel.event_listener" event="kernel.request" method="onEarlyKernelRequest" priority="255" />
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="-1" />
<argument>%kernel.default_locale%</argument>
<argument type="service" id="router" on-invalid="ignore" />
</service>
</services>
</container>

View File

@ -46,6 +46,16 @@ class RequestHelper extends Helper
return $this->request->get($key, $default);
}
/**
* Returns the locale
*
* @return string
*/
public function getLocale()
{
return $this->request->getLocale();
}
/**
* Returns the canonical name of this helper.
*

View File

@ -46,16 +46,6 @@ class SessionHelper extends Helper
return $this->session->get($name, $default);
}
/**
* Returns the locale
*
* @return string
*/
public function getLocale()
{
return $this->session->getLocale();
}
public function getFlash($name, $default = null)
{
return $this->session->getFlash($name, $default);

View File

@ -2,6 +2,7 @@
$container->loadFromExtension('framework', array(
'secret' => 's3cr3t',
'default_locale' => 'fr',
'form' => null,
'csrf_protection' => array(
'enabled' => true,
@ -19,7 +20,6 @@ $container->loadFromExtension('framework', array(
),
'session' => array(
'auto_start' => true,
'default_locale' => 'fr',
'storage_id' => 'session.storage.native',
'name' => '_SYMFONY',
'lifetime' => 86400,

View File

@ -6,13 +6,13 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config secret="s3cr3t" ide="file%%link%%format">
<framework:config secret="s3cr3t" ide="file%%link%%format" default-locale="fr">
<framework:csrf-protection enabled="true" field-name="_csrf" />
<framework:form />
<framework:esi enabled="true" />
<framework:profiler only-exceptions="true" />
<framework:router resource="%kernel.root_dir%/config/routing.xml" type="xml" />
<framework:session auto-start="true" default-locale="fr" storage-id="session.storage.native" name="_SYMFONY" lifetime="86400" path="/" domain="example.com" secure="true" httponly="true" />
<framework:session auto-start="true" storage-id="session.storage.native" name="_SYMFONY" lifetime="86400" path="/" domain="example.com" secure="true" httponly="true" />
<framework:templating assets-version="SomeVersionScheme" cache="/path/to/cache" >
<framework:loader>loader.foo</framework:loader>
<framework:loader>loader.bar</framework:loader>

View File

@ -1,5 +1,6 @@
framework:
secret: s3cr3t
default_locale: fr
form: ~
csrf_protection:
enabled: true
@ -13,7 +14,6 @@ framework:
type: xml
session:
auto_start: true
default_locale: fr
storage_id: session.storage.native
name: _SYMFONY
lifetime: 86400

View File

@ -76,8 +76,7 @@ abstract class FrameworkExtensionTest extends TestCase
$container = $this->createContainerFromFile('full');
$this->assertTrue($container->hasDefinition('session'), '->registerSessionConfiguration() loads session.xml');
$this->assertEquals('fr', $container->getParameter('session.default_locale'));
$this->assertEquals('%session.default_locale%', $container->getDefinition('session')->getArgument(1));
$this->assertEquals('fr', $container->getParameter('kernel.default_locale'));
$this->assertTrue($container->getDefinition('session_listener')->getArgument(1));
$this->assertEquals('session.storage.native', (string) $container->getAlias('session.storage'));

View File

@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Tests\EventListener;
use Symfony\Bundle\FrameworkBundle\EventListener\LocaleListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class LocaleListenerTest extends \PHPUnit_Framework_TestCase
{
public function testDefaultLocaleWithoutSession()
{
$listener = new LocaleListener('fr');
$event = $this->getEvent($request = Request::create('/'));
$listener->onEarlyKernelRequest($event);
$this->assertEquals('fr', $request->getLocale());
}
public function testDefaultLocaleWithSession()
{
$request = Request::create('/');
session_name('foo');
$request->cookies->set('foo', 'value');
$session = $this->getMock('Symfony\Component\HttpFoundation\Session', array('get'), array(), '', false);
$session->expects($this->once())->method('get')->will($this->returnValue('es'));
$request->setSession($session);
$listener = new LocaleListener('fr');
$event = $this->getEvent($request);
$listener->onEarlyKernelRequest($event);
$this->assertEquals('es', $request->getLocale());
}
public function testLocaleFromRequestAttribute()
{
$request = Request::create('/');
session_name('foo');
$request->cookies->set('foo', 'value');
$request->attributes->set('_locale', 'es');
$listener = new LocaleListener('fr');
$event = $this->getEvent($request);
// also updates the session _locale value
$session = $this->getMock('Symfony\Component\HttpFoundation\Session', array('set'), array(), '', false);
$session->expects($this->once())->method('set')->with('_locale', 'es');
$request->setSession($session);
$listener->onKernelRequest($event);
$this->assertEquals('es', $request->getLocale());
}
public function testLocaleSetForRoutingContext()
{
// the request context is updated
$context = $this->getMock('Symfony\Component\Routing\RequestContext');
$context->expects($this->once())->method('setParameter')->with('_locale', 'es');
$router = $this->getMock('Symfony\Component\Routing\Router', array('getContext'), array(), '', false);
$router->expects($this->once())->method('getContext')->will($this->returnValue($context));
$request = Request::create('/');
$request->attributes->set('_locale', 'es');
$listener = new LocaleListener('fr', $router);
$listener->onKernelRequest($this->getEvent($request));
}
private function getEvent(Request $request)
{
return new GetResponseEvent($this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'), $request, HttpKernelInterface::MASTER_REQUEST);
}
}

View File

@ -39,6 +39,13 @@ class RequestHelperTest extends \PHPUnit_Framework_TestCase
$this->assertNull($helper->getParameter('foo'));
}
public function testGetLocale()
{
$helper = new RequestHelper($this->request);
$this->assertEquals('en', $helper->getLocale());
}
public function testGetName()
{
$helper = new RequestHelper($this->request);

View File

@ -60,13 +60,6 @@ class SessionHelperTest extends \PHPUnit_Framework_TestCase
$this->assertNull($helper->get('foo'));
}
public function testGetLocale()
{
$helper = new SessionHelper($this->request);
$this->assertEquals('en', $helper->getLocale());
}
public function testGetName()
{
$helper = new SessionHelper($this->request);

View File

@ -15,7 +15,6 @@ use Symfony\Component\Translation\Translator as BaseTranslator;
use Symfony\Component\Translation\Loader\LoaderInterface;
use Symfony\Component\Translation\MessageSelector;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Session;
use Symfony\Component\Config\ConfigCache;
/**
@ -27,7 +26,6 @@ class Translator extends BaseTranslator
{
protected $container;
protected $options;
protected $session;
protected $loaderIds;
/**
@ -42,11 +40,9 @@ class Translator extends BaseTranslator
* @param MessageSelector $selector The message selector for pluralization
* @param array $loaderIds An array of loader Ids
* @param array $options An array of options
* @param Session $session A Session instance
*/
public function __construct(ContainerInterface $container, MessageSelector $selector, $loaderIds = array(), array $options = array(), Session $session = null)
public function __construct(ContainerInterface $container, MessageSelector $selector, $loaderIds = array(), array $options = array())
{
$this->session = $session;
$this->container = $container;
$this->loaderIds = $loaderIds;
@ -74,8 +70,8 @@ class Translator extends BaseTranslator
*/
public function getLocale()
{
if (null === $this->locale && null !== $this->session) {
$this->locale = $this->session->getLocale();
if (null === $this->locale && $this->container->has('request')) {
$this->locale = $this->container->get('request')->getLocale();
}
return $this->locale;

View File

@ -7,8 +7,8 @@ framework:
validation: { enabled: true, enable_annotations: true }
form: ~
test: ~
default_locale: en
session:
default_locale: en
auto_start: true
storage_id: session.storage.filesystem

View File

@ -82,6 +82,8 @@ class Request
protected $method;
protected $format;
protected $session;
protected $locale;
protected $defaultLocale = 'en';
static protected $formats;
@ -892,6 +894,21 @@ class Request
$this->format = $format;
}
public function setDefaultLocale($locale)
{
$this->setPhpDefaultLocale($this->defaultLocale = $locale);
}
public function setLocale($locale)
{
$this->setPhpDefaultLocale($this->locale = $locale);
}
public function getLocale()
{
return null === $this->locale ? $this->defaultLocale : $this->locale;
}
/**
* Checks whether the method is safe or not.
*
@ -1262,4 +1279,17 @@ class Request
'rss' => array('application/rss+xml'),
);
}
private function setPhpDefaultLocale($locale)
{
// if either the class Locale doesn't exist, or an exception is thrown when
// setting the default locale, the intl module is not installed, and
// the call can be ignored:
try {
if (class_exists('Locale', false)) {
\Locale::setDefault($locale);
}
} catch (\Exception $e) {
}
}
}

View File

@ -27,25 +27,19 @@ class Session implements \Serializable
protected $attributes;
protected $flashes;
protected $oldFlashes;
protected $locale;
protected $defaultLocale;
protected $closed;
/**
* Constructor.
*
* @param SessionStorageInterface $storage A SessionStorageInterface instance
* @param string $defaultLocale The default locale
* @param SessionStorageInterface $storage A SessionStorageInterface instance
*/
public function __construct(SessionStorageInterface $storage, $defaultLocale = 'en')
public function __construct(SessionStorageInterface $storage)
{
$this->storage = $storage;
$this->defaultLocale = $defaultLocale;
$this->locale = $defaultLocale;
$this->flashes = array();
$this->oldFlashes = array();
$this->attributes = array();
$this->setPhpDefaultLocale($this->defaultLocale);
$this->started = false;
$this->closed = false;
}
@ -68,8 +62,6 @@ class Session implements \Serializable
if (isset($attributes['attributes'])) {
$this->attributes = $attributes['attributes'];
$this->flashes = $attributes['flashes'];
$this->locale = $attributes['locale'];
$this->setPhpDefaultLocale($this->locale);
// flag current flash messages to be removed at shutdown
$this->oldFlashes = $this->flashes;
@ -183,7 +175,6 @@ class Session implements \Serializable
$this->attributes = array();
$this->flashes = array();
$this->setPhpDefaultLocale($this->locale = $this->defaultLocale);
}
/**
@ -224,30 +215,6 @@ class Session implements \Serializable
return $this->storage->getId();
}
/**
* Returns the locale
*
* @return string
*/
public function getLocale()
{
return $this->locale;
}
/**
* Sets the locale.
*
* @param string $locale
*/
public function setLocale($locale)
{
if (false === $this->started) {
$this->start();
}
$this->setPhpDefaultLocale($this->locale = $locale);
}
/**
* Gets the flash messages.
*
@ -356,7 +323,6 @@ class Session implements \Serializable
$this->storage->write('_symfony2', array(
'attributes' => $this->attributes,
'flashes' => $this->flashes,
'locale' => $this->locale,
));
}
@ -380,26 +346,13 @@ class Session implements \Serializable
public function serialize()
{
return serialize(array($this->storage, $this->defaultLocale));
return serialize($this->storage);
}
public function unserialize($serialized)
{
list($this->storage, $this->defaultLocale) = unserialize($serialized);
$this->storage = unserialize($serialized);
$this->attributes = array();
$this->started = false;
}
private function setPhpDefaultLocale($locale)
{
// if either the class Locale doesn't exist, or an exception is thrown when
// setting the default locale, the intl module is not installed, and
// the call can be ignored:
try {
if (class_exists('Locale', false)) {
\Locale::setDefault($locale);
}
} catch (\Exception $e) {
}
}
}

View File

@ -127,11 +127,7 @@ class HttpUtils
try {
$parameters = $this->router->match($request->getPathInfo());
if (isset($parameters['_locale'])) {
$context->setParameter('_locale', $parameters['_locale']);
} elseif ($session = $request->getSession()) {
$context->setParameter('_locale', $session->getLocale());
}
$context->setParameter('_locale', isset($parameters['_locale']) ? $parameters['_locale'] : $request->getLocale());
} catch (\Exception $e) {
// let's hope user doesn't use the locale in the path
}

View File

@ -146,10 +146,7 @@ class RequestMatcherTest extends \PHPUnit_Framework_TestCase
{
$matcher = new RequestMatcher();
$request = Request::create('/en/login');
$session = new Session(new ArraySessionStorage());
$session->setLocale('en');
$request->setSession($session);
$request->setLocale('en');
$matcher->matchPath('^/{_locale}/login$');
$this->assertFalse($matcher->matches($request));

View File

@ -128,34 +128,28 @@ class SessionTest extends \PHPUnit_Framework_TestCase
public function testSerialize()
{
$defaultLocale = 'en';
$this->session = new Session($this->storage, $defaultLocale);
$this->session = new Session($this->storage);
$compare = serialize(array($this->storage, $defaultLocale));
$compare = serialize($this->storage);
$this->assertSame($compare, $this->session->serialize());
$this->session->unserialize($compare);
$_defaultLocale = new \ReflectionProperty(get_class($this->session), 'defaultLocale');
$_defaultLocale->setAccessible(true);
$_storage = new \ReflectionProperty(get_class($this->session), 'storage');
$_storage->setAccessible(true);
$this->assertEquals($_defaultLocale->getValue($this->session), $defaultLocale, 'options match');
$this->assertEquals($_storage->getValue($this->session), $this->storage, 'storage match');
}
public function testSave()
{
$this->storage = new ArraySessionStorage();
$defaultLocale = 'fr';
$this->session = new Session($this->storage, $defaultLocale);
$this->session = new Session($this->storage);
$this->session->set('foo', 'bar');
$this->session->save();
$compare = array('_symfony2' => array('attributes' => array('foo' => 'bar'), 'flashes' => array(), 'locale' => 'fr'));
$compare = array('_symfony2' => array('attributes' => array('foo' => 'bar'), 'flashes' => array()));
$r = new \ReflectionObject($this->storage);
$p = $r->getProperty('data');
@ -164,27 +158,6 @@ class SessionTest extends \PHPUnit_Framework_TestCase
$this->assertSame($p->getValue($this->storage), $compare);
}
public function testLocale()
{
$this->assertSame('en', $this->session->getLocale(), 'default locale is en');
$this->assertSame('en', \Locale::getDefault(), '\Locale::getDefault() is en');
$this->session->setLocale('de');
$this->assertSame('de', $this->session->getLocale(), 'locale is de');
$this->assertSame('de', \Locale::getDefault(), '\Locale::getDefault() is de');
$this->session = $this->getSession();
$this->session->setLocale('fr');
$this->assertSame('fr', $this->session->getLocale(), 'locale is fr');
$this->assertSame('fr', \Locale::getDefault(), '\Locale::getDefault() is fr');
}
public function testLocaleAfterClear()
{
$this->session->clear();
$this->assertEquals('en', $this->session->getLocale());
}
public function testGetId()
{
$this->assertNull($this->session->getId());
@ -194,9 +167,6 @@ class SessionTest extends \PHPUnit_Framework_TestCase
{
$this->session->start();
$this->assertSame('en', $this->session->getLocale());
$this->assertSame('en', \Locale::getDefault());
$this->assertSame(array(), $this->session->getFlashes());
$this->assertSame(array(), $this->session->all());
}
@ -210,7 +180,6 @@ class SessionTest extends \PHPUnit_Framework_TestCase
$expected = array(
'attributes'=>array('foo'=>'bar'),
'flashes'=>array(),
'locale'=>'en'
);
$saved = $this->storage->read('_symfony2');
$this->assertSame($expected, $saved);
@ -227,7 +196,6 @@ class SessionTest extends \PHPUnit_Framework_TestCase
$expected = array(
'attributes'=>array('foo'=>'bar'),
'flashes'=>array(),
'locale'=>'en'
);
$saved = $this->storage->read('_symfony2');
$this->assertSame($expected, $saved);