Laravel Release Update 9.32
Tags: laravel, update, release, php
Published: 2022-10-04 23:18:11

Foi liberado em 28/09 o release 9.32 do Laravel.

A grande novidade deste release é a inclusão do helper **Benchmark**. 

Com ele é possível medir o tempo de execução de qualquer processo dentro da aplicação de forma isolada, bastando para isso adicionar a referência `use Illuminate\Support\Benchmark;` na classe onde a análise será feita.

---

A classe **Benchmark** possui apenas dois métodos estáticos a saber:


```php
public static function measure(Closure|array $benchmarkables, int $iterations = 1): array|float
```


```php
public static function dd(Closure|array $benchmarkables, int $iterations = 1): void
```


Ambos recebem dois parâmetros:
* `$benchmarkables`: array de funções para análise do tempo de processamento

* `$iterations`: inteiro que indica a quantidade de iterações que serão aplicadas às funções do parâmetro anterior. Este parâmetro é opcional.

O método `measure` retorna um array com o tempo de cada função executada. Já o método `dd`, como o próprio nome diz, executa um `dd` (_dump and die_) comando bem comum em PHP geralmente utilizado para mostrar o valor de uma variável na camada de apresentação.

### Testando

Para efeitos didáticos, vamos montar um exemplo simples onde consultaremos um cliente através do seu ID, utilizando 5 abordagens distintas.

:warning: Conhecimento prévio sobre aplicações Laravel (criação, configuração e execução) é requerido.

As consultas serão feitas em uma tabela que possui 1037 registros, através de uma contoller chamada `CustomerController`. Apesar do pouco volume de informações, ressalto que a máquina onde os testes serão executados não possui uma performance elevada, equilibrando os resultados.

> Migração

O arquivo de migração abaixo dará uma noção da estrutura da tabela de clientes, ajudando a compreender melhor o ambiente utilizado nos testes.

```php
<?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');
    }
};

```

> Rota

Vamos editar o arquivo de rotas de api (\routes\api.php) criando uma nova rota e adicionando a referência para a _controller_ `CustomerController` que criaremos em breve:

```php
Route::get('/{id}/show', [CustomerController::class, 'show']);
```

E a referência para a controller:

```php
use App\Http\Controllers\CustomerController;
```

> Controller

Nossa controller terá apenas um método responsável por consultar o cliente de acordo com o ID informado.

Abaixo como a classe deve se parecer:

```php
<?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);
        }
    }
}

```

Perceba que à frente de cada funções adicionei um álias: 'Scenario 1', 'Scenario 2', etc.

Esse álias, apesar de opcional, ajudará bastante a identificar qual tempo refere-se a qual função analisada.


> Testando

Assim que a aplicação estiver executando vamos fazer uma chamada à rota que configuramos anteriormente e informar um código para pesquisar o cliente.

{% embed https://youtu.be/qHmg4T9tx0A %}

---

### Explicando

> Cenário 1

A consulta realizada neste cenário é a mais básica, onde utilizamos o próprio modelo para buscar o cliente através da chave primária com o método `find`.

Apesar do tempo não ser um dos melhores, há que se frisar que existe um custo de processamento para converter o resultado no modelo Customer.


> Cenário 2

Neste o tempo melhorou um pouquinho em relação ao cenário anterior. A diferença é que passamos a coluna `id` diretamente para consultar.

Acredito que se a coluna `id` não fosse indexada o resultado seria significativamente pior.

E neste cenário ainda temos o custo de conversão do resultado da consulta no modelo `Customer`.


> Cenário 3

A partir deste cenário ficamos mais próximos do banco de dados realizando consultas consideradas mais 'brutas'.

Por conta dessa abordagem note que os tempos de retorno são melhores justamente por eliminarmos o processamento feito na camada de abstração do Eloquent ORM.


> Cenário 4

A única diferença em relação ao cenário anterior é que estamos utilizando o método `get()` ao invés do método `first()`.

Ocorre que ao executarmos o método `first()` há um processamento adicional para retornar somente o primeiro registro da consulta o que não acontece com o método `get()`.


> Cenário 5 

Na maioria dos testes este se mostrou o mais rápido de todos porque passamos uma consulta 'bruta' ao banco de dados filtrando o cliente pelo ID informado no parâmetro.

Apesar do tempo consideravelmente menor é importante lembrar que este tipo de consulta não se aplica a todas as situações possíveis. 

Um bom exemplo onde o resultado poderia se apresentar mais produtivo, seriam as funções de agregação de dados (`sum`, `max`, `count`, etc) onde é menos custoso já trazer as consultas agrupadas ao invés de fazer um processamento adicional na aplicação. 

--- 

Todos os testes foram executados 10 vezes, conforme informamos no parâmetro opcional `$iterations` do método estático `measure()`. Então, o resultado mostrado refere-se ao tempo médio das 10 tentativas realizadas.

Ao suprimir este parâmetro cada uma das funções será executada apenas uma vez.

---

### Adições

Este release também trouxe outras funcionalidades:

> Caminho do arquivo na função 'dd'

A partir deste release, sempre que utilizarmos a função `dd` (dump and die) o caminho completo do arquivo também fará parte do resultado

(mais detalhes [aqui](https://github.com/laravel/framework/pull/44211))

---

> Encriptar e decriptar arquivos .env

Foram adicionados dois novos comandos ao script `artisan`, com a finalidade de gerar um arquivo encriptado a partir do arquivo '.env', bem como decriptá-lo.

Para encriptar: `php artisan env:encrypt `

Para decriptar: `php artisan env:decrypt`

Lembrando que os comandos devem ser executados utilizando o terminal de sua preferência a partir do diretório raiz da aplicação. 

(mais detalhes [aqui](https://github.com/laravel/framework/pull/44034)) 


---

A lista completa das novas funcionalidades deste release, bem como correções e melhoramentos, pode ser encontrada [aqui](https://laravel-news.com/laravel-9-32-0) (em inglês).


Até breve!
:sunglasses: