generated from mirzaev/pot
	
		
			
				
	
	
		
			298 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			298 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | ||
| 
 | ||
| declare(strict_types=1);
 | ||
| 
 | ||
| namespace mirzaev\site\account\models;
 | ||
| 
 | ||
| // Файлы проекта
 | ||
| use mirzaev\site\account\models\account_model as account;
 | ||
| 
 | ||
| // Фреймворк ArangoDB
 | ||
| use mirzaev\arangodb\collection,
 | ||
|   mirzaev\arangodb\document;
 | ||
| 
 | ||
| // Библиотека для ArangoDB
 | ||
| use ArangoDBClient\Document as _document;
 | ||
| 
 | ||
| // Встроенные библиотеки
 | ||
| use exception;
 | ||
| 
 | ||
| /**
 | ||
|  * Модель сессий
 | ||
|  *
 | ||
|  * @package mirzaev\site\account\models
 | ||
|  * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 | ||
|  */
 | ||
| final class session_model extends core
 | ||
| {
 | ||
|   /**
 | ||
|    * Коллекция
 | ||
|    */
 | ||
|   public const COLLECTION = 'session';
 | ||
| 
 | ||
|   /**
 | ||
|    * Данные сессии из базы данных 
 | ||
|    */
 | ||
|   public _document $document;
 | ||
| 
 | ||
|   /**
 | ||
|    * Конструктор 
 | ||
|    *
 | ||
|    * Инициализация сессии и запись в свойство $this->document
 | ||
|    *
 | ||
|    * @param ?string $hash Хеш сессии в базе данных
 | ||
|    * @param ?int $expires Дата окончания работы сессии (используется при создании новой сессии)
 | ||
|    * @param array &$errors Журнал ошибок
 | ||
|    *
 | ||
|    * @return static Инстанция сессии
 | ||
|    */
 | ||
|   public function __construct(?string $hash = null, ?int $expires = null, array &$errors = [])
 | ||
|   {
 | ||
|     try {
 | ||
|       if (collection::init(static::$db->session, self::COLLECTION)) {
 | ||
|         // Инициализирована коллекция
 | ||
| 
 | ||
|         if (isset($hash) && $session = collection::search(static::$db->session, sprintf(
 | ||
|           <<<AQL
 | ||
|             FOR d IN %s
 | ||
|             FILTER d.hash == '$hash' && d.expires > %d
 | ||
|             RETURN d
 | ||
|           AQL,
 | ||
|           self::COLLECTION,
 | ||
|           time()
 | ||
|         ))) {
 | ||
|           // Найдена сессия по хешу
 | ||
| 
 | ||
|           // Запись в свойство
 | ||
|           $this->document = $session;
 | ||
|         } else if ($session = collection::search(static::$db->session, sprintf(
 | ||
|           <<<AQL
 | ||
|             FOR d IN %s
 | ||
|             FILTER d.ip == '%s' && d.expires > %d
 | ||
|             RETURN d
 | ||
|           AQL,
 | ||
|           self::COLLECTION,
 | ||
|           $_SERVER['REMOTE_ADDR'],
 | ||
|           time()
 | ||
|         ))) {
 | ||
|           // Найдена сессия по данным пользователя
 | ||
| 
 | ||
|           // Запись в свойство
 | ||
|           $this->document = $session;
 | ||
|         } else {
 | ||
|           // Не найдена сессия
 | ||
| 
 | ||
|           // Запись сессии в базу данных
 | ||
|           $_id = document::write(static::$db->session, self::COLLECTION, [
 | ||
|             'ip' => $_SERVER['REMOTE_ADDR'],
 | ||
|             'expires' => $expires ?? time() + 604800
 | ||
|           ]);
 | ||
| 
 | ||
|           if ($session = collection::search(static::$db->session, sprintf(
 | ||
|             <<<AQL
 | ||
|               FOR d IN %s
 | ||
|               FILTER d._id == '$_id' && d.expires > %d
 | ||
|               RETURN d
 | ||
|             AQL,
 | ||
|             self::COLLECTION,
 | ||
|             time()
 | ||
|           ))) {
 | ||
|             // Найдена созданная сессия
 | ||
| 
 | ||
|             // Запись хеша
 | ||
|             $session->hash = sodium_bin2hex(sodium_crypto_generichash($_id));
 | ||
| 
 | ||
|             if (document::update(static::$db->session, $session)) {
 | ||
|               // Записано обновление
 | ||
| 
 | ||
|               // Запись в свойство
 | ||
|               $this->document = $session;
 | ||
|             } else throw new exception('Не удалось записать данные сессии');
 | ||
|           } else throw new exception('Не удалось создать или найти созданную сессию');
 | ||
|         }
 | ||
|       } else throw new exception('Не удалось инициализировать коллекцию');
 | ||
|     } catch (exception $e) {
 | ||
|       // Запись в журнал ошибок
 | ||
|       $errors[] = [
 | ||
|         'text' => $e->getMessage(),
 | ||
|         'file' => $e->getFile(),
 | ||
|         'line' => $e->getLine(),
 | ||
|         'stack' => $e->getTrace()
 | ||
|       ];
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   public function __destruct() {
 | ||
|     // Закрыть сессию
 | ||
|   }
 | ||
| 
 | ||
|   /**
 | ||
|    * Связь сессии с аккаунтом
 | ||
|    *
 | ||
|    * @param _document $account Инстанция аккаунта
 | ||
|    * @param array &$errors Журнал ошибок
 | ||
|    *
 | ||
|    * @return bool Статус выполнения
 | ||
|    */
 | ||
|   public function connect(_document $account, array &$errors = []): bool
 | ||
|   {
 | ||
|     try {
 | ||
|       if (
 | ||
|         collection::init(static::$db->session, self::COLLECTION)
 | ||
|         && collection::init(static::$db->session, account::COLLECTION)
 | ||
|         && collection::init(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, true)
 | ||
|       ) {
 | ||
|         // Инициализирована коллекция
 | ||
| 
 | ||
|         if (document::write(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, [
 | ||
|           '_from' => $this->document->getId(),
 | ||
|           '_to' => $account->getId()
 | ||
|         ])) {
 | ||
|           // Создано ребро: session -> account
 | ||
| 
 | ||
|           return true;
 | ||
|         } else throw new exception('Не удалось создать ребро: session -> account');
 | ||
|       } else throw new exception('Не удалось инициализировать коллекцию');
 | ||
|     } catch (exception $e) {
 | ||
|       // Запись в журнал ошибок
 | ||
|       $errors[] = [
 | ||
|         'text' => $e->getMessage(),
 | ||
|         'file' => $e->getFile(),
 | ||
|         'line' => $e->getLine(),
 | ||
|         'stack' => $e->getTrace()
 | ||
|       ];
 | ||
|     }
 | ||
| 
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   /**
 | ||
|    * Поиск связанного аккаунта
 | ||
|    *
 | ||
|    * @param array &$errors Журнал ошибок
 | ||
|    *
 | ||
|    * @return ?_document Инстанция аккаунта, если удалось найти
 | ||
|    */
 | ||
|   public function account(array &$errors = []): ?_document
 | ||
|   {
 | ||
|     try {
 | ||
|       if (
 | ||
|         collection::init(static::$db->session, self::COLLECTION)
 | ||
|         && collection::init(static::$db->session, account::COLLECTION)
 | ||
|         && collection::init(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, true)
 | ||
|       ) {
 | ||
|         // Инициализированы коллекции
 | ||
| 
 | ||
|         if ($account = collection::search(static::$db->session, sprintf(
 | ||
|           <<<AQL
 | ||
|             FOR document IN %s
 | ||
|             LET edge = (
 | ||
|               FOR edge IN %s
 | ||
|               FILTER edge._from == '%s'
 | ||
|               SORT edge._key DESC
 | ||
|               LIMIT 1
 | ||
|               RETURN edge
 | ||
|             )
 | ||
|             FILTER document._id == edge[0]._to
 | ||
|             LIMIT 1
 | ||
|             RETURN document
 | ||
|           AQL,
 | ||
|           account::COLLECTION,
 | ||
|           self::COLLECTION . '_edge_' . account::COLLECTION,
 | ||
|           $this->document->getId()
 | ||
|         ))) {
 | ||
|           // Найден аккаунт
 | ||
| 
 | ||
|           return $account;
 | ||
|         } else throw new exception('Не удалось найти аккаунт');
 | ||
|       } else throw new exception('Не удалось инициализировать коллекцию');
 | ||
|     } catch (exception $e) {
 | ||
|       // Запись в журнал ошибок
 | ||
|       $errors[] = [
 | ||
|         'text' => $e->getMessage(),
 | ||
|         'file' => $e->getFile(),
 | ||
|         'line' => $e->getLine(),
 | ||
|         'stack' => $e->getTrace()
 | ||
|       ];
 | ||
|     }
 | ||
| 
 | ||
|     return null;
 | ||
|   }
 | ||
| 
 | ||
|   /**
 | ||
|    * Записать
 | ||
|    *
 | ||
|    * Ищет свойство в инстанции сессии, если не находит, то ищет его в инстанции документа сессии из базы данных,
 | ||
|    * затем записывает в него переданные данные. Динамическая инициализация свойств происходит в инстанции
 | ||
|    * документа сессии из базы данных 
 | ||
|    *
 | ||
|    * @param string $name Название свойства
 | ||
|    * @param mixed $value Содержимое для записи
 | ||
|    *
 | ||
|    * @return void
 | ||
|    */
 | ||
|   public function __set(string $name, mixed $value = null): void
 | ||
|   {
 | ||
|     if (isset($this->{$name})) $this->{$name} = $value;
 | ||
|     else $this->document->{$name} = $value;
 | ||
|   }
 | ||
| 
 | ||
|   /**
 | ||
|    * Прочитать
 | ||
|    *
 | ||
|    * Ищет свойство в инстанции сессии, если не находит, то ищет его в инстанции документа сессии из базы данных
 | ||
|    *
 | ||
|    * @param string $name Название свойства
 | ||
|    *
 | ||
|    * @return mixed Данные свойства инстанции сессии или инстанции документа сессии из базы данных
 | ||
|    */
 | ||
|   public function __get(string $name): mixed
 | ||
|   {
 | ||
|     return $this->{$name} ?? $this->document->{$name};
 | ||
|   }
 | ||
| 
 | ||
|   /**
 | ||
|    * Проверить инициализированность
 | ||
|    *
 | ||
|    * Ищет свойство в инстанции сессии, если не находит, то ищет его в инстанции документа сессии из базы данных,
 | ||
|    * затем проверяет его инициализированность через встроенную функцию isset()
 | ||
|    *
 | ||
|    * @param string $name Название свойства
 | ||
|    *
 | ||
|    * @return bool Свойство инициализировано?
 | ||
|    */
 | ||
|   public function __isset(string $name): bool
 | ||
|   {
 | ||
|     return isset($this->{$name}) || isset($this->document->{$name});
 | ||
|   }
 | ||
| 
 | ||
|   /**
 | ||
|    * Удалить
 | ||
|    *
 | ||
|    * Деинициализирует свойство в инстанции сессии и в инстанции документа сессии из базы данных
 | ||
|    *
 | ||
|    * @param string $name Название свойства
 | ||
|    *
 | ||
|    * @return void
 | ||
|    */
 | ||
|   public function __unset(string $name): void
 | ||
|   {
 | ||
|     unset($this->{$name}, $this->document->{$name});
 | ||
|   }
 | ||
| 
 | ||
|   /**
 | ||
|    * Выполнить метод
 | ||
|    *
 | ||
|    * Ищет метод в инстанции сессии, если не находит, то ищет его в инстанции документа сессии из базы данных
 | ||
|    *
 | ||
|    * @param string $name Название метода
 | ||
|    * @param array $arguments Аргументы
 | ||
|    *
 | ||
|    * @return void
 | ||
|    */
 | ||
|   public function __call(string $name, array $arguments = []): mixed
 | ||
|   {
 | ||
|     return method_exists($this, $name) ? $this->{$name}($arguments) : $this->document->{$name}($arguments);
 | ||
|   }
 | ||
| }
 |