Visibilidade
Esta aula aborda os modificadores de visibilidade em PHP (public, private, protected), o conceito de encapsulamento e a implementação de getters e setters. Além disso, discute boas práticas para garantir código seguro e bem estruturado.
Nesta aula, vamos explorar um dos pilares da Programação Orientada a Objetos: a visibilidade dos membros de uma classe. Em PHP, os modificadores de visibilidade controlam o acesso a propriedades e métodos, permitindo que você defina quem pode ler ou modificar determinados dados. Compreender e aplicar corretamente esses conceitos é fundamental para criar código seguro, modular e de fácil manutenção.
Veremos os três modificadores disponíveis: public, private e protected. Em seguida, discutiremos o encapsulamento, que é a prática de ocultar detalhes internos de uma classe, e como implementar getters e setters para controlar o acesso a propriedades privadas. Por fim, apresentaremos boas práticas para usar visibilidade de forma eficiente.
public, private, protected
Os modificadores de visibilidade determinam o nível de acesso a propriedades e métodos de uma classe. Eles são essenciais para o princípio do encapsulamento.
- public: O membro pode ser acessado de qualquer lugar: dentro da classe, de classes filhas e de fora da classe (instâncias).
- private: O membro só pode ser acessado de dentro da própria classe. Nem mesmo classes filhas têm acesso.
- protected: O membro pode ser acessado de dentro da classe e de classes filhas (herança), mas não de fora da classe.
Exemplo:
class MinhaClasse {
public $publico = "Público";
private $privado = "Privado";
protected $protegido = "Protegido";
public function mostrarPrivado() {
return $this->privado; // permitido
}
}
$obj = new MinhaClasse();
echo $obj->publico; // Funciona
echo $obj->privado; // Erro: Cannot access private property
echo $obj->protegido; // Erro: Cannot access protected property
echo $obj->mostrarPrivado(); // Funciona
Encapsulamento
Encapsulamento é o conceito de esconder os detalhes internos de uma classe e expor apenas uma interface controlada. Isso é alcançado usando modificadores de visibilidade, principalmente private e protected, combinados com métodos públicos que permitem acesso seguro aos dados. O encapsulamento protege a integridade dos dados, evita efeitos colaterais indesejados e facilita a manutenção do código.
Por exemplo, imagine uma classe ContaBancaria que possui um saldo. Se o saldo for público, qualquer parte do código pode alterá-lo diretamente, sem validação. Ao torná-lo privado e fornecer métodos para depositar e sacar, podemos garantir que o saldo nunca fique negativo.
class ContaBancaria {
private float $saldo;
public function __construct(float $saldoInicial) {
if ($saldoInicial < 0) {
throw new InvalidArgumentException("Saldo inicial não pode ser negativo");
}
$this->saldo = $saldoInicial;
}
public function depositar(float $valor): void {
if ($valor <= 0) {
throw new InvalidArgumentException("Valor do depósito deve ser positivo");
}
$this->saldo += $valor;
}
public function sacar(float $valor): void {
if ($valor <= 0) {
throw new InvalidArgumentException("Valor do saque deve ser positivo");
}
if ($valor > $this->saldo) {
throw new RuntimeException("Saldo insuficiente");
}
$this->saldo -= $valor;
}
public function getSaldo(): float {
return $this->saldo;
}
}
Getters e setters
Getters e setters são métodos públicos usados para acessar e modificar propriedades privadas ou protegidas. Eles permitem adicionar lógica de validação ou transformação durante o acesso, mantendo o encapsulamento. Convencionalmente, um getter tem o prefixo get seguido do nome da propriedade (ex.: getNome) e um setter tem o prefixo set (ex.: setNome).
Exemplo:
class Pessoa {
private string $nome;
private int $idade;
public function getNome(): string {
return $this->nome;
}
public function setNome(string $nome): void {
if (strlen($nome) < 3) {
throw new InvalidArgumentException("Nome deve ter pelo menos 3 caracteres");
}
$this->nome = $nome;
}
public function getIdade(): int {
return $this->idade;
}
public function setIdade(int $idade): void {
if ($idade < 0 || $idade > 150) {
throw new InvalidArgumentException("Idade inválida");
}
$this->idade = $idade;
}
}
No PHP moderno, também é possível usar a funcionalidade de propriedades tipadas (typed properties) e, a partir do PHP 8.1, o suporte a propriedades readonly e a promoção de propriedades no construtor, que podem reduzir a necessidade de setters em alguns casos.
Boas práticas
- Prefira privado por padrão: Comece com
privatepara todas as propriedades e métodos, e torne-osprotectedoupublicapenas quando houver necessidade real. - Use getters e setters quando necessário: Nem toda propriedade precisa de getter/setter. Se a propriedade é puramente interna, não exponha. Se for expor, considere se é necessário validação ou se o acesso direto é suficiente.
- Evite getters/setters que apenas retornam/atribuem sem lógica: Isso pode ser um sinal de que a propriedade deveria ser pública. Use a funcionalidade de
readonlydo PHP 8.1 para propriedades imutáveis. - Mantenha a consistência: Se uma classe expõe getters, geralmente é bom ter setters correspondentes (a menos que a propriedade seja imutável).
- Documente a visibilidade: Comente o propósito de membros não públicos para facilitar a compreensão do código.
- Use type hints: Declare tipos para propriedades e métodos para evitar erros e melhorar a legibilidade.
Exemplo de boas práticas combinadas:
class Produto {
private string $nome;
private float $preco;
public function __construct(string $nome, float $preco) {
$this->setNome($nome);
$this->setPreco($preco);
}
public function getNome(): string {
return $this->nome;
}
public function setNome(string $nome): void {
if (empty(trim($nome))) {
throw new InvalidArgumentException("Nome não pode ser vazio");
}
$this->nome = $nome;
}
public function getPreco(): float {
return $this->preco;
}
public function setPreco(float $preco): void {
if ($preco < 0) {
throw new InvalidArgumentException("Preço não pode ser negativo");
}
$this->preco = $preco;
}
}
Referências
- PHP: Visibilidade - Manual
- PHP: Propriedades - Manual
- PHP: Classes e Objetos - Básico
- PHP: Declarações de Tipo
- PHP 8.1: Novidades (readonly, etc.)
- PSR-12: Guia de Estilo de Código
Exercícios
-
Crie uma classe
Carrocom propriedades privadas:marca(string),modelo(string) eano(int). Implemente getters e setters para todas as propriedades, validando que o ano deve ser maior que 1885. No setter de ano, lance uma exceção se o ano for inválido.✓ Resposta:class Carro { private string $marca; private string $modelo; private int $ano; public function getMarca(): string { return $this->marca; } public function setMarca(string $marca): void { $this->marca = $marca; } public function getModelo(): string { return $this->modelo; } public function setModelo(string $modelo): void { $this->modelo = $modelo; } public function getAno(): int { return $this->ano; } public function setAno(int $ano): void { if ($ano <= 1885) { throw new InvalidArgumentException("Ano inválido. Deve ser maior que 1885."); } $this->ano = $ano; } } -
Explique a diferença entre
privateeprotected. Dê um exemplo de herança ondeprotectedé útil.✓ Resposta:privatelimita o acesso à própria classe.protectedpermite acesso também em classes filhas. Exemplo: uma classe baseAnimalcom método protegidorespirar()que é usado pela classe filhaPeixe.class Animal { protected function respirar(): void { echo "Respirando..."; } } class Peixe extends Animal { public function mergulhar(): void { $this->respirar(); // permitido echo "Mergulhando..."; } } -
Crie uma classe
ContaCorrentecom uma propriedade privadasaldo(float). Implemente um método públicotransferir(ContaCorrente $destino, float $valor)que transfere valor da conta atual para a conta destino, desde que o saldo seja suficiente. Use encapsulamento para acessar o saldo da conta destino.✓ Resposta:class ContaCorrente { private float $saldo = 0; public function getSaldo(): float { return $this->saldo; } public function depositar(float $valor): void { if ($valor <= 0) throw new InvalidArgumentException("Valor inválido"); $this->saldo += $valor; } public function transferir(ContaCorrente $destino, float $valor): void { if ($valor <= 0) throw new InvalidArgumentException("Valor inválido"); if ($this->saldo < $valor) throw new RuntimeException("Saldo insuficiente"); $this->saldo -= $valor; // Acessa o saldo da conta destino via método público $destino->depositar($valor); } } -
Por que é considerado má prática tornar todas as propriedades públicas? Dê um exemplo de um problema que pode ocorrer.
✓ Resposta:
Propriedades públicas violam o encapsulamento, permitindo que qualquer código externo modifique o estado interno sem validação. Exemplo: uma classe
Datacom propriedade pública$diapode receber um valor inválido como 32, quebrando a lógica da classe. -
Escreva um código que demonstre o uso de um método privado dentro de um método público. Crie uma classe
Calculadoracom um método privadovalidarNumero($n)que verifica se o número é positivo, e um método públicoraizQuadrada($n)que usa o método privado antes de calcular.✓ Resposta:class Calculadora { private function validarNumero($n): void { if (!is_numeric($n) || $n < 0) { throw new InvalidArgumentException("Número deve ser positivo"); } } public function raizQuadrada($n): float { $this->validarNumero($n); return sqrt($n); } }