Nesta aula, vamos explorar os modificadores de visibilidade em PHP: public, private e protected. Esses modificadores controlam o acesso a propriedades e métodos de classes, sendo fundamentais para o encapsulamento e a segurança do código. Entender como usá-los corretamente é essencial para escrever código orientado a objetos robusto e de fácil manutenção.

public, private, protected

Em PHP, os modificadores de visibilidade determinam de onde uma propriedade ou método pode ser acessado. O modificador public permite acesso de qualquer lugar, tanto dentro quanto fora da classe. O private restringe o acesso apenas à própria classe que o define. Já o protected permite acesso à própria classe e às suas subclasses (herança).

Vamos ver um exemplo prático:

class MinhaClasse {
    public $publico = "Acessível de qualquer lugar";
    private $privado = "Acessível apenas dentro da classe";
    protected $protegido = "Acessível na classe e subclasses";

    public function mostrarPrivado() {
        return $this->privado; // Permitido
    }
}

$obj = new MinhaClasse();
echo $obj->publico; // Funciona
echo $obj->mostrarPrivado(); // Funciona (método público)
// echo $obj->privado; // Erro: não pode acessar propriedade privada
// echo $obj->protegido; // Erro: não pode acessar propriedade protegida

O modificador protected é útil quando queremos que subclasses possam acessar determinados membros, mas não o código externo. Já o private é o mais restritivo, garantindo que apenas a própria classe manipule seus dados internos.

Encapsulamento

Encapsulamento é um dos pilares da programação orientada a objetos. Ele consiste em 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 acessam ou modificam os dados de forma segura.

O encapsulamento traz benefícios como:

  • Proteção dos dados contra acesso indevido ou inconsistente.
  • Facilidade de manutenção: alterações internas não afetam o código externo.
  • Maior modularidade e reuso.

Exemplo de encapsulamento com validação:

class ContaBancaria {
    private $saldo;

    public function __construct($saldoInicial = 0) {
        $this->saldo = max(0, $saldoInicial); // Garante saldo não negativo
    }

    public function depositar($valor) {
        if ($valor > 0) {
            $this->saldo += $valor;
            return true;
        }
        return false;
    }

    public function getSaldo() {
        return $this->saldo;
    }
}

$conta = new ContaBancaria(100);
$conta->depositar(50);
echo $conta->getSaldo(); // 150

Note que o saldo nunca pode ser acessado diretamente, apenas através de métodos que garantem regras de negócio.

Getters e setters

Getters e setters são métodos públicos que permitem acessar e modificar propriedades privadas ou protegidas. Eles são a forma mais comum de implementar encapsulamento. Um getter retorna o valor da propriedade; um setter define um novo valor, podendo incluir validações.

Exemplo de getters e setters:

class Pessoa {
    private $nome;
    private $idade;

    // Getter para nome
    public function getNome() {
        return $this->nome;
    }

    // Setter para nome
    public function setNome($nome) {
        $this->nome = trim($nome);
    }

    // Getter para idade
    public function getIdade() {
        return $this->idade;
    }

    // Setter para idade com validação
    public function setIdade($idade) {
        if ($idade >= 0 && $idade <= 150) {
            $this->idade = $idade;
        } else {
            throw new InvalidArgumentException("Idade inválida");
        }
    }
}

$pessoa = new Pessoa();
$pessoa->setNome("João");
$pessoa->setIdade(25);
echo $pessoa->getNome() . " tem " . $pessoa->getIdade() . " anos.";

No PHP moderno, é comum usar type hints e declarações de tipos nos getters e setters para maior segurança.

Boas práticas

Ao trabalhar com visibilidade e encapsulamento, algumas boas práticas incluem:

  • Prefira propriedades privadas por padrão: só torne algo público se houver uma necessidade clara.
  • Use getters e setters consistentes: mesmo que inicialmente não haja validação, eles permitem adicionar lógica futuramente sem quebrar o código.
  • Evite setters para propriedades que não devem ser alteradas: nesse caso, forneça apenas getters e defina o valor no construtor.
  • Não exponha referências a objetos mutáveis: se uma propriedade é um array ou objeto, retorne uma cópia ou uma versão imutável.
  • Documente a visibilidade: use comentários ou anotações para explicar o propósito de cada membro.

Exemplo de boa prática com imutabilidade:

class Configuracao {
    private array $dados;

    public function __construct(array $dados) {
        $this->dados = $dados;
    }

    public function get(string $chave, $default = null) {
        return $this->dados[$chave] ?? $default;
    }

    // Não fornece setter para manter imutável
}

$config = new Configuracao(['db_host' => 'localhost']);
echo $config->get('db_host');

Seguir essas práticas ajuda a criar código mais seguro, de fácil manutenção e com menos bugs.

Referências

Exercícios

  1. Crie uma classe Carro com propriedades privadas marca, modelo e ano. Implemente getters e setters para todas elas, validando que o ano seja maior que 1886.

    ✓ Resposta:
    class Carro {
        private $marca;
        private $modelo;
        private $ano;
    
        public function getMarca() {
            return $this->marca;
        }
    
        public function setMarca($marca) {
            $this->marca = $marca;
        }
    
        public function getModelo() {
            return $this->modelo;
        }
    
        public function setModelo($modelo) {
            $this->modelo = $modelo;
        }
    
        public function getAno() {
            return $this->ano;
        }
    
        public function setAno($ano) {
            if ($ano > 1886) {
                $this->ano = $ano;
            } else {
                throw new InvalidArgumentException("Ano deve ser maior que 1886");
            }
        }
    }
  2. Explique a diferença entre private e protected e dê um exemplo onde o uso de protected é mais adequado.

    ✓ Resposta:

    private restringe o acesso apenas à classe que define o membro, enquanto protected permite acesso também às subclasses. Um exemplo onde protected é mais adequado: uma classe base Animal com um método emitirSom que é sobrescrito pelas subclasses. O método pode ser protected para que as subclasses o acessem, mas não o código externo.

  3. Implemente uma classe Contador com uma propriedade privada $valor inicializada em 0. Crie métodos públicos incrementar() e getValor(). O método incrementar deve aumentar o valor em 1 e retornar o novo valor.

    ✓ Resposta:
    class Contador {
        private $valor = 0;
    
        public function incrementar() {
            $this->valor++;
            return $this->valor;
        }
    
        public function getValor() {
            return $this->valor;
        }
    }
  4. Crie uma classe Produto com propriedades privadas nome, preco e quantidade. Implemente um método público calcularTotal() que retorna o preço vezes a quantidade. Use encapsulamento adequado.

    ✓ Resposta:
    class Produto {
        private $nome;
        private $preco;
        private $quantidade;
    
        public function __construct($nome, $preco, $quantidade) {
            $this->nome = $nome;
            $this->preco = $preco;
            $this->quantidade = $quantidade;
        }
    
        public function getNome() {
            return $this->nome;
        }
    
        public function getPreco() {
            return $this->preco;
        }
    
        public function getQuantidade() {
            return $this->quantidade;
        }
    
        public function calcularTotal() {
            return $this->preco * $this->quantidade;
        }
    }
  5. Escreva um código que demonstre a tentativa de acesso a uma propriedade private fora da classe e o erro gerado. Em seguida, mostre como acessá-la corretamente através de um método público.

    ✓ Resposta:
    class Exemplo {
        private $segredo = "valor secreto";
    
        public function getSegredo() {
            return $this->segredo;
        }
    }
    
    $obj = new Exemplo();
    // echo $obj->segredo; // Gera erro: Cannot access private property
    // Forma correta:
    echo $obj->getSegredo(); // Exibe "valor secreto"