Desafio PHP
Value Object CPF com validação e readonly
Enunciado
Implemente um Value Object Cpf como readonly class, recebendo a string no construtor. A propriedade valor deve ser privada e acessível via método valor(): string. No construtor, valide que a string tem 11 dígitos numéricos (use preg_match('/^\d{11}$/', $cpf)); se não, lance InvalidArgumentException. Implemente também equals(Cpf $outro): bool comparando os valores. Crie um try/catch demonstrando um CPF válido e um inválido.
Requisitos
- A classe deve ser
readonly classcom propriedadeprivate string $valor. - O construtor deve lançar
InvalidArgumentExceptionse o formato não casar com/^\d{11}$/. - Deve existir
valor(): stringeequals(Cpf $outro): bool. - O programa deve capturar a exceção do CPF inválido e imprimir a mensagem.
Código inicial
<?php
declare(strict_types=1);
readonly class Cpf
{
// TODO: propriedade privada, validação, valor() e equals()
}
try {
$a = new Cpf('12345678901');
$b = new Cpf('12345678901');
echo $a->valor(), PHP_EOL;
echo $a->equals($b) ? 'iguais' : 'diferentes', PHP_EOL;
new Cpf('123');
} catch (InvalidArgumentException $e) {
echo 'Erro: ', $e->getMessage(), PHP_EOL;
}
Saída esperada
12345678901
iguais
Erro: CPF inválido
Ver dica
No construtor, valide antes de atribuir. Use preg_match para checar o formato e lance InvalidArgumentException com mensagem clara.
Mostrar solução
<?php
declare(strict_types=1);
readonly class Cpf
{
// privado: ninguém de fora mexe direto no valor
private string $valor;
public function __construct(string $cpf)
{
// Valida antes de atribuir; readonly só aceita uma escrita
if (!preg_match('/^\d{11}$/', $cpf)) {
throw new InvalidArgumentException('CPF inválido');
}
$this->valor = $cpf;
}
public function valor(): string
{
return $this->valor;
}
public function equals(Cpf $outro): bool
{
return $this->valor === $outro->valor();
}
}
try {
$a = new Cpf('12345678901');
$b = new Cpf('12345678901');
echo $a->valor(), PHP_EOL;
echo $a->equals($b) ? 'iguais' : 'diferentes', PHP_EOL;
new Cpf('123'); // dispara a exceção
} catch (InvalidArgumentException $e) {
echo 'Erro: ', $e->getMessage(), PHP_EOL;
}
Passo a passo
readonly class Cpfgarante que a instância não mude depois de criada.private string $valorimpede acesso direto de fora; só o métodovalor()expõe.- No construtor,
preg_matchvalida exatamente 11 dígitos; se falhar, lançamosInvalidArgumentException. - A atribuição
$this->valor = $cpf;ocorre depois da validação, pois readonly só permite uma escrita. valor()retorna a string encapsulada, mantendo o acesso controlado.equals()compara dois CPFs pelo valor, não por identidade de objeto.- No bloco
try, criamos dois CPFs iguais, comparamos e depois tentamos criar um inválido. - O
catchcaptura a exceção e imprime a mensagem.
Por que funciona
Value Objects representam um valor e devem ser imutáveis: dois CPFs com a mesma string são equivalentes, independentemente de serem o mesmo objeto. A visibilidade private esconde o dado bruto e força o uso de valor(), enquanto readonly impede mutações posteriores. A validação no construtor garante que nenhuma instância inválida exista.
Erros comuns
- Atribuir antes de validar:
$this->valor = $cpf; if (...) throw ...— a propriedade readonly já foi escrita e o objeto inválido pode escapar. - Usar
==em vez de===emequals: pode dar falso positivo com strings numéricas. - Esquecer o
use InvalidArgumentExceptionou o\inicial: em namespace, a classe não é encontrada. - Marcar
valorcomopublic readonlye achar que ainda há encapsulamento — quem tem o objeto lê direto, sem passar pelo método.
Outra forma de resolver
Uma alternativa é usar static factory com fromString:
public static function fromString(string $cpf): self
{
return new self($cpf);
}
Útil quando você quer um nome mais expressivo que new Cpf(...) e centralizar a validação num único ponto.
Saída esperada
12345678901
iguais
Erro: CPF inválido