Construtores e destrutores são métodos especiais em PHP que permitem controlar o ciclo de vida dos objetos. O construtor é chamado automaticamente quando um objeto é instanciado, e o destrutor é chamado quando o objeto é destruído ou o script termina. Nesta aula, exploraremos o método __construct, a promoção de propriedades (PHP 8+), o método __destruct e boas práticas de inicialização.

Esses recursos são fundamentais para escrever código orientado a objetos limpo e eficiente, garantindo que os objetos sejam configurados corretamente e que recursos como conexões de banco de dados ou arquivos sejam liberados adequadamente.

__construct

O método __construct é o construtor de uma classe. Ele é executado automaticamente quando um novo objeto é criado com a palavra-chave new. Sua principal finalidade é inicializar as propriedades do objeto ou executar ações necessárias para o funcionamento do objeto.

O construtor pode receber parâmetros, permitindo que valores sejam passados no momento da instanciação. Diferente de outras linguagens, o PHP não permite múltiplos construtores (sobrecarga), mas você pode usar valores padrão ou argumentos variáveis para simular comportamentos diferentes.

<?php

class Usuario {
    public string $nome;
    public string $email;

    public function __construct(string $nome, string $email) {
        $this->nome = $nome;
        $this->email = $email;
        echo "Usuário criado: $nome\n";
    }
}

$usuario = new Usuario("João", "joao@email.com");
// Saída: Usuário criado: João
?>

No exemplo acima, o construtor recebe dois parâmetros e os atribui às propriedades do objeto. Note que o construtor pode executar qualquer código, como validações, logging ou inicialização de dependências.

Promoção de propriedades (PHP 8)

Desde o PHP 8, é possível usar a promoção de propriedades no construtor. Isso permite declarar e inicializar propriedades diretamente nos parâmetros do construtor, reduzindo a verbosidade do código. A sintaxe combina a declaração da propriedade com a atribuição.

Para usar a promoção, basta adicionar um modificador de visibilidade (public, protected ou private) antes do parâmetro. O PHP automaticamente cria a propriedade e atribui o valor recebido. Isso elimina a necessidade de declarar a propriedade separadamente e escrever a atribuição manual.

<?php

class Usuario {
    // Promoção de propriedades: declaração e atribuição em uma linha
    public function __construct(
        public string $nome,
        public string $email
    ) {
        echo "Usuário criado: $nome\n";
    }
}

$usuario = new Usuario("Maria", "maria@email.com");
echo $usuario->nome; // Maria
?>

Além de reduzir código, a promoção de propriedades torna a intenção mais clara. Você pode misturar parâmetros promovidos com parâmetros comuns (sem visibilidade), mas os promovidos devem vir antes dos não promovidos. Também é possível definir valores padrão e tipos complexos.

__destruct

O método __destruct é o destrutor de uma classe. Ele é chamado automaticamente quando o objeto é destruído, seja porque o script terminou, seja porque não há mais referências ao objeto (coleta de lixo). O destrutor não aceita parâmetros e não retorna valor.

O destrutor é útil para liberar recursos externos, como fechar conexões de banco de dados, arquivos abertos ou liberar memória alocada. É importante notar que o destrutor não é chamado explicitamente pelo programador; o PHP gerencia isso automaticamente.

<?php

class Arquivo {
    private $handle;

    public function __construct(string $caminho) {
        $this->handle = fopen($caminho, "w");
        echo "Arquivo aberto.\n";
    }

    public function escrever(string $texto) {
        fwrite($this->handle, $texto);
    }

    public function __destruct() {
        if ($this->handle) {
            fclose($this->handle);
            echo "Arquivo fechado.\n";
        }
    }
}

$arquivo = new Arquivo("teste.txt");
$arquivo->escrever("Olá, mundo!");
// Quando o script terminar ou o objeto for destruído, o destrutor será chamado
?>

No exemplo, o destrutor garante que o arquivo seja fechado, mesmo que o programador esqueça de fazê-lo. Isso evita vazamento de recursos.

Inicialização

Além do construtor, existem outras formas de inicializar objetos em PHP. Uma delas é o método __init (não é um método mágico padrão), mas a abordagem mais comum é usar o construtor. No entanto, em alguns padrões de projeto, como Factory ou Singleton, a inicialização pode ser feita de maneira diferente.

Outra técnica é usar um método estático para criar e retornar uma instância, permitindo validações ou configurações antes da construção. Isso é conhecido como “named constructor” ou “static factory method”.

<?php

class Usuario {
    public function __construct(
        public string $nome,
        public string $email
    ) {}

    // Factory method
    public static function criarComEmail(string $nome, string $email): self {
        // validação do email
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException("Email inválido");
        }
        return new self($nome, $email);
    }
}

$usuario = Usuario::criarComEmail("Ana", "ana@email.com");
?>

Além disso, o PHP permite inicializar propriedades com valores padrão na declaração. Isso é útil quando o construtor não recebe todos os parâmetros. Por exemplo:

<?php

class Config {
    public string $host = "localhost";
    public int $porta = 3306;

    public function __construct(string $host = null, int $porta = null) {
        if ($host !== null) {
            $this->host = $host;
        }
        if ($porta !== null) {
            $this->porta = $porta;
        }
    }
}
?>

Boas práticas

Ao usar construtores, evite realizar operações pesadas ou com efeitos colaterais que possam falhar. Se a inicialização pode lançar exceções, documente isso. Prefira a promoção de propriedades quando possível, pois reduz boilerplate. Use destrutores apenas para liberar recursos; não confie neles para lógica de negócios, pois seu tempo de execução é imprevisível. Considere usar padrões como Factory para construções complexas.

Referências

Exercícios

  1. Crie uma classe Carro com propriedades marca, modelo e ano. Utilize um construtor para inicializar essas propriedades. Instancie um objeto e exiba os valores.

    ✓ Resposta:
    <?php
    class Carro {
        public string $marca;
        public string $modelo;
        public int $ano;
    
        public function __construct(string $marca, string $modelo, int $ano) {
            $this->marca = $marca;
            $this->modelo = $modelo;
            $this->ano = $ano;
        }
    }
    
    $carro = new Carro("Toyota", "Corolla", 2020);
    echo "{$carro->marca} {$carro->modelo} ({$carro->ano})";
    ?>
    
  2. Refatore a classe Carro do exercício anterior usando promoção de propriedades do PHP 8. Mantenha o mesmo comportamento.

    ✓ Resposta:
    <?php
    class Carro {
        public function __construct(
            public string $marca,
            public string $modelo,
            public int $ano
        ) {}
    }
    
    $carro = new Carro("Honda", "Civic", 2021);
    echo "{$carro->marca} {$carro->modelo} ({$carro->ano})";
    ?>
    
  3. Crie uma classe Logger que abre um arquivo para escrita no construtor e o fecha no destrutor. O construtor deve receber o nome do arquivo. Implemente um método log(string $mensagem) que escreve a mensagem no arquivo.

    ✓ Resposta:
    <?php
    class Logger {
        private $handle;
    
        public function __construct(string $arquivo) {
            $this->handle = fopen($arquivo, "a");
        }
    
        public function log(string $mensagem): void {
            fwrite($this->handle, date("Y-m-d H:i:s") . " - $mensagem\n");
        }
    
        public function __destruct() {
            if ($this->handle) {
                fclose($this->handle);
            }
        }
    }
    
    $logger = new Logger("app.log");
    $logger->log("Iniciando aplicação");
    // O arquivo será fechado automaticamente
    ?>
    
  4. Explique por que o destrutor não deve ser usado para lógica de negócios crítica. Dê um exemplo de situação em que isso poderia causar problemas.

    ✓ Resposta:

    O destrutor é chamado em momentos imprevisíveis: quando o objeto sai do escopo, quando o script termina ou durante a coleta de lixo. Se você colocar lógica de negócios no destrutor, como enviar um email ou atualizar um banco de dados, pode ocorrer que essa lógica seja executada em um estado inconsistente (por exemplo, após uma exceção não tratada) ou nunca seja executada se o objeto for mantido em memória. Além disso, exceções lançadas no destrutor não podem ser capturadas adequadamente. Um exemplo problemático: uma classe que envia um email de confirmação no destrutor; se o script falhar antes, o email pode ser enviado indevidamente ou não ser enviado.

  5. Crie uma classe Configuracao que utiliza um método estático carregar(string $arquivo) como factory. O método deve ler um arquivo JSON e retornar uma instância com as propriedades preenchidas. Use promoção de propriedades no construtor.

    ✓ Resposta:
    <?php
    class Configuracao {
        public function __construct(
            public string $host,
            public string $usuario,
            public string $senha
        ) {}
    
        public static function carregar(string $arquivo): self {
            $dados = json_decode(file_get_contents($arquivo), true);
            return new self(
                $dados['host'],
                $dados['usuario'],
                $dados['senha']
            );
        }
    }
    
    // Supondo config.json: {"host":"localhost","usuario":"root","senha":"123"}
    $config = Configuracao::carregar("config.json");
    echo $config->host; // localhost
    ?>