Você sabe o que é "softdelete"? Parte 1
Tags: beginners, laravel, php
Published: 2022-06-28 16:08:07



Softdelete é um recurso disponível na grande maioria dos ORMs do mercado.

A ideia por trás deste conceito é não listar registros marcados como deletados quando a consulta é feita através do modelo objeto-relacional.

Na prática o que querybuilder do ORM faz é injetar uma instrução para mostrar somente registros onde a coluna "deleted_at" (especificamente o Eloquent ORM) esteja nula.

Existem aplicações que fazem uso desse recurso quando não querem perder o registro deletado deixando-o invisível.

Para obter o mesmo comportamento usando SQL puro é preciso incluir esse filtro explicitamente na cláusula `where`.

Entretanto este tipo de implementação (SQL puro) traz riscos à aplicação pois torna necessário codificar a mesma condição em diversos pontos do projeto. 

Detalhes acerca da performance não serão abordados neste artigo porque o foco está em mostrar como implementar e testar esta técnica.

---

## Ferramentas necessárias

- PHP 8 ou superior
- MySQL Server versão 5.7.38 ou superior
- Composer versão 2.0 ou superior
- Editor de textos

Existem instaladores que trazem embutidos o PHP, MySQL e Apache (servidor web), como WAMPSERVER, XAMPP e Laragon.

Deixo a seu critério a escolha de alguns desses pacotes, ou outro que seja familiar a você.

Para o editor de textos estou utilizando o VSCode, por ser gratuito e permitir a instalação de diversas extensões.

---

## Projeto
O projeto a ser desenvolvido é uma aplicação Laravel conhecido framework de desenvolvimento web e está, no momento em que escrevo este artigo, na versão 9.



> Repositório

