Любите загадки? Событие еще доступно на сайте.
Поддержите нас - каждый вклад важен!
Поддержите нас - каждый вклад важен!

Сессия HTTP

Введение

Поскольку приложения, использующие HTTP, не имеют состояния, то сессии позволяют хранить пользовательскую информацию между несколькими запросами. Эта пользовательская информация обычно помещается в постоянное хранилище, к которому можно получить доступ из последующих запросов.

Laravel предлагает множество различных типов хранилищ сессий, доступ к которым осуществляется через выразительный унифицированный API. Осуществлена поддержка популярных типов хранилищ, таких как Memcached, Redis и база данных.

Конфигурирование

Конфигурационный файл сессии вашего приложения расположен в config/session.php. Обязательно просмотрите параметры, доступные вам в этом файле. По умолчанию Laravel ориентирован на использование драйвера file сессии, который подходит для многих приложений. Если ваше приложение будет балансировать нагрузку между несколькими веб-серверами, то вам следует выбрать централизованное хранилище, к которому могут получить доступ все серверы, например Redis или база данных.

Параметр конфигурации driver сессии определяет, где будут храниться данные сессии для каждого запроса. Laravel содержит несколько отличных драйверов из коробки:

  • file – сессии хранятся в storage/framework/sessions.
  • cookie – сессии хранятся в безопасных, зашифрованных файлах Cookies.
  • database – сессии хранятся в реляционной базе данных.
  • memcached / redis – сессии хранятся в одном из этих быстрых хранилищ на основе кеша.
  • dynamodb – сессии хранятся в AWS DynamoDB.
  • array – сессии хранятся в массиве PHP и не будет сохранены.

Драйвер array в основном используется во время тестирования и предотвращает сохранение данных, находящихся в сессии.

Предварительная подготовка драйверов

Драйвер database

При использовании драйвера database сессии, вам нужно будет создать таблицу, содержащую записи сессии. Пример объявления Schema для таблицы ниже:

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::create('sessions', function (Blueprint $table) {
    $table->string('id')->primary();
    $table->foreignId('user_id')->nullable()->index();
    $table->string('ip_address', 45)->nullable();
    $table->text('user_agent')->nullable();
    $table->text('payload');
    $table->integer('last_activity')->index();
});

Вы можете использовать команду session:table Artisan для генерации этой миграции. Чтобы узнать больше о миграции баз данных, вы можете ознакомиться с полной документацией по миграции:

php artisan session:table

php artisan migrate

Redis

Перед использованием Redis с Laravel вам нужно будет либо установить расширение PHP PhpRedis через PECL, либо установить пакет predis/predis (~ 1.0) через Composer. Для получения дополнительной информации о настройке Redis обратитесь к документации Redis Laravel.

В параметре connection конфигурационного файла config/session.php указывается, какое соединение Redis используется сессией.

Взаимодействие с сессией

Получение данных

В Laravel есть два основных способа работы с данными сессии: через глобальный помощник session или через экземпляр Request. Во-первых, давайте посмотрим на доступ к сессии через экземпляр Request, тип которого может быть объявлен в замыкании маршрута или методе контроллера. Помните, что зависимости методов контроллера автоматически внедряются через контейнер служб Laravel:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * Показать профиль конкретного пользователя.
     */
    public function show(Request $request, string $id): View
    {
        $value = $request->session()->get('key');

        //

        $user = $this->users->find($id);

        return view('user.profile', ['user' => $user]);
    }
}

Когда вы извлекаете элемент из сессии, вы также можете передать значение по умолчанию в качестве второго аргумента метода get. Это значение по умолчанию будет возвращено, если указанный ключ не существует в сессии. Если вы передаете замыкание в качестве значения по умолчанию методу get, а запрошенный ключ не существует, то будет выполнено замыкание с последующим возвратом его результата:

$value = $request->session()->get('key', 'default');

$value = $request->session()->get('key', function () {
    return 'default';
});

Глобальный помощник session

Вы также можете использовать глобальную помощник session для получения / сохранения данных сессии. Когда помощник session вызывается с одним строковым аргументом, тогда он возвращает значение этого ключа сессии. Когда помощник вызывается с массивом пар ключ / значение, эти значения сохраняются в сессию:

Route::get('/home', function () {
    // Получить часть данных из сессии ...
    $value = session('key');

    // Получить часть данных из сессии с указанием значения по умолчанию ...
    $value = session('key', 'default');

    // Сохранить часть данных в сессию ...
    session(['key' => 'value']);
});

Существует небольшая практическая разница между использованием сессии через экземпляр HTTP-запроса и использованием глобального помощника session. Оба метода тестируемые с помощью метода assertSessionHas, который доступен во всех ваших тестах.

