Validar, gerar e formatar CNPJ em PHP
Funções em PHP 8 para validar, gerar e formatar CNPJ numérico e alfanumérico, testes com PHPUnit 10 e DataProvider e chamada à API com cURL.
Boa parte dos validadores de CNPJ em PHP que circulam por aí começa com preg_replace('/\D/', '', $cnpj). Com o CNPJ alfanumérico, essa linha passa a apagar letras legítimas e transforma um CNPJ válido em lixo. As funções desta página usam PHP 8 com strict_types, só dependem do PCRE embutido e tratam os dois formatos com o mesmo cálculo: cada caractere vale ord($c) - 48, o que dá 0 a 9 para dígitos e 17 a 42 para letras de A a Z. O impacto em banco, máscaras e integrações está no guia CNPJ alfanumérico: o que muda no seu sistema.
Validar CNPJ em PHP (numérico e alfanumérico)
<?php
declare(strict_types=1);
namespace Cnpj;
const PESOS_1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
const PESOS_2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
function calcularDigito(string $base, array $pesos): int
{
$soma = 0;
for ($i = 0; $i < strlen($base); $i++) {
$soma += (ord($base[$i]) - 48) * $pesos[$i];
}
$resto = $soma % 11;
return $resto < 2 ? 0 : 11 - $resto;
}
function validarCnpj(string $valor): bool
{
$cnpj = preg_replace('#[./\-\s]#', '', strtoupper($valor));
if (preg_match('/^[A-Z0-9]{12}[0-9]{2}$/D', $cnpj) !== 1) {
return false;
}
if (preg_match('/^(.)\1{11}/', $cnpj) === 1) {
return false;
}
$base = substr($cnpj, 0, 12);
$dv1 = calcularDigito($base, PESOS_1);
$dv2 = calcularDigito($base . $dv1, PESOS_2);
return substr($cnpj, 12) === $dv1 . $dv2;
}O que cada parte resolve:
strtoupperantes da limpeza faz12.abc.345/01de-35ser tratado como12.ABC.345/01DE-35.- Só pontos, barra, hífen e espaços são removidos. Outros símbolos ficam na string e a regex seguinte recusa a entrada.
[A-Z0-9]{12}[0-9]{2}com o modificadorDaceita letras na raiz e na ordem, exige dígitos nos verificadores e impede que$aceite uma quebra de linha no fim./^(.)\1{11}/recusa bases repetidas como 00.000.000/0000-00, que fecham a conta, mas não são CNPJs válidos.- A comparação é entre strings.
$dv1 . $dv2vira"81", esubstr($cnpj, 12)também, então===funciona sem conversão implícita.
Conferindo com 11.222.333/0001-81: a primeira soma é 102, o resto é 3 e o dígito é 8; a segunda soma é 120, o resto é 10 e o dígito é 1. Com 12.ABC.345/01DE-35, as somas são 459 e 424, os restos 8 e 6, e os dígitos 3 e 5. Em Laravel, embrulhe validarCnpj em uma ValidationRule para reaproveitá-la nos Form Requests.
Gerar CNPJ válido em PHP
function gerarCnpj(bool $alfanumerico = false, bool $formatado = false): string
{
$alfabeto = $alfanumerico ? '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' : '0123456789';
$max = strlen($alfabeto) - 1;
do {
$base = '';
for ($i = 0; $i < 12; $i++) {
$base .= $alfabeto[random_int(0, $max)];
}
} while (preg_match('/^(.)\1{11}$/', $base) === 1);
$dv1 = calcularDigito($base, PESOS_1);
$dv2 = calcularDigito($base . $dv1, PESOS_2);
$cnpj = $base . $dv1 . $dv2;
return $formatado ? formatarCnpj($cnpj) : $cnpj;
}random_int usa o gerador criptográfico do sistema. O CNPJ gerado passa na validação, mas pode coincidir com um CNPJ real por acaso; use-o em seeders e factories de teste. Para fixar a matriz, troque a ordem sorteada por 0001, como explica o guia da estrutura do CNPJ. Para volume em SQL ou CSV sem código, use o gerador de CNPJ em lote.
Formatar e limpar CNPJ em PHP
function limparCnpj(string $valor): string
{
return preg_replace('/[^A-Z0-9]/', '', strtoupper($valor));
}
function formatarCnpj(string $valor): string
{
$c = limparCnpj($valor);
if (strlen($c) !== 14) {
throw new \InvalidArgumentException('CNPJ precisa de 14 caracteres');
}
return sprintf('%s.%s.%s/%s-%s', substr($c, 0, 2), substr($c, 2, 3), substr($c, 5, 3), substr($c, 8, 4), substr($c, 12, 2));
}formatarCnpj('12abc34501de35') devolve "12.ABC.345/01DE-35", e aplicar a função de novo no resultado não muda nada. Grave sempre a versão limpa, em uma coluna CHAR(14): uma coluna numérica não comporta letras e ainda perde o zero à esquerda. Para validar o formato com máscara em uma única expressão, veja o guia de regex de CNPJ.
Testes
No PHPUnit 10, o provedor de dados usa o atributo #[DataProvider] e precisa ser public static. Como as funções estão no namespace Cnpj, o teste as importa 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 Cnpj\formatarCnpj;
use function Cnpj\gerarCnpj;
use function Cnpj\validarCnpj;
require_once __DIR__ . '/../src/cnpj.php';
final class CnpjTest extends TestCase
{
public static function casos(): array
{
return [
'numérico válido' => ['11.222.333/0001-81', true],
'alfanumérico válido' => ['12.ABC.345/01DE-35', true],
'dígito errado' => ['11.222.333/0001-82', false],
'repetido' => ['00.000.000/0000-00', false],
];
}
#[DataProvider('casos')]
public function testValidarCnpj(string $cnpj, bool $esperado): void
{
$this->assertSame($esperado, validarCnpj($cnpj));
}
public function testGeradosSaoValidos(): void
{
for ($i = 0; $i < 1000; $i++) {
$this->assertTrue(validarCnpj(gerarCnpj()));
$this->assertTrue(validarCnpj(gerarCnpj(alfanumerico: true)));
}
}
public function testFormatarEhIdempotente(): void
{
$this->assertSame('12.ABC.345/01DE-35', formatarCnpj(formatarCnpj('12abc34501de35')));
}
}As chaves do array viram o nome de cada caso na saída do PHPUnit. Outros números para a tabela estão nos exemplos de CNPJ alfanumérico para teste.
Usar a API em vez de reimplementar
A API do cpf.dev.br valida e gera CNPJs por HTTP, sem chave, com limite de 60 requisições por minuto por IP. Envie o CNPJ sem máscara ou passe-o por rawurlencode, que troca a barra por %2F. curl_exec devolve false em erro de rede, e esse caso precisa ser tratado antes do json_decode.
<?php
function getJson(string $url): array
{
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5]);
$corpo = curl_exec($ch);
if ($corpo === false) {
throw new RuntimeException('Falha na requisição: ' . curl_error($ch));
}
return json_decode($corpo, true, flags: JSON_THROW_ON_ERROR);
}
$dados = getJson('https://www.cpf.dev.br/api/v1/cnpj/validar/' . rawurlencode('12.ABC.345/01DE-35'));
// ['valido' => true, 'cnpj' => '12ABC34501DE35', 'formatado' => '12.ABC.345/01DE-35',
// 'formato' => 'alfanumerico', 'raiz' => '12ABC345', 'ordem' => '01DE', 'matriz' => false]
$query = http_build_query(['quantidade' => 5, 'formato' => 'alfanumerico', 'formatado' => 'true']);
$cnpjs = getJson('https://www.cpf.dev.br/api/v1/cnpj/gerar?' . $query)['cnpjs'];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 CNPJ.