Сессия HTTP
11.x
.
Почему это важно?
Введение
Поскольку приложения, использующие 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
.