symfony/symfony — pull request #2853 by drak (2011-12-12 16:36:36+00:00)
Title: [WIP][HttpFoundation] Refactor session handling and flash messages

Bug fix: yes
Feature addition: yes
Backwards compatibility break: yes
Symfony2 tests pass: yes
Fixes the following tickets: #2607, #2591, #2717, #1863, #2773
References the following tickets: #2592, #2543, #2541, #2510
Todo: -

__Introduction__

This extensive PR is a refactor with minimal BC breaks of the `[HttpFoundation]` component's session management which fixes several issues in the current implementation. This PR includes all necessary changes to other bundles and components is documented in the `CHANGELOG-2.1` and `UPGRADING-2.1`.

__Unit Tests__

All unit and functional tests pass.

__Summary of Changes__

- Session object now implements `SessionInterface`
- Attributes now handled by `AttributeBagInterface`
- Added two AttributeBag implementations: `AttributeBag` replicates the current Symfony2 attributes behaviour, and the second, `NamespacedAttributeBag` introduces structured namespaced representation using '/' in the key. Both are BC. `FrameworkBundle` defaults to the old behaviour at the moment
- Flash messages now handled by `FlashBagInterface`
- Flash messages now support flash types and can store multiple messages per type. By default, the component offers four types, `INFO`, `NOTICE`, `WARNING`, `ERROR` and can be extended as required.
- Changed the way flash messages expire, they now expire on use rather than automatically
- Introduced a base object, `AbstractSessionStorage` for session storage drivers
- Introduced a `SessionSaveHandlerInterface` when using custom session save handlers
- Introduced a `NullSessionStorage` driver which allows for unsaved sessions
- Introduced new session storage drivers for Memcache and Memcached
- Introduced new session storage drivers for PHP's native SQLite, Memcache and Memcached support
- Fixed bugs where session attributes are not saved and all cases where flash messages would get lost
- Wrote new tests, refactored related existing tests and increased test coverage extensively.

__Rationale/Details__

I'll explain more detail in the following sections.

__Functional Testing__

I've introduced `FunctionalTestFileSessionStorage` which replaces `FilesystemSessionStorage` to emulate a PHP session for functional testing. Essentially the same methodology of functional testing has been maintained but without interrupting the other session storage drivers interaction with real PHP sessions.

__Session Workflow__

PHP sessions follow a specific workflow which is not being followed by the current session management implementation and is responsible for some unpredictable bugs and behaviours.

Basically, PHP session workflow is as follows: `open`, `read`, `write`, `close`. In between these can occur, `destroy` and `garbage collection`. These actions are handled by `session save handlers` and one is always registered in all cases. By default, the `files` save handler (internally to PHP) is registered by PHP at the start of code execution.

PHP offers the possibility to change the save handler to another internal type, for example one provided by a PHP extension (think SQLite, Memcache etc), or you can register a user type and set your own handlers. However __in all cases__ PHP requires the handlers.

The handlers are called when the following things occur:

- `open` and `read` when `session_start()` or the session autostarts when PHP outputs some display
- `destroy` when `session_regenerate_id(true)` is called
- `write` and `close` when PHP shuts down or when `session_write_close()` is called
- `garbage collection` is called randomly according to configurable probability

The next VERY IMPORTANT aspect of this PR is that `$_SESSION` plays an important part in this workflow because the contents of the $_SESSION is populated __after__ the `read` handler returns the previously saved serialised session data. The `write` handler is sent the serialised `$_SESSION` contents for save. Please note the serialisation is a different format to `serialize()`.

For this reason, any session implementation cannot get rid of using `$_SESSION`.

I wrote more details on this issue [here](https://github.com/symfony/symfony/issues/2607#issuecomment-2858300)

In order to make writing session storage drivers simple, I created a light base class `AbstractSessionStorage` and the `SessionSaveHandlerInterface` which allows you to quickly write native and custom save handler drivers.

__Flash Messages [BC BREAK]__

Flash messages currently allow representation of a single message per `$name`. Fabien designed the original system so that `$name` was equivalent to flash message type. The current PR changes three things relating to flash messages.

- The flash messages API may store multiple messages per type
- Introduced four default flash message types using constants, `INFO`, `NOTICE`, `WARNING`, `ERROR` and defaults to `NOTICE`
- Flash messages expire explicitly when retrieved for display to the user as opposed to immediately on the next page load.

The last issue fixes potential cases when flash messages are lost due to an unexpected intervening page-load (an error for example). The API `get()` has a flag which allows you to override the `clear()` action.

__Flash message translation__

This PR does not cover translation of flash messages because messages should be translated before calling the flash message API. This is because flash messages are used to present messages to the user after a specific action, and in any case, immediately on the next page load. Since we know the locale of the request in every case we can translate the message before storing. Secondly, translation is simply a string manipulation. Translation API calls should always have the raw untranslated string present because it allows for extraction of translation catalogs. For a complete answer see my answer [here](https://github.com/symfony/symfony/pull/2543#issuecomment-2858707)

__Session attribute and structured namespacing__

__This has been implemented without changing the current default behaviour__ but details are below:

Attributes are currently stored in a flat array which limits the potential of session attributes:

Here are some examples to see why this 'structured namespace' methodology is extremely convenient over using a flat system. Let's look at an example with csrf tokens. Let's say we have multiple csrftokens stored by form ID (allowing multiple forms on the page and tabbed browsing).

If we're using a flat system, you might have

'tokens' => array('a' => 'a6c1e0b6',
'b' => 'f4a7b1f3')

With a flat system when you get the key `tokens`, you will get back an array, so now you have to analyse the array. So if you simply want to add another token, you have to follow three steps: get the session attribute `tokens`, have to add to the array, and lastly set the entire array back to the session.

$tokens = $session->get('tokens');
$tokens['c'] = $value;
$session->set('tokens', $tokens);

Doable, but you can see it's pretty long winded.

With structured namespacing you can simply do:

$session->set('c', $value, '/tokens');

There are several ways to implement this, either with an additional `$namespace` argument, or by treating a character in the `$key` as a namespacer. `NamespacedAttributeBag` currently treats `/` as a namespacer so you can represent `user.tokens/a` for example. The namespace character is configurable in `NamespacedAttributeBag`.