Reading Arbitrary Session Data in PHP

A requirement for a web app I’m currently working on is that an admin should be able to see a list of a user’s open sessions. I had no idea how to approach it at first — ultimately it was much less daunting than it appeared, though not without its challenges.

The biggest of these was reliably unserializing arbitrary session data (i.e., not the current session). By default, PHP does not serialize session data like it serializes all other arrays — and session_decode, the function it uses to unserialize session data, can only ever populate $_SESSION, not a variable of your choosing.

The special format is basically key|value[;keyN|valueN], where key is a simple string and value is a serialized value. It’s tempting to rely on the pipe and semi-colon and use a regular expression to extract the keys and values — in fact, if you look at the comments on the documentation for session_decode, you’ll find a number of increasingly complex and unsightly examples. But as this comment notes, a regular expression will never be completely reliable.

(I’ll take a slight detour here to say that for this reason, I tried using wddx as my session.serialize_handler setting. This seemed promising — just use wddx_unserialize and voilà. It did work initially, but it turns out there was a fatal incompatibility with Symfony. When a user logs in with bad credentials, Symfony puts the exception object in the session; the exception object references the request object, which itself references the session object. wddx_serialize cannot handle circular references and throws an exception.)

The solution in the comment above was okay, but I couldn’t pass up a little code golf challenge. The following is much simpler:

$session = [];
while ($serialized) {
    list($key, $value) = explode("|", $serialized, 2);
    $session[$key] = unserialize($value);
    $serialized = substr($value, strlen(serialize($session[$key])));
}

This reliably works for two reasons: 1) a session key will never contain a pipe, and 2) as noted in the comment above, unserialize will return the first value it can complete, ignoring any trailing characters in the string passed to it. It may choke on malformed session data, but then you’d have a different problem to solve.

And then . . . I was so pleased with myself that I completely missed that it was all for naught. PHP 5.5.4 introduced php_serialize as a session.serialize_handler option, which gets rid of that old pipe-delimited business in favor of simply serializing session data like any other array. But if you’re not yet at 5.5.4, the above should work great.