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;
} );

You may also like:

Going native with WooCommerce sessions

WooCommerce comes complete with its own database-backed session handler. Information about the shopper, the contents of their cart, any flash...