• Час читання ~5 хв
  • 28.01.2025

Для ще швидшого виграшу в програмах Laravel з високим трафіком ви можете кешувати автентифікованих користувачів, щоб уникнути поїздки до бази даних.

У цій статті я покажу вам, як — але це не швидке рішення; Нам доведеться розглянути, що відбувається, коли користувач оновлюється або видаляється.

Давайте закешуємо.

Віддаєте перевагу дивитися? Ця стаття доступна у вигляді безкоштовного відео на Codecourse!

Для кожної аутентифікованої сторінки або запиту API у вашому додатку, Laravel отримає свіжого користувача з бази даних із запитом, подібним до цього (залежно від ID, звичайно):

select * from `users` where `id` = 1 limit 1

Наразі немає можливості автоматично кешувати цей об'єкт користувача. Отже, поки ваш користувач аутентифікований, цей запит завжди буде виконуватися для кожного запиту.

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

Першим кроком є розуміння механізму Auth провайдера Laravel.

За замовчуванням Laravel використовує функцію EloquentUserProvider для керування автентифікованими користувачами. Цей клас містить багато корисних методів, таких як retrieveById, rehashPasswordIfRequired і validateCredentials. В принципі, все необхідне для отримання та оновлення користувача щодо аутентифікації.

Ви можете додати нового провайдера за допомогою методуAuth::provider, як це:

Auth::provider('someCustomProvider', function (Application $app, array $config) {
    // return a custom provider here
});

Як тільки ви додасте цього провайдера під час виконання, ви можете поміняти його місцями у файлі config/auth.php

