3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
10 namespace Zend\Session
;
12 use Zend\EventManager\EventManagerInterface
;
15 * Session ManagerInterface implementation utilizing ext/session
17 class SessionManager
extends AbstractManager
20 * Default options when a call to {@link destroy()} is made
21 * - send_expire_cookie: whether or not to send a cookie expiring the current session cookie
22 * - clear_storage: whether or not to empty the storage object of any stored values
25 protected $defaultDestroyOptions = array(
26 'send_expire_cookie' => true,
27 'clear_storage' => false,
31 * @var string value returned by session_name()
36 * @var EventManagerInterface Validation chain to determine if session is valid
38 protected $validatorChain;
43 * @param Config\ConfigInterface|null $config
44 * @param Storage\StorageInterface|null $storage
45 * @param SaveHandler\SaveHandlerInterface|null $saveHandler
46 * @throws Exception\RuntimeException
48 public function __construct(Config\ConfigInterface
$config = null, Storage\StorageInterface
$storage = null, SaveHandler\SaveHandlerInterface
$saveHandler = null)
50 parent
::__construct($config, $storage, $saveHandler);
51 register_shutdown_function(array($this, 'writeClose'));
55 * Does a session exist and is it currently active?
59 public function sessionExists()
61 $sid = defined('SID') ?
constant('SID') : false;
62 if ($sid !== false && $this->getId()) {
74 * if No session currently exists, attempt to start it. Calls
75 * {@link isValid()} once session_start() is called, and raises an
76 * exception if validation fails.
78 * @param bool $preserveStorage If set to true, current session storage will not be overwritten by the
79 * contents of $_SESSION.
81 * @throws Exception\RuntimeException
83 public function start($preserveStorage = false)
85 if ($this->sessionExists()) {
89 $saveHandler = $this->getSaveHandler();
90 if ($saveHandler instanceof SaveHandler\SaveHandlerInterface
) {
91 // register the session handler with ext/session
92 $this->registerSaveHandler($saveHandler);
97 $storage = $this->getStorage();
99 // Since session is starting, we need to potentially repopulate our
101 if ($storage instanceof Storage\SessionStorage
&& $_SESSION !== $storage) {
102 if (!$preserveStorage) {
103 $storage->fromArray($_SESSION);
105 $_SESSION = $storage;
106 } elseif ($storage instanceof Storage\StorageInitializationInterface
) {
107 $storage->init($_SESSION);
110 if (!$this->isValid()) {
111 throw new Exception\
RuntimeException('Session validation failed');
116 * Destroy/end a session
118 * @param array $options See {@link $defaultDestroyOptions}
121 public function destroy(array $options = null)
123 if (!$this->sessionExists()) {
127 if (null === $options) {
128 $options = $this->defaultDestroyOptions
;
130 $options = array_merge($this->defaultDestroyOptions
, $options);
134 if ($options['send_expire_cookie']) {
135 $this->expireSessionCookie();
138 if ($options['clear_storage']) {
139 $this->getStorage()->clear();
144 * Write session to save handler and close
146 * Once done, the Storage object will be marked as isImmutable.
150 public function writeClose()
152 // The assumption is that we're using PHP's ext/session.
153 // session_write_close() will actually overwrite $_SESSION with an
154 // empty array on completion -- which leads to a mismatch between what
155 // is in the storage object and $_SESSION. To get around this, we
156 // temporarily reset $_SESSION to an array, and then re-link it to
157 // the storage object.
159 // Additionally, while you _can_ write to $_SESSION following a
160 // session_write_close() operation, no changes made to it will be
161 // flushed to the session handler. As such, we now mark the storage
162 // object isImmutable.
163 $storage = $this->getStorage();
164 if (!$storage->isImmutable()) {
165 $_SESSION = $storage->toArray(true);
166 session_write_close();
167 $storage->fromArray($_SESSION);
168 $storage->markImmutable();
173 * Attempt to set the session name
175 * If the session has already been started, or if the name provided fails
176 * validation, an exception will be raised.
178 * @param string $name
179 * @return SessionManager
180 * @throws Exception\InvalidArgumentException
182 public function setName($name)
184 if ($this->sessionExists()) {
185 throw new Exception\
InvalidArgumentException(
186 'Cannot set session name after a session has already started'
190 if (!preg_match('/^[a-zA-Z0-9]+$/', $name)) {
191 throw new Exception\
InvalidArgumentException(
192 'Name provided contains invalid characters; must be alphanumeric only'
204 * Proxies to {@link session_name()}.
208 public function getName()
210 if (null === $this->name
) {
211 // If we're grabbing via session_name(), we don't need our
212 // validation routine; additionally, calling setName() after
213 // session_start() can lead to issues, and often we just need the name
214 // in order to do things such as setting cookies.
215 $this->name
= session_name();
223 * Can safely be called in the middle of a session.
226 * @return SessionManager
228 public function setId($id)
230 if ($this->sessionExists()) {
231 throw new Exception\
RuntimeException('Session has already been started, to change the session ID call regenerateId()');
240 * Proxies to {@link session_id()}
244 public function getId()
252 * Regenerate the session ID, using session save handler's
253 * native ID generation Can safely be called in the middle of a session.
255 * @param bool $deleteOldSession
256 * @return SessionManager
258 public function regenerateId($deleteOldSession = true)
260 session_regenerate_id((bool) $deleteOldSession);
265 * Set the TTL (in seconds) for the session cookie expiry
267 * Can safely be called in the middle of a session.
269 * @param null|int $ttl
270 * @return SessionManager
272 public function rememberMe($ttl = null)
275 $ttl = $this->getConfig()->getRememberMeSeconds();
277 $this->setSessionCookieLifetime($ttl);
282 * Set a 0s TTL for the session cookie
284 * Can safely be called in the middle of a session.
286 * @return SessionManager
288 public function forgetMe()
290 $this->setSessionCookieLifetime(0);
295 * Set the validator chain to use when validating a session
297 * In most cases, you should use an instance of {@link ValidatorChain}.
299 * @param EventManagerInterface $chain
300 * @return SessionManager
302 public function setValidatorChain(EventManagerInterface
$chain)
304 $this->validatorChain
= $chain;
309 * Get the validator chain to use when validating a session
311 * By default, uses an instance of {@link ValidatorChain}.
313 * @return EventManagerInterface
315 public function getValidatorChain()
317 if (null === $this->validatorChain
) {
318 $this->setValidatorChain(new ValidatorChain($this->getStorage()));
320 return $this->validatorChain
;
324 * Is this session valid?
326 * Notifies the Validator Chain until either all validators have returned
327 * true or one has failed.
331 public function isValid()
333 $validator = $this->getValidatorChain();
334 $responses = $validator->triggerUntil('session.validate', $this, array($this), function ($test) {
337 if ($responses->stopped()) {
338 // If execution was halted, validation failed
341 // Otherwise, we're good to go
346 * Expire the session cookie
348 * Sends a session cookie with no value, and with an expiry in the past.
352 public function expireSessionCookie()
354 $config = $this->getConfig();
355 if (!$config->getUseCookies()) {
359 $this->getName(), // session name
361 $_SERVER['REQUEST_TIME'] - 42000, // TTL for cookie
362 $config->getCookiePath(),
363 $config->getCookieDomain(),
364 $config->getCookieSecure(),
365 $config->getCookieHttpOnly()
370 * Set the session cookie lifetime
372 * If a session already exists, destroys it (without sending an expiration
373 * cookie), regenerates the session ID, and restarts the session.
378 protected function setSessionCookieLifetime($ttl)
380 $config = $this->getConfig();
381 if (!$config->getUseCookies()) {
385 // Set new cookie TTL
386 $config->setCookieLifetime($ttl);
388 if ($this->sessionExists()) {
389 // There is a running session so we'll regenerate id to send a new cookie
390 $this->regenerateId();
395 * Register Save Handler with ext/session
397 * Since ext/session is coupled to this particular session manager
398 * register the save handler with ext/session.
400 * @param SaveHandler\SaveHandlerInterface $saveHandler
403 protected function registerSaveHandler(SaveHandler\SaveHandlerInterface
$saveHandler)
405 return session_set_save_handler(
406 array($saveHandler, 'open'),
407 array($saveHandler, 'close'),
408 array($saveHandler, 'read'),
409 array($saveHandler, 'write'),
410 array($saveHandler, 'destroy'),
411 array($saveHandler, 'gc')