Validar CPF em Laravel: Custom Rule com FormRequest
Validação de CPF em Laravel usando Custom Rule, FormRequest e exibição de erro no Blade. A lógica segue o algoritmo de módulo 11. Este artigo foca na integração com o framework.
Custom Rule
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class CpfRule implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$cpf = preg_replace('/[^0-9]/', '', (string) $value);
if (strlen($cpf) !== 11) {
$fail('O :attribute não é um CPF válido.');
return;
}
if (preg_match('/^(\d)\1{10}$/', $cpf)) {
$fail('O :attribute não é um CPF válido.');
return;
}
for ($t = 9; $t < 11; $t++) {
$sum = 0;
for ($c = 0; $c < $t; $c++) {
$sum += (int) $cpf[$c] * (($t + 1) - $c);
}
$digit = ((10 * $sum) % 11) % 10;
if ((int) $cpf[$t] !== $digit) {
$fail('O :attribute não é um CPF válido.');
return;
}
}
}
}Análise do código
A classe implementa ValidationRule, interface padrão do Laravel 11+ que substitui a antiga Rule. O método validate recebe o valor e uma closure $fail que é chamada apenas quando a validação falha. Se o método terminar sem chamar $fail, o valor é considerado válido.
preg_replace('/[^0-9]/', '', $value) remove pontos e traço, aceitando CPF com ou sem máscara. O regex ^(\d)\1{10}$ rejeita sequências de 11 dígitos iguais. O loop calcula ambos os dígitos verificadores usando a mesma lógica da implementação PHP pura.
FormRequest
<?php
namespace App\Http\Requests;
use App\Rules\CpfRule;
use Illuminate\Foundation\Http\FormRequest;
class StorePessoaRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'nome' => ['required', 'string', 'max:255'],
'cpf' => ['required', 'string', new CpfRule],
'email' => ['required', 'email'],
];
}
public function messages(): array
{
return [
'cpf.required' => 'O CPF é obrigatório.',
];
}
}No controller, basta type-hint o FormRequest. O Laravel valida automaticamente antes de executar o método:
public function store(StorePessoaRequest $request)
{
// $request->validated() já contém apenas dados válidos
$pessoa = Pessoa::create($request->validated());
return redirect()->route('pessoas.show', $pessoa);
}Exibição no Blade
<form method="POST" action="/pessoas">
@csrf
<div>
<label for="cpf">CPF</label>
<input
type="text"
name="cpf"
id="cpf"
inputmode="numeric"
placeholder="000.000.000-00"
value="{{ old('cpf') }}"
class="@error('cpf') border-red-500 @enderror"
/>
@error('cpf')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
<button type="submit">Salvar</button>
</form>old('cpf') preserva o valor digitado após erro de validação. A diretiva @error exibe a mensagem retornada pelo $fail da Rule.
Testes
<?php
namespace Tests\Unit\Rules;
use App\Rules\CpfRule;
use PHPUnit\Framework\TestCase;
class CpfRuleTest extends TestCase
{
private CpfRule $rule;
private array $errors;
protected function setUp(): void
{
$this->rule = new CpfRule;
$this->errors = [];
}
private function validate(string $value): bool
{
$this->errors = [];
$this->rule->validate('cpf', $value, function ($message) {
$this->errors[] = $message;
});
return empty($this->errors);
}
public function testCpfValidoComMascara(): void
{
$this->assertTrue($this->validate('529.982.247-25'));
}
public function testCpfValidoSemMascara(): void
{
$this->assertTrue($this->validate('52998224725'));
}
public function testCpfComDigitosRepetidos(): void
{
$this->assertFalse($this->validate('111.111.111-11'));
}
public function testCpfComDigitoVerificadorIncorreto(): void
{
$this->assertFalse($this->validate('529.982.247-26'));
}
public function testCpfComTamanhoIncorreto(): void
{
$this->assertFalse($this->validate('123.456.789'));
}
public function testCpfComZerosAEsquerda(): void
{
$this->assertTrue($this->validate('000.000.001-91'));
}
}Use o gerador de CPF válido para criar números de teste em lote.
Veja também: validar CPF em PHP puro.