'providers' => [
    'users' => [
        'driver' => 'someCustomProvider',
        'model' => env('AUTH_MODEL', App\Models\User::class),
    ],

Тепер ми трохи краще розуміємо провайдерів аутентифікації, давайте створимо свій!

Почніть зі створення класу провайдера, CachedEloquentUserProvider. Ви можете назвати це як завгодно або розмістити його де завгодно у вашому додатку:

namespace App\Auth\Providers;
use Illuminate\Auth\EloquentUserProvider;
class CachedEloquentUserProvider extends EloquentUserProvider
{
    public function retrieveById($identifier)
    {
        //
    }
}

Це розширює базуEloquentUserProvider, щоб забезпечити всі додаткові функції, які ми хочемо зберегти. Ми займаємося лише тим, retrieveById щоб перевизначити спосіб отримання користувача.

Зараз ми нічого не робимо, щоб отримати користувача (ми розповімо про це далі), але давайте підключимо його до нашого користувацького провайдера в AppServerProvider' методі':

public function boot(): void
{
    Auth::provider('cachedEloquent', function (Application $app, array $config) {
        return new CachedEloquentUserProvider(
            $app['hash'],
            $config['model']
        );
    });
}

У конструкторі для оригіналу EloquentUserProviderнам потрібно передати поточний хешувальник (відповідає за хешування паролів тощо), а також простір імен моделі з конфігурації, який представляє Користувача (зазвичай App\Models\User).

bootОсь чому ми розглянули ці дві речі вище.

Тепер перемкніть драйвер config/auth.php на cachedEloquent (або як ви його назвали).

'providers' => [
    'users' => [
        'driver' => 'cachedEloquent',
        'model' => env('AUTH_MODEL', App\Models\User::class),
    ],

На цьому етапі ваша програма не зможе отримати автентифікованих користувачів, оскільки ми залишили новий retrieveById метод порожнім.

Настав час заповнити retrieveById метод кешованою версією користувача.

Варіанти тут нескінченні, але ось хороший початок:

public function retrieveById($identifier)
{
    return cache()->remember('user_' . $identifier, now()->addHours(2), function () use ($identifier) {
        return parent::retrieveById($identifier);
    });
}

$identifier тут просто ID користувача, тому ми передаємо це батьківському retrieveById методу, щоб він зробив свою справу. Але, звичайно ж, ми використовуємо cache()->remember для кешування і повернення результату.

Ось трохи коротший спосіб отримати той самий результат за допомогою функції зі стрілкою PHP:

public function retrieveById($identifier)
{
    return cache()->remember(
        'user_' . $identifier,
        now()->addHours(2),
        fn () => parent::retrieveById($identifier)
    );
}

Будь-який метод працює добре; це залежить від того, чи робите ви багато іншого в рамках закриття, і в цьому випадку ви виберете стандартну функцію зворотного виклику.

За замовчуванням Laravel використовує базу даних як драйвер кешу. Ви захочете це змінити, інакше ми повернемося до отримання кешованої версії користувача з бази даних... знов.

CACHE_STORE=redis

Як тільки це зміниться, ви зможете увійти у свою програму та побачити початковий запит до бази даних для користувача. Однак при оновленні ми витягуємо дані користувача з нашого (в даному випадку Redis) кешу!

Кешування – це чудово, але нам потрібно подбати про перебір (недійсність) кешу, коли щось зміниться.

Якщо ваш користувач оновить свої дані у вашій програмі прямо зараз, він не побачить ці зміни відразу, і йому доведеться чекати, поки термін дії кешу закінчиться. Не ідеальний.

Щоб обійти це, ми можемо просто спостерігати за змінами for User і зробити кеш недійсним вручну.

Почніть зі створення файлу UserObserver:

php artisan make:observer UserObserver

Open it up та реєстрації подій для updated та deleted.

class UserObserver
{
    public function updated(User $user)
    {
        cache()->forget('user_' . $user->id);
    }
    public function deleted(User $user)
    {
        cache()->forget('user_' . $user->id);
    }
}

Наш ключ кешу було встановлено на user_[id], тому ми просто робимо цей ключ недійсним.

Зареєструйте спостерігача на User моделі, і все готово:

use App\Observers\UserObserver;
#[ObservedBy(UserObserver::class)]
class User extends Authenticatable
{
    //...
}

коли користувачі змінюються або видаляються, кеш стає недійсним, і ми в кінцевому підсумку кешуємо свіжі дані (або просто видаляємо їх повністю, якщо користувач видаляється).

Важливо зазначити, що це дуже спрощений погляд на визнання недійсними кешованих даних користувача за допомогою двох подій Eloquent. Насправді, коли ваша програма стає складнішою, можуть бути крайні випадки, коли ви або не хочете визнавати кеш недійсним, або кеш не стає недійсним, коли ви хочете.

Наприклад:

  • Якщо ви використовуєте DB фасад у будь-якому місці вашої програми для оновлення користувача, кеш не буде визнано недійсним, оскільки подія Eloquent не буде запущена
  • Якщо дані в базі даних оновлюються вручну, кеш не буде визнано недійсним
  • Якщо оновлюється непублічний стовпець (наприклад, email_verified_at) для вашого користувача, це зробить кеш недійсним, але ви, можливо, цього не хочете.
  • Якщо у вас є завдання в черзі (наприклад), яке регулярно оновлює вашого користувача (наприклад, коли він востаннє робив запит), це постійно призведе до втрати чинності кешу.

Як я вже говорив на початку статті, це не швидке і просте рішення, яке не вимагає обмірковування в майбутньому.

Так, це прискорить вашу подачу заявки. Тим не менш, це вимагає трохи більше зусиль з вашого боку, щоб переконатися, що кеш не повертає застарілі дані або ви не робите кеш недійсним занадто часто та не робите автентифіковане кешування марним.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Про автора CrazyBoy49z
WORK EXPERIENCE
Контакти
Ukraine, Lutsk
+380979856297

Ми знищуємо "шахеди" щодня. Щоразу — це врятовані життя. Але нам потрібна мобільність: бус або прицеп. Кожен донат = ще одна ніч під захистом.

🚐 Збір на бус для мого екіпажу, полк 1020 🎯 Ціль: 500 000 ₴
🔗 Посилання на банку 💳 4441 1111 2546 4663