Native session handler for WooCommerce
This code can be used as a standalone plugin, or mu-plugin. It is a simple (and naive) session handler for WooCommerce, that uses native PHP session handling instead of WooCommerce's own database-backed implementation.
<?php
/**
* Plugin name: WooCommerce Native Sessions
* Description: Uses PHP native sessions for WooCommerce shopping sessions.
* Version: 2024.02.16
* License: GPL-3.0
*
* Use this with (mu-)plugin with caution, and add appropriate testing!
*
* 1. This plugin replaces WooCommerce's standard session handler (database-backed) with one
* that takes advantage of native PHP sessions. There are lots of reasons you might want
* to do this, and some potential drawbacks.
*
* 2. This plugin represents an experiment, it's fairly naive and far from being 'battle
* tested'.
*/
// Register our replacement session handler.
add_filter( 'woocommerce_session_handler', function ( $existing ) {
/**
* This rather awkward class-in-if-block-in-function structure could be avoided if
* we placed the class definition in its own file and lazy-loaded it. As it is, we
* are using a single-file approach and this structure protects us from scenarios
* where the base class is undefined during early WP initialization.
*
* @todo consider if we need to continue using `maybe_serialize()` calls or can drop.
*/
if ( ! defined( WooCommerce_Native_Sessions::class ) ) {
/**
* Simple native session implementation for WooCommerce.
*
* 1. When updating this code, be wary of adding strict typing around inherited methods.
*
* 2. We inherit from WC_Session_Handler instead of directly from WC_Session to take
* advantage of its get_customer_id() implementation (which is consumed by other
* components).
*/
class WooCommerce_Native_Sessions extends WC_Session_Handler {
private const CONTAINER_KEY = 'wcsession';
private bool $initialized = false;
private bool $logged_init_error = false;
/**
* Initialize.
*/
public function init() {
$this->initialized = session_start();
if ( $this->initialized && ! isset( $_SESSION[ self::CONTAINER_KEY ] ) ) {
$_SESSION[ self::CONTAINER_KEY ] = [];
}
}
/**
* Tries to set a session value.
*
* @param string $key
* @param mixed $value
*
* @return void
*/
public function set( $key, $value ) {
if ( $this->uninitialized() ) {
return;
}
$key = sanitize_key( $key );
$value = maybe_serialize( $value );
$_SESSION[ self::CONTAINER_KEY ][ $key ] = $value;
}
/**
* Support array-access style unsetting.
*
* @param mixed $key Key to unset.
*/
public function __unset( $key ) {
if ( $this->uninitialized() ) {
return;
}
$key = sanitize_key( $key );
unset( $_SESSION[ self::CONTAINER_KEY ][ $key ] );
}
/**
* Support array-access style checking of whether a key is set.
*
* @param string $key
*
* @return bool
*/
public function __isset( $key ) {
if ( $this->uninitialized() ) {
return false;
}
$key = sanitize_key( $key );
return isset( $_SESSION[ self::CONTAINER_KEY ][ $key ] );
}
/**
* Make it easy to detect if the session was not initialized, so we cnm bail early and safely.
*
* This also takes care of logging a warning (but only once per session, to avoid flooding the
* logs).
*
* @return bool
*/
private function uninitialized(): bool {
if ( $this->initialized ) {
return false;
}
if ( ! $this->logged_init_error ) {
wc_get_logger()->warning( __( 'Could not modify or verify session data, the session was not initialized', 'woocommerce-native-sessions' ) );
$this->logged_init_error = true;
}
return false;
}
}
// If we were able to define the class, return the class name.
return WooCommerce_Native_Sessions::class;
}
// Otherwise, return the existing class name.
return $existing;
} );