O projeto está publicado no GitHub e pode ser acessado através deste link:
[Larave 9 - Soft Deletes](https://github.com/MarcioPolicarpo/laravel9-soft-delete)

---


Para criar um novo projeto acesse o terminal de sua preferência e digite o seguinte comando:

```
composer create-project --prefer-dist laravel/laravel softdelete
```

O tempo para conclusão desta etapa dependerá de alguns fatores como velocidade de conexão disponível e configuração do computador.

Quando esta etapa estiver concluída, acesse a pasta do projeto porque a partir deste ponto toda interação no terminal deve ser executada dentro da pasta do projeto.

Dito isto, vamos criar a migração.

```
php artisan make:migration create_invoices_table
```


Além das colunas desejadas, precisamos adicionar uma coluna para guardar a data e hora que o registro foi "deletado".

O schema builder possui um método helper específico para criação da coluna para atender ao conceito de soft-delete. 

```php
$table->softDeletes('deleted_at', 0);
```

O método $table->softDeletes tem dois parâmetros opcionais. No primeiro informamos o nome da coluna e o no segundo a precisão.

Se chamarmos o método sem informar parâmetros, o schema builder criará uma coluna chamada `deleted_at` com precisão de segundos.

```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('invoices', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->date('issue_date');
            $table->string('customer_name', 50);
            $table->string('customer_email', 50);
            $table->string('address', 100);
            $table->string('city', 100);
            $table->string('country', 100);
            $table->string('post_code', 16);
            $table->float('tax');
            $table->smallInteger('total_items');
            $table->float('total_value');
            $table->softDeletes();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('invoices', function(Blueprint $table) {
            $table->dropSoftDeletes();
        });
        Schema::dropIfExists('invoices');
    }
};

```

---

Em seguida, vamos criar e configurar o modelo. 

```
php artisan make:model Invoice
```

Fazemos isso adicionando o namespace  `Illuminate\Database\Eloquent\SoftDeletes` e referenciando a classe SoftDeletes ao use da classe.

Como boa prática indicamos o nome da tabela para o modelo através da propriedade projegida $table. 

Além disso, vou deixar as colunas `id`, `created_at `e `updated_at` invisíveis para consultas feitas através do modelo.

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Invoice extends Model
{
    use HasFactory, SoftDeletes;

    protected $table = 'invoices';

    protected $hidden = [
        'id',
        'created_at',
        'updated_at',
    ];
}

```

---

Agora criaremos a controller para integarir com a camada de persistência. Desenvolveremos inicialmente dois métodos.

O primeiro retornará todos os registros do banco.

```php
public function getInvoices()
{
	$invoices = Invoice::all();
	$totalRecords = $invoices->count();

	return response()->json([
		'total' => $totalRecords,
		'data' => $invoices,
	]);
}
```

E o segundo retornará o registro de acordo com o `id` informado no parâmetro.

```php
public function getInvoiceById($id)
{
	$invoice = Invoice::find($id);

	if ($invoice) {
		return $invoice;
	} else {
		return response()->json(['message' => 'Invoice not found'], 404);
	}
}
```

Note que em ambos estou utilizando o modelo para fazer as consultas.

---

Em seguida criaremos as rotas para acessar os métodos da controller, editando o arquivo **\route\api.php**.

```php
<?php

use App\Http\Controllers\InvoiceController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::controller(InvoiceController::class)->prefix('/invoice')->group(function () {
    Route::get('/', 'getInvoices');
    Route::get('/{id}/info', 'getInvoiceById');
});

```

---

Para ajudar no processo de inserção de registros no banco de dados utilizaremos a técnica de **model factory**, criando uma classe que herda de `\Illuminate\Database\Eloquent\Factories\Factory`.

Acessando novamente o terminal digitaremos o seguinte comando:

```php
php artisan make:factory InvoiceFactory
```

Abrindo a classe recém criada vamos editar o método `definition()`, adicionando instruções para cada coluna da migração.

```php
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Invoice>
 */
class InvoiceFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition()
    {
        return [
            'issue_date' => $this->faker->dateTimeBetween('-2 years', 'now'),
            'customer_name' => $this->faker->name(),
            'customer_email' => $this->faker->safeEmail(),
            'address' => $this->faker->address(),
            'city' => $this->faker->city(),
            'country' => $this->faker->country(),
            'post_code' => $this->faker->postcode(),
            'tax' => 3,
            'total_items' => $this->faker->numberBetween(1, 36),
            'total_value' => $this->faker->randomFloat(2, 0, 3000),
            //
        ];
    }
}

```
Para concluir vamos editar o método `run()` da classe **\database\seeders\DatabaseSeeder.php** adicionando uma chamada para o helper `factory`. Este método helper recebe dois parâmetros opcionais onde o primeiro indica quantos registros devem ser criados e o segundo define métodos de manipulação de estado para o modelo. 

Especificamente para este projeto criaremos 100 registros.

```php
<?php

namespace Database\Seeders;

use App\Models\Invoice;
use Illuminate\Database\Seeder;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        \App\Models\Invoice::factory(100)->create();
    }
}

```

---

Acessando o terminal novamente vamos executar a migração.

```
php artisan migrate
```

E logo em seguida geramos os registros no banco de dados.

```
php artisan db:seed
```

Agora basta iniciar o servidor na porta 8100

```
php artisan serve --port=8100
```

---
## Testando a aplicação

Utilizando a extensão Thunder no VSCode faremos algumas requisições. 

A primeira será para a rota que retorna todos os registros.

Note que temos exatamente 100.

![Request to get all invoicesImage description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vmyk48s02s11l8h1z26n.jpg)



A segunda requisição retorna as informações de um registro passado como parâmetro.

Podemos ver que a coluna `deleted_at` está nula.


![Request to get invoice id](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/digft7xgni1911st1nk2.jpg)

---

Na [segunda parte](https://dev.to/marciopolicarpo/voce-sabe-o-que-e-softdelete-parte-2-6p5) deste artigo criaremos os métodos para deletar, restaurar e consultar registros deletados.
Até breve.