Релиз Laravel 9.32 был выпущен 28 сентября.

Большой новинкой этого выпуска является включение помощника Ориентир.

С его помощью можно измерить время выполнения любого процесса внутри приложения изолированно, просто добавив ссылку use Illuminate\Support\Benchmark; в классе, где будет проводиться анализ.


Класс Ориентир имеет только два статических метода, а именно:

public static function measure(Closure|array $benchmarkables, int $iterations = 1): array|float
Войти в полноэкранный режим

Выйти из полноэкранного режима

public static function dd(Closure|array $benchmarkables, int $iterations = 1): void
Войти в полноэкранный режим

Выйти из полноэкранного режима

Оба принимают два параметра:

  • $benchmarkables: набор функций для анализа времени обработки

  • $iterations: целое число, указывающее количество итераций, которые будут применены к функциям предыдущего параметра. Этот параметр является необязательным.

Метод measure возвращает массив со временем выполнения каждой функции. Метод ddкак следует из названия, выполняет dd (свалить и умереть) очень распространенная в PHP команда, обычно используемая для отображения значения переменной на уровне представления.


тестирование

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

⚠️ Требуется предварительное знание приложений Laravel (создание, настройка и выполнение).

Запросы будут выполняться в таблице с 1037 записями через контроллер, называемый CustomerController. Несмотря на небольшой объем информации, подчеркну, что машина, на которой будут проводиться тесты, не отличается высокой производительностью, уравновешивая результаты.

Миграция

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

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('customers', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained();
            $table->string('last_name');
            $table->string('first_name');
            $table->string('email')->nullable();
            $table->string('phone', 30)->nullable();
            $table->string('street');
            $table->string('city');
            $table->string('building_number', 30);
            $table->string('country');
            $table->string('post_code');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('customers');
    }
};

Войти в полноэкранный режим

Выйти из полноэкранного режима

Рота

Давайте отредактируем файл маршрутов API (\routes\api.php), создав новый маршрут и добавив ссылку на контроллер CustomerController который мы создадим в ближайшее время:

Route::get('/{id}/show', [CustomerController::class, 'show']);
Войти в полноэкранный режим

Выйти из полноэкранного режима

И ссылка на контроллер:

use App\Http\Controllers\CustomerController;
Войти в полноэкранный режим

Выйти из полноэкранного режима

Контроллер

В нашем контроллере будет только один метод, отвечающий за запрос клиента по заданному ID.

Ниже показано, как должен выглядеть класс:

<?php

namespace App\Http\Controllers;

use App\Models\Customer;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Http\Request;
use Illuminate\Support\Benchmark;
use Illuminate\Support\Facades\DB;

class CustomerController extends Controller
{
    public function show(int $id) 
    {        
        $customer = Customer::find($id);
        $result = Benchmark::measure(
            [
                'Scenario 1' => fn() => Customer::find($id),
                'Scenario 2' => fn() => Customer::where('id', ($id))->get(),
                'Scenario 3' => fn() => DB::table('customers')->where('id', $id)->first(),
                'Scenario 4' => fn() => DB::table('customers')->where('id', $id)->get(),
                'Scenario 5' => fn() => DB::select('select * from customers where id = ?', [$id])
            ], 10);

        if ($customer) {
            return response()->json([
                'time' => $result,
                'data' => $customer]);
        } else {            
            return response()->json(['message' => 'Customer not found'], 404);
        }
    }
}

Войти в полноэкранный режим

Выйти из полноэкранного режима

Обратите внимание, что перед каждой функцией я добавил псевдоним: «Сценарий 1», «Сценарий 2» и т. д.

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

тестирование

Как только приложение запустится, мы вызовем маршрут, который мы настроили ранее, и сообщим код для поиска клиента.




объяснение

Сцена 1

Запрос, выполняемый в этом сценарии, является самым основным, где мы используем саму модель для поиска клиента по первичному ключу с помощью метода find.

Хотя время выбрано не самое удачное, следует отметить, что существуют затраты на обработку для преобразования результата в модель клиента.

Сценарий 2

На этот раз погода немного улучшилась по сравнению с предыдущим сценарием. Отличие в том, что мы передаем столбец id непосредственно проконсультироваться.

Я считаю, что если столбец id не индексировались, результат был бы значительно хуже.

И в этом сценарии у нас все еще есть стоимость преобразования результата запроса в модель. Customer.

Сценарий 3

Из этого сценария мы приближаемся к базе данных, выполняющей запросы, которые считаются более «сырыми».

Обратите внимание, что из-за этого подхода время обработки лучше именно потому, что мы исключаем обработку, выполняемую на уровне абстракции Eloquent ORM.

Сценарий 4

Единственное отличие от предыдущего сценария в том, что мы используем метод get() вместо метода first().

Бывает, что когда мы выполняем метод first() существует дополнительная обработка для возврата только первой записи запроса, которую метод не get().

Сценарий 5

В большинстве тестов этот оказался самым быстрым из всех, потому что мы передавали «сырой» запрос в базу данных, фильтруя клиента по идентификатору, указанному в параметре.

Несмотря на значительно более короткое время, важно помнить, что этот тип консультации не распространяется на все возможные ситуации.

Хорошим примером, где результат может быть более продуктивным, могут быть функции агрегации данных (sum, max, countи т. д.), где дешевле объединять запросы, чем выполнять дополнительную обработку в приложении.


Все тесты запускались 10 раз, как указано в необязательном параметре. $iterations статического метода measure(). Таким образом, показанный результат относится к среднему времени 10 выполненных попыток.

При отключении этого параметра каждая из функций будет выполняться только один раз.



дополнения

В этом выпуске также появились другие функции:

Путь к файлу в функции ‘dd’

Начиная с этого выпуска, всякий раз, когда мы используем функцию dd (dump and die) полный путь к файлу также будет частью результата

(подробнее здесь)


Шифровать и расшифровывать файлы .env

В скрипт добавлены две новые команды artisanчтобы сгенерировать зашифрованный файл из файла ‘.env’, а также расшифровать его.

Чтобы зашифровать: php artisan env:encrypt

Чтобы расшифровать: php artisan env:decrypt

Помните, что команды должны выполняться с помощью терминала по вашему выбору из корневого каталога приложения.

(подробнее здесь)


Полный список новых функций в этом выпуске, а также исправления и улучшения можно найти здесь (по-английски).

До скорого!
😎