Получение всех данных сессии

Если вы хотите получить все данные сессии, то вы можете использовать метод all:

$data = $request->session()->all();

Получение части данных сессии

Методы only и except могут быть использованы для извлечения подмножества данных сессии:

$data = $request->session()->only(['username', 'email']);

$data = $request->session()->except(['username', 'email']);

Определение наличия элемента в сессии

Чтобы определить, присутствует ли элемент в сессии, вы можете использовать метод has. Метод has возвращает true, если элемент присутствует, и не равен null:

if ($request->session()->has('users')) {
    // ...
}

Чтобы определить, присутствует ли элемент в сессии, даже если его значение равно null, то вы можете использовать метод exists:

if ($request->session()->exists('users')) {
    // ...
}

Чтобы определить, отсутствует ли элемент в сессии, вы можете использовать метод missing. Метод missing возвращает true, если элемент имеет значение null или если элемент отсутствует:

if ($request->session()->missing('users')) {
    // ...
}

Сохранение данных

Для сохранения данных в сессии вы обычно будете использовать метод put экземпляра запроса или глобального помощника session:

// Через экземпляр запроса ...
$request->session()->put('key', 'value');

// Через глобальный помощник «session» ...
session(['key' => 'value']);

Добавление в массив значений сессии

Метод push используется для вставки нового значения в значение сессии, которое является массивом. Например, если ключ user.teams содержит массив названий команд, то вы можете поместить новое значение в массив следующим образом:

$request->session()->push('user.teams', 'developers');

Получение с последующим удалением элемента

Метод pull извлекает и удаляет элемент из сессии единым выражением:

$value = $request->session()->pull('key', 'default');

Увеличение и уменьшение отдельных значений в сессии

Если данные вашей сессии содержат целое число, которое вы хотите увеличить или уменьшить, то вы можете использовать методы increment и decrement:

$request->session()->increment('count');

$request->session()->increment('count', $incrementBy = 2);

$request->session()->decrement('count');

$request->session()->decrement('count', $decrementBy = 2);

Кратковременные данные

По желанию можно сохранить элементы в сессии только для следующего запроса. Вы можете сделать это с помощью метода flash. Данные, хранящиеся в сессии с использованием этого метода, будут доступны немедленно и во время следующего HTTP-запроса. После следующего HTTP-запроса данные будут удалены. Кратковременные данные в первую очередь полезны для краткосрочных статусных сообщений:

$request->session()->flash('status', 'Task was successful!');

Если вам нужно сохранить кратковременные данные для нескольких запросов, то вы можете использовать метод reflash, который сохранит все данные для дополнительного запроса. Если вам нужно сохранить конкретные кратковременные данные, то вы можете использовать метод keep:

$request->session()->reflash();

$request->session()->keep(['username', 'email']);

Чтобы сохранить ваши кратковременные данные только для текущего запроса, вы можете использовать метод now:

$request->session()->now('status', 'Task was successful!');

Удаление данных

Метод forget удалит часть данных из сессии. Если вы хотите удалить все данные из сессии, то вы можете использовать метод flush:

// Удалить единственный ключ ...
$request->session()->forget('name');

// Удалить несколько ключей ...
$request->session()->forget(['name', 'status']);

$request->session()->flush();

Пересоздание идентификатора сессии

Пересоздание идентификатора сессии часто выполняется для предотвращения использования злоумышленниками атаки, называемой фиксацией сессии, на ваше приложение.

Laravel автоматически пересоздает идентификатор сессии во время аутентификации, если вы используете один из стартовых комплектов приложений Laravel или Laravel Fortify; однако, если вам необходимо вручную повторно сгенерировать идентификатор сессии, то вы можете использовать метод regenerate:

$request->session()->regenerate();

Если вам нужно повторно сгенерировать идентификатор сессии и удалить все данные из нее одним выражением, то вы можете использовать метод invalidate:

$request->session()->invalidate();

Блокировка сессии

Чтобы использовать блокировку сессии, ваше приложение должно использовать драйвер кеша, поддерживающий атомарные блокировки.
В настоящее время этими драйверами кеширования являются memcached, dynamodb, redis и database, file и array.
Кроме того, вы не можете использовать драйвер сессии cookie.

По умолчанию Laravel позволяет выполнять запросы, использующие оду и ту же сессию, одновременно. Так, например, если вы используете HTTP-библиотеку JavaScript для выполнения двух HTTP-запросов к вашему приложению, то они будут выполняться одновременно. Для многих приложений это не проблема; однако потеря данных сессии может произойти в небольшом подмножестве приложений, выполняющих одновременные запросы к двум различным конечным точкам приложения, которые записывают данные в сессию.

