Validar, gerar e formatar CPF em PHP
Funções em PHP 8 para validar, gerar e formatar CPF com módulo 11, testes com PHPUnit 10 e DataProvider e chamada à API com cURL.
Validar CPF em PHP aparece em quase todo cadastro brasileiro, de formulários em Laravel a integrações com WordPress. As funções desta página usam PHP 8 com strict_types, não dependem de extensão além do PCRE, que vem embutido, e ficam em um namespace próprio para não colidir com outros helpers do projeto. A lógica é a mesma das ferramentas do site, explicada passo a passo no guia do algoritmo do módulo 11.
Validar CPF em PHP
<?php
declare(strict_types=1);
namespace Cpf;
function calcularDigito(string $digitos, int $pesoInicial): int
{
$soma = 0;
for ($i = 0; $i < strlen($digitos); $i++) {
$soma += (int) $digitos[$i] * ($pesoInicial - $i);
}
$resto = $soma % 11;
return $resto < 2 ? 0 : 11 - $resto;
}
function validarCpf(string $valor): bool
{
$cpf = preg_replace('/[.\-\s]/', '', $valor);
if (preg_match('/^[0-9]{11}$/D', $cpf) !== 1) {
return false;
}
if (preg_match('/^(\d)\1{10}$/', $cpf) === 1) {
return false;
}
$dv1 = calcularDigito(substr($cpf, 0, 9), 10);
$dv2 = calcularDigito(substr($cpf, 0, 10), 11);
return $cpf[9] === (string) $dv1 && $cpf[10] === (string) $dv2;
}O que cada parte resolve:
preg_replace('/[.\-\s]/', '', $valor)remove pontos, hífen e espaços. Letras e outros símbolos continuam na string e fazem a checagem seguinte falhar.- O modificador
Dimpede que$aceite uma quebra de linha no fim. A limpeza já remove\n, mas o padrão fica correto mesmo que ela mude. /^(\d)\1{10}$/recusa sequências como 111.111.111-11, que fecham a conta do módulo 11, mas não são consideradas válidas.- A comparação usa strings.
$cpf[9]é o caractere"2", e(string) $dv1também, então===funciona sem conversões implícitas.
Conferindo com 529.982.247-25: a primeira soma é 295, o resto é 9, e o dígito é 2; a segunda soma é 347, o resto é 6, e o dígito é 5. As duas comparações dão true. Com 529.982.247-26, a segunda compara "6" com "5" e a função devolve false.
Em Laravel, embrulhe validarCpf em uma regra de validação (ValidationRule) para reaproveitá-la em todos os Form Requests.
Gerar CPF válido em PHP
random_int usa o gerador criptográfico do sistema operacional, disponível desde o PHP 7.
function gerarCpf(bool $formatado = false): string
{
do {
$base = '';
for ($i = 0; $i < 9; $i++) {
$base .= (string) random_int(0, 9);
}
} while (preg_match('/^(\d)\1{8}$/', $base) === 1);
$dv1 = calcularDigito($base, 10);
$dv2 = calcularDigito($base . $dv1, 11);
$cpf = $base . $dv1 . $dv2;
return $formatado ? formatarCpf($cpf) : $cpf;
}O CPF gerado passa na validação, mas pode coincidir com um CPF real por acaso. Use-o em seeders e factories de teste, não em produção. Para um volume maior já pronto em SQL ou CSV, o gerador de CPF em lote resolve sem código.
Formatar e limpar CPF em PHP
function limparCpf(string $valor): string
{
return preg_replace('/\D/', '', $valor);
}
function formatarCpf(string $valor): string
{
$d = limparCpf($valor);
if (strlen($d) !== 11) {
throw new \InvalidArgumentException('CPF precisa de 11 dígitos');
}
return vsprintf('%s.%s.%s-%s', str_split($d, 3));
}str_split('52998224725', 3) devolve ['529', '982', '247', '25'], exatamente os quatro blocos da máscara, e vsprintf monta "529.982.247-25". Grave no banco sempre a versão limpa, em uma coluna CHAR(11); o guia sobre validar CPF no banco de dados explica por que uma coluna numérica perde o zero à esquerda.
Testes
No PHPUnit 10, o provedor de dados é declarado com o atributo #[DataProvider] e precisa ser public static. Como as funções estão no namespace Cpf, o teste importa validarCpf com use function e carrega o arquivo com require_once, caso ele ainda não esteja no autoload do Composer.
<?php
declare(strict_types=1);
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use function Cpf\validarCpf;
require_once __DIR__ . '/../src/cpf.php';
final class CpfTest extends TestCase
{
public static function casos(): array
{
return [
'válido' => ['529.982.247-25', true],
'repetido' => ['111.111.111-11', false],
'dígito errado' => ['529.982.247-26', false],
];
}
#[DataProvider('casos')]
public function testValidarCpf(string $cpf, bool $esperado): void
{
$this->assertSame($esperado, validarCpf($cpf));
}
}As chaves do array viram o nome de cada caso na saída do PHPUnit, o que facilita achar a linha que quebrou. Outros casos de borda estão no guia de erros comuns ao validar CPF.
Usar a API em vez de reimplementar
A API do cpf.dev.br valida e gera CPFs por HTTP, sem chave, com limite de 60 requisições por minuto por IP. Para uma consulta simples, file_get_contents basta; com cURL você controla o tempo limite.
<?php
$json = file_get_contents('https://www.cpf.dev.br/api/v1/cpf/validar/529.982.247-25');
$dados = json_decode($json, true, flags: JSON_THROW_ON_ERROR);
// ['valido' => true, 'cpf' => '52998224725', 'formatado' => '529.982.247-25', 'uf' => ['ES', 'RJ']]
$query = http_build_query(['quantidade' => 5, 'uf' => 'SP', 'formatado' => 'true']);
$ch = curl_init('https://www.cpf.dev.br/api/v1/cpf/gerar?' . $query);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5]);
$cpfs = json_decode(curl_exec($ch), true, flags: JSON_THROW_ON_ERROR)['cpfs'];Em formulários, a função local continua sendo a melhor escolha: não depende de rede nem conta no limite. Para testar um número à mão, use o validador de CPF.