commit e7cabb75222526f4decdbe85027245e1feae7d0d Author: Arsen Mirzaev Tatyano-Muradovich Date: Sun Mar 21 06:10:22 2021 +1000 Инициализация diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..aafa267 --- /dev/null +++ b/composer.json @@ -0,0 +1,39 @@ +{ + "name": "mirzaev/yii2-arangodb-sessions", + "description": "Yii\\web\\DbSession implementation for use with ArangoDB", + "keywords": [ + "Yii2", + "ArangoDB", + "DbSession" + ], + "type": "yii2-extension", + "license": "AGPL-3.0-or-later", + "homepage": "https://git.hood.su/mirzaev/yii2/arangodb/sessions", + "authors": [ + { + "name": "Arsen Mirzaev Tatyano-Muradovich", + "email": "red@hood.su", + "homepage": "https://hood.su/mirzaev", + "role": "Developer" + } + ], + "require": { + "php": "^8.0.0", + "yiisoft/yii2": "2.*", + "triagens/arangodb": "~3.2", + "mirzaev/yii2-arangodb": "~2.1.x-dev" + }, + "require-dev": { + "yiisoft/yii2-debug": "*" + }, + "autoload": { + "psr-4": { + "mirzaev\\yii2\\arangodb\\sessions\\": "mirzaev/yii2-arangodb-sessions" + } + }, + "autoload-dev": { + "psr-4": { + "mirzaev\\yii2\\arangodb\\sessions\\tests\\": "mirzaev/yii2-arangodb-sessions/tests" + } + } +} \ No newline at end of file diff --git a/mirzaev/yii2-arangodb-sessions/ArangoDbSession.php b/mirzaev/yii2-arangodb-sessions/ArangoDbSession.php new file mode 100644 index 0000000..ff1cc18 --- /dev/null +++ b/mirzaev/yii2-arangodb-sessions/ArangoDbSession.php @@ -0,0 +1,288 @@ + + */ +final class ArangoDbSession extends DbSession { + + /** + * Идентификатор компонента (Component ID) + */ + public Connection|array|string $db = 'arangodb'; + + /** + * Название документа для хранения данных сеанса (Document name for storing session data) + */ + public string $sessions = 'sessions'; + + /** + * Буфер данных для записи в документ сессий (Data buffer for write to session document) + */ + protected array $buffer = []; + + + /** + * Инициализация компонента (Component initialization) + * + * @throws InvalidConfigException if $this->db is invalid. + */ + public function init(): self + { + parent::init(); + + return $this->db = Instance::ensure($this->db, Connection::class); + } + + /** + * Открыть сессию (Open session) + * + * @param string $path session save path + * @param string $name session name + * + * @return bool Статус открытия сессии (Session opening status) + * + * @internal Do not call this method directly + */ + public function openSession($path, $name): bool + // public function openSession(string $save, string $session_name): bool + { + if ($this->getUseStrictMode()) { + $id = $this->getId(); + if (!$this->getReadQuery($id)->exists()) { + // This session id does not exist, mark it for forced regeneration + $this->_forceRegenerateId = $id; + } + } + + return parent::openSession($path, $name); + } + + /** + * Метод не создаст эффект пока сессия неактивна (getIsActive()). Убедитесь, что открыли (open()) перед вызовом + * This method has no effect when session is not [[getIsActive()|active]]. Make sure to call [[open()]] before calling it. + * + * @param bool $delete Удалить прошлую сессию (Delete old session) + * + * @see $this->open() + * @see $this->getIsActive() + * @see https://secure.php.net/session_regenerate_id + */ + public function regenerateID($delete = false): void + // public function regenerateID(bool $delete = false): void + { + $oldID = session_id(); + + // if no session is started, there is nothing to regenerate + if (empty($oldID)) { + return; + } + + parent::regenerateID(false); + + $newID = session_id(); + + // if session id regeneration failed, no need to create/update it. + if (empty($newID)) { + Yii::warning('Failed to generate new session ID', __METHOD__); + return; + } + + $row = $this->db->useMaster(function() use ($oldID) { + return (new Query())->from($this->sessionTable) + ->where(['id' => $oldID]) + ->createCommand($this->db) + ->queryOne(); + }); + + if ($row !== false) { + if ($delete) { + $this->db->createCommand() + ->update($this->sessionTable, ['id' => $newID], ['id' => $oldID]) + ->execute(); + } else { + $row['id'] = $newID; + $this->db->createCommand() + ->insert($this->sessionTable, $row) + ->execute(); + } + } else { + // shouldn't reach here normally + $this->db->createCommand() + ->insert($this->sessionTable, $this->composeFields($newID, '')) + ->execute(); + } + } + + /** + * Закрыть сессию (Close session) + * + * Закрывает текущую сессию и сохраняет данные + * Ends the current session and store session data + */ + public function close(): void + { + if ($this->getIsActive()) { + // prepare writeCallback fields before session closes + $this->fields = $this->composeFields(); + YII_DEBUG ? session_write_close() : @session_write_close(); + } + } + + /** + * Прочитать сессию (Read session) + * + * @param string $id Идентификатор сессии (Session ID) + * + * @return string Данные сессии (The session data) + * + * @internal Do not call this method directly + */ + public function readSession($id): string + // public function readSession(string $id): string + { + $query = $this->getReadQuery($id); + + if ($this->readCallback !== null) { + $fields = $query->one($this->db); + return $fields === false ? '' : $this->extractData($fields); + } + + $data = $query->select(['data'])->scalar($this->db); + return $data === false ? '' : $data; + } + + /** + * Записать сессию (Write session) + * + * @param string $id Идентификатор сессии (Session ID) + * @param string $data Данные сессии (Session data) + * + * @return bool Статус записи сессии (Session writing status) + * + * @internal Do not call this method directly + */ + public function writeSession($id, $data): bool + // public function writeSession(string $id, string $data): bool + { + if ($this->getUseStrictMode() && $id === $this->_forceRegenerateId) { + //Ignore write when forceRegenerate is active for this id + return true; + } + + // exception must be caught in session write handler + // https://secure.php.net/manual/en/function.session-set-save-handler.php#refsect1-function.session-set-save-handler-notes + try { + // ensure backwards compatability (fixed #9438) + if ($this->writeCallback && !$this->fields) { + $this->fields = $this->composeFields(); + } + // ensure data consistency + if (!isset($this->fields['data'])) { + $this->fields['data'] = $data; + } else { + $_SESSION = $this->fields['data']; + } + // ensure 'id' and 'expire' are never affected by [[writeCallback]] + $this->fields = array_merge($this->fields, [ + 'id' => $id, + 'expire' => time() + $this->getTimeout(), + ]); + $this->fields = $this->typecastFields($this->fields); + $this->db->createCommand()->upsert($this->sessionTable, $this->fields)->execute(); + $this->fields = []; + } catch (Exception $e) { + Yii::$app->errorHandler->handleException($e); + return false; + } + + return true; + } + + /** + * Удалить сессию (Delete session) + * + * @param string $id Идентификатор сессии (Session ID) + * + * @return bool Статус удаления сессии (Session deleting status) + * + * @internal Do not call this method directly + */ + public function destroySession($id) + { + $this->db->createCommand() + ->delete($this->sessionTable, ['id' => $id]) + ->execute(); + + return true; + } + + /** + * Удалить неиспользуемые данные (Delete garbage) + *. + * @param int $max Периодичность очистки в секундах (Cleaning frequency in seconds) + * + * @return bool Статус очистки неиспользуемых данных сессии (Session garbage deleting status) + * + * @internal Do not call this method directly + */ + public function gcSession($max): bool + // public function gcSession(int $max): bool + { + $this->db->createCommand() + ->delete($this->sessionTable, '[[expire]]<:expire', [':expire' => time()]) + ->execute(); + + return true; + } + + /** + * Генерация запроса для чтения сеанса (Generating a query to read a session) + * + * @param string $id Идентификатор сессии (Session ID) + */ + protected function getReadQuery($id): Query + // protected function getReadQuery(string $id): Query + { + return (new Query()) + ->from($this->sessionTable) + ->where('[[expire]]>:expire AND [[id]]=:id', [':expire' => time(), ':id' => $id]); + } + + /** + * Конвертация для отправки в PDO (Convertation for sending to PDO) + * + * Default implementation casts field `data` to `\PDO::PARAM_LOB`. + * You can override this method in case you need special type casting. + * + * @param array $data Данные для конвертации (Data for convertation) [name => value] + */ + protected function typecastFields($data): array + // protected function typecastFields(array $data): array + { + // if (isset($data['data']) && !is_array($data['data']) && !is_object($data['data'])) { + // $data['data'] = new PdoValue($data['data'], \PDO::PARAM_LOB); + // } + + // return $data; + + return []; + } +} \ No newline at end of file