PageRenderTime 47ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/session/memcached.php

https://github.com/erikbrannstrom/core
PHP | 354 lines | 176 code | 60 blank | 118 comment | 26 complexity | 7db15d5f686e914f8e6e84aca19449f8 MD5 | raw file
  1. <?php
  2. /**
  3. * Fuel is a fast, lightweight, community driven PHP5 framework.
  4. *
  5. * @package Fuel
  6. * @version 1.0
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2011 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Fuel\Core;
  13. // --------------------------------------------------------------------
  14. class Session_Memcached extends \Session_Driver {
  15. /**
  16. * array of driver config defaults
  17. */
  18. protected static $_defaults = array(
  19. 'cookie_name' => 'fuelmid', // name of the session cookie for memcached based sessions
  20. 'servers' => array( // array of servers and portnumbers that run the memcached service
  21. array('host' => '127.0.0.1', 'port' => 11211, 'weight' => 100)
  22. )
  23. );
  24. /*
  25. * @var storage for the memcached object
  26. */
  27. protected $memcached = false;
  28. // --------------------------------------------------------------------
  29. public function __construct($config = array())
  30. {
  31. // merge the driver config with the global config
  32. $this->config = array_merge($config, is_array($config['memcached']) ? $config['memcached'] : static::$_defaults);
  33. $this->config = $this->_validate_config($this->config);
  34. // adjust the expiration time to the maximum possible for memcached
  35. $this->config['expiration_time'] = min($this->config['expiration_time'], 2592000);
  36. }
  37. // --------------------------------------------------------------------
  38. /**
  39. * driver initialisation
  40. *
  41. * @access public
  42. * @return void
  43. */
  44. public function init()
  45. {
  46. // generic driver initialisation
  47. parent::init();
  48. if ($this->memcached === false)
  49. {
  50. // do we have the PHP memcached extension available
  51. if ( ! class_exists('Memcached') )
  52. {
  53. throw new \Fuel_Exception('Memcached sessions are configured, but your PHP installation doesn\'t have the Memcached extension loaded.');
  54. }
  55. // instantiate the memcached object
  56. $this->memcached = new \Memcached();
  57. // add the configured servers
  58. $this->memcached->addServers($this->config['servers']);
  59. // check if we can connect to the server(s)
  60. if ($this->memcached->getVersion() === false)
  61. {
  62. throw new \Fuel_Exception('Memcached sessions are configured, but there is no connection possible. Check your configuration.');
  63. }
  64. }
  65. }
  66. // --------------------------------------------------------------------
  67. /**
  68. * create a new session
  69. *
  70. * @access public
  71. * @return void
  72. */
  73. public function create()
  74. {
  75. // create a new session
  76. $this->keys['session_id'] = $this->_new_session_id();
  77. $this->keys['previous_id'] = $this->keys['session_id']; // prevents errors if previous_id has a unique index
  78. $this->keys['ip_hash'] = md5(\Input::ip().\Input::real_ip());
  79. $this->keys['user_agent'] = \Input::user_agent();
  80. $this->keys['created'] = $this->time->get_timestamp();
  81. $this->keys['updated'] = $this->keys['created'];
  82. // create the session record
  83. $this->_write_memcached($this->keys['session_id'], serialize(array()));
  84. // and set the session cookie
  85. $this->_set_cookie();
  86. }
  87. // --------------------------------------------------------------------
  88. /**
  89. * read the session
  90. *
  91. * @access public
  92. * @param boolean, set to true if we want to force a new session to be created
  93. * @return void
  94. */
  95. public function read($force = false)
  96. {
  97. // get the session cookie
  98. $cookie = $this->_get_cookie();
  99. // if no session cookie was present, create it
  100. if ($cookie === false or $force)
  101. {
  102. $this->create();
  103. }
  104. // read the session file
  105. $payload = $this->_read_memcached($this->keys['session_id']);
  106. if ($payload === false)
  107. {
  108. // try to find the previous one
  109. $payload = $this->_read_memcached($this->keys['previous_id']);
  110. if ($payload === false)
  111. {
  112. // cookie present, but session record missing. force creation of a new session
  113. $this->read(true);
  114. return;
  115. }
  116. }
  117. // unpack the payload
  118. $payload = $this->_unserialize($payload);
  119. // session referral?
  120. if (isset($payload['rotated_session_id']))
  121. {
  122. $payload = $this->_read_memcached($payload['rotated_session_id']);
  123. if ($payload === false)
  124. {
  125. // cookie present, but session record missing. force creation of a new session
  126. $this->read(true);
  127. return;
  128. }
  129. else
  130. {
  131. // update the session
  132. $this->keys['previous_id'] = $this->keys['session_id'];
  133. $this->keys['session_id'] = $payload['rotated_session_id'];
  134. // unpack the payload
  135. $payload = $this->_unserialize($payload);
  136. }
  137. }
  138. if (isset($payload[0])) $this->data = $payload[0];
  139. if (isset($payload[1])) $this->flash = $payload[1];
  140. parent::read();
  141. }
  142. // --------------------------------------------------------------------
  143. /**
  144. * write the session
  145. *
  146. * @access public
  147. * @return void
  148. */
  149. public function write()
  150. {
  151. // do we have something to write?
  152. if ( ! empty($this->keys))
  153. {
  154. parent::write();
  155. // rotate the session id if needed
  156. $this->rotate(false);
  157. // session payload
  158. $payload = $this->_serialize(array($this->data, $this->flash));
  159. // create the session file
  160. $this->_write_memcached($this->keys['session_id'], $payload);
  161. // was the session id rotated?
  162. if ( isset($this->keys['previous_id']) && $this->keys['previous_id'] != $this->keys['session_id'])
  163. {
  164. // point the old session file to the new one, we don't want to lose the session
  165. $payload = $this->_serialize(array('rotated_session_id' => $this->keys['session_id']));
  166. $this->_write_memcached($this->keys['previous_id'], $payload);
  167. }
  168. $this->_set_cookie();
  169. }
  170. }
  171. // --------------------------------------------------------------------
  172. /**
  173. * destroy the current session
  174. *
  175. * @access public
  176. * @return void
  177. */
  178. public function destroy()
  179. {
  180. // do we have something to destroy?
  181. if ( ! empty($this->keys))
  182. {
  183. // delete the key from the memcached server
  184. if ($this->memcached->delete($this->config['cookie_name'].'_'.$this->keys['session_id']) === false)
  185. {
  186. throw new \Fuel_Exception('Memcached returned error code "'.$this->memcached->getResultCode().'" on delete. Check your configuration.');
  187. }
  188. }
  189. // reset the stored session data
  190. $this->keys = $this->flash = $this->data = array();
  191. }
  192. // --------------------------------------------------------------------
  193. /**
  194. * Writes the memcached entry
  195. *
  196. * @access private
  197. * @return boolean, true if it was an existing session, false if not
  198. */
  199. protected function _write_memcached($session_id, $payload)
  200. {
  201. // session payload
  202. $payload = $this->_serialize(array($this->data, $this->flash));
  203. // write it to the memcached server
  204. if ($this->memcached->set($this->config['cookie_name'].'_'.$this->keys['session_id'], $payload, $this->config['expiration_time']) === false)
  205. {
  206. throw new \Fuel_Exception('Memcached returned error code "'.$this->memcached->getResultCode().'" on write. Check your configuration.');
  207. }
  208. }
  209. // --------------------------------------------------------------------
  210. /**
  211. * Reads the memcached entry
  212. *
  213. * @access private
  214. * @return mixed, the payload if the file exists, or false if not
  215. */
  216. protected function _read_memcached($session_id)
  217. {
  218. // fetch the session data from the Memcached server
  219. return $this->memcached->get($this->config['cookie_name'].'_'.$this->keys['session_id']);
  220. }
  221. // --------------------------------------------------------------------
  222. /**
  223. * validate a driver config value
  224. *
  225. * @param array array with configuration values
  226. * @access public
  227. * @return array validated and consolidated config
  228. */
  229. public function _validate_config($config)
  230. {
  231. $validated = array();
  232. foreach ($config as $name => $item)
  233. {
  234. if ($name == 'memcached' and is_array($item))
  235. {
  236. foreach ($item as $name => $value)
  237. {
  238. switch ($name)
  239. {
  240. case 'cookie_name':
  241. if ( empty($value) OR ! is_string($value))
  242. {
  243. $value = 'fuelmid';
  244. }
  245. break;
  246. case 'servers':
  247. // do we have a servers config
  248. if ( empty($value) OR ! is_array($value))
  249. {
  250. $value = array('default' => array('host' => '127.0.0.1', 'port' => '11211'));
  251. }
  252. // validate the servers
  253. foreach ($value as $key => $server)
  254. {
  255. // do we have a host?
  256. if ( ! isset($server['host']) OR ! is_string($server['host']))
  257. {
  258. throw new \Fuel_Exception('Invalid Memcached server definition in the session configuration.');
  259. }
  260. // do we have a port number?
  261. if ( ! isset($server['port']) OR ! is_numeric($server['port']) OR $server['port'] < 1025 OR $server['port'] > 65535)
  262. {
  263. throw new \Fuel_Exception('Invalid Memcached server definition in the session configuration.');
  264. }
  265. // do we have a relative server weight?
  266. if ( ! isset($server['weight']) OR ! is_numeric($server['weight']) OR $server['weight'] < 0)
  267. {
  268. // set a default
  269. $value[$key]['weight'] = 0;
  270. }
  271. }
  272. break;
  273. default:
  274. // unknown property
  275. continue;
  276. }
  277. $validated[$name] = $value;
  278. }
  279. }
  280. else
  281. {
  282. // skip all config array properties
  283. if (is_array($item))
  284. {
  285. continue;
  286. }
  287. // global config, was validated in the driver
  288. $validated[$name] = $item;
  289. }
  290. }
  291. // validate all global settings as well
  292. return parent::_validate_config($validated);
  293. }
  294. }