1. Основы
  2. HTTP-сессии

Присоединяйся к нашему Telegram сообществу @webblend!

Здесь ты найдешь сниппеты по Laravel и полезные советы по веб-разработке.

Введение

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

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

Настройка

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

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

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

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

Предварительные требования драйвера

База данных

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

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();
});

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

php artisan session:table
 
php artisan migrate

Redis

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

Примечание В файле конфигурации session опция connection может использоваться для указания, какое соединение 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 PHP для извлечения и сохранения данных в сеансе. Когда помощник session вызывается с одним строковым аргументом, он вернет значение этого ключа сеанса. Когда помощник вызывается с массивом пар ключ / значение, эти значения будут сохранены в сеансе:

Route::get('/home', function () {
// Получение данных из сессии...
$value = session('key');
 
// Указание значения по умолчанию...
$value = session('key', 'default');
 
// Сохранение данных в сессии...
session(['key' => 'value']);
});

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

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

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

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

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

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

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

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

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

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

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-данные

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

$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 application starter kits или Laravel Fortify; однако, если вам нужно вручную пересоздать идентификатор сеанса, вы можете использовать метод regenerate:

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

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

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

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

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

По умолчанию Laravel позволяет одновременно выполнять запросы с использованием одной сессии. Так, например, если вы используете библиотеку JavaScript HTTP для выполнения двух 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) {
// Вернуть реализацию SessionHandlerInterface...
return new MongoSessionHandler;
});
}
}

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