DEV Community

Cover image for Você sabe o que é "softdelete"? Parte 1
Marcio Policarpo
Marcio Policarpo

Posted on • Edited on

2 1

Você sabe o que é "softdelete"? Parte 1

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


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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

$table->softDeletes('deleted_at', 0);
Enter fullscreen mode Exit fullscreen mode

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

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

Enter fullscreen mode Exit fullscreen mode

Em seguida, vamos criar e configurar o modelo.

php artisan make:model Invoice
Enter fullscreen mode Exit fullscreen mode

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_ate updated_at invisíveis para consultas feitas através do modelo.

<?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',
    ];
}

Enter fullscreen mode Exit fullscreen mode

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.

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

    return response()->json([
        'total' => $totalRecords,
        'data' => $invoices,
    ]);
}
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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

Enter fullscreen mode Exit fullscreen mode

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 artisan make:factory InvoiceFactory
Enter fullscreen mode Exit fullscreen mode

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

<?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),
            //
        ];
    }
}

Enter fullscreen mode Exit fullscreen mode

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

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();
    }
}

Enter fullscreen mode Exit fullscreen mode

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

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

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

php artisan db:seed
Enter fullscreen mode Exit fullscreen mode

Agora basta iniciar o servidor na porta 8100

php artisan serve --port=8100
Enter fullscreen mode Exit fullscreen mode

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

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


Na segunda parte deste artigo criaremos os métodos para deletar, restaurar e consultar registros deletados.
Até breve.

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay