Храните сессии в базе данных

Дата обновления перевода 2023-07-10

Храните сессии в базе данных

Symfony по умолчанию хранит сессии в файлах. Если ваше приложение обслуживается несколькими серверами, вам понадобится вместо этого использовать базу данных, чтобы сессии работали в разных серверах.

Symfony может хранить сессии в различных БД (относительных, NoSQL и "ключ-значение"), но рекомендует БД "ключ-значение" вроде Redis для наилучшей производительности.

Храните сессии в БД "ключ-значение" (Redis)

Этот раздел предполагает, что у вас есть полностью рабочий сервер Redis, а также установленное и сконфигурированное расширение phpredis.

У вас есть два разных варианта использования Redis для хранения сессий:

(1) Первый вариант, основанный на PHP, заключается в конфигурации обработчика сессии Redis прямо в файле сервера php.ini:

1
2
3
; php.ini
session.save_handler = redis
session.save_path = "tcp://192.168.0.178:6379?auth=REDIS_PASSWORD"

(2) Второй вариант, основанный на Symfony, заключается в конфигурации сессий Redis следующим образом.

Для начала, определите сервис Symfony для соединения с сервером Redis:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# config/services.yaml
services:
    # ...
    Redis:
        # вы также можете использовать классы \RedisArray, \RedisCluster или \Predis\Client
        class: Redis
        calls:
            - connect:
                - '%env(REDIS_HOST)%'
                - '%env(int:REDIS_PORT)%'

            # раскомментируйте следующее, если ваш сервер Redis требует пароль
            # - auth:
            #     - '%env(REDIS_PASSWORD)%'

Теперь передайте это соединение \Redis как аргумент сервиса, ассоциированный с RedisSessionHandler. Этот аргумента такж может быть \RedisArray, \RedisCluster, \Predis\Client, и RedisProxy:

1
2
3
4
5
6
7
8
9
10
# config/services.yaml
services:
    # ...
    Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler:
        arguments:
            - '@Redis'
            # вы можете по желанию передать массив опций. Единственными опциями являются 'prefix' и 'ttl',
            # которые определяют используемый для ключей префикс, чтобы избежать коллизий на сервере Redis
            # и срока окончания действия для любой заданной записи (в секундах), по умолчанию - 'sf_s' и null:
            # - { 'prefix': 'my_prefix', 'ttl': 600 }

Далее, используйте опцию конфигурации handler_id , чтобы указать Symfony использовать этот сервис в качестве обработчика сессии:

1
2
3
4
5
# config/packages/framework.yaml
framework:
    # ...
    session:
        handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler

Вот и все! Symfony теперь будет использовать ваш сервер Redis для чтения и записи данных сессии. Главный недостаток этого решения в том, что Redis не производит блокировки сесии, поэтому вы можете столкнуться с состояниями гонки при доступе к сессиям. Например, вы можете увидеть ошибку "Невалидный токен CSRF", так как два запроса были сделаны параллельно, и только первый из них сохранил CSRF-токен в сессии.

See also

Если вы используете Memcached вместо Redis, следуйте схожему подходу, но замените RedisSessionHandler на MemcachedSessionHandler.

Храните сессии в реляционной базе данных (MariaDB, MySQL, PostgreSQL)

Symfony включает в себя PdoSessionHandler для хранения сессий в реляционных БД вроде MariaDB, MySQL и PostgreSQL. Чтобы использовать это, для начала зарегистрируйте новый сервис обработчика с идентификационными данными вашей БД:

1
2
3
4
5
6
7
8
9
10
11
# config/services.yaml
services:
    # ...

    Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler:
        arguments:
            - '%env(DATABASE_URL)%'

            # вы также можете использовать конфигурацию PDO, но это требует передачи двух аргументов
            # - 'mysql:dbname=mydatabase; host=myhost; port=myport'
            # - { db_username: myuser, db_password: mypassword }

Tip

При использовании MySQL в качестве БД, DSN, определенная в DATABASE_URL может содержать опции charset и unix_socket в качестве параметров строки запроса.

Затем, используйте опцию конфигурации handler_id , чтобы указать Symfony использовать этот сервис в качестве обработчика сесии:

1
2
3
4
5
# config/packages/framework.yaml
framework:
    session:
        # ...
        handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler

Конфигурация имен таблиц и столбцов сессии

Таблица, используемая дл хранения сессий, называется sessions по умолчанию, и определяет определенные имена столбцов. Вы можете сконфигурировать эти значения с помощью передачи второго аргумента сервису PdoSessionHandler:

1
2
3
4
5
6
7
8
# config/services.yaml
services:
    # ...

    Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler:
        arguments:
            - '%env(DATABASE_URL)%'
            - { db_table: 'customer_session', db_id_col: 'guid' }

Вот параметры, которые вы можете сконфигурировать:

db_table (по умолчанию sessions):
Имя таблицы сессий в вашей БД;
db_username: (по умолчанию: '')
Имя пользователя, используемое для соединения, используя конфигурацию PDO (если используется соединение, основанное на переменной окружения DATABASE_URL, оно переопределяет имя пользователя, определенное в переменной окружения).
db_password: (по умолчанию: '')
Парль, используемый для соединения, при использовании конфигурации PDO (если используется соединение, основанное на переменной окружения DATABASE_URL, оно переопределяет пароль, определенный в переменной окружения).
db_id_col (по умолчанию sess_id):
Имя столбца, где хранить ID сессии (тип столбца: VARCHAR(128));
db_data_col (по умолчанию sess_data):
Имя столбца, где хранить данные сессии (тип столбца: BLOB);
db_time_col (по умолчанию sess_time):
Имя столбца, где хранить временную отметку создания сессии (тип столбца: INTEGER);
db_lifetime_col (по умолчанию sess_lifetime):
Имя столбца, где хранить жизненный цикл сессии (тип столбца: INTEGER);
db_connection_options (по умолчанию: [])
Массив опций соединения, относящихся к драйверу;
lock_mode (по умолчанию: LOCK_TRANSACTIONAL)
Стратегия блокировки БД для избежания состояний гонки. Возможные значения - LOCK_NONE (не блокировать), LOCK_ADVISORY (блокировать на уровне приложения) и LOCK_TRANSACTIONAL (блокировать на уровне строчки).

Подготовка базы данных к хранению сессий

До сохранения сессий в БД, вы должны создать таблицу, хранящую информацию. Обработчик сессии предоставляет метод под названием createTable() для настройки этой таблицы для вас, в соответствии с используемым движком БД:

1
2
3
4
5
try {
    $sessionHandlerService->createTable();
} catch (\PDOException $exception) {
    // таблица не могла быть создана по какой-то причине
}

Если вы предпочитаете устанавливать таблицу самостоятельно, рекомендуется сгенрировать пустую миграцию БД с помощью следующей команды:

1
$ php bin/console doctrine:migrations:generate

Затем, найдите соотетствующий SQL для вашей БД ниже, добавьте его к файлу, и запустите миграцию с помощью следующей команды:

1
$ php bin/console doctrine:migrations:migrate

MariaDB/MySQL

1
2
3
4
5
6
7
CREATE TABLE `sessions` (
    `sess_id` VARBINARY(128) NOT NULL PRIMARY KEY,
    `sess_data` BLOB NOT NULL,
    `sess_lifetime` INTEGER UNSIGNED NOT NULL,
    `sess_time` INTEGER UNSIGNED NOT NULL,
    INDEX `sessions_sess_lifetime_idx` (`sess_lifetime`)
) COLLATE utf8mb4_bin, ENGINE = InnoDB;

Note

Тип столбца BLOB (который используется createTable() по умолчанию) хранит до 64 кБ. Если данные сессии пользователя превышают это значение, может быть вызвано исключение, или их сессия может быть беззвучно сброшена. Рассмотрите использование MEDIUMBLOB, если вам нужно больше места.

PostgreSQL

1
2
3
4
5
6
7
CREATE TABLE sessions (
    sess_id VARCHAR(128) NOT NULL PRIMARY KEY,
    sess_data BYTEA NOT NULL,
    sess_lifetime INTEGER NOT NULL,
    sess_time INTEGER NOT NULL
);
CREATE INDEX sessions_sess_lifetime_idx ON sessions (sess_lifetime);

Microsoft SQL Server

1
2
3
4
5
6
7
CREATE TABLE sessions (
    sess_id VARCHAR(128) NOT NULL PRIMARY KEY,
    sess_data NVARCHAR(MAX) NOT NULL,
    sess_lifetime INTEGER NOT NULL,
    sess_time INTEGER NOT NULL,
    INDEX sessions_sess_lifetime_idx (sess_lifetime)
);

Храните сессии в базе данных NoSQL (MongoDB)

Symfony включает в себя MongoDbSessionHandler для хранения сессий в БД MongoDB NoSQL. Для начала, убедитесь, что у вас есть рабочее соединение MongoDB в вашем приложении Symfony, как объясняется в статье конфигурация DoctrineMongoDBBundle.

Затем, зарегистрируйте новый обработчик сервиса для MongoDbSessionHandler, и передайте соединение MongoDB в качестве аргумента:

1
2
3
4
5
6
7
# config/services.yaml
services:
    # ...

    Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler:
        arguments:
            - '@doctrine_mongodb.odm.default_connection'

Далее, используйте опцию конфигурации handler_id , чтобы указать Symfony использовать этот сервис в качестве обработчика сессии:

1
2
3
4
5
# config/packages/framework.yaml
framework:
    session:
        # ...
        handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler

Note

MongoDB ODM 1.x работает только с наследуемым дравером, что больше не поддерживается классом сессии Symfony. Установите пакет alcaeus/mongo-php-adapter, чтобы извлечь основной объект \MongoDB\Client, или обновить MongoDB ODM до 2.0.

Вот и все! Symfony теперь будет использовать ваш сервер MongoDB, чтобы считывать и записывать данные сессии. Вам не нужно делать ничего, чтобы запустить коллекцию вашей сессии. Однако, вы можете захотеть добавить индекс, чтобы улучшить производительность вашего сбора мусора. Выполните это из оболочки MongoDB:

1
2
use session_db
db.session.createIndex( { "expires_at": 1 }, { expireAfterSeconds: 0 } )

Конфигурация имен полей сессии

Коллекция, используемая для хранения сессий, определяет определенные имена полей. Вы можете сконфигурировать эти значения со вторым аргументом, переданным сервису MongoDbSessionHandler:

1
2
3
4
5
6
7
8
# config/services.yaml
services:
    # ...

    Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler:
        arguments:
            - '@doctrine_mongodb.odm.default_connection'
            - { id_field: '_guid', 'expiry_field': 'eol' }

Вот параметры, которые вы можете сконфигурировать:

id_field (по умолчанию _id):
Имя поля, где хранить ID сессии;
data_field (по умолчанию data):
Имя поля, где хранить данные сессии;
time_field (по умолчанию time):
Имя поля, где хранить временную отметку создания сессии;
expiry_field (по умолчанию expires_at):
Имя поля, где хранить жизненный цикл сессии.