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.

Você vai aprender
  • A diferença entre algo "de cada objeto" e algo "da classe".
  • Propriedades static, com um contador de instâncias.
  • Métodos static, chamados com Classe::metodo(), e métodos "fábrica".
  • self:: e a ideia do static:: (late static binding).
  • Constantes de classe com const, e as constantes tipadas do PHP 8.3.
  • Quando static ajuda 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çãoO caderno de cada alunoO quadro da sala
Quantas cópiasUma por objetoUma só, compartilhada
Dentro da classe$this->estoqueself::$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 static vem 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?

RecursoMuda?AcessoQuando usar
const X = … na classeNuncaLoja::XValores fixos que pertencem a um assunto (frete, limites, nomes)
static $xSimLoja::$xEstado compartilhado que muda (contadores, cache)
define("X", …) / const X soltoNuncaXConfigurações globais do projeto todo
EnumNuncaStatus::PagoUma lista fechada de opções (status de pedido)

Quando usar (e quando não usar) static

Bons usos
  • 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.
Cuidado com o excesso

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

Non-static method … cannot be called statically
<?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.

Using $this when not in object context (dentro de um método 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.

Acessar uma propriedade estática com a seta
<?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.)

Esquecer o 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++;.

Undefined constant / Cannot access private constant
<?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

EscritaO 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::NOMELê 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:

echo (9.9);

3. Constante

Complete para ler a constante FRETE da classe Loja, de fora dela:

echo ;

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::$prop e self::metodo(); fora: Classe::$prop e Classe::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 com Classe::NOME. Tipadas desde o 8.3: public const float FRETE = 15.0;.
  • Use static com moderação: propriedade estática pública é quase uma variável global.