classes/Base/Session/SessionManager.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php
/**
 * Fixin Framework
 *
 * Copyright (c) Attila Jenei
 *
 * http://www.fixinphp.com
 */

namespace Fixin\Base\Session;

use DateTimeImmutable;
use Fixin\Base\Cookie\CookieManagerInterface;
use Fixin\Model\Repository\RepositoryInterface;
use Fixin\Resource\Prototype;
use Fixin\Support\Strings;
use Fixin\Support\Types;

class SessionManager extends Prototype implements SessionManagerInterface
{
    protected const
        DATA_REGENERATED = 'regenerated',
        THIS_SETS = [
            self::COOKIE_MANAGER => [self::LAZY_LOADING => CookieManagerInterface::class],
            self::COOKIE_NAME => Types::STRING,
            self::LIFETIME => Types::INT,
            self::REGENERATION_FORWARD_TIME => Types::INT,
            self::REPOSITORY => [self::LAZY_LOADING => RepositoryInterface::class]
        ];

    /**
     * @var SessionAreaInterface[]
     */
    protected $areas;

    /**
     * @var CookieManagerInterface|false|null
     */
    protected $cookieManager;

    /**
     * @var string
     */
    protected $cookieName = 'session';

    /**
     * @var SessionEntityInterface
     */
    protected $entity;

    /**
     * @var integer
     */
    protected $lifetime = 0;

    /**
     * @var bool
     */
    protected $modified = false;

    /**
     * @var integer
     */
    protected $regenerationForwardTime = 1;

    /**
     * @var RepositoryInterface|false|null
     */
    protected $repository;

    /**
     * @var string
     */
    protected $sessionId;

    /**
     * @var bool
     */
    protected $started = false;

    /**
     * @return $this
     */
    public function clear(): SessionManagerInterface
    {
        $this->start();

        $this->areas = [];
        $this->modified = true;

        return $this;
    }

    public function deleteGarbageSessions(int $lifetime): int
    {
        $request = $this->getRepository()->createRequest();
        $request->getWhere()->compare(SessionEntityInterface::ACCESS_TIME, '<', new DateTimeImmutable('+' . $lifetime . ' MINUTES'));

        return $request->delete();
    }

    protected function generateId(): string
    {
        return sha1(Strings::generateRandomAlnum(24) . uniqid('', true) . microtime(true));
    }

    public function getArea(string $name): SessionAreaInterface
    {
        $this->start();

        // Existing area
        if (isset($this->areas[$name])) {
            return $this->areas[$name];
        }

        // New area
        $this->modified = true;

        return $this->areas[$name] = $this->resourceManager->clone('*\Base\Session\SessionArea', SessionAreaInterface::class);
    }

    protected function getCookieManager(): CookieManagerInterface
    {
        return $this->cookieManager ?: $this->loadLazyProperty(static::COOKIE_MANAGER);
    }

    public function getCookieName(): string
    {
        return $this->cookieName;
    }

    public function getLifetime(): int
    {
        return $this->lifetime;
    }

    public function getRegenerationForwardTime(): int
    {
        return $this->regenerationForwardTime;
    }

    protected function getRepository(): RepositoryInterface
    {
        return $this->repository ?: $this->loadLazyProperty(static::REPOSITORY);
    }

    public function isModified(): bool
    {
        $this->start();

        if ($this->modified) {
            return true;
        }

        foreach ($this->areas as $area) {
            if ($area->isModified()) {
                return true;
            }
        }

        return false;
    }

    protected function loadEntity(SessionEntityInterface $entity): void
    {
        $this->entity = $entity;
        $this->areas = $entity->getData();
        $this->sessionId = $entity->getSessionId();

        if ($this->lifetime) {
            $this->setupCookie();
        }

        $request = $this->getRepository()->createRequest();
        $request->getWhere()->compare(SessionEntityInterface::SESSION_ID, '=', $this->sessionId);
        $request->update([SessionEntityInterface::ACCESS_TIME => new DateTimeImmutable()]);
    }

    /**
     * @return $this
     */
    public function regenerateId(): SessionManagerInterface
    {
        $this->start();

        $this->sessionId = $this->generateId();
        $this->modified = true;

        if ($this->entity->isStored()) {
            $this->entity
            ->setData([static::DATA_REGENERATED => $this->sessionId])
            ->setAccessTime(new DateTimeImmutable())
            ->save();

            $this->entity = $this->getRepository()->create();
        }

        $this->setupCookie();

        return $this;
    }

    /**
     * @return $this
     */
    public function save(): SessionManagerInterface
    {
        if ($this->started && $this->isModified()) {
            $this->entity
                ->setSessionId($this->sessionId)
                ->setData($this->areas)
                ->setAccessTime(new DateTimeImmutable())
                ->save();

            $this->modified = false;

            foreach ($this->areas as $area) {
                $area->setModified(false);
            }
        }

        return $this;
    }

    protected function setupCookie(): void
    {
        $this->getCookieManager()->set($this->cookieName, $this->sessionId)
            ->setExpireTime($this->lifetime)
            ->setPath('/');
    }

    /**
     * @return $this
     */
    public function start(): SessionManagerInterface
    {
        if (!$this->started) {
            $this->started = true;

            $sessionId = $this->getCookieManager()->get($this->cookieName);
            if ($sessionId && $this->startWith($sessionId)) {
                return $this;
            }

            // New session
            $this->areas = [];
            $this->entity = $this->getRepository()->create();
            $this->regenerateId();
        }

        return $this;
    }

    /**
     * Start with stored session id
     */
    protected function startWith(string $sessionId): bool
    {
        $request = $this->getRepository()->createRequest();
        $where = $request->getWhere()->compare(SessionEntityInterface::SESSION_ID, '=', $sessionId);

        if ($this->lifetime) {
            $where->compare(SessionEntityInterface::ACCESS_TIME, '>=', new DateTimeImmutable('+' . $this->lifetime . ' MINUTES'));
        }

        /** @var SessionEntityInterface $entity */
        if ($entity = $request->fetchFirst()) {
            $data = $entity->getData();
            if (isset($data[static::DATA_REGENERATED])) {
                return ($entity->getAccessTime() >= new DateTimeImmutable('-' . $this->regenerationForwardTime . ' MINUTES')) ? $this->startWith($data[static::DATA_REGENERATED]) : false;
            }

            $this->loadEntity($entity);

            return true;
        }

        return false;
    }
}