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.

Você vai aprender
  • A sintaxe de class e as regras para dar nome a uma classe.
  • Criar objetos com new e usar a seta ->.
  • Criar vários objetos e guardá-los num array.
  • Ler o var_dump de um objeto (o que é aquele #1).
  • Por que $outra = $caneca não cria um segundo produto (objetos são passados por identificador).
  • Comparar objetos (== e ===), conferir o tipo com instanceof e ::class.
  • O new sem 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 tipo string, 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 propriedade nome. Repare: sem o $ antes de nome.
$ 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ço funciona; 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 um Produto. 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_dump de 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.

ArrayObjeto
$b = $a;Copia o array inteiroCopia só o identificador: as duas variáveis usam o mesmo objeto
Mudar $b muda $a?NãoSim (é o mesmo objeto)
Passar para uma funçãoA função recebe uma cópiaA função mexe no objeto original
Quero uma cópia de verdadeJá é cópiaUse 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
OperadorCom 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
true se $x é um objeto da classe Produto (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
FormaO que éQuando usar
new Produto()Objeto de uma classe que você declarouQuase 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 stdClassPara dados rápidos, sem regras. Prefira uma classe.
new $classe()O nome da classe vem de uma variávelCódigo que escolhe a classe na hora (frameworks, fábricas). Cuidado: nunca use texto vindo do usuário.

Quando dá errado

Class "…" not found
<?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).

Call to a member function … on null
<?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 ?->).

Usar ponto em vez de seta
<?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.

Um $ 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.)

Object of class … could not be converted to string
<?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).

Cannot redeclare class
<?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

EscritaO que fazExemplo
class Nome { }Declara uma classeclass Produto { }
new Nome()Cria um objeto$c = new Produto();
$obj->propLê/altera uma propriedade$c->preco = 9.9;
$obj->metodo()Chama um método$c->descricao()
new Nome()->metodo() PHP 8.4+Cria e já usanew Saudacao()->ola()
$a = $b (objetos)As duas variáveis passam a usar o mesmo objeto-
clone $objCria uma cópia independente$promo = clone $camiseta;
== / ===Mesmos valores / mesmo objeto$a === $c
instanceofÉ objeto desta classe?$c instanceof Produto
get_class($obj), $obj::classNome da classe do objeto"Produto"
Nome::classNome da classe como textoProduto::class
var_dump($obj)Mostra classe, #id e propriedadesobject(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":

class { }

2. A seta

Complete para mudar o preço do objeto $caneca para 19.90:

$caneca;

3. Cópia de verdade

Complete para $copia ser um objeto independente de $caneca:

$copia = ;

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; cada new é um objeto novo, com número próprio (#1, #2… no var_dump).
  • A seta -> acessa propriedades e métodos, sem $ depois dela. O ponto é concatenação.
  • Variáveis guardam o identificador do objeto: $b = $a e passar para funções compartilham o mesmo objeto. Para copiar, clone.
  • == compara valores; === pergunta "é o mesmo objeto?".
  • instanceof confere a classe; ::class e get_class dão o nome dela.
  • No PHP 8.4+, new Produto()->metodo() dispensa os parênteses em volta.