/php-session-automerge

DO NOT USE! WORK IN PROGRESS! A PHP Session Handler with Auto-Merge Algorithm.

Primary LanguagePHPMIT LicenseMIT

PHP Session Handler with Auto-Merge

  • No locking means extremely fast and non-blocking requests
  • Auto-merge reduces the chance of data corruption and merge conflicts
  • Ability to provide custom logic for resolving application-specific merge conflicts
  • Supports Memcached and Redis out of the box, but easy to extend to other storage mechanisms

Basic Usage

This library comes with session handlers for Memcached and Redis (both the PhpRedis extension and the PRedis library).

Step 1: Get a handler instanace. This part depends on what storage back-end you want to use.

<?php
use EduCom\SessionAutomerge\SessionhandlerMemcached;
use EduCom\SessionAutomerge\SessionhandlerPhpRedis;
use EduCom\SessionAutomerge\SessionhandlerPRedis;

// Memcached
$memcached = new Memcached;
$memcached->addServer('localhost', 11211);
$handler = new SessionhandlerMemcached($memcached);

// PhpRedis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$handler = new SessionhandlerPhpRedis($redis);

// PRedis
$client = new Predis\Client('tcp://10.0.0.1:6379');
$handler = new SessionhandlerPRedis($client);

Step 2: Set options if needed

// All options are public properties and have sensible defaults
// You can override any of them easily if desired.
$handler->ttl = 7200;

Step 3: Set the session handler

// 2nd parameter `true` means the session will auto-close 
// at the end of the request if `session_close()` was not explicitly called
session_set_save_handler($handler, true);

Options

  • ttl - Number of seconds to keep a session active. Defaults to 3600 (1 hour)
  • prefix - A prefix to add to all session ids. Defaults to session_
  • readonly - If true, changes to $_SESSION during the request will be ignored

Recommended PHP Settings

Use HTTPS site-wide to protect your user's sessions. (check out https://letsencrypt.org/ for free SSL certificates).

Enable the following php.ini settings for more security:

  • session.cookie_secure - protects again session hijacking (Note: requires site-wide HTTPS)
  • session.cookie_httponly - protects against XSS attacks in some browsers
  • session.use_strict_mode - protects against session fixation attacks

Do not store objects in the session. Stick to primitive data types. This will reduce chances of future bugs and make your sessions portable

Changing the Serialization Method

By default, data is run through serialize before being passed to the storage backend.

If you extend a handler class, it's easy to customize the serialization behavior.

<?php
use EduCom\SessionAutomerge\SessionHandlerMemcached;

class MySessionHandler extends SessionHandlerMemcached {
    public function serialize(array $data) {
        return json_encode($data);
    }
    public function unserialize($string) {
        return json_decode($string, true);
    }
}

Conflict Resolution

By default, if a merge conflict is encountered when saving the session, the latest value always takes precedence.

You can change this behavior and provide custom merge logic by extending a handler class resolveConflict method. The method takes the following arguments:

  • $key is the array key in $_SESSION that has a conflict
  • $initial was the intial value of the key at the start of the current request. null means the key did not exist.
  • $new is the new value of the key at the end of the current request. null means the key was deleted.
  • $external is the latest value of the key from the database (set in a different request). null means the key was deleted.
<?php
use EduCom\SessionAutomerge\SessionHandlerMemcached;

class MySessionHandler extends SessionHandlerMemcached {
    public function resolveConflict($key, $initial, $new, $external) {
        // Special handling for a known session key 'items_viewed'
        if($key === 'items_viewed') {
            // Get the new items added during this request
            $items = array_diff($initial, $new);
            
            // Merge these newly added items with the external change
            $merged = array_merge($external, $items);
            
            return $merged;
        }
        
        // Fall back to default resolution method
        return parent::resolveConflict($key,$initial,$new,$external); // TODO: Change the autogenerated stub
    }
}

Adding New Storage Mechanism

If you want to use something other than Redis or Memcached, it's easy to extend the SessionHandlerBase class.

At a minimum, you need to define the abstract methods set, get, and delete.

If you need configuration parameters (e.g. database client, file path, etc.) you can create a __construct method.

Here's a fully working example of storing sessions in the filesystem.

<?php
use EduCom\SessionAutomerge\SessionHandlerBase;

class FileSessionHandler extends SessionHandlerBase {
    public $path = '/tmp/';
    
    public function __construct($path='/tmp/') {
        $this->path = $path;
    }
    
    public function set($key, array $session_data) {
        // TODO: support ttl
        $ttl = $this->ttl;
        
        $serialized = $this->serialize($session_data);
        file_put_contents($this->path.$key, $serialized);
        return true;
    }
    
    public function get($key) {
        $raw = file_get_contents($this->path.$key);
        return $this->unserialize($raw);
    }

    public function delete($key) {
        unlink($this->path.$key);
        return true;
    }
}

Most storage mechanisms won't need this, but you can define a gc (garbage collection), open, or close method as well. These follow the same pattern as SessionHandlerInterface.

Logging Errors

Occasionally errors will happen (e.g. Memcached request fails due to network glitch).

All errors are handled gracefully to avoid data corruption.

In the case of an error, the errorLog method will be called. By default it will just a trigger_error call.

You can override the errorLog method in a child class if needed (e.g. to tie into your application's monolog instance).