Пример кэширования в Laravel

Добавлено: 19/12/2023 15:40 |  Обновлено: 19/12/2023 17:00 |  Добавил: nick |  Просмотры: 240 Комментарии: 0
Вводная часть
В данном материале показан пример кэширования в Laravel. Посмотрим какая разница будет в скорости извлечения данных из БД (в данном примере MySQL) и извлечения данных из кэша. Местом хранения кэша будет файловая система (в Laravel задана по умолчанию). В качестве веб-приложения используется простая реализация блога с 50 000 записей.

Миграция, модель и фабрика
Создадим файл миграции и модель для постов с помощью команды: php artisan make:model Post --migration.

Файл миграции будет иметь следующее содержимое:
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePostsTable extends Migration
{
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('body');
            $table->boolean('published')->default(0);
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('posts');
    }
}
Как можем видеть каждый пост будет состоять из заголовка - title, текста поста - body и флага (опубликован или нет пост) - published.

Файл модели будет иметь следующее содержимое:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;

class Post extends Model
{
    use HasFactory;

    public static function getPosts($source)
    {
        switch ($source = 'bd') {
            case 'bd':
                return static::where('published', 1)->orderBy('id', 'desc')->take(10)->get();
                break;
            case 'cache':
                return Cache::rememberForever(md5(__NAMESPACE__ . __CLASS__ . __METHOD__),
                    function() {
                        return static::where('published', 1)->orderBy('id', 'desc')->take(10)->get();
                    });
                break;
        }
    }

    public static function countAll($source)
    {
        switch ($source) {
            case 'bd':
                return static::count();
                break;
            case 'cache':
                return Cache::rememberForever(md5(__NAMESPACE__ . __CLASS__ . __METHOD__),
                    function() {
                        return static::count();
                    });
                break;
        }        
    }

        public static function countPublished($source)
    {
        switch ($source) {
            case 'bd':
                return static::where('published', 1)->count();
                break;
            case 'cache':
                return Cache::rememberForever(md5(__NAMESPACE__ . __CLASS__ . __METHOD__),
                    function() {
                        return static::where('published', 1)->count();
                    });
                break;
        }        
    }
}   
В модели 3 метода: getPosts($source), countAll($source) и countPublished($source). В параметре $source передается значение того, откуда мы ходим получить данные: из модели или из кэша.
  • getPosts($source) - получаем список последних 10 опубликованных постов
  • countAll($source) - получаем количество всех постов
  • countPublished($source) - получаем количество опубликованных постов
Создадим таблицу постов в БД с помощью команды: php artisan migrate.

Чтобы заполнить таблицу автоматически, создадим фабрику PostFactory с помощью команды: php artisan make:factory PostFactory.

Файл фабрики будет иметь следующее содержимое:
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

class PostFactory extends Factory
{
    public function definition()
    {
        return [
            'title' => $this->faker->sentence,
            'body' => $this->faker->paragraph,
            'published' => $this->faker->numberBetween(0, 1),
        ];
    }
}   
Теперь заполним таблицу данными с помощью tinker. Запустим его с помощью команды: php artisan tinker.

Создать, например, 5000 постов можно командой (в Laravel 8): \App\Models\Post::factory(5000)->create();

Маршруты
Для работы примера в файле web.php (папка routes) добавим 2 дополнительные строки:
Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::get('/flush', [PostController::class, 'flush'])->name('flush');
Маршрут /posts выводит список 10 постов из модели, а маршрут /flush обнуляет содержимое кэша.

Контроллер
Теперь создадим контроллер, отвечающий за обработку этих маршрутов – PostController. Для этого используем следующую команду: php artisan make:controller PostController.

Файл контроллера будет иметь следующее содержимое:
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Post;
use Illuminate\Support\Facades\Cache;

class PostController extends Controller
{
    public function index(Request $request) {
        $source = $request->get('source');

        $time_start = microtime(true);
        if ($source) {
            switch ($source) {
                case 'bd':
                    $posts = Post::getPosts('bd');
                    $numberAll = Post::countAll('bd');
                    $numberPublished = Post::countPublished('bd');
                    break;
                case 'cache':
                    $posts = Post::getPosts('cache');
                    $numberAll = Post::countAll('cache');
                    $numberPublished = Post::countPublished('cache');
                    break;
            }
        } else {
            $posts = Post::getPosts('bd');
            $numberAll = Post::countAll('bd');
            $numberPublished = Post::countPublished('bd');
        }
        $time_end = microtime(true);
        $execution_time = round(($time_end - $time_start) * 1000, 2);
        return view('index', compact('posts', 'numberAll', 'numberPublished', 'execution_time'));
    }

    public function flush() {
        Cache::flush();
        return redirect()->route('posts.index', 'source=bd&time='.microtime(true));
    }
}   
В файле контроллера у нас есть 2 метода index(Request $request) и flush(). С помощью первого мы получаем список из 10 постов, а с помощью второго чистим кэш. В первом методе в качестве параметра используем содержимое реквеста, чтобы определить откуда извлечь данные: из БД или из кэша. Время работы замеряем с помощью php-функции microtime().

Страница с постами
Нам нужна страница, на которую будет выводиться список постов. Для этого создадим новый вид – index.blade.php (папка resources/views). Его содержимое должно быть следующим:
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>
<body>
    <div id="app" class="container">
        <div class="text-center my-3">
            <h1>Кэширование постов</h1>
            <h3>
                Всего постов: {{$numberAll}} | Опубликованных постов: {{$numberPublished}}<br>
                Время в миллисекундах: {{$execution_time}} 
            </h3>
         
            <a href="{{route('posts.index', 'source=bd&amp;time='.microtime(true))}}" class="btn btn-primary">Загрузить из бд</a>
            <a href="{{route('posts.index', 'source=cache&amp;time='.microtime(true))}}" class="btn btn-primary">Загрузить из кэша</a>
            <a href="{{route('flush', 'time='.microtime(true))}}" class="btn btn-secondary">Очистить кэш</a> 

            <p><small>Выборка последних 10 опубликованных постов</small></p>        
        </div>

        <div class="row">
            @foreach ($posts as $post)
                <article class="card col-sm-8 offset-sm-2 p-3 my-1">
                    <h5>
                        <span class="badge bg-secondary">ID {{$post-&gt;id}}</span>
                        <span class="badge bg-success">Опубликован</span>
                        {{$post-&gt;title}}
                    </h5>
                    <body>
                        {{$post-&gt;body}}      
                    </body>   
                </article>
            @endforeach
        </div>
    </div>
</body>
</html>   
На странице используем bootstrap. Также вы можете заметить, что в ссылках используется get-параметр time со значением php-функции microtime(), это необходимо, для того, чтобы сам браузер не кэшировал страницу, и можно было бы каждый раз делать запрос к БД или кэшу самого приложения, а не к кэшу браузера.

В видео (YouTube) можно наглядно посмотреть разницу в скорости извлечения данных из кэша и из БД на примере 50 000 записей.

Оставьте свой комментарий

Комментариев нет