<?php 

/**
 * This class provides a class with behaviour similar to the $_SESSION variable.
 * Data is automatically saved on exit.
 *
 * Care should be taken when using this class to store objects. It will not detect changes to objects
 * automatically. Instead, a call to set(...) should be done to notify this class of changes.
 *
 * @author Olav Morken, UNINETT AS.
 * @package simpleSAMLphp
 * @version $Id$
 */
class SimpleSAML_MemcacheStore {


	/**
	 * This variable contains the id for this data.
	 *
	 * This variable is serialized.
	 */
	private $id = NULL;


	/**
	 * This variable contains an array with all key-value pairs stored
	 * in this object.
	 *
	 * This variable is serialized.
	 */
	private $data = NULL;


	/**
	 * This variable indicates whether our shutdown function has been registered.
	 *
	 * This variable isn't serialized.
	 */
	private $shutdownFunctionRegistered = FALSE;



	/**
	 * This function is used to find an existing storage object. It will return NULL if no storage object
	 * with the given id is found.
	 *
	 * @param $id  The id of the storage object we are looking for. A id consists of lowercase
	 *             alphanumeric characters.
	 * @return The corresponding MemcacheStorage object if the data is found or NULL if it isn't found.
	 */
	public static function find($id) {
		assert(self::isValidID($id));

		$serializedData = SimpleSAML_Memcache::get($id);
		if($serializedData === NULL) {
			return NULL;
		}

		$data = unserialize($serializedData);

		if(!($data instanceof self)) {
			SimpleSAML_Logger::warning('Retrieved key from memcache did not contain a MemcacheStore object.');
			return NULL;
		}

		return $data;
	}


	/**
	 * This constructor is used to create a new storage object. The storage object will be created with the
	 * specified id and the initial content passed in the data argument.
	 *
	 * If there exists a storage object with the specified id, then it will be overwritten.
	 *
	 * @param $id    The id of the storage object.
	 * @param $data  An array containing the initial data of the storage object.
	 */
	public function __construct($id, $data = array()) {
		/* Validate arguments. */
		assert(self::isValidID($id));
		assert(is_array($data));

		$this->id = $id;
		$this->data = $data;
	}


	/**
	 * This magic function is called on serialization of this class. It returns a list of the names of the
	 * variables which should be serialized.
	 *
	 * @return List of variables which should be serialized.
	 */
	private function __sleep() {
		return array('id', 'data');
	}


	/**
	 * This function retrieves the specified key from this storage object.
	 *
	 * @param $key  The key we should retrieve the value of.
	 * @return The value of the specified key, or NULL of the key wasn't found.
	 */
	public function get($key) {
		if(!array_key_exists($key, $this->data)) {
			return NULL;
		}

		return $this->data[$key];
	}


	/**
	 * This function sets the specified key to the specified value in this
	 * storage object.
	 *
	 * @param $key    The key we should set.
	 * @param $value  The value we should set the key to.
	 */
	public function set($key, $value) {
		$this->data[$key] = $value;

		/* Register the shutdown function if it isn't registered yet. */
		if(!$this->shutdownFunctionRegistered) {
			$this->registerShutdownFunction();
		}
	}


	/**
	 * This function stores this storage object to the memcache servers.
	 */
	public function save() {
		/* Write to the memcache servers. */
		SimpleSAML_Memcache::set($this->id, serialize($this));
	}


	/**
	 * This function determines whether the argument is a valid id.
	 * A valid id is a string containing lowercase alphanumeric
	 * characters.
	 *
	 * @param $id  The id we should validate.
	 * @return  TRUE if the id is valid, FALSE otherwise.
	 */
	private static function isValidID($id) {
		if(!is_string($id)) {
			return FALSE;
		}

		if(strlen($id) < 1) {
			return FALSE;
		}

		if(preg_match('/[^0-9a-z]/', $id)) {
			return FALSE;
		}

		return TRUE;
	}


	/**
	 * Register our shutdown function.
	 */
	private function registerShutdownFunction() {
		register_shutdown_function(array($this, 'shutdown'));
		$this->shutdownFunctionRegistered = TRUE;
	}


	/**
	 * Shutdown function. Calls save and updates flag indicating that the function has been called.
	 *
	 * This function is only public because this is a requirement of the way callbacks work in PHP.
	 */
	public function shutdown() {
		$this->save();
		$this->shutdownFunctionRegistered = FALSE;
	}

}
?>