Hoa central
Session.php
Go to the documentation of this file.
1 <?php
2 
37 namespace Hoa\Session;
38 
39 use Hoa\Core;
40 use Hoa\Iterator;
41 
52 class Session
53  implements Core\Event\Source,
54  \ArrayAccess,
55  \Countable,
57 {
63  const EVENT_CHANNEL = 'hoa://Event/Session/';
64 
71  const TOP_NAMESPACE = '__Hoa__';
72 
79  const PROFILE = 0;
80 
87  const BUCKET = 1;
88 
94  const NO_CACHE = 'nocache';
95 
101  const CACHE_PUBLIC = 'public';
102 
108  const CACHE_PRIVATE = 'private';
109 
115  const CACHE_PRIVATE_NO_EXPIRE = 'private_no_expire';
116 
122  protected static $_started = false;
123 
129  protected $_namespace = null;
130 
136  protected $_profile = null;
137 
143  protected $_bucket = null;
144 
150  protected static $_lock = [];
151 
152 
153 
167  public function __construct(
168  $namespace = '_default',
169  $cache = null,
170  $cacheExpire = null
171  ) {
172  if (false !== strpos($namespace, '/')) {
173  throw new Exception(
174  'Namespace must not contain a slash (/); given %s.',
175  0,
176  $namespace
177  );
178  }
179 
180  $this->_namespace = $namespace;
181 
182  if (false === array_key_exists($namespace, static::$_lock)) {
183  static::$_lock[$namespace] = false;
184  }
185 
186  if (true === $this->isLocked()) {
187  throw new Exception\Locked(
188  'Namespace %s is locked because it has been unset.',
189  1,
190  $namespace
191  );
192  }
193 
194  static::start($cache, $cacheExpire);
195  $this->initialize();
196 
197  $channel = static::EVENT_CHANNEL . $namespace;
198  $expired = $channel . ':expired';
199 
200  if (false === Core\Event::eventExists($channel)) {
201  Core\Event::register($channel, 'Hoa\Session');
202  }
203 
204  if (false === Core\Event::eventExists($expired)) {
205  Core\Event::register($expired, 'Hoa\Session');
206  }
207 
208  if (true === $this->isExpired()) {
209  $this->hasExpired();
210  }
211 
212  $this->_profile['last_used']->setTimestamp(time());
213 
214  return;
215  }
216 
226  public static function start($cache = null, $cacheExpire = null)
227  {
228  if (null === $cache) {
229  $cache = session_cache_limiter();
230  }
231 
232  if (null === $cacheExpire) {
233  $cacheExpire = session_cache_expire();
234  }
235 
236  if (true === static::$_started) {
237  return;
238  }
239 
240  if (headers_sent($filename, $line)) {
241  throw new Exception(
242  'Session must be started before any ouput; ' .
243  'output started in %s at line %d.',
244  2,
245  [$filename, $line]
246  );
247  }
248 
249  if (false === defined('SID')) {
250  session_cache_limiter($cache);
251 
252  if (static::NO_CACHE !== $cache) {
253  session_cache_expire($cacheExpire);
254  }
255 
256  if (false === session_start()) {
257  throw new Exception(
258  'Error when starting session. Cannot send session cookie.',
259  3
260  );
261  }
262  }
263 
264  static::$_started = true;
265 
266  if (!isset($_SESSION[static::TOP_NAMESPACE])) {
267  $_SESSION[static::TOP_NAMESPACE] = [];
268  }
269 
270  return;
271  }
272 
279  protected function initialize($reset = false)
280  {
281  $namespace = $this->getNamespace();
282 
283  if (true === $reset) {
284  unset($_SESSION[static::TOP_NAMESPACE][$namespace]);
285  }
286 
287  if (!isset($_SESSION[static::TOP_NAMESPACE][$namespace])) {
288  $_SESSION[static::TOP_NAMESPACE][$namespace] = [
289  static::PROFILE => [
290  'started' => new \DateTime(),
291  'last_used' => new \DateTime(),
292  'lifetime' => new \DateTime(
293  '+' . ini_get('session.gc_maxlifetime') . ' second'
294  )
295  ],
296  static::BUCKET => []
297  ];
298  }
299 
300  $handle = &$_SESSION[static::TOP_NAMESPACE][$namespace];
301  $this->_profile = &$handle[static::PROFILE];
302  $this->_bucket = &$handle[static::BUCKET];
303 
304  return;
305  }
306 
312  public function getNamespace()
313  {
314  return $this->_namespace;
315  }
316 
322  public static function isStarted()
323  {
324  return static::$_started;
325  }
326 
332  public function isEmpty()
333  {
334  if (true === $this->isLocked()) {
335  $this->__destruct();
336  }
337 
338  return empty($this->_bucket);
339  }
340 
346  public function isExpired()
347  {
348  if (true === $this->isLocked()) {
349  $this->__destruct();
350 
351  return true;
352  }
353 
354  $lifetime = $this->_profile['lifetime'];
355  $current = new \DateTime();
356 
357  if ($lifetime > $current) {
358  return false;
359  }
360 
361  return true;
362  }
363 
375  public function hasExpired($exception = true)
376  {
377  $namespace = $this->getNamespace();
378 
379  if (true === $this->isLocked()) {
380  throw new Exception\Locked(
381  'Namespace %s is locked because it has been unset.',
382  4,
383  $namespace
384  );
385  }
386 
387  $this->initialize(true);
388  $expired = static::EVENT_CHANNEL . $namespace . ':expired';
389 
390  if (true === $exception &&
391  false === event($expired)->isListened()) {
392  throw new Exception\Expired(
393  'Namespace %s has expired. All data belonging to this ' .
394  'namespace are lost.',
395  5,
396  $namespace
397  );
398  }
399 
400  Core\Event::notify(
401  $expired,
402  $this,
403  new Core\Event\Bucket()
404  );
405 
406  return;
407  }
408 
414  public function isLocked()
415  {
416  return static::$_lock[$this->getNamespace()];
417  }
418 
425  public function getProfile()
426  {
427  if (true === $this->isLocked()) {
428  throw new Exception\Locked(
429  'Namespace %s is locked because it has been unset.',
430  6,
431  $this->getNamespace()
432  );
433  }
434 
435  return $this->_profile;
436  }
437 
446  public function rememberMe($modify)
447  {
448  if (true === $this->isLocked()) {
449  throw new Exception\Locked(
450  'Namespace %s is locked because it has been unset.',
451  7,
452  $this->getNamespace()
453  );
454  }
455 
456  return $this->_profile['lifetime']->modify($modify);
457  }
458 
466  public function forgetMe()
467  {
468  if (true === $this->isLocked()) {
469  return null;
470  }
471 
472  return $this->_profile['lifetime']->setTimestamp(time() - 1);
473  }
474 
482  public function offsetExists($offset)
483  {
484  if (true === $this->isLocked()) {
485  throw new Exception\Locked(
486  'Namespace %s is locked because it has been unset.',
487  8,
488  $this->getNamespace()
489  );
490  }
491 
492  return array_key_exists($offset, $this->_bucket);
493  }
494 
502  public function offsetGet($offset)
503  {
504  if (true === $this->isLocked()) {
505  throw new Exception\Locked(
506  'Namespace %s is locked because it has been unset.',
507  9,
508  $this->getNamespace()
509  );
510  }
511 
512  if (false === $this->offsetExists($offset)) {
513  return null;
514  }
515 
516  return $this->_bucket[$offset];
517  }
518 
527  public function offsetSet($offset, $value)
528  {
529  if (true === $this->isLocked()) {
530  throw new Exception\Locked(
531  'Namespace %s is locked because it has been unset.',
532  10,
533  $this->getNamespace()
534  );
535  }
536 
537  if (null === $offset) {
538  $this->_bucket[] = $value;
539  } else {
540  $this->_bucket[$offset] = $value;
541  }
542 
543  return $this;
544  }
545 
553  public function offsetUnset($offset)
554  {
555  if (true === $this->isLocked()) {
556  throw new Exception\Locked(
557  'Namespace %s is locked because it has been unset.',
558  11,
559  $this->getNamespace()
560  );
561  }
562 
563  unset($this->_bucket[$offset]);
564 
565  return;
566  }
567 
573  public function count()
574  {
575  if (true === $this->isLocked()) {
576  return 0;
577  }
578 
579  return count($this->_bucket);
580  }
581 
588  public function getIterator()
589  {
590  if (true === $this->isLocked()) {
591  throw new Exception\Locked(
592  'Namespace %s is locked because it has been unset.',
593  12,
594  $this->getNamespace()
595  );
596  }
597 
598  return new Iterator\Map($this->_bucket);
599  }
600 
607  public function writeAndClose()
608  {
609  if (true === $this->isLocked()) {
610  throw new Exception\Locked(
611  'Namespace %s is locked because it has been unset.',
612  13,
613  $this->getNamespace()
614  );
615  }
616 
617  if (false === static::$_started) {
618  return false;
619  }
620 
621  session_write_close();
622 
623  return true;
624  }
625 
632  public function clean()
633  {
634  $this->_bucket = [];
635 
636  return;
637  }
638 
646  public function delete()
647  {
648  $namespace = $this->getNamespace();
649  $channel = static::EVENT_CHANNEL . $namespace;
650  $this->hasExpired(false);
651  unset($_SESSION[static::TOP_NAMESPACE][$namespace]);
652  Core\Event::unregister($channel);
653  Core\Event::unregister($channel . ':expired');
654  static::$_lock[$namespace] = true;
655 
656  return;
657  }
658 
667  public static function destroy()
668  {
669  static::start();
670 
671  if (true == ini_get('session.use_cookies')) {
672  if (headers_sent($filename, $line)) {
673  throw new Exception(
674  'Headers have been already sent, cannot destroy cookie; ' .
675  'output started in %s at line %d.',
676  14,
677  [$filename, $line]
678  );
679  }
680 
681  $parameters = session_get_cookie_params();
682  setcookie(
683  session_name(),
684  '',
685  time() - 1,
686  $parameters['path'],
687  $parameters['domain'],
688  $parameters['secure'],
689  $parameters['httponly']
690  );
691  }
692 
693  session_destroy();
694  static::$_started = false;
695  // let locks unchanged.
696 
697  return;
698  }
699 
705  public static function getId()
706  {
707  return session_id();
708  }
709 
716  public static function newId($deleteOldSession = false)
717  {
718  return session_regenerate_id($deleteOldSession);
719  }
720 }
721 
725 Core\Consistency::flexEntity('Hoa\Session\Session');
rememberMe($modify)
Definition: Session.php:446
hasExpired($exception=true)
Definition: Session.php:375
offsetExists($offset)
Definition: Session.php:482
__construct($namespace= '_default', $cache=null, $cacheExpire=null)
Definition: Session.php:167
initialize($reset=false)
Definition: Session.php:279
const CACHE_PRIVATE_NO_EXPIRE
Definition: Session.php:115
offsetUnset($offset)
Definition: Session.php:553
static destroy()
Definition: Session.php:667
static isStarted()
Definition: Session.php:322
offsetSet($offset, $value)
Definition: Session.php:527
static newId($deleteOldSession=false)
Definition: Session.php:716
offsetGet($offset)
Definition: Session.php:502
static start($cache=null, $cacheExpire=null)
Definition: Session.php:226