*/ protected array $config; /** @param iterable $config */ public function __construct( SessionHandlerInterface $sessionHandler, iterable $config = [], ?string $id = null, ) { $this->sessionHandler = $sessionHandler; if(!is_array($config)) { $config = iterator_to_array($config); } /** @var array $config */ $this->config = $config; if(is_null($id)) { $id = $this->getId(); } $this->id = $id; $sessionPath = $this->normaliseSavePath( $config["save_path"] ?? self::DEFAULT_SESSION_PATH ); $sessionName = $config["name"] ?? self::DEFAULT_SESSION_NAME; $this->attemptStart($sessionPath, $sessionName, $config); $this->sessionHandler->open($sessionPath, $sessionName); $this->store = $this->readSessionData(); if(is_null($this->store)) { $this->store = new SessionStore(__NAMESPACE__, $this); } } public function kill():void { $this->sessionHandler->destroy($this->getId()); $params = session_get_cookie_params(); setcookie( session_name() ?: "", "", -1, $params["path"], $params["domain"], $params["secure"], $params["httponly"] ); } public function getStore( string $namespace, bool $createIfNotExists = false ):?SessionStoreInterface { return $this->store->getStore( $namespace, $createIfNotExists ); } public function get(string $key):mixed { return $this->store->get($key); } public function set(string $key, mixed $value):void { $this->store->set($key, $value); } public function contains(string $key):bool { return $this->store->contains($key); } public function remove(string $key):void { $this->store->remove($key); } public function getId():string { $id = session_id(); if(empty($id)) { session_id($this->createNewId()); } return session_id() ?: ""; } protected function normaliseSavePath(string $path):string { if($this->isDsn($path)) { return $path; } $path = str_replace( ["/", "\\"], DIRECTORY_SEPARATOR, $path ); if($path[0] !== DIRECTORY_SEPARATOR) { $path = implode(DIRECTORY_SEPARATOR, [ sys_get_temp_dir(), $path, ]); } return $path; } protected function isDsn(string $path):bool { return (bool)preg_match('/^[a-z][a-z0-9+.-]*:\/\//i', $path); } /** @SuppressWarnings("PHPMD.Superglobals") */ protected function createNewId():string { if(($this->config["use_trans_sid"] ?? null) && !$this->config["use_cookies"]) { return $_GET[$this->config["name"]] ?? session_create_id(); } return session_create_id() ?: ""; } /** @SuppressWarnings("PHPMD.EmptyCatchBlock") */ protected function readSessionData():?SessionStore { try { $data = $this->sessionHandler->read($this->id) ?: ""; $store = unserialize($data); if ($store instanceof SessionStore) { return $store; } } // PHPCS:ignore catch (Throwable) {} return null; } public function write():bool { return $this->sessionHandler->write( $this->id, serialize($this->store) ); } /** * @param string $sessionPath * @param string $sessionName * @param array $config * @SuppressWarnings("PHPMD.UnusedFormalParameter") * @return void */ // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed private function attemptStart( string $sessionPath, string $sessionName, array $config, ?string $unusedContext = null, ):void { $sessionOptions = $this->getSessionOptions( $sessionPath, $sessionName, $config, ); $this->tryStartSession($sessionOptions); } /** * @param array $config * @return array */ private function getSessionOptions( string $sessionPath, string $sessionName, array $config ):array { $defaultConfig = [ "use_only_cookies" => true, "use_cookies" => true, "use_trans_sid" => false, "cookie_lifetime" => self::DEFAULT_SESSION_LIFETIME, "cookie_path" => self::DEFAULT_COOKIE_PATH, "cookie_domain" => self::DEFAULT_SESSION_DOMAIN, "cookie_secure" => self::DEFAULT_SESSION_SECURE, "cookie_httponly" => self::DEFAULT_SESSION_HTTPONLY, "cookie_samesite" => self::DEFAULT_COOKIE_SAMESITE, "use_strict_mode" => self::DEFAULT_STRICT_MODE, ]; $config = array_merge($defaultConfig, $config); return [ "save_path" => $sessionPath, "name" => $sessionName, "serialize_handler" => "php_serialize", "use_only_cookies" => $config["use_only_cookies"], "use_cookies" => $config["use_cookies"], "use_trans_sid" => $config["use_trans_sid"] ?? false, "cookie_lifetime" => $config["cookie_lifetime"], "cookie_path" => $config["cookie_path"], "cookie_domain" => $config["cookie_domain"], "cookie_secure" => $config["cookie_secure"], "cookie_httponly" => $config["cookie_httponly"], "cookie_samesite" => $config["cookie_samesite"], "use_strict_mode" => $config["use_strict_mode"], ]; } /** * @param array $sessionOptions * @SuppressWarnings("PHPMD.EmptyCatchBlock") */ private function tryStartSession(array $sessionOptions):void { $startAttempts = 0; do { $success = false; try { if(session_status() !== PHP_SESSION_ACTIVE) { $success = session_start($sessionOptions); } } // PHPCS:ignore catch(Throwable) {} if(!$success) { if(session_status() === PHP_SESSION_ACTIVE) { session_destroy(); } if(session_id()) { session_regenerate_id(true); } } $startAttempts++; } while(!$success && $startAttempts <= 1); } }