celed';
}
$pedido->status = Pedido::STATUS_PAID;
Enter fullscreen mode Exit fullscreen mode
Melhorou? Um pouco. Ganhou autocomplete e centralizou os valores. Mas ainda tem furo: nada impede alguém de gravar `$pedido->status = 'qualquer_coisa'` direto. A constante é uma sugestão, não uma regra. E você continua sem um lugar único pra pendurar comportamento (a cor do badge, o label bonito, se aquele status permite cancelamento...).
Dá pra fazer melhor. E de fábrica.
## [](#a-solu%C3%A7%C3%A3o-enums-de-verdade)A solução: Enums de verdade
O PHP 8.1 trouxe os **backed enums** — enums com um valor por trás. É um tipo novo, de verdade, que só aceita os casos que você definiu:
<?php
namespace App\Enums;
enum StatusPedido: string
{
case Pending = 'pending';
case Paid = 'paid';
case Canceled = 'canceled';
}
```
Enter fullscreen mode Exit fullscreen mode
Repara no `: string` ali no topo: é isso que faz cada caso ter um valor de string por trás, perfeito pra salvar no banco. Agora `StatusPedido::Paid` é um **objeto tipado**, não um texto solto. E aqui mora a mágica: no Laravel, você fala pro model tratar a coluna como esse enum, usando o cast:
```
class Pedido extends Model
{
protected function casts(): array
{
return [
'status' => StatusPedido::class,
];
}
}
```
Enter fullscreen mode Exit fullscreen mode
Pronto. A partir daqui o Eloquent faz a ponte sozinho: grava a string no banco, devolve o enum quando você lê:
```
$pedido->status = StatusPedido::Paid; // grava 'paid' no banco
$pedido->status; // volta como StatusPedido::Paid (objeto)
if ($pedido->status === StatusPedido::Paid) {
// type-safe, com autocomplete, sem chance de typo
}
```
Enter fullscreen mode Exit fullscreen mode
Tentou atribuir uma string inválida? Estoura na hora. O valor errado nem chega no banco.
## [](#como-usar-na-pr%C3%A1tica)Como usar na prática
**Comparação sem medo.** Acabou o `=== 'paid'`. Agora é `=== StatusPedido::Paid`, e o editor te entrega o autocomplete de todos os casos:
```
if ($pedido->status === StatusPedido::Canceled) {
return back()->with('erro', 'Esse pedido já foi cancelado.');
}
```
Enter fullscreen mode Exit fullscreen mode
**Comportamento junto do dado.** O melhor dos enums: eles têm métodos. Aquela lógica de "qual a cor do badge" para de viver espalhada na blade e vira parte do próprio status:
```
enum StatusPedido: string
{
case Pending = 'pending';
case Paid = 'paid';
case Canceled = 'canceled';
public function label(): string
{
return match($this) {
self::Pending => 'Aguardando pagamento',
self::Paid => 'Pago',
self::Canceled => 'Cancelado',
};
}
public function cor(): string
{
return match($this) {
self::Pending => 'yellow',
self::Paid => 'green',
self::Canceled => 'red',
};
}
}
```
Enter fullscreen mode Exit fullscreen mode
Na view, fica limpo assim:
```
<span class="badge badge-{{ $pedido->status->cor() }}">
{{ $pedido->status->label() }}
</span>
```
Enter fullscreen mode Exit fullscreen mode
**Preencher um select.** O método `cases()` já te dá todos os valores possíveis, prontos pra iterar:
```
@foreach (StatusPedido::cases() as $status)
<option value="{{ $status->value }}">{{ $status->label() }}</option>
@endforeach
```
Enter fullscreen mode Exit fullscreen mode
## [](#pegadinha-raw-value-endraw-vs-raw-name-endraw-vs-o-caso-em-si)Pegadinha: `value` vs. `name` vs. o caso em si
Aqui rola uma confusão comum. Um backed enum tem três coisas fáceis de embolar:
- `StatusPedido::Paid` — o caso em si, o objeto tipado. É o que você usa em comparação.
- `StatusPedido::Paid->value` — a string por trás (`'paid'`). É o que vai pro banco, pro `value` do `<option>`, pra API.
- `StatusPedido::Paid->name` — o nome do caso no código (`'Paid'`). Raramente é o que você quer mostrar pro usuário.
Regra rápida: comparou lógica? Usa o caso. Precisou do valor cru (banco, HTML, JSON)? Usa `->value`. E pra transformar uma string de volta em enum — tipo o que chega de um request — tem o `from()` e o `tryFrom()`:
```
StatusPedido::from('paid'); // StatusPedido::Paid
StatusPedido::from('inexistente'); // estoura ValueError
StatusPedido::tryFrom('xpto'); // null, sem estourar (ótimo pra input do usuário)
```
Enter fullscreen mode Exit fullscreen mode
## [](#b%C3%B4nus-valida%C3%A7%C3%A3o-de-request-de-brinde)Bônus: validação de request de brinde
Já que estamos falando de dados que entram, o Laravel valida enum direto na regra. Se alguém mandar um status que não existe no request, nem passa:
```
use Illuminate\Validation\Rule;
'status' => ['required', Rule::enum(StatusPedido::class)],
```
Enter fullscreen mode Exit fullscreen mode
Junta isso com o Form Request que você já usa e o status inválido morre antes de chegar no controller. String mágica não passa nem na porta.
## [](#antes-de-voc%C3%AA-fechar-a-aba)Antes de você fechar a aba
Faz um teste rápido: dá um grep por `'pending'`, `'paid'` ou qualquer status do seu projeto. Quantos lugares apareceram? Cada um deles é uma string mágica esperando pra te dar dor de cabeça numa refatoração.
Me conta aí embaixo: qual é a string mágica mais cabeluda que ainda vive no seu código? Status de pedido, tipo de usuário, forma de pagamento... todo projeto tem a sua. 😄
E se esse post te deu vontade de matar umas strings soltas hoje, salva ele e manda pro colega que ainda escreve `=== 'paid'` por aí.