Symfony Sessions introduction 23/04/2014
Reference: This article is intended to be as complete as possible and is kept up to date.
Last reviewed: 22/02/2016.
TL;DR: jump to the conclusion.
The Symfony HttpFoundation component
is a library which provides an Object Oriented implementation of the HTTP
protocol: it wraps PHP's variable superglobals ($_POST
, $_GET
, $_SERVER
,
etc) inside objects (Request
, Response
, Session
, etc).
The idea behind it: web applications should receive a Request and return a Response.
In this article, we'll focus on the Session management which solves many issues (for example the PHP session already started error nightmare).
This introduction will show you how to use it in a "non-symfony" project:
Basics
In almost any cases, you'll only deal with the following three methods of the
Session
object:
<?php
namespace Symfony\Component\HttpFoundation\Session;
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
class Session implements SessionInterface, \IteratorAggregate, \Countable
{
public function registerBag(SessionBagInterface $bag);
public function start();
public function getBag($name);
}
A Bag
is a group of attributes stored in the session. Again, in most cases
you'll only deal with the following four methods of the AttributeBag
object:
<?php
namespace Symfony\Component\HttpFoundation\Session\Attribute;
class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable
{
public function __construct($storageKey = '_sf2_attributes');
public function setName($name);
public function get($name, $default = null);
public function set($name, $value);
}
When using the sessions, you'll generally need to bootstrap things up as follows:
<?php
$session = new Session();
$myAttributeBag = new AttributeBag('my_storage_key');
$myAttributeBag->setName('some_descriptive_name');
$session->registerBag($myAttributeBag);
$session->start();
The session MUST be started by Symfony, and it SHOULD be started after the bag registrations.
Examples
Here's some code samples to make things clear.
Simple attributes
Let's assume that our session looks like this:
<?php
$_SESSION = array(
'user' => array(
'first_name' => 'Arthur',
'last_name' => 'Dent',
),
);
Here's the bootstrap code we need:
<?php
$session = new Session();
$userAttributeBag = new AttributeBag('user');
$session->registerBag($userAttributeBag);
$session->start();
The equivalent to:
<?php
$firstName = 'Ford';
if (isset($_SESSION['user']['first_name'])) {
$firstName = $_SESSION['user']['first_name'];
}
$_SESSION['user']['last_name'] = 'Prefect';
Would be:
<?php
$userAttributeBag = $session->getBag('user');
$firstName = $userAttributeBag->get('first_name', 'Ford');
$userAttributeBag->set('last_name', 'Prefect');
Deep attributes
Now, let's assume we have a session which has deep attributes:
<?php
$_SESSION = array(
'authentication' => array(
'tokens' => array(
'github' => 'A45E96F',
'twitter' => '11AEBC980D456E4EF',
),
),
);
Here's the bootstrap code we need:
<?php
$session = new Session();
$authenticationAttributeBag = new NamespacedAttributeBag('authentication');
$session->registerBag($authenticationAttributeBag);
$session->start();
The equivalent to:
<?php
$_SESSION['authentication']['tokens']['github'] = 'AEB558F02C3B346';
Would be:
<?php
$authenticationAttributeBag = $session->getBag($authenticationAttributeBag);
$authenticationAttributeBag->set('tokens/github', 'AEB558F02C3B346');
Going further
The Session
has been designed to contain a group of attribute bags. But when
working with legacy sessions, you might have to access attributes which are
located at the root of the session. Here's how to extend the Session
to allow
this.
Root attributes
A root attribute might look like:
<?php
$_SESSION = array(
'attribute' => 'value',
);
You need to create your own kind of Bag
:
<?php
namespace Acme\Session;
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
class RootAttributeBag implements SessionBagInterface
{
private $name = 'single_attribute';
/** @var string */
private $storageKey;
/** @var mixed */
private $attribute;
public function __construct($storageKey)
{
$this->storageKey = $storageKey;
}
/** {@inheritdoc} */
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
/** {@inheritdoc} */
public function initialize(array &$array)
{
$attribute = !empty($array) ? $array[0] : null;
$this->attribute = &$attribute;
}
/** {@inheritdoc} */
public function getStorageKey()
{
return $this->storageKey;
}
/** {@inheritdoc} */
public function clear()
{
$this->attribute = null;
}
public function get()
{
return $this->attribute;
}
public function set($value)
{
$this->attribute = $value;
}
}
The storage key
will be directly the attribute's key.
We also need to hack a Storage
class which supports our Bag
:
<?php
namespace Acme\Session;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
class LegacySessionStorage extends NativeSessionStorage
{
/** {@inheritdoc} */
protected function loadSession(array &$session = null)
{
if (null === $session) {
$session = &$_SESSION;
}
$bags = array_merge($this->bags, array($this->metadataBag));
foreach ($bags as $bag) {
$key = $bag->getStorageKey();
// We cast $_SESSION[$key] to an array, because of the SessionBagInterface::initialize() signature
$session[$key] = isset($session[$key]) ? (array) $session[$key] : array();
$bag->initialize($session[$key]);
}
$this->started = true;
$this->closed = false;
}
}
Finally, we'll need the following bootstrap code:
<?php
use Acme\Session\LegacySessionStorage;
use Acme\Session\RootAttributeBag;
use Symfony\Component\HttpFoundation\Session\Session;
$sessionStorage = new LegacySessionStorage();
$session = new Session($sessionStorage);
// before: $_SESSION['attribute']
$legacyBag = new RootAttributeBag('attribute');
$legacyBag->setName('legacy');
// after: $session->getBag('legacy')->get()
$session->registerBag($legacyBag);
Documentation
The official documentation provides useful information about how the session use it. For example it explains how to manage flash messages.
It also explains how the session works behind the scene with useful tips on how to write the session in a database.
Some cookbooks are also available. You can find for instance one describing how to use session proxy which is useful if you want to encrypt the session data or to make it read only.
Troubleshooting
The common cases of problems encountered are due to the fact that the session was started before Symfony2 did.
To fix this, check in your php.ini
that the session.auto_start
option is set
to 0
(its default value).
If the session isn't auto started, it means that the application is starting the
session itself. If you cannot prevent this, use
PhpBridgeSessionStorage
with
NativeFileSessionHandler
:
<?php
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage;
$sessionHandler = new NativeFileSessionHandler();
$sessionStorage = new PhpBridgeSessionStorage($sessionHandler);
$session = new Session($sessionStorage);
Another trouble you can encounter: you register some bags but they're always
empty, even though the $_SESSION
contains the targeted values.
This would be because you register your bags after starting the session: if you
can't do otherwise then simply call $session->migrate()
after your bag
registration, this will reload the values.
Finally when doing AJAX request you might notice slow performances, or non persistence of the data. This might be caused by a session locking mechanism which can be solved like this by saving manually the session:
<?php
$session->save();
// session_write_close(); // Only required before Symfony 2.1
Conclusion
By wrapping $_SESSION
and session_*()
functions, Session
allows you to
make your code more testable
(you can mock it)
and to solve starting session issues (just make sure to be the first to start
it).
It's divided into AttributeBag
which are arrays of parameters: this allows you
to organize your session by namespaces.
I hope you found some useful tips in this article, if you have any comments or questions don't be shy and drop me a line on Twitter.