Обработчик сессий на PHP
Вход Регистрация

Обработчик сессий на PHP

Этот скрипт может хранить данные сессии в MySQL и в разделяемой памяти кэш-системы APC.

Он может регистрировать функции класса для обработки текущей сессии пользователя.

Значения сессии сохраняются и извлекаются в основном из общей памяти с использованием APC, а также из таблицы базы данных MySQL, если записи кэша APC не установлены для текущей сессии.

Лицензия GPL.

Системные требования скрипта:

PHP не младше 4.0 версии, БД MySQL.

Исходник скрипта


	/*
	 *	APC_handler	v1.0
	 */

	/*
	 * APC_handler is sessionhandler class which combine memory (apc) with
	 * mysql base to store session data. If is not possible to write
	 * data to memory (the keeped free memory can be set), then the handler write them to base.
	 * User can not log in (with login()) twice in the same time with different browsers (SID-s),
	 * and the is_logged() function returns SID of logged user which can be compare with current.
	 * For multiple logins do not use login() & is_logged, no need for them.
	 *
	 */

	/*
	 * 	APC_handler public functions:
	 *
	 *	mysql_connect ($host, $user, $password);	=>	define data for mysql connection
	 *	mysql_base($base, $sess_table, $logg_table)	=>	define base, sessions & loggeds table for store the data
	 *	expire($minutes)				=>	define session expiration, 0-> never expire
	 *	keep_free($MB)					=>	if the size of free memory below $MB start to write into base
	 *	set_session_handler()				=>	activate the handler
	 *	login($user)					=>	sets user to logged (only if session is started) returns TRUE or FALSE if fails
	 *	is_logged($user)				=>	returns SID if user is logged or FALSE (the user can be logged only from one SID at the same time)
	 *	logout($user)					=>	logout
	 *	base2mem()					=>	copy all session data (without logged data) from base to memory (how many is possible)
	 *	mem2base()					=>	copy all session data (without logged data) from memory to base
	 *	get_connection()				=>	returns connection resource for reuse (persistent connection)
	 *	reset()						=>	empty the memory & base used by sessions (the other data in memory will be keeped)
	 *								this function must to be called before set_session_handler(); this function is usable for testing.
	 *	no_apc()					=>	if this function is called before set_session_handler the handler will only use base to store data
	 *								exist sessions will copy from memory to base and new logins will write to base
	 *								(old logins will still read from memory as long not expired)
	 *	keep_base_on_restart()				=>	if this function is called on system restart the session data in mysql base will be keeped
	 *								(by default the session data will be deleted). The logged users anyway will be deleted on restart,
	 *								and the session data in memory will be lost. If the session data are important, you can
	 *								call this function, and before restart copy memory to base, and after copy it back.
	 */

	/*
	 *	APC_handler limitations:
	 *
	 *	The handler stores the data in memory in form sess:SID=>value & logged:SID=>value, and sets
	 *	one variable in memory md5('session') to check system restarts. Overwriting them, clearing the cache or system restart will result data loss.
	 * 	If the system is restarted, the handler will create a new empty session space if is not called keep_base_on_restart() function.
	 *	The base2mem and mem2base can be used to make backup copy from data in memory.
	 *	The handler use persisten connection for base, the server must support it (how many is allowed).
	 *	The handler cannot overload the apc memory, but if another process overload it, may cause unexpected result.
	 *
	 */

	/*	APC_handler history:
	 *
	 *	v1.0	First release
	 */


	if (! class_exists('APC_handler', FALSE))	{

		class APC_handler	{

			// mysql data
			var $base			=	'temp';
			var $sessions			=	'sessions';
			var $loggeds			=	'loggeds';

			// default expiration 10 min
			var $expire			=	600;

			// store connection & handler is set
			var $mysqli			=	FALSE;

			// settings
			var $active			=	FALSE;
			var $mem_keep			=	10485760; // (10*1024*1024)
			var $no_apc			=	FALSE;
			var $keep_base_on_restart	=	FALSE;
			var $base_wipe			=	FALSE;


			/**
			 * Sets vars for connection
			 */

			public function	mysql_connect ($host=FALSE, $user=FALSE, $password=FALSE)	{

				$this->host	=	(string)$host;
				$this->user	=	(string)$user;
				$this->password	=	(string)$password;

				if (! (strlen($this->host) && strlen($this->user) && strlen($this->password))) $this->error('Invalid host, user or password defined for mysql connection !');
			}


			/**
			 * Sets bases and tables
			 */

			public function mysql_base($base='', $sess_table='', $logg_table='')	{

				$this->base	=	$base;
				$this->sessions	=	$sess_table;
				$this->loggeds	=	$logg_table;
			}


			/**
			 * Sets the expiration
			 */

			public function expire($minutes=0)	{

				if ($this->active) $this->error('Can\'t set expiration after activated handler !');

				$this->expire=(integer)($minutes*60);

				if ($this->expire < 0 )	$this->error('Expiration can not be negative !');
			}


			/**
			 * Sets the memory size which will keep free
			 */

			public function keep_free($MB)	{

				$this->mem_keep=(integer)($MB*1024*1024);
				if ($this->mem_keep <= 0) $this->error('Memory size to keep free can not be zero or negative !');
			}


			/**
			 * Delete the memory and base data
			 */

			public function reset()	{

				$cached		=	apc_cache_info('user');
				$cached		=	$cached['cache_list'];

				foreach ($cached as $cache)	{

					if ((substr($cache['info'],0,5)!='sess:') && (substr($cache['info'],0,7)!='logged:')) continue;

					apc_delete($cache['info']);
				}

				apc_delete(md5('session'));

				// the base will deleted at the apc_session_open()
				$this->base_wipe=TRUE;
			}


			/**
			 * Sets storing data only to base
			 */

			public function no_apc()	{

				$this->no_apc=TRUE;
			}


			/**
			 * Sets keeping mysql data if system is restarted
			 */

			public function keep_base_on_restart()	{

				$this->keep_base_on_restart=TRUE;
			}

			/**
			 * Store variable to mem returns TRUE or FALSE if fails
			 */

			private function store($var, $value)	{

				if ($this->no_apc) return FALSE;

				$usage=apc_sma_info();

				if ($usage['avail_mem'] < $this->mem_keep) return FALSE;

				if (! apc_store($var, $value)) return FALSE;

				return TRUE;
			}


			/**
			 * User login
			 */

			public function login($user=FALSE)	{

				if ((! $this->active) or (! strlen($user)))	return FALSE;

				$SID=session_id();

				if ($SID=='') $this->error('No session started, can\'t set user to logged !');

				// First run the gc and delete expired sessions

				$this->apc_session_gc();

				// check is logged to not log twice

				if ($this->is_logged($user)) return FALSE;

				$apc_user=md5($user);

				if ($this->store("logged:$SID", $apc_user)) return TRUE;

				apc_delete("logged:$SID");

				$this->mysqli->query("INSERT INTO $this->base.$this->loggeds VALUES ('$SID', '$user')");

				return TRUE;
			}


			/**
			 * Log out (keep the session data),
			 * php's session_destroy will destroy the
			 * session & the logged user to
			 */

			public function logout($user=FALSE)	{

				$SID=$this->is_logged($user);
				if ($SID=session_id()) $this->delete_logged($SID);
			}


			/**
			 * Checks the user is logged returns SID
			 */

			public function is_logged($user=FALSE)	{

				if ((! $this->active) or (! strlen($user)))	return FALSE;

				$apc_user	=	md5($user);
				$SID		=	FALSE;
				$time		=	time();

				$cached		=	apc_cache_info('user');
				$cached		=	$cached['cache_list'];

				foreach ($cached as $cache)	{

					if (substr($cache['info'],0,7)!='logged:') continue;

					$stored=apc_fetch($cache['info'], $sucess);

					if (($sucess) && ($stored==$apc_user))	{

						$SID=str_replace('logged:', '', $cache['info']);
						break;
					}
				}

				unset($cached, $cache);

				if (($this->mysqli) && (! $SID))	{

					$result=$this->mysqli->query("SELECT `SID` FROM $this->base.$this->loggeds WHERE `user`='$user' LIMIT 1");

					if ($result->num_rows)	{

						$row = $result->fetch_row();
						$SID=$row[0];
					}
				}

				// check the SID expiration
				if (($SID) && ($this->expire))	{

					if (apc_exists("sess:$SID")) 	{

						$s_value=apc_fetch("sess:$SID", $sucess);
						if (($sucess) && ($time > $s_value[0])) $SID=FALSE;
					}	else	{

						$result=$this->mysqli->query("SELECT `expiration` FROM $this->base.$this->sessions WHERE `SID`='$SID' AND expiration > $time LIMIT 1");
						if (! $result->num_rows) $SID=FALSE;
					}
				}

				return $SID;
			}


			/**
			 * Copy sessions from base to memory
			 */

			public function base2mem()	{

				if (! $this->active)	$this->error('No session started ! Can\'t copy data!');

				$result=$this->mysqli->query("SELECT * FROM $this->base.$this->sessions");

				if ($result->num_rows)	{

					while ($row = $result->fetch_row())	{

						$SID		= $row[0];
						$expiration	= $row[1];
						$value		= $row[2];

						if ($this->store("sess:$SID", array($expiration, $value)))	{

							$this->mysqli->query("DELETE FROM $this->base.$this->sessions WHERE `SID` = '$SID' LIMIT 1");
						}	else	{

							break;
						}
					}
				}
			}


			/**
			 * Copy sessions from memory to base
			 */

			public function mem2base()	{

				if (! $this->active)	$this->error('No session started ! Can\'t copy data!');

				$cached		=	apc_cache_info('user');
				$cached		=	$cached['cache_list'];

				foreach ($cached as $cache)	{

					if (substr($cache['info'],0,5)!='sess:') continue;

					$SID=str_replace('sess:','', $cache['info']);

					$stored = apc_fetch($cache['info'], $sucess);

					if ($sucess)	{

						$expiration	=	$stored[0];
						$value		=	addslashes($stored[1]);

						if (! $this->mysqli->query("INSERT INTO $this->base.$this->sessions VALUES ('$SID', '$expiration', '$value')"))	{

							$this->mysqli->query("UPDATE $this->base.$this->sessions SET `expiration`='$expiration', `value`='$value' WHERE `SID`='$SID' LIMIT 1");
						}

						apc_delete($cache['info']);
					}
				}
			}


			/**
			 * Returns persistent connection resource for reuse
			 */

			public function get_connection()	{

				return $this->mysqli;
			}


			/**
			 * Displays error and stop
			 */

			private function error($errstr)	{

				echo "APC_handler error: $errstr";
				exit;
			}


			/**
			 * If the system was restarted delete the session
			 * (and logged users) data chunks from base
			 */

			private function empty_base()	{

				if (($this->base_wipe) or (! $this->keep_base_on_restart)) $this->mysqli->query("DELETE FROM $this->base.$this->sessions");
				$this->mysqli->query("DELETE FROM $this->base.$this->loggeds");
			}


			/**
			 * Checks for APC support and opens new mysqli
			 * persistent connection with utf-8 collation
			 */

			public function apc_session_open ()	{

				if (! function_exists('apc_add')) $this->error('Server not supports APC cache !');

				if (! $this->mysqli)	{

					$this->mysqli = new mysqli("p:$this->host", $this->user, $this->password, $this->base);
					if ($this->mysqli->connect_error)	{
						$this->error('Can\'t connect to mysql server ('.$this->mysqli->connect_errno. ') '. $this->mysqli->connect_error.' !');
					}
					$this->mysqli->query("SET NAMES 'utf8' COLLATE 'utf8_unicode_ci'");
				}

				// checks for stored value
				if (! apc_exists(md5('session')))	$this->empty_base();

				// sets value to apc to know if the system was restarted
				apc_store(md5('session'), TRUE);

				// if no_apc mode is enabled copy session from memory to base
				if ($this->no_apc) $this->mem2base();

				// run the garbage collector with session.gc_probability/session.gc_divisor=1/100
				if ((integer)rand(1,100)==10) $this->apc_session_gc();

				return TRUE;
			}


			/**
			 * Write the values to memory or base
			 */

			public function apc_session_write($SID, $value)	{

				$time		=	time();
				$expiration 	= 	$time + $this->expire;

				// store in apc to not expire and leave the
				// garbage collector to clean it if is expired

				if ($this->store("sess:$SID", array($expiration, $value))) return;

				// delete from APC to not keep twice
				apc_delete("sess:$SID");

				// write to base

				$value		=	addslashes($value);

				$result=$this->mysqli->query("INSERT INTO $this->base.$this->sessions VALUES ('$SID', '$expiration', '$value')");

				if ($result===FALSE) $this->mysqli->query("UPDATE $this->base.$this->sessions SET `expiration`='$expiration', `value`='$value' WHERE `SID`='$SID' LIMIT 1");

				return TRUE;
			}


			/**
			 * Read session variable
			 */

			public function apc_session_read($SID)	{

				$value	=	'';
				$time	=	time();

				if (apc_exists("sess:$SID")) 	{

					$value=apc_fetch("sess:$SID", $sucess);

					// array(expiration, value)
					if (($sucess) && (($value[0] > $time) or (! $this->expire))) $value=$value[1];

				}	else	{

					if ($this->expire) {$expire_str="AND expiration > $time";} else {$expire_str='';}

					$result=$this->mysqli->query("SELECT `value` FROM $this->base.$this->sessions WHERE `SID`='$SID' $expire_str LIMIT 1");

					if ($result->num_rows)	{

						$row	=	$result->fetch_row();
						$value	=	$row[0];
					}
				}

				return $value;
			}


			/**
			 * Session close
			 */

			public function apc_session_close()	{

				return TRUE;
			}


			/**
			 * Destroy the session delete the values
			 */

			public function apc_session_destroy($SID)	{

				// in the same time not will exist
				// parts of same session in apc & base

				if (apc_exists("sess:$SID"))	{

					apc_delete("sess:$SID");
				}	else	{

					$this->mysqli->query("DELETE FROM $this->base.$this->sessions WHERE `SID`='$SID' LIMIT 1");
				}

				$this->delete_logged($SID);

				return TRUE;
			}


			/**
			 * The real garbage collector which
 			 * runs before data read
			 */

			public function apc_session_gc()	{

				if (! $this->expire) return; // if the expiration is 0 leave them

				$time		=	time();
				$cached		=	apc_cache_info('user');
				$cached		=	$cached['cache_list'];

				foreach ($cached as $cache)	{

					if (substr($cache['info'],0,5)!='sess:') continue;

					$stored=apc_fetch($cache['info'], $sucess);

					if (($sucess) && ($stored[0] < $time))	{

						apc_delete($cache['info']);
						$this->delete_logged(str_replace('sess:', '', $cache['info']));
					}
				}

				unset($cached, $cache);

				$result=$this->mysqli->query("SELECT `SID` FROM $this->base.$this->sessions WHERE expiration < $time");

				if ($result->num_rows)	{

					while ($row = $result->fetch_row())	{

						$SID=$row[0];

						$this->mysqli->query("DELETE FROM $this->base.$this->sessions WHERE `SID` = '$SID' LIMIT 1");
						$this->delete_logged($SID);
					}
				}
			}


			/**
			 * Garbage collector for set_save_handler
			 * does nothing, only for syntax is here
			 */

			public function apc_session_gc_fake ($lifetime)	{
				return;
			}


			/**
			 * Delete logged user
			 */

			private function delete_logged($SID)	{

				if (apc_exists("logged:$SID"))	{

					apc_delete("logged:$SID");
				}	else	{

					if ($this->mysqli) $this->mysqli->query("DELETE FROM $this->base.$this->loggeds WHERE `SID` = '$SID' LIMIT 1");
				}
			}


			/**
			 * Sets the handler
			 */

			public function set_session_handler()	{

				$this->active=TRUE;

				session_set_save_handler (
					array(&$this, "apc_session_open"),
					array(&$this, "apc_session_close"),
					array(&$this, "apc_session_read"),
					array(&$this, "apc_session_write"),
					array(&$this, "apc_session_destroy"),
					array(&$this, "apc_session_gc_fake")
				);
			}
		}
	}

Скачать архивы


Комментировать

captcha

Вход

Зарегистрируйтесь, если нет учетной записи

Напомнить пароль
Регистрация
Напомнить пароль
Войти в личный кабинет