Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 50 additions & 19 deletions src/validator-docs/Rules/Cnpj.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,70 @@

namespace geekcom\ValidatorDocs\Rules;

use function mb_strlen;
use function preg_match;

final class Cnpj extends Sanitization
{
private const CNPJ_BASE_LENGTH = 12;
private const REGEX_CNPJ_BASE = '/^[A-Z\d]{12}$/';
private const REGEX_CNPJ_FULL = '/^[A-Z\d]{12}\d{2}$/';
private const ASCII_ZERO = 48; // '0' in ASCII
private const ZEROED_CNPJ = '00000000000000';

// Weights from right to left: 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5 (for DV1)
// and 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6 (for DV2)
private const DV1_WEIGHTS = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
private const DV2_WEIGHTS = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];

public function validateCnpj($attribute, $value): bool
{
$c = $this->sanitize($value);
$cnpjClean = strtoupper($this->sanitizeCNPJAlphanumeric($value));
if ($cnpjClean !== self::ZEROED_CNPJ && preg_match(self::REGEX_CNPJ_FULL, $cnpjClean)) {
$providedDV = substr($cnpjClean, self::CNPJ_BASE_LENGTH);
$calculatedDV = $this->calculateCNPJCheckDigits(substr($cnpjClean, 0, self::CNPJ_BASE_LENGTH));

if (mb_strlen($c) != 14 || preg_match("/^{$c[0]}{14}$/", $c)) {
return false;
return $providedDV === $calculatedDV;
}

$b = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
return false;
}

for (
$i = 0, $n = 0; $i < 12; $n += $c[$i] * $b[++$i]
) {
}
/**
* Calculate CNPJ check digits (DV) using the new alphanumeric rules.
*/
public function calculateCNPJCheckDigits($cnpj): string
{
$cnpjClean = strtoupper($this->sanitizeCNPJAlphanumeric($cnpj));
if (preg_match(self::REGEX_CNPJ_BASE, $cnpjClean) && substr(self::ZEROED_CNPJ, 0, strlen($cnpjClean)) !== $cnpjClean) {
// Calculate DV1
$sumDV1 = 0;
for ($i = 0; $i < self::CNPJ_BASE_LENGTH; $i++) {
$charValue = ord($cnpjClean[$i]) - self::ASCII_ZERO;
$sumDV1 += $charValue * self::DV1_WEIGHTS[$i];
}

if ($c[12] != ((($n %= 11) < 2) ? 0 : 11 - $n)) {
return false;
}
$remainder1 = $sumDV1 % 11;
$dv1 = ($remainder1 < 2) ? 0 : 11 - $remainder1;

for (
$i = 0, $n = 0; $i <= 12; $n += $c[$i] * $b[$i++]
) {
}
// Calculate DV2 (includes DV1 at the end)
$sumDV2 = 0;
for ($i = 0; $i < self::CNPJ_BASE_LENGTH; $i++) {
$charValue = ord($cnpjClean[$i]) - self::ASCII_ZERO;
$sumDV2 += $charValue * self::DV2_WEIGHTS[$i];
}
$sumDV2 += $dv1 * self::DV2_WEIGHTS[self::CNPJ_BASE_LENGTH];

$remainder2 = $sumDV2 % 11;
$dv2 = ($remainder2 < 2) ? 0 : 11 - $remainder2;

if ($c[13] != ((($n %= 11) < 2) ? 0 : 11 - $n)) {
return false;
return "{$dv1}{$dv2}";
}

return true;
throw new \InvalidArgumentException('Invalid CNPJ');
}

private function sanitizeCNPJAlphanumeric(string $value): string
{
return preg_replace('/[.\-\/]/', '', strtoupper(trim($value)));
}
}
39 changes: 26 additions & 13 deletions tests/TestValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,35 @@ public function cpf()
$this->assertTrue($incorrect->fails());
}

/** @test **/
public function cnpj()
/**
* @test
* @dataProvider cnpjDataProvider
**/
public function cnpj(string $cnpj, bool $expected): void
{
$correct = Validator::make(
['certo' => '53.084.587/0001-20'],
['certo' => 'cnpj']
);

$incorrect = Validator::make(
['errado' => '51.084.587/0001-20'],
['errado' => 'cnpj']
);
$valid = Validator::make(['cnpj_attribute' => $cnpj], ['cnpj_attribute' => 'cnpj'])->passes();

$this->assertTrue($correct->passes());
$this->assertSame($expected, $valid);
}

$this->assertTrue($incorrect->fails());
public function cnpjDataProvider(): array
{
return [
'Alphanumeric CNPJ with dots and dashes 1' => ['T6.JSP.XPS/0001-11', true],
'Alphanumeric CNPJ with dots and dashes 2' => ['T6.JSP.XPS/J84K-69', true],
'Alphanumeric CNPJ with dots and dashes 3' => ['D2.M97.AA0/0001-63', true],
'Alphanumeric CNPJ with dots and dashes 4' => ['W9.7VY.JMY/0001-81', true],
'Alphanumeric CNPJ clean 1' => ['E5SGVHX9000190', true],
'Alphanumeric CNPJ clean 2' => ['E5SGVHX960LR53', true],
'Alphanumeric CNPJ clean 3' => ['12ABC34501DE35', true],
'Numeric CNPJ with dots and dashes 1' => ['32.332.643/0001-29', true],
'Numeric CNPJ with dots and dashes 2' => ['32.332.643/3060-40', true],
'Numeric CNPJ clean 1' => ['38725833000192', true],
'Numeric CNPJ clean 2' => ['38725833223060', true],
'Invalid CNPJ' => ['12.345.678/0001-00', false],
'Zeroed CNPJ' => ['00.000.000/0000-00', false],
'CNPJ with invalid characters' => ['32.332.643/0001-9!', false],
];
}

/** @test **/
Expand Down