Na programação orientada a objetos, membros estáticos pertencem à classe em si, e não a instâncias individuais. Eles são compartilhados entre todas as instâncias e podem ser acessados sem criar um objeto. Nesta aula, exploraremos propriedades e métodos static, o uso de self vs static (late static binding), constantes de classe e o padrão Singleton, que utiliza membros estáticos para garantir uma única instância.

Esses conceitos são fundamentais para escrever código mais eficiente e organizado, especialmente quando você precisa de valores globais ou comportamentos que não dependem de estado de objeto.

Propriedades e métodos static

Propriedades e métodos declarados como static podem ser acessados diretamente pela classe, sem instância. Propriedades estáticas são úteis para armazenar valores compartilhados, como contadores ou configurações. Métodos estáticos são frequentemente usados para funções utilitárias ou fábricas.

Para acessar um membro estático dentro da classe, use self::; fora da classe, use NomeDaClasse::. Exemplo:

<?php
class Math {
    public static float $pi = 3.14159;

    public static function circleArea(float $radius): float {
        return self::$pi * $radius * $radius;
    }
}

echo Math::$pi; // 3.14159
echo Math::circleArea(5); // 78.53975
?>

self vs static (late static binding)

Quando você usa self:: em um método estático, ele se refere à classe onde o método foi definido, independentemente da herança. Já static:: usa late static binding: ele resolve o nome da classe no momento da execução, permitindo que subclasses substituam membros estáticos.

Isso é útil para polimorfismo com métodos estáticos. Exemplo:

<?php
class ParentClass {
    public static function who() {
        echo __CLASS__;
    }

    public static function testSelf() {
        self::who();
    }

    public static function testStatic() {
        static::who();
    }
}

class ChildClass extends ParentClass {
    public static function who() {
        echo __CLASS__;
    }
}

ChildClass::testSelf();   // ParentClass
ChildClass::testStatic(); // ChildClass
?>

No exemplo, self::who() chama o método de ParentClass porque foi definido lá, enquanto static::who() respeita a classe que chamou (ChildClass).

Constantes de classe

Constantes de classe são valores imutáveis associados à classe. Elas são declaradas com const e, por padrão, são públicas. Diferente de propriedades estáticas, constantes não têm o cifrão e não podem ser alteradas após a definição. São acessadas com self:: ou NomeDaClasse::.

Exemplo:

<?php
class Config {
    const APP_NAME = 'Meu App';
    const VERSION = '1.0.0';

    public static function getAppInfo(): string {
        return self::APP_NAME . ' v' . self::VERSION;
    }
}

echo Config::APP_NAME; // Meu App
echo Config::getAppInfo(); // Meu App v1.0.0
?>

Constantes são úteis para valores fixos como nomes, versões, limites, etc. A partir do PHP 8.1, constantes de classe podem ser tipadas e ter visibilidade (public, protected, private).

Singleton

O padrão Singleton garante que uma classe tenha apenas uma instância e fornece um ponto global de acesso a ela. Geralmente usa um método estático getInstance() que cria a instância na primeira chamada e a retorna nas chamadas subsequentes. O construtor é privado para evitar criação externa.

Exemplo:

<?php
class Database {
    private static ?Database $instance = null;
    private function __construct() {}
    private function __clone() {}
    public function __wakeup() {
        throw new \Exception("Cannot unserialize singleton");
    }

    public static function getInstance(): Database {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function query(string $sql): string {
        return "Executing: $sql";
    }
}

$db1 = Database::getInstance();
$db2 = Database::getInstance();
var_dump($db1 === $db2); // true
?>

O Singleton é útil para conexões de banco de dados, logs, configurações globais, etc. Mas deve ser usado com moderação, pois pode dificultar testes e criar acoplamento.

Boas práticas e observações

Evite usar membros estáticos em excesso, pois eles podem tornar o código menos testável e mais acoplado. Prefira injeção de dependência quando possível. Use constantes para valores fixos e imutáveis. Lembre-se de que métodos estáticos não podem acessar membros não estáticos diretamente.

Referências

Exercícios

  1. Crie uma classe Counter com uma propriedade estática $count e um método estático increment() que aumenta o contador. Crie também um método getCount() para retornar o valor. Teste chamando Counter::increment() várias vezes e exiba o resultado.

    ✓ Resposta:
    <?php
    class Counter {
        private static int $count = 0;
    
        public static function increment(): void {
            self::$count++;
        }
    
        public static function getCount(): int {
            return self::$count;
        }
    }
    
    Counter::increment();
    Counter::increment();
    Counter::increment();
    echo Counter::getCount(); // 3
    ?>
  2. Explique a diferença entre self:: e static:: em herança. Dê um exemplo onde o uso de static:: é necessário para obter o comportamento polimórfico esperado.

    ✓ Resposta:

    self:: refere-se à classe onde o método foi definido, enquanto static:: resolve a classe chamadora em tempo de execução (late static binding). Exemplo:

    <?php
    class Animal {
        public static function who() { echo 'Animal'; }
        public static function testSelf() { self::who(); }
        public static function testStatic() { static::who(); }
    }
    class Cachorro extends Animal {
        public static function who() { echo 'Cachorro'; }
    }
    
    Cachorro::testSelf();   // Animal
    Cachorro::testStatic(); // Cachorro
    ?>
  3. Crie uma classe MathConstants com constantes PI e E (valor de Euler). Em seguida, crie um método estático que retorna a soma dessas constantes. Use a classe para exibir o resultado.

    ✓ Resposta:
    <?php
    class MathConstants {
        const PI = 3.14159;
        const E = 2.71828;
    
        public static function sum(): float {
            return self::PI + self::E;
        }
    }
    
    echo MathConstants::sum(); // 5.85987
    ?>
  4. Implemente um Singleton para uma classe Logger que possui um método log(string $message) que imprime a mensagem com um timestamp. Garanta que apenas uma instância exista e que ela não possa ser clonada ou desserializada.

    ✓ Resposta:
    <?php
    class Logger {
        private static ?Logger $instance = null;
        private function __construct() {}
        private function __clone() {}
        public function __wakeup() {
            throw new \Exception("Cannot unserialize singleton");
        }
    
        public static function getInstance(): Logger {
            if (self::$instance === null) {
                self::$instance = new self();
            }
            return self::$instance;
        }
    
        public function log(string $message): void {
            echo "[" . date('Y-m-d H:i:s') . "] $message\n";
        }
    }
    
    $logger = Logger::getInstance();
    $logger->log("Teste de log");
    ?>
  5. Considere a classe ParentClass com um método estático whoAmI() que retorna o nome da classe usando static::class. Crie uma subclasse ChildClass que não sobrescreve o método. Chame ChildClass::whoAmI() e explique o resultado.

    ✓ Resposta:
    <?php
    class ParentClass {
        public static function whoAmI(): string {
            return static::class;
        }
    }
    class ChildClass extends ParentClass {}
    
    echo ChildClass::whoAmI(); // ChildClass
    ?>

    O resultado é 'ChildClass' porque static::class usa late static binding e retorna o nome da classe chamadora (ChildClass), mesmo que o método não tenha sido sobrescrito.