HTTP сессии

Введение

HTTP-приложения не имеют своего внутреннего состояния. Сессии — способ сохранения информации о пользователе между отдельными запросами. Laravel поставляется со множеством различных механизмов сессий, доступных через единый выразительный API. Из коробки поддерживаются такие популярные системы, как Memcached, Redis и СУБД.

Настройка

Настройки сессии содержатся в файле config/session.php. Обязательно просмотрите параметры, доступные вам в этом файле. По умолчанию Laravel использует драйвер сессий file , который подходит для большинства приложений. Для увеличения производительности сессий в продакшне вы можете использовать драйверы memcached или redis.

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

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

{tip} Драйвер массива используется во время тестирования и не сохраняет данные для последующих запросов.

Требования для драйверов

Database

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

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

Для создания этой миграции вы можете использовать Artisan-команду session:table:

php artisan session:table

php artisan migrate

Redis

Чтобы использовать сессии Redis в Laravel, необходимо установить пакет predis/predis (~1.0) с помощью Composer. Вы можете настроить подключения Redis в конфиге database. А в конфиге session в параметре connection можно указать конкретное подключение Redis для сессии.

Использование сессий

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

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

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

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

        //
    }
}

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

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

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

Глобальный хелпер Session

Также вы можете использовать глобальную PHP-функцию session для извлечения и помещения данных в сессию. При вызове session с одним строковым аргументом, метод вернёт значение ключа этой сессии. При вызове хелпера с массивом пар ключ/значение, эти значения будут сохранены в сессии:

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

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

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

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

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

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

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

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

Для проверки существования значения в сессии можно использовать метод has. Этот метод вернёт true, если значение существует и не равно null:

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

Для проверки существования значения в сессии, даже если оно равно null, можно использовать метод exists. Этот метод вернёт true, если значение существует:

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

Хранение данных

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

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

// Через глобальный хелпер...
session(['key' => 'value']);

Запись данных в массивы сессии

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

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

Чтение и удаление элемента

Метод pull прочитает и удалит элемент из сессии за одно действие:

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

Флеш-данные

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

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

Для сохранения одноразовых данных в течение большего числа запросов используйте метод reflash, который оставит все эти данные для следующего запроса. А если вам надо хранить только определённые данные, то используйте метод keep:

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

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

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

Метод forget удалит куски данных из сессии. Для удаления из сессии всех данных используйте метод flush:

$request->session()->forget('key');

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

Обновление ID сессии

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

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

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

Добавление своих драйверов сессий

Реализация драйвера

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

<?php

namespace App\Extensions;

class MongoHandler 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) {}
}

{tip} В Laravel нет стандартной директории для ваших расширений. Вы можете разместить их где угодно. В этом примере мы создали директорию Extensions для хранения в нём MongoHandler.

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

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

Регистрация драйвера

После реализации драйвера его можно зарегистрировать в фреймворке. Для добавления дополнительных драйверов для работы с сессиями в Laravel используйте метод extend фасада Session. Вам надо вызвать метод extend из метода boot сервис-провайдера. Это можно сделать в имеющемся AppServiceProvider или создать абсолютно новый провайдер:

<?php

namespace App\Providers;

use App\Extensions\MongoSessionStore;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;

class SessionServiceProvider extends ServiceProvider
{
    /**
     * Выполнение пост-регистрационной загрузки сервисов.
     *
     * @return void
     */
    public function boot()
    {
        Session::extend('mongo', function ($app) {
            // Return implementation of SessionHandlerInterface...
            return new MongoSessionStore;
        });
    }

    /**
     * Регистрация привязок в контейнере.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

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