PHP Construtores
Até agora, a Ana criava um produto vazio com new Produto() e preenchia as propriedades uma por uma. É fácil esquecer uma. O construtor resolve isso: é um método especial que roda sozinho no momento do new e recebe os dados que o objeto precisa para nascer completo. Nesta aula você também vai ver a promoção de propriedades do PHP 8.0, que faz a mesma coisa com muito menos código.
- Escrever um construtor com
__constructe passar os dados nonew. - Dar valores padrão aos parâmetros do construtor.
- Validar os dados e recusar um objeto inválido.
- A promoção de propriedades no construtor (PHP 8.0).
- Criar objetos com argumentos nomeados:
new Produto(nome: "Caneca", preco: 25.90). - A ideia do
__destruct, o método que roda quando o objeto é descartado.
O problema: objetos "pela metade"
Com propriedades sem valor inicial (como você viu em Propriedades e Métodos), basta esquecer uma atribuição para o programa quebrar mais adiante:
<?php
class Produto
{
public string $nome;
public float $preco;
}
$caneca = new Produto();
$caneca->nome = "Caneca";
// a Ana esqueceu o preço...
echo "{$caneca->nome}: R$ {$caneca->preco}\n";
$ php esqueceu.php
Fatal error: Uncaught Error: Typed property Produto::$preco must not be accessed before initialization in /home/ana/loja/esqueceu.php:12
Stack trace:
#0 {main}
thrown in /home/ana/loja/esqueceu.php on line 12
O erro aparece na linha 12, longe da linha onde o preço deveria ter sido dado. Num programa grande, achar onde a Ana "esqueceu" é difícil. O ideal é que seja impossível criar um produto sem nome e sem preço.
Pense na maternidade: um bebê não sai do hospital sem certidão de nascimento com nome, data e local. O construtor é essa certidão: o objeto só "nasce" com os dados obrigatórios.
O construtor: __construct
<?php
class Produto
{
public string $nome;
public float $preco;
public int $estoque;
public function __construct(string $nome, float $preco, int $estoque)
{
$this->nome = $nome;
$this->preco = $preco;
$this->estoque = $estoque;
echo "Produto criado: {$this->nome}\n";
}
}
$caneca = new Produto("Caneca", 25.90, 10);
$camiseta = new Produto("Camiseta", 49.90, 4);
echo "{$caneca->nome} custa R$ {$caneca->preco} ({$caneca->estoque} em estoque)\n";
$ php construtor.php Produto criado: Caneca Produto criado: Camiseta Caneca custa R$ 25.9 (10 em estoque)
- public function __construct(…)
- O construtor. O nome é fixo: dois sublinhados e
construct("construir"). Métodos que começam com__são métodos mágicos: o PHP os chama sozinho em momentos especiais (veja Métodos Mágicos). - string $nome, float $preco, int $estoque
- Parâmetros, como em qualquer função. São variáveis locais do construtor: somem quando ele termina.
- $this->nome = $nome;
- Copia o valor do parâmetro
$nomepara a propriedadenomedo objeto. Os dois têm o mesmo nome por costume, mas são coisas diferentes:$nomeé o parâmetro,$this->nomeé a propriedade. - new Produto("Caneca", 25.90, 10)
- Os valores entre parênteses no
newvão para os parâmetros do construtor, na ordem. - : (sem tipo de retorno)
- O construtor não tem tipo de retorno e não devolve nada: quem "devolve" o objeto é o
new.
Repare na saída: a mensagem Produto criado apareceu duas vezes, uma em cada new. Ninguém chamou __construct diretamente: o PHP chamou sozinho. A sequência de um new é:
- O PHP cria um objeto novo, com as propriedades nos valores iniciais (ou não inicializadas).
- Chama o
__constructdesse objeto, passando os valores dos parênteses. - Quando o construtor termina, o
newentrega o objeto pronto para a variável.
Agora, esquecer o preço vira um erro na linha do new, que é exatamente onde está o problema (veja Quando dá errado).
Valores padrão
Parâmetros do construtor podem ter valor padrão, como os de qualquer função. Os opcionais ficam no fim:
<?php
class Produto
{
public string $nome;
public float $preco;
public int $estoque;
public function __construct(string $nome, float $preco, int $estoque = 0)
{
$this->nome = $nome;
$this->preco = $preco;
$this->estoque = $estoque;
}
}
$caneca = new Produto("Caneca", 25.90, 10);
$chaveiro = new Produto("Chaveiro", 12.00);
echo "{$caneca->nome}: {$caneca->estoque}\n";
echo "{$chaveiro->nome}: {$chaveiro->estoque}\n";
$ php padrao.php Caneca: 10 Chaveiro: 0
Validar no construtor
O construtor é o porteiro do objeto: é o lugar certo para recusar dados inválidos. Se algo está errado, ele lança uma exceção (throw) e o objeto não é criado. (Exceções têm a sua aula: Exceções.)
<?php
class Produto
{
public string $nome;
public float $preco;
public function __construct(string $nome, float $preco)
{
if (trim($nome) === "") {
throw new InvalidArgumentException("O nome não pode ser vazio");
}
if ($preco < 0) {
throw new InvalidArgumentException("Preço negativo: $preco");
}
$this->nome = trim($nome);
$this->preco = $preco;
}
}
$caneca = new Produto(" Caneca ", 25.90);
echo "[{$caneca->nome}]\n";
try {
$erro = new Produto("Camiseta", -5);
} catch (InvalidArgumentException $e) {
echo "Não criei o produto: " . $e->getMessage() . "\n";
}
$outro = new Produto("", 10);
$ php validar.php
[Caneca] « o construtor também limpou os espaços
Não criei o produto: Preço negativo: -5 « o try/catch tratou a exceção
Fatal error: Uncaught InvalidArgumentException: O nome não pode ser vazio in /home/ana/loja/validar.php:10
Stack trace:
#0 /home/ana/loja/validar.php(29): Produto->__construct('', 10.0)
#1 {main}
thrown in /home/ana/loja/validar.php on line 10
Três situações: a caneca passou nas regras (e o nome foi limpo com trim); a camiseta com preço negativo foi recusada, e o try/catch tratou o problema; o produto sem nome foi recusado sem try, e o programa parou com Uncaught ("não capturada"). Nos três casos, nenhum produto inválido chegou a existir.
Promoção de propriedades no construtor PHP 8.0+
Olhe de novo o construtor da caneca. Cada dado aparece três vezes: na declaração da propriedade, no parâmetro e na atribuição $this->nome = $nome. O PHP 8.0 criou um atalho: escreva a visibilidade na frente do parâmetro, e ele vira propriedade e recebe o valor sozinho. Isso se chama promoção de propriedades no construtor (constructor property promotion):
<?php
class Produto
{
public function __construct(
public string $nome,
public float $preco,
public int $estoque = 0,
) {
}
}
$caneca = new Produto("Caneca", 25.90, 10);
var_dump($caneca);
$ php promocao.php
object(Produto)#1 (3) {
["nome"]=>
string(6) "Caneca"
["preco"]=>
float(25.9)
["estoque"]=>
int(10)
}
O resultado é idêntico ao da versão longa: o objeto tem as três propriedades com os valores certos. Compare as duas formas:
| Forma longa (qualquer versão) | Com promoção (PHP 8.0+) |
|---|---|
class Produto
{
public string $nome;
public float $preco;
public int $estoque;
public function __construct(
string $nome,
float $preco,
int $estoque = 0,
) {
$this->nome = $nome;
$this->preco = $preco;
$this->estoque = $estoque;
}
} | class Produto
{
public function __construct(
public string $nome,
public float $preco,
public int $estoque = 0,
) {
}
} |
- public string $nome
- Dentro dos parênteses do construtor, a palavra
public(ouprotected/private, de Visibilidade) é o que "promove" o parâmetro a propriedade. Sem ela, é um parâmetro comum. - um parâmetro por linha
- Costume para construtores promovidos: fica fácil de ler e de acrescentar um campo.
- , (vírgula depois do último)
- A "vírgula final" é permitida (PHP 8.0+) e recomendada: ao acrescentar um parâmetro, você não esquece a vírgula.
- { }
- O corpo pode ficar vazio. As atribuições acontecem sozinhas, antes do corpo rodar.
Misturando: promovidos, comuns e código no corpo
Nem todo parâmetro precisa virar propriedade, e o corpo pode ter código. Aqui o número só serve para montar o código do produto:
<?php
class Produto
{
public string $codigo;
public function __construct(
public string $nome,
public float $preco,
int $numero,
) {
$this->codigo = "P-" . str_pad((string) $numero, 4, "0", STR_PAD_LEFT);
}
}
$caneca = new Produto("Caneca", 25.90, 7);
echo "{$caneca->codigo} {$caneca->nome}\n";
var_dump(isset($caneca->numero));
$ php misto.php P-0007 Caneca bool(false) « $numero não tem public: é só um parâmetro, não virou propriedade
Missão: reescreva a classe com promoção de propriedades: apague as três declarações e as três atribuições, e ponha public na frente de cada parâmetro do construtor. A saída deve continuar Camiseta: R$ 49.9 (4 em estoque), sem avisos.
<?php
class Produto
{
public string $nome;
public float $preco;
public int $estoque;
public function __construct(string $nome, float $preco, int $estoque = 0)
{
$this->nome = $nome;
$this->preco = $preco;
$this->estoque = $estoque;
}
}
$camiseta = new Produto("Camiseta", 49.90, 4);
echo "{$camiseta->nome}: R$ {$camiseta->preco} ({$camiseta->estoque} em estoque)\n";
<?php
class Produto
{
public function __construct(
public string $nome,
public float $preco,
public int $estoque = 0,
) {
}
}
$camiseta = new Produto("Camiseta", 49.90, 4);
echo "{$camiseta->nome}: R$ {$camiseta->preco} ({$camiseta->estoque} em estoque)\n";
Argumentos nomeados com construtores PHP 8.0+
Com muitos parâmetros, new Produto("Caneca", 25.90, 0, true, "Cozinha") fica difícil de ler: o que é o 0? E o true? Os argumentos nomeados (que você conheceu em Parâmetros) funcionam no new também: escreva o nome do parâmetro, dois-pontos e o valor:
<?php
class Produto
{
public function __construct(
public string $nome,
public float $preco,
public int $estoque = 0,
public bool $ativo = true,
public string $categoria = "Geral",
) {
}
}
$a = new Produto("Caneca", 25.90, 0, true, "Cozinha");
$b = new Produto(nome: "Caneca", preco: 25.90, categoria: "Cozinha");
$c = new Produto(preco: 3.50, nome: "Adesivo");
$d = new Produto("Camiseta", 49.90, ativo: false);
echo "{$b->nome} | {$b->estoque} | {$b->categoria}\n";
echo "{$c->nome} | {$c->preco} | {$c->categoria}\n";
var_dump($d->ativo);
$ php nomeados.php Caneca | 0 | Cozinha Adesivo | 3.5 | Geral bool(false)
| Linha | O que mostra |
|---|---|
$a = new Produto("Caneca", 25.90, 0, true, "Cozinha"); | Tudo por posição: para dar a categoria, a Ana teve que repetir os padrões 0 e true. |
$b = new Produto(nome: "Caneca", preco: 25.90, categoria: "Cozinha"); | Com nomes, ela pula os do meio: estoque e ativo ficam no padrão. |
$c = new Produto(preco: 3.50, nome: "Adesivo"); | Com nomes, a ordem não importa. |
$d = new Produto("Camiseta", 49.90, ativo: false); | Dá para misturar: primeiro os posicionais, depois os nomeados. |
Os nomes são os dos parâmetros do construtor, sem o $. Com a promoção, eles coincidem com os nomes das propriedades, o que torna a leitura ainda mais clara.
__destruct: quando o objeto vai embora (só a ideia)
O contrário do construtor é o destrutor, __destruct ("destruir"). O PHP o chama sozinho quando o objeto deixa de ser usado: quando nenhuma variável aponta mais para ele, ou no fim do script.
<?php
class Conexao
{
public function __construct(public string $nome)
{
echo "Abrindo {$this->nome}\n";
}
public function __destruct()
{
echo "Fechando {$this->nome}\n";
}
}
$a = new Conexao("banco");
$b = new Conexao("arquivo de log");
unset($a);
echo "Fim do script\n";
$ php destrutor.php Abrindo banco Abrindo arquivo de log Fechando banco « o unset tirou a última "etiqueta" do objeto: ele foi destruído na hora Fim do script Fechando arquivo de log « o $b foi destruído quando o script terminou
Na prática, você quase nunca vai escrever um __destruct: o PHP já libera memória, fecha arquivos e conexões sozinho no fim de cada script (e cada página da web é um script curto). Ele aparece em classes que guardam um recurso que precisa ser "devolvido" (um arquivo aberto, uma trava). Basta saber que existe e quando roda.
Formas de criar um objeto com dados
| Forma | Vantagem | Quando usar |
|---|---|---|
Sem construtor; atribuir depois do new | Simples | Só em testes rápidos. Fácil esquecer um campo. |
| Construtor na forma longa | Funciona em qualquer versão; dá para tratar cada valor antes de guardar | Código antigo, ou quando cada valor precisa de transformação. |
| Construtor com promoção | Curto, sem repetição | O padrão no PHP moderno. Combine com validação no corpo, se precisar. |
Método estático "fábrica" (Produto::deArray($dados)) | Vários jeitos de criar, cada um com nome próprio | Quando os dados vêm de fontes diferentes (formulário, banco, JSON). Veja static. |
Quando dá errado
<?php
class Produto
{
public string $nome;
public float $preco;
public function __construct(string $nome, float $preco)
{
$this->nome = $nome;
$this->preco = $preco;
}
}
$caneca = new Produto("Caneca");
$ php poucos.php
Fatal error: Uncaught ArgumentCountError: Too few arguments to function Produto::__construct(), 1 passed in /home/ana/loja/poucos.php on line 14 and exactly 2 expected in /home/ana/loja/poucos.php:7
Stack trace:
#0 /home/ana/loja/poucos.php(14): Produto->__construct('Caneca')
#1 {main}
thrown in /home/ana/loja/poucos.php on line 7
O que significa: "argumentos de menos para Produto::__construct(): 1 passado na linha 14, e exatamente 2 esperados". O Stack trace mostra a chamada: Produto->__construct('Caneca'), só com o nome.
Como consertar: passe todos os obrigatórios no new (new Produto("Caneca", 25.90)) ou dê um valor padrão ao parâmetro que pode faltar.
<?php
class Produto
{
public string $nome;
public float $preco;
public function __construct(string $nome, float $preco)
{
$this->nome = $nome;
$this->preco = $preco;
}
}
$caneca = new Produto("Caneca", "barato");
$ php tipo-errado.php
Fatal error: Uncaught TypeError: Produto::__construct(): Argument #2 ($preco) must be of type float, string given, called in /home/ana/loja/tipo-errado.php on line 14 and defined in /home/ana/loja/tipo-errado.php:7
Stack trace:
#0 /home/ana/loja/tipo-errado.php(14): Produto->__construct('Caneca', 'barato')
#1 {main}
thrown in /home/ana/loja/tipo-errado.php on line 7
O que significa: o segundo argumento ($preco) devia ser float, mas chegou uma string. A mensagem diz onde foi chamado (linha 14) e onde o construtor está definido (linha 7).
Como consertar: passe um número. Se o valor vem de um formulário (onde tudo é texto), converta e valide antes (veja Validar Dados).
<?php
class Produto
{
public string $nome = "sem nome";
public function _construct(string $nome)
{
$this->nome = $nome;
}
}
$c = new Produto("Caneca");
echo $c->nome . "\n";
$ php construct-errado.php sem nome
O que significa: nenhum erro, mas o nome não foi guardado. _construct (com um sublinhado) é um método comum, que ninguém chamou. E o PHP não reclama dos argumentos extras do new, porque a classe não tem construtor. O mesmo acontece com um método chamado igual à classe (function Produto(…)): era o construtor no PHP 4, e desde o PHP 8 é só um método comum.
Como consertar: são dois sublinhados: __construct.
public na promoção
<?php
class Produto
{
public function __construct(string $nome, float $preco)
{
}
}
$c = new Produto("Caneca", 25.90);
echo $c->nome . "\n";
$ php sem-public.php Warning: Undefined property: Produto::$nome in /home/ana/loja/sem-public.php on line 10
O que significa: sem a visibilidade, string $nome é só um parâmetro: o valor chega ao construtor e some. O objeto não tem propriedade nome.
Como consertar: public string $nome dentro dos parênteses.
<?php
class Produto
{
public string $nome;
public function __construct(public string $nome)
{
}
}
$ php repetida.php
Fatal error: Cannot redeclare Produto::$nome in /home/ana/loja/repetida.php on line 6
Stack trace:
#0 {main}
O que significa: a propriedade foi declarada duas vezes: na classe (linha 4) e promovida no construtor (linha 6).
Como consertar: escolha uma forma. Com promoção, apague a declaração de cima.
<?php
class Produto
{
public function __construct(
public string $nome,
public float $preco,
) {
}
}
$c = new Produto(nome: "Caneca", preço: 25.90);
$ php nome-errado.php
Fatal error: Uncaught Error: Unknown named parameter $preço in /home/ana/loja/nome-errado.php:11
Stack trace:
#0 {main}
thrown in /home/ana/loja/nome-errado.php on line 11
O que significa: "parâmetro nomeado desconhecido $preço". O construtor tem $preco, sem cedilha.
Como consertar: use exatamente o nome do parâmetro: preco: 25.90.
<?php
class Produto
{
public function __construct(
public string $nome,
public float $preco,
) {
}
}
$c = new Produto(nome: "Caneca", 25.90);
$ php posicional-depois.php
Fatal error: Cannot use positional argument after named argument in /home/ana/loja/posicional-depois.php on line 11
Stack trace:
#0 {main}
O que significa: depois de um argumento com nome, o PHP não sabe a que posição um argumento sem nome corresponde. O arquivo nem começa a rodar.
Como consertar: primeiro os posicionais, depois os nomeados: new Produto("Caneca", preco: 25.90), ou todos com nome.
Referência
| Escrita | O que faz |
|---|---|
public function __construct(tipo $p) { … } | Construtor: roda sozinho no new |
new Classe(arg1, arg2) | Passa os argumentos para o construtor |
tipo $p = valor | Parâmetro opcional, com valor padrão |
public tipo $p (nos parênteses do construtor) PHP 8.0+ | Promoção: vira propriedade e recebe o valor |
new Classe(nome: valor) PHP 8.0+ | Argumento nomeado |
throw new InvalidArgumentException("…") | Recusa dados inválidos no construtor |
public function __destruct() { … } | Destrutor: roda quando o objeto é descartado |
Missão final
Missão: escreva a classe Cliente com um construtor promovido com três propriedades públicas: nome (texto), email (texto) e cidade (texto, com padrão "Recife"). Escreva também o método ficha(): string, que devolve "Nome (email) - Cidade". Crie a Ana só com nome e e-mail, e o Bruno usando argumentos nomeados (nome:, email:, cidade: "Natal"). A saída deve ser Ana (ana@exemplo.com) - Recife e Bruno (bruno@exemplo.com) - Natal.
<?php
// escreva a classe Cliente aqui
$ana = new Cliente("Ana", "ana@exemplo.com");
// crie o $bruno com argumentos nomeados
echo $ana->ficha() . "\n";
echo $bruno->ficha() . "\n";
<?php
class Cliente
{
public function __construct(
public string $nome,
public string $email,
public string $cidade = "Recife",
) {
}
public function ficha(): string
{
return "{$this->nome} ({$this->email}) - {$this->cidade}";
}
}
$ana = new Cliente("Ana", "ana@exemplo.com");
$bruno = new Cliente(nome: "Bruno", email: "bruno@exemplo.com", cidade: "Natal");
echo $ana->ficha() . "\n";
echo $bruno->ficha() . "\n";
Exercícios
1. O nome do construtor
Complete a declaração do construtor:
2. Promoção
Complete o parâmetro para ele virar uma propriedade pública do tipo float:
3. Argumento nomeado
O construtor é __construct(public string $nome, public int $estoque = 0, public bool $ativo = true). Complete para criar um "Chaveiro" inativo, sem informar o estoque:
4. Quando o __construct é executado?
5. O construtor é __construct(string $nome, float $preco). O que acontece com new Produto("Caneca")?
6. Em __construct(public string $nome, int $codigo), quais propriedades o objeto ganha?
7. Qual destas chamadas dá erro de sintaxe?
Resumo
- O construtor
__construct(dois sublinhados) roda sozinho a cadanewe recebe os argumentos dos parênteses. - Use-o para que todo objeto nasça completo e para validar os dados (lançando uma exceção se algo estiver errado).
- Parâmetros podem ter valor padrão; os opcionais ficam no fim.
- Promoção (8.0+):
public tipo $nomenos parênteses do construtor declara a propriedade e guarda o valor. É o padrão no PHP moderno. - Argumentos nomeados:
new Produto(nome: "Caneca", preco: 25.90); pule opcionais e esqueça a ordem. Posicionais sempre antes dos nomeados. __destructroda quando o objeto é descartado; raramente é necessário.