PHP static
Tudo o que você escreveu até agora pertencia a cada objeto: cada produto tem o seu nome, o seu preço, o seu estoque. Mas algumas coisas pertencem à classe inteira: quantos produtos já foram criados, o valor do frete da loja, uma função para formatar dinheiro. Para isso existem os membros static (estáticos) e as constantes de classe.
- A diferença entre algo "de cada objeto" e algo "da classe".
- Propriedades
static, com um contador de instâncias. - Métodos
static, chamados comClasse::metodo(), e métodos "fábrica". self::e a ideia dostatic::(late static binding).- Constantes de classe com
const, e as constantes tipadas do PHP 8.3. - Quando
staticajuda e quando atrapalha.
A ideia: o que é de cada um e o que é de todos
Numa sala de aula, cada aluno tem o seu caderno: o que um escreve não aparece no caderno do outro. Mas o quadro é um só, da sala inteira: o que a professora escreve ali, todos veem. Nas classes:
| Normal (de instância) | static (da classe) | |
|---|---|---|
| Comparação | O caderno de cada aluno | O quadro da sala |
| Quantas cópias | Uma por objeto | Uma só, compartilhada |
| Dentro da classe | $this->estoque | self::$criados |
| Fora da classe | $caneca->estoque (precisa de um objeto) | Produto::$criados (não precisa de objeto) |
Propriedades static: o contador de produtos
<?php
class Produto
{
public static int $criados = 0;
public function __construct(public string $nome)
{
self::$criados++;
}
}
echo "Antes: " . Produto::$criados . "\n";
$caneca = new Produto("Caneca");
$camiseta = new Produto("Camiseta");
$adesivo = new Produto("Adesivo");
echo "Depois: " . Produto::$criados . "\n";
$ php contador.php Antes: 0 « a propriedade existe antes de qualquer objeto Depois: 3 « cada construtor somou 1 no mesmo contador
- public static int $criados = 0;
- Uma propriedade da classe. A palavra
staticvem depois da visibilidade. Existe uma só, não importa quantos objetos sejam criados (nem se nenhum for). - self::$criados++
self("a própria") é a classe onde o código está escrito;::(dois-pontos duplos) acessa o que é estático. Repare: com$depois do::, diferente da seta.- Produto::$criados
- Fora da classe, use o nome dela no lugar de
self.
O :: tem um nome curioso em PHP: Paamayim Nekudotayim ("dois-pontos duplos" em hebraico). Você talvez veja esse nome em mensagens de erro antigas.
Por objeto × por classe, lado a lado
<?php
class Produto
{
public int $vendidos = 0; // um por objeto
public static int $vendasDaLoja = 0; // um só, da classe
public function __construct(public string $nome)
{
}
public function vender(int $quantidade): void
{
$this->vendidos += $quantidade;
self::$vendasDaLoja += $quantidade;
}
}
$caneca = new Produto("Caneca");
$camiseta = new Produto("Camiseta");
$caneca->vender(3);
$camiseta->vender(2);
$caneca->vender(1);
echo "Canecas vendidas: {$caneca->vendidos}\n";
echo "Camisetas vendidas: {$camiseta->vendidos}\n";
echo "Total da loja: " . Produto::$vendasDaLoja . "\n";
$ php compara-static.php Canecas vendidas: 4 « o caderno da caneca: 3 + 1 Camisetas vendidas: 2 « o caderno da camiseta Total da loja: 6 « o quadro: todas as vendas somadas
Missão: cada pedido precisa de um número único, em sequência. Acrescente na classe uma propriedade private static int $proximo = 1; e, no construtor, faça $this->numero = self::$proximo++;. A saída deve ser Pedido #1 - Ana, Pedido #2 - Bruno e Pedido #3 - Carla.
<?php
class Pedido
{
public int $numero = 0;
public function __construct(public string $cliente)
{
// dê o número ao pedido aqui
}
}
$pedidos = [new Pedido("Ana"), new Pedido("Bruno"), new Pedido("Carla")];
foreach ($pedidos as $p) {
echo "Pedido #{$p->numero} - {$p->cliente}\n";
}
<?php
class Pedido
{
private static int $proximo = 1;
public int $numero = 0;
public function __construct(public string $cliente)
{
$this->numero = self::$proximo++;
}
}
$pedidos = [new Pedido("Ana"), new Pedido("Bruno"), new Pedido("Carla")];
foreach ($pedidos as $p) {
echo "Pedido #{$p->numero} - {$p->cliente}\n";
}
Repare que a propriedade estática pode ser private: aí só a classe enxerga o contador, e ninguém de fora consegue "pular" números.
Métodos static
Um método estático pertence à classe e é chamado sem objeto: Classe::metodo(). Ele serve para tarefas que não dependem dos dados de um objeto específico, como formatar dinheiro:
<?php
class Dinheiro
{
public static function formatar(float $valor): string
{
return "R$ " . number_format($valor, 2, ",", ".");
}
public static function somar(float ...$valores): float
{
return round(array_sum($valores), 2);
}
}
echo Dinheiro::formatar(1234.5) . "\n";
$total = Dinheiro::somar(25.90, 49.90, 3.50);
echo "Total: " . Dinheiro::formatar($total) . "\n";
$ php metodo-static.php R$ 1.234,50 Total: R$ 79,30
Ninguém escreveu new Dinheiro(): a classe aqui funciona como uma "caixa de ferramentas" com nome. A regra de ouro: dentro de um método estático não existe $this, porque não há "este objeto" (veja o erro em Quando dá errado).
Métodos fábrica: jeitos diferentes de criar
Um uso muito comum de métodos estáticos é criar objetos a partir de fontes diferentes. Cada jeito ganha um nome que explica de onde vêm os dados (por isso são chamados de "construtores nomeados" ou métodos fábrica):
<?php
class Produto
{
public function __construct(
public string $nome,
public float $preco,
) {
}
public static function deArray(array $dados): self
{
return new self($dados["nome"], (float) $dados["preco"]);
}
public static function deLinhaCsv(string $linha): self
{
[$nome, $preco] = explode(";", $linha);
return new self(trim($nome), (float) str_replace(",", ".", $preco));
}
}
$a = Produto::deArray(["nome" => "Caneca", "preco" => "25.90"]);
$b = Produto::deLinhaCsv("Camiseta; 49,90");
echo "{$a->nome}: {$a->preco}\n";
echo "{$b->nome}: {$b->preco}\n";
$ php fabrica.php Caneca: 25.9 Camiseta: 49.9
- : self
- Tipo de retorno: "devolve um objeto desta classe".
- new self(…)
- Cria um objeto da própria classe (o mesmo que
new Produto(…), sem repetir o nome). - Produto::deArray(…)
- Lê melhor que um
new Produto(…)com um monte de conversões em volta: o nome conta de onde vêm os dados.
Você já usou métodos fábrica do próprio PHP, talvez sem saber: DateTime::createFromFormat(…), em Datas e Horas.
self:: × static:: (só a ideia)
Existe um parente do self::: o static::. A diferença só aparece com herança. self é sempre a classe onde o código foi escrito; static é a classe que foi chamada. Num exemplo rápido (o extends você vai aprender em Herança: ProdutoDigital é uma "filha" de Produto):
<?php
class Produto
{
public static function comSelf(): Produto
{
return new self();
}
public static function comStatic(): static
{
return new static();
}
}
class ProdutoDigital extends Produto
{
}
echo get_class(ProdutoDigital::comSelf()) . "\n";
echo get_class(ProdutoDigital::comStatic()) . "\n";
$ php lsb.php Produto « new self(): a classe onde o método foi escrito ProdutoDigital « new static(): a classe usada na chamada
Esse comportamento se chama late static binding ("ligação estática tardia"): o PHP decide qual classe é static só na hora da chamada. Na prática: em métodos fábrica de classes que terão filhas, prefira new static() e o tipo de retorno : static. Nas outras situações, self:: basta.
Constantes de classe
Uma constante é um valor fixo, que nunca muda enquanto o programa roda (você conheceu as constantes soltas em Constantes). Dentro de uma classe, elas são declaradas com const e acessadas com ::, como os membros estáticos, mas sem $:
<?php
class Loja
{
const NOME = "Lojinha da Ana";
public const float FRETE = 15.0;
public const float FRETE_GRATIS_ACIMA = 100.0;
private const array CUPONS = ["ANA10" => 10, "BEMVINDO" => 5];
public static function frete(float $subtotal): float
{
return $subtotal >= self::FRETE_GRATIS_ACIMA ? 0 : self::FRETE;
}
public static function desconto(string $cupom): int
{
return self::CUPONS[$cupom] ?? 0;
}
}
echo Loja::NOME . "\n";
echo "Frete para R$ 75,80: " . Loja::frete(75.80) . "\n";
echo "Frete para R$ 120,00: " . Loja::frete(120.00) . "\n";
echo "Cupom ANA10: " . Loja::desconto("ANA10") . "%\n";
echo "Cupom XYZ: " . Loja::desconto("XYZ") . "%\n";
$ php constantes.php Lojinha da Ana Frete para R$ 75,80: 15 Frete para R$ 120,00: 0 Cupom ANA10: 10% Cupom XYZ: 0%
- const NOME = "Lojinha da Ana";
- A forma mais simples: sem visibilidade (é
public) e sem tipo. Por convenção, nomes de constante são em MAIÚSCULAS, com_entre as palavras. - public const float FRETE = 15.0;
- Com visibilidade e tipo. O tipo em constantes de classe chegou no PHP 8.3.
- private const array CUPONS
- Constantes também podem ser privadas: só a classe usa a tabela de cupons.
- self::FRETE
- Dentro da classe. Fora:
Loja::FRETE. Com um objeto, também funciona:$loja::NOME.
Constantes tipadas PHP 8.3+
Com o tipo declarado, o PHP confere o valor logo ao ler a classe:
<?php
class Loja
{
public const int PARCELAS_MAXIMAS = "doze";
}
echo Loja::PARCELAS_MAXIMAS;
$ php const-tipo-errado.php
Fatal error: Cannot use string as value for class constant Loja::PARCELAS_MAXIMAS of type int in /home/ana/loja/const-tipo-errado.php on line 4
Stack trace:
#0 {main}
"Não dá para usar uma string como valor da constante Loja::PARCELAS_MAXIMAS, do tipo int". Repare que o echo da linha 6 nem rodou: o erro aparece antes. Em PHP 8.2 ou anterior, escreva const PARCELAS_MAXIMAS = 12;, sem o tipo. O tipo faz mais diferença em interfaces e em classes filhas, que podem redefinir constantes: com tipo, a filha não pode trocar o número por um texto.
Constante, propriedade estática ou define?
| Recurso | Muda? | Acesso | Quando usar |
|---|---|---|---|
const X = … na classe | Nunca | Loja::X | Valores fixos que pertencem a um assunto (frete, limites, nomes) |
static $x | Sim | Loja::$x | Estado compartilhado que muda (contadores, cache) |
define("X", …) / const X solto | Nunca | X | Configurações globais do projeto todo |
| Enum | Nunca | Status::Pago | Uma lista fechada de opções (status de pedido) |
Quando usar (e quando não usar) static
- Constantes da classe (
Loja::FRETE). - Métodos fábrica (
Produto::deArray()). - Funções utilitárias "puras", que só dependem dos parâmetros (
Dinheiro::formatar()). - Contadores e números de sequência simples, como os desta aula.
Uma propriedade estática pública é, na prática, uma variável global com sobrenome: qualquer parte do programa pode mudá-la, e fica difícil saber quem mudou. Ela também dificulta os testes automáticos, porque o valor "vaza" de um teste para o outro. Se você está criando muitas coisas estáticas, provavelmente precisa de objetos normais.
Quando dá errado
<?php
class Produto
{
public function __construct(public string $nome = "Caneca")
{
}
public function descricao(): string
{
return "Produto: " . $this->nome;
}
}
echo Produto::descricao();
$ php nao-static.php
Fatal error: Uncaught Error: Non-static method Produto::descricao() cannot be called statically in /home/ana/loja/nao-static.php:13
Stack trace:
#0 {main}
thrown in /home/ana/loja/nao-static.php on line 13
O que significa: descricao() é um método normal (de objeto): ele precisa de "um produto" para descrever. Chamá-lo pela classe (Produto::) não diz qual produto.
Como consertar: crie um objeto e chame pela seta: (new Produto())->descricao(). Se o método realmente não usa $this, declare-o static.
<?php
class Produto
{
public string $nome = "Caneca";
public static function descricao(): string
{
return "Produto: " . $this->nome;
}
}
echo Produto::descricao();
$ php this-static.php
Fatal error: Uncaught Error: Using $this when not in object context in /home/ana/loja/this-static.php:8
Stack trace:
#0 /home/ana/loja/this-static.php(11): Produto::descricao()
#1 {main}
thrown in /home/ana/loja/this-static.php on line 8
O que significa: o método é estático, então não existe objeto, e $this não existe.
Como consertar: se o método precisa de dados de um objeto, tire o static (e chame com $obj->), ou receba o objeto como parâmetro.
<?php
class Produto
{
public static int $criados = 0;
}
$p = new Produto();
echo $p->criados;
echo "\n";
echo $p::$criados . "\n";
$ php seta-static.php
Notice: Accessing static property Produto::$criados as non static in /home/ana/loja/seta-static.php on line 7
Warning: Undefined property: Produto::$criados in /home/ana/loja/seta-static.php on line 7
« o echo da linha 7 não mostrou nada
0 « $p::$criados funciona
O que significa: $p->criados procura uma propriedade do objeto, e ela não existe: a propriedade é da classe. O PHP avisa (Notice = "observação") e não acha nada.
Como consertar: propriedades estáticas usam :: e $: Produto::$criados (ou $p::$criados). (Métodos estáticos, por outro lado, até funcionam com a seta: $obj->formatar(10); mas use Classe::formatar(10) para deixar claro que é estático.)
self::
<?php
class Produto
{
public static int $criados = 0;
public function __construct()
{
$criados++;
}
}
new Produto();
echo Produto::$criados . "\n";
$ php static-sem-self.php Warning: Undefined variable $criados in /home/ana/loja/static-sem-self.php on line 8 0
O que significa: $criados sozinho é uma variável local do construtor, que não existe. O contador da classe ficou em 0.
Como consertar: self::$criados++;.
<?php
class Loja
{
const NOME = "Lojinha da Ana";
}
$loja = new Loja();
echo $loja::NOME . "\n";
echo Loja::NOMEE . "\n";
$ php const-objeto.php
Lojinha da Ana
Fatal error: Uncaught Error: Undefined constant Loja::NOMEE in /home/ana/loja/const-objeto.php:8
Stack trace:
#0 {main}
thrown in /home/ana/loja/const-objeto.php on line 8
O que significa: o nome da constante está errado (NOMEE). Se a constante existe mas é private, a mensagem é outra: Error: Cannot access private constant Loja::CUPONS. E tentar mudar uma constante (Loja::NOME = "Outra";) nem roda: Parse error: syntax error, unexpected token "=".
Como consertar: confira o nome (maiúsculas importam em constantes) e a visibilidade.
Referência
| Escrita | O que faz |
|---|---|
public static tipo $x = valor; | Propriedade da classe (uma só para todos) |
public static function f() { … } | Método da classe (sem $this) |
self::$x / self::f() | Dentro da classe: membro estático da classe onde o código está |
static::f() / new static() | A classe que foi chamada (late static binding) |
Classe::$x / Classe::f() | Fora da classe |
const NOME = valor; | Constante de classe |
public const tipo NOME = valor; PHP 8.3+ | Constante tipada |
self::NOME / Classe::NOME | Lê a constante (sem $) |
Missão final
Missão: a Ana compra peças de fora. Escreva a classe Cambio com uma constante tipada public const float DOLAR = 5.40; e um método estático paraReais(float $dolares): string que devolve, por exemplo, US$ 10.00 = R$ 54,00 (use number_format($dolares, 2) para o dólar e number_format(…, 2, ",", ".") para o real, e self::DOLAR na conta). A saída deve ter US$ 10.00 = R$ 54,00 e US$ 25.50 = R$ 137,70.
<?php // escreva a classe Cambio aqui echo Cambio::paraReais(10) . "\n"; echo Cambio::paraReais(25.5) . "\n";
<?php
class Cambio
{
public const float DOLAR = 5.40;
public static function paraReais(float $dolares): string
{
$reais = $dolares * self::DOLAR;
return "US$ " . number_format($dolares, 2) . " = R$ " . number_format($reais, 2, ",", ".");
}
}
echo Cambio::paraReais(10) . "\n";
echo Cambio::paraReais(25.5) . "\n";
Exercícios
1. Dentro da classe
Complete para somar 1 à propriedade estática $criados, dentro de um método da própria classe:
2. Fora da classe
Complete para chamar o método estático formatar da classe Dinheiro:
3. Constante
Complete para ler a constante FRETE da classe Loja, de fora dela:
4. Três objetos Produto foram criados. Quantas cópias de public static int $criados existem?
5. O que acontece ao usar $this dentro de um método static chamado com Produto::metodo()?
6. Num método da classe Produto, chamado como ProdutoDigital::criar() (filha de Produto), que classe new static() cria?
7. Qual declaração só funciona do PHP 8.3 em diante?
Resumo
static= da classe, não do objeto: uma cópia só, compartilhada (o quadro da sala).- Dentro da classe:
self::$propeself::metodo(); fora:Classe::$propeClasse::metodo(). Com$para propriedades. - Métodos estáticos não têm
$this. Servem para utilitários e métodos fábrica (Produto::deArray()). selfé a classe onde o código foi escrito;staticé a classe chamada (late static binding).- Constantes de classe:
const NOME = valor;, lidas comClasse::NOME. Tipadas desde o 8.3:public const float FRETE = 15.0;. - Use
staticcom moderação: propriedade estática pública é quase uma variável global.