Saving PHP Sessions in Redis

Share this article

Sessions allow a web-based application to maintain state across multiple HTTP requests. You can register any number of variables as session variables which are then typically stored in a temporary directory on the server or passed to the client browser through cookies. Subsequent requests have access to the saved information and the application’s state is preserved across multiple requests. PHP’s default handling of session data is probably sufficient for most applications, but sometimes a project will demand a different storage approach. Luckily, the session handling routines can be overridden either by a series of functions (in older versions of PHP) or a class with methods (in newer versions) which handle various aspects of session management. In this article we’ll learn how to create a custom session handler which implements PHP’s SessionHandlerInterface interface and stores the session data in a Redis database.

Why Custom Storage?

You may be wondering why someone might want (or need) to go through the trouble of writing a custom handler. If so, it may be beneficial to consider the following. It’s common when applications reside within a server farm for each host to handle requests for load balancing and redundancy. Session data stored in temporary files would only be accessible to their particular host. Since the server handling a request may not be the same one that handled the previous request, guaranteed access to state information is impossible without an alternative handling mechanism. Session data could be stored to a central storage mechanism and be made available to all machines in the cluster. On a shared hosting server, it’s likely that all temporary session files are stored within the same directory. The directory and its contents is accessible to any processes initiated under the web service account which has the potential to pose a serious security risk. A malicious user who can access someone else’s session data has the ability to impersonate the legitimate user. Custom session handling also provides a greater degree of flexibility. Because you’re specifying a new method of storage and access for the information, you can manipulate the data any way you want to meet your needs. Perhaps you may want to retain information for security auditing or troubleshooting. Custom handlers can be used to as hooks to expand an application in terms of size and functionality in any number of ways.

Session Management Actions

Since we’re going to manage our own session data instead of letting PHP handle it for us, any code we write needs to address each of the six session management tasks.
  • open – opens a resource to the storage mechanism designed to save session information.
  • close – closes the storage resource and initiates any other necessary cleanup activities.
  • read – retrieves previously saved session data from the storage mechanism.
  • write – stores new session data which the application needs to remember.
  • destroy – resets a session and discards any information from it.
  • gc – purges data from the storage mechanism after it has become stale and is no longer needed.
Each of these tasks must be addressed when writing custom session handlers; PHP will not let us override the open process but forget to override the garbage collection process, for example. It’s all or nothing. In versions of PHP prior to 5.4, we would need to specify six callables (one function or method for handling each task) in the order above as arguments to the session_set_save_handler() function. In 5.4 and later, we can create a class that implements the SessionHandlerInterface interface and pass an instance instead. The interface exposes six methods with the same signatures as would be used for the functions in the pre-5.4 approach.

Storage Mechanism

Your choice of a storage mechanism is dictated by your needs. It could be anything—remote temporary files, a MySQL database, an LDAP server, shared memory segments, an XML-RPC service, an IMAP inbox, etc. For this article I’m going to illustrate storing session data in Redis. PHP automatically generates a unique identifier to track the session and link it to a specific client. Because this token is unique for each session, the identifier is well suited for use as a key (in fact, this token usually serves as the filename in PHP’s default disk-based handling approach). The session data is also automatically serialized and unserialized by PHP. That is, the method that receives the data for storing is passed the data already serialized, and the method that retrieves the data is expected to return serialized data. The functions session_encode() and session_decode() are also available if we need them for any reason, but generally we can simply store and retrieve the session data as provided. Of course it’s important to clean up stale sessions that are no longer needed. For example, if we’re storing session data in a MySQL database for example, we’d want to include a timestamp with each record. The timestamp would be checked by our method that overrides the garbage collection behavior. But with Redis, we can take advantage of its EXPIRE command. EXPIRE sets a timeout or a TTL (time to live) on a key, and the key is automatically deleted after the timeout expires.

Show Me the Code!

We now know what functionality the SessionHandlerInterface interface promises, and we have a rough idea how the methods should interact with Redis, we can write our code. Without further ado, here’s the class:
<?php
class RedisSessionHandler implements SessionHandlerInterface
{
    public $ttl = 1800; // 30 minutes default
    protected $db;
    protected $prefix;

    public function __construct(PredisClient $db, $prefix = 'PHPSESSID:') {
        $this->db = $db;
        $this->prefix = $prefix;
    }

    public function open($savePath, $sessionName) {
        // No action necessary because connection is injected
        // in constructor and arguments are not applicable.
    }

    public function close() {
        $this->db = null;
        unset($this->db);
    }

    public function read($id) {
        $id = $this->prefix . $id;
        $sessData = $this->db->get($id);
        $this->db->expire($id, $this->ttl);
        return $sessData;
    }

    public function write($id, $data) {
        $id = $this->prefix . $id;
        $this->db->set($id, $data);
        $this->db->expire($id, $this->ttl);
    }

    public function destroy($id) {
        $this->db->del($this->prefix . $id);
    }

