Marcio Policarpo
Posted on June 28, 2022
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
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.
$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
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
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.
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.
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
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 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
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
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.
A segunda requisição retorna as informações de um registro passado como parâmetro.
Podemos ver que a coluna deleted_at
está nula.
Na segunda parte deste artigo criaremos os métodos para deletar, restaurar e consultar registros deletados.
Até breve.
Posted on June 28, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.