Дата обновления перевода 2021-09-30

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

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

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

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

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

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

  • YAML
     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)%'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <!-- вы также можете использовать классы \RedisArray, \RedisCluster или \Predis\Client -->
            <service id="Redis" class="Redis">
                <call method="connect">
                    <argument>%env(REDIS_HOST)%</argument>
                    <argument>%env(int:REDIS_PORT)%</argument>
                </call>
    
                <!-- раскомментируйте следующее, если ваш сервер Redis требует пароль:
                <call method="auth">
                    <argument>%env(REDIS_PASSWORD)%</argument>
                </call> -->
            </service>
        </services>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    // ...
    $container
        // вы также можете использовать классы \RedisArray, \RedisCluster или \Predis\Client
        ->register('Redis', \Redis::class)
        ->addMethodCall('connect', ['%env(REDIS_HOST)%', '%env(int:REDIS_PORT)%'])
        // раскомментируйте следующее, если ваш сервер Redis требует пароль:
        // ->addMethodCall('auth', ['%env(REDIS_PASSWORD)%'])
    ;
    

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

  • YAML
     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 }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <!-- config/services.xml -->
    <services>
        <service id="Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler">
            <argument type="service" id="Redis"/>
            <!-- вы можете по желанию передать массив опций. Единственными опциями являются 'prefix' и 'ttl',
                 которые определяют используемый для ключей префикс, чтобы избежать коллизий на сервере Redis
                 и срока окончания действия для любой заданной записи (в секундах), по умолчанию - 'sf_s' и null:
            <argument type="collection">
                <argument key="prefix">my_prefix</argument>
                <argument key="ttl">600</argument>
            </argument> -->
        </service>
    </services>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // config/services.php
    use Symfony\Component\DependencyInjection\Reference;
    use Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler;
    
    $container
        ->register(RedisSessionHandler::class)
        ->addArgument(
            new Reference('Redis'),
            // вы можете по желанию передать массив опций. Единственными опциями являются 'prefix' и 'ttl',
            // которые определяют используемый для ключей префикс, чтобы избежать коллизий на сервере Redis
            // и срока окончания действия для любой заданной записи (в секундах), по умолчанию - 'sf_s' и null:
            // ['prefix' => 'my_prefix', 'ttl' => 600],
        );
    

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

  • YAML
    1
    2
    3
    4
    5
    # config/packages/framework.yaml
    framework:
        # ...
        session:
            handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler
    
  • XML
    1
    2
    3
    4
    5
    <!-- config/packages/framework.xml -->
    <framework:config>
        <!-- ... -->
        <framework:session handler-id="Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler"/>
    </framework:config>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // config/packages/framework.php
    use Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler;
    use Symfony\Config\FrameworkConfig;
    
    return static function (FrameworkConfig $framework) {
        // ...
        $framework->session()
            ->handlerId(RedisSessionHandler::class)
        ;
    };
    

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

See also

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

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

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

  • YAML
     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 }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <!-- config/services.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:framework="http://symfony.com/schema/dic/symfony"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd
            https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    
        <services>
            <service id="Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler" public="false">
                <argument>%env(DATABASE_URL)%</argument>
    
                <!-- вы также можете использовать конфигурацию PDO, но это требует передачи двух аргументов: -->
                <!-- <argument>mysql:dbname=mydatabase; host=myhost; port=myport</argument>
                    <argument type="collection">
                        <argument key="db_username">myuser</argument>
                        <argument key="db_password">mypassword</argument>
                    </argument> -->
            </service>
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // config/services.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    
    use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
    
    return static function (ContainerConfigurator $container) {
        $services = $configurator->services();
    
        $services->set(PdoSessionHandler::class)
            ->args([
                '%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 в качестве параметров строки запроса.

New in version 5.3: Поддержка опций charset и unix_socket была представлена в Symfony 5.3.

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

  • YAML
    1
    2
    3
    4
    5
    # config/packages/framework.yaml
    framework:
        session:
            # ...
            handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler
    
  • XML
    1
    2
    3
    4
    5
    6
    <!-- config/packages/framework.xml -->
    <framework:config>
        <!-- ... -->
        <framework:session
            handler-id="Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler"/>
    </framework:config>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // config/packages/framework.php
    use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
    use Symfony\Config\FrameworkConfig;
    
    return static function (FrameworkConfig $framework) {
        // ...
        $framework->session()
            ->handlerId(PdoSessionHandler::class)
        ;
    };
    

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

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

  • YAML
    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' }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- config/services.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <service id="Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler" public="false">
                <argument>%env(DATABASE_URL)%</argument>
                <argument type="collection">
                    <argument key="db_table">customer_session</argument>
                    <argument key="db_id_col">guid</argument>
                </argument>
            </service>
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // config/services.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    
    use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
    
    return static function (ContainerConfigurator $container) {
        $services = $configurator->services();
    
        $services->set(PdoSessionHandler::class)
            ->args([
                '%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() для настройки этой таблицы для вас, в соответствии с используемым движком БД:

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_time_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 в качестве аргумента:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    # config/services.yaml
    services:
        # ...
    
        Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler:
            arguments:
                - '@doctrine_mongodb.odm.default_connection'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <!-- config/services.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:framework="http://symfony.com/schema/dic/symfony"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd
            https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    
        <services>
            <service id="Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler" public="false">
                <argument type="service">doctrine_mongodb.odm.default_connection</argument>
            </service>
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // config/services.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    
    use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler;
    
    return static function (ContainerConfigurator $container) {
        $services = $configurator->services();
    
        $services->set(MongoDbSessionHandler::class)
            ->args([
                service('doctrine_mongodb.odm.default_connection'),
            ])
        ;
    };
    

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

  • YAML
    1
    2
    3
    4
    5
    # config/packages/framework.yaml
    framework:
        session:
            # ...
            handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler
    
  • XML
    1
    2
    3
    4
    5
    6
    <!-- config/packages/framework.xml -->
    <framework:config>
        <!-- ... -->
        <framework:session
            handler-id="Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler"/>
    </framework:config>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // config/packages/framework.php
    use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler;
    use Symfony\Config\FrameworkConfig;
    
    return static function (FrameworkConfig $framework) {
        // ...
        $framework->session()
            ->handlerId(MongoDbSessionHandler::class)
        ;
    };
    

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.ensureIndex( { "expires_at": 1 }, { expireAfterSeconds: 0 } )

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

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

  • YAML
    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' }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- config/services.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <service id="Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler" public="false">
                <argument type="service">doctrine_mongodb.odm.default_connection</argument>
                <argument type="collection">
                    <argument key="id_field">_guid</argument>
                    <argument key="expiry_field">eol</argument>
                </argument>
            </service>
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // config/services.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    
    use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler;
    
    return static function (ContainerConfigurator $container) {
        $services = $configurator->services();
    
        $services->set(MongoDbSessionHandler::class)
            ->args([
                service('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):
Имя поля, где хранить жизненный цикл сессии.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.