Чтобы смягчить это, Laravel предлагает функциональность, которая позволяет ограничивать количество одновременных запросов для текущей сессии. Для начала вы можете просто привязать метод block к определению вашего маршрута. В этом примере входящий запрос к конечной точке /profile получит блокировку сессии. Пока эта блокировка удерживается, любые входящие запросы к конечным точкам /profile или /order с одним и тем же идентификатором сессии будут ждать завершения выполнения первого запроса, прежде чем они будут выполнены:

Route::post('/profile', function () {
    // ...
})->block($lockSeconds = 10, $waitSeconds = 10)

Route::post('/order', function () {
    // ...
})->block($lockSeconds = 10, $waitSeconds = 10)

Метод block принимает два необязательных аргумента. Первый аргумент, принимаемый методом block – это максимальное количество секунд, в течение которых блокировка сессии должна удерживаться, прежде чем она будет снята. Конечно, если выполнение запроса завершится до этого времени, блокировка будет снята раньше.

Второй аргумент, принимаемый методом block – это количество секунд, в течение которых запрос должен ждать при попытке получить блокировку сессии. Если запрос не сможет получить блокировку сессии в течение указанного количества секунд, то будет выброшено исключение Illuminate\Contracts\Cache\LockTimeoutException.

Если ни один из этих аргументов не передан, то блокировка будет получена максимум на 10 секунд, а запросы будут ждать максимум 10 секунд при попытке получить блокировку:

Route::post('/profile', function () {
    // ...
})->block()

Добавление пользовательских драйверов сессии

Реализация пользовательского драйвера

Если ни один из существующих драйверов сессии не соответствует потребностям вашего приложения, то Laravel позволяет написать собственный обработчик сессии. Ваш собственный драйвер сессии должен реализовывать SessionHandlerInterface, встроенный в PHP. Этот интерфейс содержит всего несколько простых методов. Заготовка реализация MongoDB выглядит следующим образом:

<?php

namespace App\Extensions;

class MongoSessionHandler implements \SessionHandlerInterface
{
    public function open($savePath, $sessionName) {}
    public function close() {}
    public function read($sessionId) {}
    public function write($sessionId, $data) {}
    public function destroy($sessionId) {}
    public function gc($lifetime) {}
}

Laravel не содержит каталога для хранения ваших расширений. Вы можете разместить их где угодно. В этом примере мы создали каталог Extensions для размещения MongoSessionHandler.

Поскольку цель этих методов не совсем понятна, давайте быстро рассмотрим, что делает каждый из этих методов:

  • Метод open обычно используется в файловых системах хранения сессии. Поскольку Laravel поставляется с драйвером file сессии, за редким исключением вам понадобится что-либо вставлять в этот метод. Вы можете просто оставить этот метод пустым.
  • Метод close, как и метод open, также обычно не учитывается. Для большинства драйверов в этом нет необходимости.
  • Метод read должен возвращать строковую версию данных сессии, связанных с переданным $sessionId. Нет необходимости выполнять сериализацию или другое кодирование при получении или хранении данных сессии в вашем драйвере, поскольку Laravel выполнит сериализацию за вас.
  • Метод write должен записать переданную строку $data, связанную с $sessionId, в какую-нибудь постоянную систему хранения, такую как MongoDB или другую систему хранения по вашему выбору. Опять же, вам не следует выполнять сериализацию – Laravel сделает это за вас.
  • Метод destroy должен удалить данные, связанные с $sessionId из постоянного хранилища.
  • Метод gc должен уничтожить все данные сессии, которые старше указанного $lifetime, которое является временной меткой UNIX. Для самоуничтожающихся систем, таких как Memcached и Redis, этот метод можно оставить пустым.

Регистрация пользовательского драйвера

Как только ваш драйвер будет реализован, вы готовы зарегистрировать его в Laravel. Чтобы добавить дополнительные драйверы в серверную часть сессии Laravel, вы можете использовать метод extend фасада Session. Вы должны вызвать метод extend в методе boot поставщика службы. Вы можете сделать это в уже существующем App\Providers\AppServiceProvider или создать совершенно новый поставщик:

<?php

namespace App\Providers;

use App\Extensions\MongoSessionHandler;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;

class SessionServiceProvider extends ServiceProvider
{
    /**
     * Регистрация любых служб приложения.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Загрузка любых служб приложения.
     */
    public function boot(): void
    {
        Session::extend('mongo', function (Application $app) {
            // Return an implementation of SessionHandlerInterface...
            return new MongoSessionHandler;
        });
    }
}

После регистрации драйвера сессии вы можете использовать драйвер mongo в конфигурационном файле config/session.php.