Setting Up Couchbase as a PHP Session Handler with igbinary Encoding

Thanks a lot Mr.@mreiche, I think i finally got it:

It’s exactly as you said: the variable $_SESSION is decoded and encoded by the session.serialize_handler. When I retrieve the $session_data within the read() function, I only receive the encoded data.

So my finally solution to Setting Up Couchbase as a PHP Session Handler with igbinary Encoding was:

Step 1: Create a PassThruTranscoder class for encoding and decoding data. In case of the $_SESSION data that is already encoded and decoded by the session.serialize_handler, the goal is to ensure that Couchbase accepts this data “as it is,” without further encoding or decoding. To use this class effectively, follow these steps:

  1. Define the PassThruTranscoder class: This class should implement the Transcoder interface, which specifies the necessary methods for encoding and decoding data.
  2. Implement the encode method: Within the PassThruTranscoder class, implement the encode method. This method is responsible for encoding data. However, in your specific case, you don’t want to modify the data during encoding. Simply return the data along with the appropriate flags, making sure not to alter the original data’s format.
  3. Implement the decode method: Similarly, in the PassThruTranscoder class, implement the decode method. This method handles the decoding of data when retrieved from Couchbase. As with encoding, ensure that the decode method doesn’t modify the data. It should return the data exactly as it was stored, preserving its original format.

By placing the PassThruTranscoder class in the same folder as autoload.php and using Couchbase\PassThruTranscoder, you organize your code effectively. This allows you to access and utilize the PassThruTranscoder class correctly in your project.

Step 2: Create a generic Couchbase class that handles connections and operations on a Couchbase database. This class includes methods for getting, writing, modifying, and deleting data.

  1. Import the necessary Couchbase classes and define the Couchbase class.
  2. Initialize a Couchbase cluster with the provided connection details (connection string, username, password) in the constructor.
  3. Implement methods for getting a collection, getting a document, erasing a document, inserting a document, and upserting a document in a specified bucket, scope, and collection.
  4. Handle exceptions that may occur during these operations.

Exemple:

<?php

require_once '/path_to/autoload.php';

use Couchbase\Cluster;
use Couchbase\ClusterOptions;
use Couchbase\GetOptions;
use Couchbase\InsertOptions;
use Couchbase\UpsertOptions;
use Couchbase\Exception\CouchbaseException;
use Couchbase\Exception\DocumentNotFoundException;
use Couchbase\PassThruTranscoder;

class myCouchbase {
	var $classname = "myCouchbase";
	private $cluster = null;
	private $Cas = null;
	private $passTranscoder;
	private CouchbaseException $Exception;

	public function __construct(string $connectionString = "", string $user = "", string $password = "", bool $passTranscoder = false) {
		if ($connectionString && $user && $password) {
			$options = new ClusterOptions();
			$options->credentials($user, $password);
			$cluster = new Cluster($connectionString, $options);
			$this->cluster = $cluster; 
			$this->passTranscoder = $passTranscoder;
		}
	}


	public function getCollection(string $bucket_label, string $scope_label, string $collection_label) {
		try {
			if ($this->cluster) {
				$bucket = $this->cluster->bucket($bucket_label);
				if($bucket) $scope = $bucket->scope($scope_label);
				if($scope) $collection = $scope->collection($collection_label);
				return $collection;
			}
		} 	
		catch (CouchbaseException $ex) {
				$this->Exception = $ex;
				return false;
			}		
	}


	public function getDocument(string $bucket_label, string $scope_label, string $collection_label, $key) {
		$collection = $this->getCollection($bucket_label, $scope_label, $collection_label);
		try {	
			$options = null;
			if($this->passTranscoder){
				$options = new GetOptions();
				$options->transcoder(new PassThruTranscoder());
			}
			
			$result = $collection->get($key, $options ? $options : null);
			$this->Cas = $result->cas();
			return $result->content();

		}	
		catch (DocumentNotFoundException $ex) {
			$this->Cas = null;
			return false;
		}
		catch (CouchbaseException $ex) {
			$this->Exception = $ex;
			return false;
		} 
	}


	public function erase(string $bucket_label, string $scope_label,string $collection_label, $key) {
		$collection = $this->getCollection($bucket_label, $scope_label, $collection_label);
		try {
			$collection->remove($key);
			return true;
		}
		catch (CouchbaseException $ex) {
			if ($ex->getCode() == "COUCHBASE_KEY_ENOENT") {
				$this->Cas = null;
				return true;
			} else {
				$this->Exception = $ex;
				return false;
			}
		}
	}


	public function insert(string $bucket_label, string $scope_label, string $collection_label, $key, $value, $expiry = 0) {
		$collection = $this->getCollection($bucket_label, $scope_label, $collection_label);
		try {
			$options = null;
			//If the insert() is called first than the get() the Cas is null (also 0 is false in php)
			if ($expiry || $this->Cas || $this->passTranscoder) {
				$options = new InsertOptions();
				if ($expiry) $options->expiry($expiry);
				if ($this->passTranscoder) $options->transcoder(new PassThruTranscoder());
			}
			$result = $collection->insert($key, $value, $options ? $options : null);
			$this->Cas = $result->cas();
			return true;
		} 
		catch (CouchbaseException $ex) {
			$this->Exception = $ex;
			return false;
		}
	}


	public function upsert(string $bucket_label, string $scope_label, string $collection_label, $key, $value, $expiry = 0) {
		$collection = $this->getCollection($bucket_label, $scope_label, $collection_label);
		try {
			$options = null;	
			//If the upsert() is called first than the get() the Cas is null (also 0 is false in php)
			if ($expiry || $this->Cas || $this->passTranscoder) { 
				$options = new UpsertOptions(); 
				if ($expiry) $options->expiry($expiry);
				if ($this->passTranscoder) $options->transcoder(new PassThruTranscoder());
			}
			$result = $collection->upsert($key, $value, $options ? $options : null);
			$this->Cas = $result->cas();
			return true;
		} 
		catch (CouchbaseException $ex) {
			$this->Exception = $ex;
			return false;
		}
	}

}
?>

Step 3: Create a CouchbaseSessionHandler class that implements the SessionHandlerInterface for managing PHP sessions using Couchbase as the session storage (example: PHP session handler for couchbase 7 · GitHub).

  1. Initialize the CouchbaseSessionHandler with connection details, bucket, scope, and collection labels.
  2. Implement the open, close, read, write, destroy, and gc methods as required by the SessionHandlerInterface.
  3. Use the myCouchbase class for handling Couchbase operations within these methods.

Step 4: Use the code to manage PHP sessions with Couchbase as the session handler.

  1. Set the session save handler to the CouchbaseSessionHandler class.
  2. Start a new session with a session ID.
  3. Update the session data and interact with Couchbase for storing and retrieving session information.

Couchbase\PassThruTranscoder is already defined.

catch (CouchbaseException $ex) {
	$this->Exception = $ex;
	return false;
}

The above is a little bit dangerous as it trusts the caller to check the return value and then examine $this->Exception. Which often results in “the upsert didn’t work, but it didn’t throw an Exception”. It’s preferable to simply not catch the exception and let the caller handle it.

//if ($expiry || $this->Cas || $this->passTranscoder) { 
	$options = new UpsertOptions(); 
	if ($expiry) $options->expiry($expiry);
	if ($this->passTranscoder) $options->transcoder(new PassThruTranscoder());
//}
$result = $collection->upsert($key, $value, $options);

May as well always use options. Even if they are just empty. Makes the code simpler. Also - upsert ignores Cas. (And UpsertOptions should not even have cas).

This topic was automatically closed after 90 days. New replies are no longer allowed.