    public function gc($maxLifetime) {
        // no action necessary because using EXPIRE
    }
}
The first thing you might notice is that the open() and gc() methods are empty. I’ve made use of dependency injection to provide the Redis connection which opens the class up to unit testing, so nothing needs to be done in open(). Nothing needs to be done in gc() because Redis will handle expiring stale keys for us. In addition to the Redis connection, the constructor also accepts a prefix. The prefix and the session ID generated by PHP are combined and used as the key for storing and retrieving values. This is primarily a means to prevent name collisions, but also provides the benefit that we’ll know what we’re looking at if we issue KEYS * in a Redis client. When PHP calls the write() method, it passes two values: the session ID and a serialized string of session data. A SET command is used to store the data in Redis and we touch the key’s TTL. Any entries placed in the super global $_SESSION array are now stored. When PHP calls the read() method, it passes the session ID. A GET command is used to retrieves the data and we also touch the TTL again – after all, if we’re accessing the session then it makes sense to consider it still fresh. Note that the session data is returned in its serialized form directly from storage. PHP receives the string, unserializes it, and populates the $_SESSION array. Using our handler is as simple as creating an instance and passing that instance to session_set_save_handler().
<?php
$db = new PredisClient();
$sessHandler = new RedisSessionHandler($db);
session_set_save_handler($sessHandler);
session_start();
When session_start() is called, PHP will use our custom handler to manage sessions instead of its default approach; no other modification to our code is necessary. If you’re stuck on a version of PHP earlier than 5.4, you can still use the class above (although you’ll have to either mock SessionHandlerInterface or remove the implements piece entirely). Its registration would look like this:
<?php
$db = new PredisClient();
$sessHandler = new RedisSessionHandler($db);
session_set_save_handler(
    array($sessHandler, 'open'),
    array($sessHandler, 'close'),
    array($sessHandler, 'read'),
    array($sessHandler, 'write'),
    array($sessHandler, 'destroy'),
    array($sessHandler, 'gc')
);
session_start();

Conclusion

In this article we’ve seen how easy it is to implement the SessionHandlerInterface interface and provide the logic needed by PHP to store session data in a Redis database. Custom session handling is transparent at the code level and the result is an exciting way to increase the security and flexibility of you application with very little effort! For more information, read what the PHP Manual has to say about custom session handling, and explore the accompanying code for this article on GitHub. Image via Fotolia

Frequently Asked Questions (FAQs) about Saving PHP Sessions in Redis

How Can I Install Redis on My Server?

To install Redis on your server, you need to follow a few simple steps. First, update your system packages using the command sudo apt-get update. Then, install Redis by typing sudo apt-get install redis-server. After the installation is complete, you can start Redis with the command sudo service redis-server start. To ensure Redis starts automatically at boot, use sudo update-rc.d redis-server defaults.

What Are the Benefits of Using Redis for PHP Sessions?

Redis offers several benefits when used for PHP sessions. It provides faster data access as it stores data in memory, leading to improved application performance. Redis also supports data replication, which ensures data safety and availability. Additionally, it supports various data structures like strings, hashes, lists, sets, and more, providing flexibility in handling session data.

How Can I Configure PHP to Use Redis for Session Handling?

To configure PHP to use Redis for session handling, you need to modify the php.ini file. Locate the session handler section and set session.save_handler to redis and session.save_path to the Redis server’s location. For example, session.save_path = "tcp://localhost:6379". Save the changes and restart your PHP service for the changes to take effect.

How Can I Verify That PHP Sessions Are Being Stored in Redis?

You can verify that PHP sessions are being stored in Redis by using the Redis CLI. Connect to your Redis server using the command redis-cli. Then, list all keys stored in Redis using the KEYS * command. If PHP sessions are being stored in Redis, you should see keys that start with PHPREDIS_SESSION:.

What Should I Do If Redis Is Not Saving PHP Sessions?

If Redis is not saving PHP sessions, check your PHP and Redis configurations. Ensure that the session.save_handler and session.save_path settings in your php.ini file are correctly set to use Redis. Also, check that your Redis server is running and accessible from your PHP server.

How Can I Secure My Redis Server?

To secure your Redis server, you can set a password. Open the Redis configuration file (redis.conf) and locate the requirepass directive. Set it to a strong password. Save the changes and restart your Redis server. Now, clients will need to authenticate using the AUTH command before they can execute commands.

Can I Use Redis for PHP Sessions in a Clustered Environment?

Yes, you can use Redis for PHP sessions in a clustered environment. Redis supports master-slave replication, which allows you to replicate data across multiple servers. This ensures high availability and data redundancy, making it suitable for use in a clustered environment.

How Can I Monitor My Redis Server?

You can monitor your Redis server using the INFO command, which provides information about the server’s state. You can also use the MONITOR command, which streams every command processed by the server. For more detailed monitoring, consider using tools like RedisInsight or Redis Monitor.

How Can I Optimize Redis for Session Handling?

To optimize Redis for session handling, consider enabling key expiration. This automatically removes old session data, freeing up memory. You can set the maxmemory policy in your redis.conf file to control how Redis should handle memory usage. Also, consider using a separate Redis instance for session data to avoid contention with other data.

Can I Use Redis for PHP Sessions in a Shared Hosting Environment?

Whether you can use Redis for PHP sessions in a shared hosting environment depends on your hosting provider. Some providers may not support Redis, while others may offer it as an add-on service. Check with your hosting provider to see if they support Redis. If they do, you can follow the same steps to configure PHP to use Redis for session handling.

Timothy BoronczykTimothy Boronczyk
View Author

Timothy Boronczyk is a native of Syracuse, New York, where he lives with no wife and no cats. He has a degree in Software Application Programming, is a Zend Certified Engineer, and a Certified Scrum Master. By day, Timothy works as a developer at ShoreGroup, Inc. By night, he freelances as a writer and editor. Timothy enjoys spending what little spare time he has left visiting friends, dabbling with Esperanto, and sleeping with his feet off the end of his bed.

Advanced
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week