Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти
Інше
Події Про нас

HTTP-сесія

Вступ

Оскільки застосунки на основі HTTP є безстановими, сесії дають спосіб зберігати інформацію про користувача між кількома запитами. Зазвичай ця інформація розміщується в постійному сховищі, доступному з наступних запитів.

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

Конфігурація

Конфігураційний файл сесій вашого застосунку зберігається за шляхом config/session.php. Обов'язково перегляньте доступні в ньому опції. За замовчуванням Laravel налаштовано на драйвер сесій database.

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

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

Драйвер array використовується насамперед під час тестування і не дає даним сесії зберігатися постійно.

Передумови драйверів

База даних

Використовуючи драйвер сесій database, вам потрібно переконатися, що у вас є таблиця бази даних для зберігання даних сесії. Зазвичай її створює типова міграція Laravel 0001_01_01_000000_create_users_table.php; однак якщо з якоїсь причини таблиці sessions у вас немає, ви можете згенерувати цю міграцію командою Artisan make:session-table:

php artisan make:session-table

php artisan migrate

Redis

Перш ніж використовувати сесії Redis із Laravel, вам потрібно встановити PHP-розширення PhpRedis через PECL або пакет predis/predis через Composer. Докладніше про налаштування Redis дивіться в документації Laravel щодо Redis.

Змінна середовища SESSION_CONNECTION або опція connection у конфігураційному файлі 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
{
    /**
     * Show the profile for the given user.
     */
    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, щоб отримувати та зберігати дані в сесії. Коли хелпер session викликається з одним рядковим аргументом, він повертає значення відповідного ключа сесії. Коли ж його викликають із масивом пар «ключ - значення», ці значення зберігаються в сесії:

Route::get('/home', function () {
    // Retrieve a piece of data from the session...
    $value = session('key');

    // Specifying a default value...
    $value = session('key', 'default');

    // Store a piece of data in the session...
    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. Він повертає true, якщо елемент присутній і не дорівнює null:

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

Щоб визначити, чи присутній елемент у сесії, навіть якщо його значення null, скористайтеся методом exists:

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

Щоб визначити, що елемента в сесії немає, скористайтеся методом missing. Він повертає true, якщо елемент відсутній:

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

Збереження даних

Щоб зберегти дані в сесії, ви зазвичай використовуватимете метод put екземпляра запиту або глобальний хелпер session:

// Via a request instance...
$request->session()->put('key', 'value');

// Via the global "session" helper...
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-запиту. Після нього флеш-дані буде видалено. Флеш-дані насамперед корисні для короткочасних повідомлень про стан:

$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:

// Forget a single key...
$request->session()->forget('name');

// Forget multiple keys...
$request->session()->forget(['name', 'status']);

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

Регенерація ідентифікатора сесії

Регенерацію ідентифікатора сесії часто виконують, щоб завадити зловмисникам скористатися атакою фіксації сесії на ваш застосунок.

Laravel автоматично регенерує ідентифікатор сесії під час автентифікації, якщо ви використовуєте один зі стартових наборів чи Laravel Fortify; однак якщо вам потрібно зробити це вручну, скористайтеся методом regenerate:

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

Якщо вам потрібно регенерувати ідентифікатор сесії й вилучити всі дані однією інструкцією, скористайтеся методом invalidate:

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

Кеш сесії

Кеш сесії Laravel дає зручний спосіб кешувати дані в межах окремої сесії користувача. На відміну від глобального кешу застосунку, дані кешу сесії автоматично ізольовані для кожної сесії й очищаються, коли сесія спливає або знищується. Кеш сесії підтримує всі звичні методи кешу Laravel - get, put, remember, forget тощо, - але в межах поточної сесії.

Кеш сесії чудово підходить для зберігання тимчасових даних, специфічних для користувача, які ви хочете зберегти між кількома запитами в межах однієї сесії, але не потребуєте зберігати назавжди. Це можуть бути дані форм, тимчасові обчислення, відповіді API чи будь-які інші ефемерні дані, прив'язані до сесії конкретного користувача.

Доступ до кешу сесії можна отримати методом cache на сесії:

$discount = $request->session()->cache()->get('discount');

$request->session()->cache()->put(
    'discount', 10, now()->plus(minutes: 5)
);

Докладніше про методи кешу Laravel дивіться в документації з кешу.

Блокування сесії

Щоб скористатися блокуванням сесії, ваш застосунок має використовувати драйвер кешу з підтримкою атомарних блокувань. Наразі це драйвери memcached, dynamodb, redis, mongodb (входить до офіційного пакета mongodb/laravel-mongodb), database, file та array. Крім того, ви не можете використовувати драйвер сесій cookie.

За замовчуванням Laravel дозволяє запитам однієї сесії виконуватися паралельно. Тож, наприклад, якщо ви використовуєте 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 приймає два необов'язкові аргументи. Перший - максимальна кількість секунд, протягом яких блокування сесії має утримуватися перед звільненням. Звісно, якщо запит завершиться раніше, блокування буде знято раніше.

Другий аргумент - кількість секунд, які запит має чекати, намагаючись отримати блокування сесії. Якщо запит не зможе отримати блокування за вказану кількість секунд, буде викинуто Illuminate\Contracts\Cache\LockTimeoutException.

Якщо не передати жодного з цих аргументів, блокування отримають максимум на 10 секунд, і запити чекатимуть на нього максимум 10 секунд:

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

Додавання власних драйверів сесії

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

Якщо жоден із наявних драйверів сесій не відповідає потребам вашого застосунку, Laravel дозволяє написати власний обробник сесій. Ваш драйвер має реалізувати вбудований у PHP інтерфейс SessionHandlerInterface, що містить лише кілька простих методів. Заготовка реалізації для 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
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Session::extend('mongo', function (Application $app) {
            // Return an implementation of SessionHandlerInterface...
            return new MongoSessionHandler;
        });
    }
}

Щойно драйвер сесій зареєстровано, ви можете вказати драйвер mongo як драйвер сесій вашого застосунку через змінну середовища SESSION_DRIVER або в конфігураційному файлі config/session.php.