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 private para todas as propriedades e métodos, e torne-os protected ou public apenas 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 readonly do 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

Exercícios

  1. Crie uma classe Carro com propriedades privadas: marca (string), modelo (string) e ano (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;
        }
    }
  2. Explique a diferença entre private e protected. Dê um exemplo de herança onde protected é útil.

    ✓ Resposta:

    private limita o acesso à própria classe. protected permite acesso também em classes filhas. Exemplo: uma classe base Animal com método protegido respirar() que é usado pela classe filha Peixe.

    class Animal {
        protected function respirar(): void {
            echo "Respirando...";
        }
    }
    
    class Peixe extends Animal {
        public function mergulhar(): void {
            $this->respirar(); // permitido
            echo "Mergulhando...";
        }
    }
  3. Crie uma classe ContaCorrente com uma propriedade privada saldo (float). Implemente um método público transferir(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);
        }
    }
  4. 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 Data com propriedade pública $dia pode receber um valor inválido como 32, quebrando a lógica da classe.

  5. Escreva um código que demonstre o uso de um método privado dentro de um método público. Crie uma classe Calculadora com um método privado validarNumero($n) que verifica se o número é positivo, e um método público raizQuadrada($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);
        }
    }