PHP 8.0+ sem nenhuma dependência. Depois do algoritmo tem a Rule do Laravel, que é onde a maioria dos projetos PHP valida CNPJ na prática.
0=0 … 9=9, A=17 … Z=42), os pesos vão de 2 a 9 da direita para a esquerda, soma-se tudo, divide-se por 11 e o dígito é 0 quando o resto é menor que 2, ou 11 menos o resto. Ver a conta detalhada.Código completo
<?php
declare(strict_types=1);
function cnpj_limpar(?string $valor): string
{
return preg_replace('/[^0-9A-Z]/', '', strtoupper($valor ?? '')) ?? '';
}
/** Calcula um dígito do módulo 11 (valor do caractere = ASCII - 48). */
function cnpj_digito(string $sequencia): int
{
$soma = 0;
$tamanho = strlen($sequencia);
for ($i = 0; $i < $tamanho; $i++) {
$valor = ord($sequencia[$i]) - 48;
$peso = (($tamanho - 1 - $i) % 8) + 2;
$soma += $valor * $peso;
}
$resto = $soma % 11;
return $resto < 2 ? 0 : 11 - $resto;
}
/** Devolve os dois dígitos verificadores da base de 12 caracteres. */
function cnpj_calcular_dv(string $base): string
{
$primeiro = cnpj_digito($base);
$segundo = cnpj_digito($base . $primeiro);
return (string) $primeiro . (string) $segundo;
}
/** Valida CNPJ numérico ou alfanumérico, com ou sem máscara. */
function cnpj_valido(?string $valor): bool
{
$cnpj = cnpj_limpar($valor);
if (!preg_match('/^[0-9A-Z]{12}\d{2}$/', $cnpj)) {
return false;
}
if (preg_match('/^(.)\1{13}$/', $cnpj)) {
return false;
}
return cnpj_calcular_dv(substr($cnpj, 0, 12)) === substr($cnpj, 12);
}
/** Aplica a máscara 00.000.000/0000-00. */
function cnpj_formatar(string $valor): string
{
$cnpj = cnpj_limpar($valor);
if (strlen($cnpj) !== 14) {
return $cnpj;
}
return sprintf(
'%s.%s.%s/%s-%s',
substr($cnpj, 0, 2),
substr($cnpj, 2, 3),
substr($cnpj, 5, 3),
substr($cnpj, 8, 4),
substr($cnpj, 12, 2)
);
}
// Exemplo oficial da Receita Federal
var_dump(cnpj_calcular_dv('12ABC34501DE')); // string(2) "35"
var_dump(cnpj_valido('12.ABC.345/01DE-35')); // bool(true)
var_dump(cnpj_valido('11.222.333/0001-81')); // bool(true)
Testes e integração com o framework
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class CnpjValido implements ValidationRule
{
public function validate(string $atributo, mixed $valor, Closure $falhar): void
{
if (!is_string($valor) || !cnpj_valido($valor)) {
$falhar('O campo :attribute não contém um CNPJ válido.');
}
}
}
// Uso no FormRequest
public function rules(): array
{
return [
'razao_social' => ['required', 'string', 'min:3'],
'cnpj' => ['required', 'string', new CnpjValido()],
];
}
Detalhes que costumam quebrar em PHP
- Use
strtouppere nãomb_strtoupperaqui: o CNPJ só tem ASCII, e a versão simples é mais rápida. - Trocar
intval($cnpj)por comparação de string é obrigatório —intval('12ABC…')devolve12e destrói o valor. - Se você guarda o CNPJ com máscara no banco, a limpeza precisa acontecer antes de qualquer
UNIQUE, senão o mesmo CNPJ entra duas vezes.
Casos de teste recomendados
Cubra pelo menos estes cenários — eles pegam praticamente todos os erros de implementação:
| Entrada | Esperado | O que testa |
|---|---|---|
12.ABC.345/01DE-35 | válido | exemplo oficial, com máscara |
12abc34501de35 | válido | normalização para maiúsculas |
11.222.333/0001-81 | válido | compatibilidade com o formato numérico |
11.222.333/0001-82 | inválido | dígito verificador errado |
12ABC34501DEAB | inválido | letra no dígito verificador |
12ABC34501DE3 | inválido | tamanho incorreto |
00.000.000/0000-00 | inválido | sequência repetida |
"" / null | inválido | entrada vazia sem exceção |
Precisa de mais massa de teste? O gerador produz até 500 CNPJs alfanuméricos válidos de uma vez, com opção de baixar em .txt.