PHP Classes e Objetos
Na aula O que é POO você viu a ideia: a classe é o molde, o objeto é a coisa feita com ele. Agora é hora de escrever: declarar uma classe, criar objetos com new, mexer neles com a seta ->, olhar por dentro com var_dump e entender uma regra que confunde muita gente: o que acontece quando você copia um objeto para outra variável.
- A sintaxe de
classe as regras para dar nome a uma classe. - Criar objetos com
newe usar a seta->. - Criar vários objetos e guardá-los num array.
- Ler o
var_dumpde um objeto (o que é aquele#1). - Por que
$outra = $canecanão cria um segundo produto (objetos são passados por identificador). - Comparar objetos (
==e===), conferir o tipo cominstanceofe::class. - O
newsem parênteses extras do PHP 8.4:new Produto()->metodo().
Declarar uma classe
Uma classe começa com a palavra class (classe), o nome e um par de chaves. Tudo o que pertence à classe fica entre as chaves:
<?php
class Produto
{
public string $nome = "";
public float $preco = 0;
}
$caneca = new Produto();
$caneca->nome = "Caneca";
$caneca->preco = 25.90;
echo $caneca->nome . " custa R$ " . $caneca->preco . "\n";
- class
- Palavra-chave que declara uma classe ("vou descrever um molde").
- Produto
- O nome da classe. Por convenção, começa com letra maiúscula e cada palavra nova também:
Produto,ItemDoPedido,CarrinhoDeCompras(esse estilo se chama PascalCase). - { … }
- O corpo da classe. Por convenção (padrão PSR-12), a chave de abrir fica na linha de baixo.
- public string $nome = "";
- Uma propriedade:
public(pública, pode ser usada de fora), do tipostring, chamada$nome, começando vazia. Os detalhes estão em Propriedades e Métodos. - new Produto()
- Cria um objeto novo a partir do molde (instancia a classe). O resultado vai para a variável
$caneca. - $caneca->nome = "Caneca";
- A seta
->(um hífen e um sinal de maior) entra no objeto e acessa a propriedadenome. Repare: sem o$antes denome.
$ php primeira.php Caneca custa R$ 25.9
O echo de um número com casas decimais mostra 25.9, com ponto e sem o zero final. Para mostrar 25,90, use number_format (veja Formatar Números); vamos fazer isso daqui a pouco.
Regras para o nome da classe
- Letras, números e
_; não pode começar com número. Acentos funcionam, mas evite (Preçofunciona;Precoé o costume). - Não pode ser uma palavra reservada do PHP (
class,function,list,array…). - Os nomes de classe não diferenciam maiúsculas de minúsculas:
new produto()também cria umProduto. Mesmo assim, escreva sempre igual à declaração: com o autoload, o nome vira nome de arquivo, e aí a diferença importa. - Costume: uma classe por arquivo, com o arquivo chamado igual à classe (
Produto.php). Nesta aula deixamos tudo num arquivo só para facilitar; você vai separar em include e require e Autoload. - Uma classe só pode ser declarada uma vez. Declarar duas vezes dá
Fatal error: Cannot redeclare class Produto(veja Quando dá errado).
Vários objetos do mesmo molde
Cada new cria um objeto novo e independente. Vamos criar três e olhar dentro de cada um com var_dump (que você conheceu em var_dump e print_r):
<?php
class Produto
{
public string $nome = "";
public float $preco = 0;
}
$caneca = new Produto();
$caneca->nome = "Caneca";
$caneca->preco = 25.90;
$camiseta = new Produto();
$camiseta->nome = "Camiseta";
$camiseta->preco = 49.90;
$vazio = new Produto();
var_dump($caneca);
var_dump($camiseta);
var_dump($vazio);
$ php varios.php
object(Produto)#1 (2) {
["nome"]=>
string(6) "Caneca"
["preco"]=>
float(25.9)
}
object(Produto)#2 (2) {
["nome"]=>
string(8) "Camiseta"
["preco"]=>
float(49.9)
}
object(Produto)#3 (2) {
["nome"]=>
string(0) ""
["preco"]=>
float(0) « o 0 que a Ana escreveu virou 0.0, porque a propriedade é float
}
Lendo o var_dump de um objeto
- object(Produto)
- É um objeto, e a classe dele é
Produto. - #1
- O identificador do objeto: um número que o PHP dá a cada objeto criado, como a senha de atendimento num banco. A caneca é o objeto 1, a camiseta o 2, o vazio o 3. Guarde esse número: ele explica a próxima seção.
- (2)
- Quantas propriedades o objeto tem.
- ["nome"]=> string(6) "Caneca"
- Cada propriedade, com o tipo e o valor, igual ao
var_dumpde um array.
O $vazio mostra uma coisa importante: os valores escritos na classe (= "", = 0) são os valores iniciais de todo objeto novo. O molde já vem "pré-preenchido".
Objetos num array
Como objetos são valores, você pode guardá-los num array e percorrer com foreach. Esse é o jeito mais comum de trabalhar com "muitos produtos":
<?php
class Produto
{
public string $nome = "";
public float $preco = 0;
}
$produtos = [];
foreach (["Caneca" => 25.90, "Camiseta" => 49.90, "Adesivo" => 3.50] as $nome => $preco) {
$p = new Produto();
$p->nome = $nome;
$p->preco = $preco;
$produtos[] = $p;
}
$total = 0;
foreach ($produtos as $produto) {
echo "- {$produto->nome}: R$ " . number_format($produto->preco, 2, ",", ".") . "\n";
$total += $produto->preco;
}
echo "Total: R$ " . number_format($total, 2, ",", ".") . "\n";
echo count($produtos) . " produtos\n";
$ php lista.php - Caneca: R$ 25,90 - Camiseta: R$ 49,90 - Adesivo: R$ 3,50 Total: R$ 79,30 3 produtos
Dois detalhes novos: $produtos[] = $p; põe o objeto no fim do array, e "{$produto->nome}" mostra uma propriedade dentro de uma string com aspas duplas. As chaves { } são recomendadas em volta de $objeto->propriedade na interpolação; com métodos ({$produto->descricao()}) elas são obrigatórias.
Experimente: rode e confira o total. Depois acrescente um produto ("Chaveiro" => 12.00) na lista do primeiro foreach e rode de novo: o total e a contagem se atualizam sozinhos. Tente também um var_dump($produtos); no final para ver os objetos #1, #2… dentro do array.
<?php
class Produto
{
public string $nome = "";
public float $preco = 0;
}
$produtos = [];
foreach (["Caneca" => 25.90, "Camiseta" => 49.90, "Adesivo" => 3.50] as $nome => $preco) {
$p = new Produto();
$p->nome = $nome;
$p->preco = $preco;
$produtos[] = $p;
}
$total = 0;
foreach ($produtos as $produto) {
echo "- {$produto->nome}: R$ " . number_format($produto->preco, 2, ",", ".") . "\n";
$total += $produto->preco;
}
echo "Total: R$ " . number_format($total, 2, ",", ".") . "\n";
echo count($produtos) . " produtos\n";
Objetos são passados por "identificador"
Com arrays, $b = $a faz uma cópia: mexer em $b não muda $a. Com objetos é diferente, e esta é a regra que mais surpreende quem vem dos arrays. Veja:
<?php
class Produto
{
public string $nome = "";
public float $preco = 0;
}
$caneca = new Produto();
$caneca->nome = "Caneca";
$caneca->preco = 25.90;
$outra = $caneca; // NÃO cria um segundo produto
$outra->preco = 19.90;
echo "caneca: " . $caneca->preco . "\n";
echo "outra: " . $outra->preco . "\n";
var_dump($caneca, $outra);
$ php identificador.php
caneca: 19.9 « mudou a $outra, e a $caneca mudou junto!
outra: 19.9
object(Produto)#1 (2) { « o mesmo #1 nas duas
["nome"]=>
string(6) "Caneca"
["preco"]=>
float(19.9)
}
object(Produto)#1 (2) {
["nome"]=>
string(6) "Caneca"
["preco"]=>
float(19.9)
}
O var_dump entrega o segredo: as duas variáveis mostram o mesmo objeto #1. A variável $caneca não guarda o objeto em si; ela guarda o identificador dele, como uma etiqueta com o número da senha. $outra = $caneca copia a etiqueta, não o produto. Existe uma caneca só, com duas etiquetas apontando para ela.
| Array | Objeto | |
|---|---|---|
$b = $a; | Copia o array inteiro | Copia só o identificador: as duas variáveis usam o mesmo objeto |
Mudar $b muda $a? | Não | Sim (é o mesmo objeto) |
| Passar para uma função | A função recebe uma cópia | A função mexe no objeto original |
| Quero uma cópia de verdade | Já é cópia | Use clone (veja readonly e Property Hooks) |
O mesmo vale para funções
Quando você passa um objeto para uma função, ela recebe o identificador e mexe no objeto original. Compare com um array:
<?php
class Produto
{
public string $nome = "";
public float $preco = 0;
}
function aplicarDesconto(Produto $produto, float $percentual): void
{
$produto->preco = $produto->preco * (1 - $percentual / 100);
}
function aplicarDescontoArray(array $produto, float $percentual): void
{
$produto["preco"] = $produto["preco"] * (1 - $percentual / 100);
}
$caneca = new Produto();
$caneca->nome = "Caneca";
$caneca->preco = 20.00;
aplicarDesconto($caneca, 10);
echo "Objeto: " . $caneca->preco . "\n";
$xicara = ["nome" => "Xícara", "preco" => 20.00];
aplicarDescontoArray($xicara, 10);
echo "Array: " . $xicara["preco"] . "\n";
$ php funcao.php Objeto: 18 « a função mudou a caneca de verdade Array: 20 « a função mudou só a cópia dela
Repare no parâmetro Produto $produto: o nome de uma classe pode ser usado como tipo, igual a int ou array (veja Tipos e Retorno). Se alguém passar outra coisa, o PHP recusa com um TypeError.
Identificador não é referência
Se você já viu o & (referência), pode achar que é a mesma coisa. Não é. A etiqueta pode ser trocada por outra sem afetar a primeira:
<?php
class Produto
{
public string $nome = "";
}
$a = new Produto();
$a->nome = "Caneca";
$b = $a; // $b guarda o mesmo identificador (#1)
$b = new Produto(); // agora $b aponta para um objeto novo (#2)
$b->nome = "Camiseta";
echo $a->nome . "\n";
echo $b->nome . "\n";
$ php nao-referencia.php Caneca « $a continua com o objeto #1, intacto Camiseta
Resumindo: mexer dentro do objeto ($b->nome = …) afeta todos que têm a mesma etiqueta; trocar a etiqueta ($b = new Produto()) só muda aquela variável.
Missão: a Ana quer criar a "camiseta em promoção" a partir da camiseta normal, mas o código abaixo estraga o preço da original. Rode e veja. Depois troque $promo = $camiseta; por $promo = clone $camiseta; (clone = "faça uma cópia do objeto") e rode de novo: a original deve continuar Camiseta: R$ 49.9 e a promoção deve sair Camiseta: R$ 39.92.
<?php
class Produto
{
public string $nome = "";
public float $preco = 0;
}
$camiseta = new Produto();
$camiseta->nome = "Camiseta";
$camiseta->preco = 49.90;
$promo = $camiseta;
$promo->preco = $promo->preco * 0.8;
echo "{$camiseta->nome}: R$ {$camiseta->preco}\n";
echo "{$promo->nome}: R$ {$promo->preco}\n";
<?php
class Produto
{
public string $nome = "";
public float $preco = 0;
}
$camiseta = new Produto();
$camiseta->nome = "Camiseta";
$camiseta->preco = 49.90;
$promo = clone $camiseta;
$promo->preco = $promo->preco * 0.8;
echo "{$camiseta->nome}: R$ {$camiseta->preco}\n";
echo "{$promo->nome}: R$ {$promo->preco}\n";
Comparar objetos: == e ===
Os dois operadores de comparação têm um sentido especial com objetos:
<?php
class Produto
{
public string $nome = "";
public float $preco = 0;
}
$a = new Produto();
$a->nome = "Caneca";
$b = new Produto();
$b->nome = "Caneca";
$c = $a;
var_dump($a == $b);
var_dump($a === $b);
var_dump($a === $c);
$ php comparar.php bool(true) « == : mesma classe e mesmos valores bool(false) « === : são objetos diferentes (#1 e #2) bool(true) « === : $c é o mesmo objeto que $a
| Operador | Com objetos, é verdade quando… |
|---|---|
== | Os dois são da mesma classe e todas as propriedades têm valores iguais ("parecidos"). |
=== | Os dois são o mesmo objeto (o mesmo #número). "É a mesma caneca", não "é uma caneca igual". |
De que classe é este objeto? instanceof e ::class
O operador instanceof ("é instância de") responde true ou false: este valor é um objeto desta classe?
<?php
class Produto
{
public string $nome = "";
}
class Cliente
{
public string $nome = "";
}
$caneca = new Produto();
$ana = new Cliente();
$lista = ["nome" => "Caneca"];
var_dump($caneca instanceof Produto);
var_dump($ana instanceof Produto);
var_dump($lista instanceof Produto);
echo get_class($caneca) . "\n";
echo $ana::class . "\n";
echo Produto::class . "\n";
$ php instanceof.php bool(true) « a caneca é um Produto bool(false) « a Ana é uma Cliente, não um Produto (mesmo tendo $nome) bool(false) « um array nunca é instância de classe nenhuma Produto Cliente Produto
- $x instanceof Produto
truese$xé um objeto da classeProduto(ou de uma classe filha dela, como você vai ver em Herança). Nunca dá erro: com um número, texto ou array, simplesmente dáfalse.- get_class($obj)
- Função que devolve o nome da classe do objeto, como texto.
- $obj::class
- O mesmo que
get_class, numa escrita mais curta (PHP 8.0+). - Produto::class
- O nome da classe como texto, sem precisar de objeto. Útil para não digitar o nome entre aspas (o editor confere se a classe existe).
Na prática, instanceof aparece antes de usar algo que pode ou não ser um objeto: if ($item instanceof Produto) { … }.
new sem parênteses extras PHP 8.4+
Às vezes você quer criar um objeto só para chamar um método dele, uma vez. Antes do PHP 8.4, era obrigatório pôr o new entre parênteses. Do 8.4 em diante, não precisa mais:
<?php
class Saudacao
{
public string $loja = "Lojinha da Ana";
public function ola(): string
{
return "Bem-vindo à {$this->loja}!";
}
}
echo (new Saudacao())->ola() . "\n"; // jeito antigo (funciona em qualquer PHP 8)
echo new Saudacao()->ola() . "\n"; // PHP 8.4+: sem os parênteses extras
echo new Saudacao()->loja . "\n";
$ php sem-parenteses.php Bem-vindo à Lojinha da Ana! Bem-vindo à Lojinha da Ana! Lojinha da Ana
A regra do 8.4 tem uma condição: os parênteses depois do nome da classe são obrigatórios. new Saudacao()->ola() funciona; new Saudacao->ola() não (veja o erro em Quando dá errado). Se o seu código precisa rodar em PHP 8.3 ou mais antigo, use a forma com parênteses em volta: (new Saudacao())->ola().
Outras formas de criar objetos
<?php
$p = new stdClass();
$p->nome = "Caneca";
$p->preco = 25.90;
var_dump($p);
$q = (object) ["nome" => "Camiseta", "preco" => 49.90];
echo $q->nome . "\n";
class Produto { public string $nome = "Adesivo"; }
$classe = "Produto";
$r = new $classe();
echo $r->nome . "\n";
$s = new produto();
echo get_class($s) . "\n";
$ php outras.php
object(stdClass)#1 (2) {
["nome"]=>
string(6) "Caneca"
["preco"]=>
float(25.9)
}
Camiseta
Adesivo
Produto « new produto() criou um Produto: nome de classe ignora maiúsculas
| Forma | O que é | Quando usar |
|---|---|---|
new Produto() | Objeto de uma classe que você declarou | Quase sempre. O formato e os tipos são garantidos. |
new stdClass() | Objeto "genérico" do PHP, sem propriedades fixas (aceita qualquer uma) | Raramente: é um array com setas. Você vai encontrá-lo ao ler JSON com json_decode sem o true. |
(object) [ … ] | Converte um array associativo num stdClass | Para dados rápidos, sem regras. Prefira uma classe. |
new $classe() | O nome da classe vem de uma variável | Código que escolhe a classe na hora (frameworks, fábricas). Cuidado: nunca use texto vindo do usuário. |
Quando dá errado
<?php
class Produto
{
public string $nome = "";
}
$caneca = new Produtto();
$ php nao-existe.php
Fatal error: Uncaught Error: Class "Produtto" not found in /home/ana/loja/nao-existe.php:6
Stack trace:
#0 {main}
thrown in /home/ana/loja/nao-existe.php on line 6
O que significa: "classe Produtto não encontrada". O nome está errado (dois t), ou a classe está em outro arquivo que não foi incluído.
Como consertar: confira a grafia. Se a classe mora em outro arquivo, inclua-o com require (ou configure o autoload).
<?php
class Produto
{
public string $nome = "";
public function descricao(): string { return $this->nome; }
}
$caneca = null;
echo $caneca->descricao();
$ php nulo.php
Fatal error: Uncaught Error: Call to a member function descricao() on null in /home/ana/loja/nulo.php:8
Stack trace:
#0 {main}
thrown in /home/ana/loja/nulo.php on line 8
O que significa: "chamada do método descricao() em null". A variável não tem objeto nenhum: ela é null. Acontece muito quando uma função de busca não acha nada e devolve null, ou quando você esqueceu o new.
Como consertar: garanta que a variável recebeu um objeto. Se ela pode mesmo ser null, confira antes (if ($caneca !== null)) ou use o operador nullsafe ?->: $caneca?->descricao() devolve null em vez de dar erro (veja ?? e ?->).
<?php
class Produto
{
public string $nome = "Caneca";
}
$caneca = new Produto();
echo $caneca.nome;
$ php ponto.php
Fatal error: Uncaught Error: Undefined constant "nome" in /home/ana/loja/ponto.php:7
Stack trace:
#0 {main}
thrown in /home/ana/loja/ponto.php on line 7
O que significa: quem vem do JavaScript ou do Java escreve objeto.propriedade. No PHP, o ponto é a concatenação: o PHP leu "junte $caneca com a constante nome", e essa constante não existe.
Como consertar: no PHP, é a seta: $caneca->nome.
$ a mais depois da seta
<?php
class Produto
{
public string $nome = "Caneca";
}
$caneca = new Produto();
echo $caneca->$nome;
$ php cifrao.php Warning: Undefined variable $nome in /home/ana/loja/cifrao.php on line 7 Warning: Undefined property: Produto::$ in /home/ana/loja/cifrao.php on line 7
O que significa: com $ depois da seta, o PHP entende "use o valor da variável $nome como nome da propriedade". Como não existe variável $nome, vem o primeiro aviso; o nome da propriedade fica vazio, e vem o segundo (Produto::$, sem nome nenhum).
Como consertar: depois da seta, sem $: $caneca->nome. (O $ só aparece na declaração, dentro da classe: public string $nome.)
<?php
class Produto
{
public string $nome = "Caneca";
}
$caneca = new Produto();
echo $caneca;
$ php echo-obj.php
Fatal error: Uncaught Error: Object of class Produto could not be converted to string in /home/ana/loja/echo-obj.php:7
Stack trace:
#0 {main}
thrown in /home/ana/loja/echo-obj.php on line 7
O que significa: o echo só sabe mostrar texto e números. Um objeto inteiro não "vira texto" sozinho.
Como consertar: mostre uma propriedade (echo $caneca->nome;), use var_dump($caneca) para depurar, ou ensine o objeto a virar texto com o método __toString (veja Métodos Mágicos).
new sem parênteses e sem os parênteses da classe
<?php
class Produto { public function nome(): string { return "Caneca"; } }
echo new Produto()->nome(), "\n";
echo new Produto->nome(), "\n";
$ php a.php Parse error: syntax error, unexpected token "->", expecting "," or ";" in /home/ana/loja/a.php on line 4
O que significa: um erro de sintaxe (Parse error) impede o arquivo inteiro de rodar, por isso nem a linha 3, que está certa, apareceu. Sem os () depois de Produto, o PHP não aceita a seta logo em seguida.
Como consertar: new Produto()->nome() (PHP 8.4+) ou (new Produto())->nome() (qualquer versão).
<?php
class Produto {}
class Produto {}
$ php dois-iguais.php
Fatal error: Cannot redeclare class Produto (previously declared in /home/ana/loja/dois-iguais.php:2) in /home/ana/loja/dois-iguais.php on line 3
Stack trace:
#0 {main}
O que significa: "não é possível declarar de novo a classe Produto (já declarada na linha 2)". Na vida real, isso costuma acontecer quando o mesmo arquivo de classe é incluído duas vezes com require.
Como consertar: declare cada classe uma vez só e inclua o arquivo com require_once (veja include e require) ou use o autoload.
Referência
| Escrita | O que faz | Exemplo |
|---|---|---|
class Nome { } | Declara uma classe | class Produto { } |
new Nome() | Cria um objeto | $c = new Produto(); |
$obj->prop | Lê/altera uma propriedade | $c->preco = 9.9; |
$obj->metodo() | Chama um método | $c->descricao() |
new Nome()->metodo() PHP 8.4+ | Cria e já usa | new Saudacao()->ola() |
$a = $b (objetos) | As duas variáveis passam a usar o mesmo objeto | - |
clone $obj | Cria uma cópia independente | $promo = clone $camiseta; |
== / === | Mesmos valores / mesmo objeto | $a === $c |
instanceof | É objeto desta classe? | $c instanceof Produto |
get_class($obj), $obj::class | Nome da classe do objeto | "Produto" |
Nome::class | Nome da classe como texto | Produto::class |
var_dump($obj) | Mostra classe, #id e propriedades | object(Produto)#1 (2) {…} |
Missão final
Missão: a lojinha agora guarda clientes. Declare uma classe Cliente com as propriedades nome e cidade (texto, começando vazias). Crie dois clientes: Ana, de Recife, e Bruno, de Natal. Guarde os dois no array $clientes e use o foreach que já está pronto. A saída deve ser Ana mora em Recife, Bruno mora em Natal e 2 clientes.
<?php
// declare a classe Cliente aqui
// crie os dois clientes e coloque no array
$clientes = [];
foreach ($clientes as $cliente) {
echo "{$cliente->nome} mora em {$cliente->cidade}\n";
}
echo count($clientes) . " clientes\n";
<?php
class Cliente
{
public string $nome = "";
public string $cidade = "";
}
$ana = new Cliente();
$ana->nome = "Ana";
$ana->cidade = "Recife";
$bruno = new Cliente();
$bruno->nome = "Bruno";
$bruno->cidade = "Natal";
$clientes = [$ana, $bruno];
foreach ($clientes as $cliente) {
echo "{$cliente->nome} mora em {$cliente->cidade}\n";
}
echo count($clientes) . " clientes\n";
Exercícios
1. Nome da classe
Seguindo a convenção, complete com o nome da classe para "item do pedido":
2. A seta
Complete para mudar o preço do objeto $caneca para 19.90:
3. Cópia de verdade
Complete para $copia ser um objeto independente de $caneca:
4. Depois de $b = $a; $b->nome = "X"; (onde $a é um objeto), quanto vale $a->nome?
5. $a e $b foram criados com dois new Produto() e têm os mesmos valores. O que var_dump($a == $b, $a === $b) mostra?
6. Qual linha funciona no PHP 8.5?
7. O que ["nome" => "Caneca"] instanceof Produto devolve?
Resumo
class Produto { … }declara o molde; nomes em PascalCase, uma classe por arquivo.new Produto()cria um objeto; cadanewé um objeto novo, com número próprio (#1,#2… novar_dump).- A seta
->acessa propriedades e métodos, sem$depois dela. O ponto é concatenação. - Variáveis guardam o identificador do objeto:
$b = $ae passar para funções compartilham o mesmo objeto. Para copiar,clone. ==compara valores;===pergunta "é o mesmo objeto?".instanceofconfere a classe;::classeget_classdão o nome dela.- No PHP 8.4+,
new Produto()->metodo()dispensa os parênteses em volta.