| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879 |
- <?php
- namespace App\Support;
- /**
- * Fonte única de verdade para o formato do CPF.
- *
- * Regra: o banco guarda SOMENTE os 11 dígitos, garantindo que a unique de
- * `users.cpf` compare CPFs de fato — e não strings com máscaras diferentes.
- * A máscara existe apenas na exibição.
- */
- class Cpf
- {
- /**
- * Formato canônico de persistência: apenas dígitos.
- */
- public static function digits(?string $cpf): ?string
- {
- if ($cpf === null) {
- return null;
- }
- $digits = preg_replace('/\D/', '', $cpf);
- return $digits === '' ? null : $digits;
- }
- /**
- * Formato de exibição: xxx.xxx.xxx-xx.
- *
- * Normaliza antes de formatar para que registros legados já mascarados
- * (ou com máscara parcial) também saiam corretos.
- */
- public static function format(?string $cpf): ?string
- {
- $digits = self::digits($cpf);
- if ($digits === null) {
- return null;
- }
- if (strlen($digits) !== 11) {
- return $digits;
- }
- return substr($digits, 0, 3) . '.'
- . substr($digits, 3, 3) . '.'
- . substr($digits, 6, 3) . '-'
- . substr($digits, 9, 2);
- }
- /**
- * Valida os dígitos verificadores, rejeitando também sequências repetidas.
- */
- public static function isValid(?string $cpf): bool
- {
- $digits = self::digits($cpf);
- if ($digits === null || strlen($digits) !== 11 || preg_match('/^(\d)\1{10}$/', $digits)) {
- return false;
- }
- foreach ([9, 10] as $position) {
- $sum = 0;
- for ($i = 0; $i < $position; $i++) {
- $sum += (int) $digits[$i] * (($position + 1) - $i);
- }
- $checkDigit = (($sum * 10) % 11) % 10;
- if ((int) $digits[$position] !== $checkDigit) {
- return false;
- }
- }
- return true;
- }
- }
|