Traços e membros estáticos

O PHP possui duas funcionalidades que a maioria das outras linguagens populares não possui (ou não projeta da mesma forma): traits e ligação estática tardia. Esta lição apresenta essas duas ferramentas poderosas e exclusivas do PHP para que você escreva um código mais elegante.

1. Traits — A magia da reutilização de código no PHP

O PHP possui herança única — uma classe só pode ter um pai. Mas, às vezes, classes não relacionadas precisam compartilhar o mesmo trecho de código (como “registrar carimbos de data e hora” ou “gerar IDs exclusivos”). Os traits surgiram exatamente para isso.

PHP
<?php
// Define a Trait
trait HasTimestamp {
    private string $createdAt;
    private string $updatedAt;
    
    public function touch(): void {
        $now = date("Y-m-d H:i:s");
        if (!isset($this->createdAt)) {
            $this->createdAt = $now;
        }
        $this->updatedAt = $now;
    }
    
    public function getCreatedAt(): string {
        return $this->createdAt ?? 'Unknown';
    }
    
    public function getUpdatedAt(): string {
        return $this->updatedAt ?? 'Unknown';
    }
}

trait HasUuid {
    public function generateUuid(): string {
        return uniqid() . bin2hex(random_bytes(8));
    }
}

// Use Traits in completely different classes
class User {
    use HasTimestamp, HasUuid;  // Use multiple Traits
    
    public function __construct(
        public string $name
    ) {
        $this->touch();
    }
}

class Article {
    use HasTimestamp;
    
    public function __construct(
        public string $title,
        public string $content
    ) {
        $this->touch();
    }
}

$user = new User("John");
echo $user->name . " created at " . $user->getCreatedAt() . "<br>";
echo "UUID: " . $user->generateUuid() . "<br>";

$article = new Article("PHP Traits Tutorial", "Main content...");
echo $article->title . " last updated at " . $article->getUpdatedAt() . "<br>";
?>
💡 Dica: Traits x Herança: use Traits quando várias classes não relacionadas (User ≠ Article) precisarem compartilhar a mesma lógica. Se as classes filhas tiverem uma relação “é um” (Dog é um Animal), use herança.


2. Resolução de conflitos entre traços

Quando várias características possuem métodos com o mesmo nome:

PHP
<?php
trait Logger {
    public function log(string $msg): void {
        echo "[LOG] {$msg}<br>";
    }
}

trait FileLogger {
    public function log(string $msg): void {
        echo "[FILE] {$msg}<br>";
    }
}

class App {
    use Logger, FileLogger {
        // Replace Logger's log with FileLogger's log
        FileLogger::log insteadof Logger;
        
        // Give the replaced method an alias so it's still callable
        Logger::log as logSimple;
    }
}

$app = new App();
$app->log("App started");      // [FILE] App started
$app->logSimple("App started"); // [LOG] App started
?>

▶ Exemplo: Uma coleção prática de características

PHP
<?php
trait Arrayable {
    public function toArray(): array {
        return get_object_vars($this);
    }
    
    public function toJson(): string {
        return json_encode($this->toArray(), JSON_UNESCAPED_UNICODE);
    }
}

trait Validatable {
    public function validate(array $rules): array {
        $errors = [];
        foreach ($rules as $field => $rule) {
            $value = $this->$field ?? null;
            if (str_contains($rule, 'required') && empty($value)) {
                $errors[$field] = "{$field} is required";
            }
        }
        return $errors;
    }
}

class Product {
    use Arrayable, Validatable;
    
    public function __construct(
        public string $name,
        public float $price,
        public string $description = '',
    ) {}
}

$p = new Product("Mechanical Keyboard", 299);
echo $p->toJson() . "<br>";
print_r($p->validate(['name' => 'required', 'price' => 'required']));
?>
▶ Experimente

3. Propriedades e métodos estáticos

Os membros estáticos pertencem à própria classe, e não às instâncias. Todos os objetos compartilham a mesma propriedade estática:

PHP
<?php
class Counter {
    private static int $count = 0;
    
    public function __construct() {
        self::$count++;  // self:: accesses static members
    }
    
    public static function getCount(): int {
        return self::$count;
    }
}

echo Counter::getCount();  // 0 (call with ClassName::method)
$a = new Counter();
$b = new Counter();
$c = new Counter();
echo Counter::getCount();  // 3 (all instances share a single $count)
?>
Propriedade de instância $this-> Propriedade estática self::$
Pertence a Cada objeto A própria classe
Memória Uma por objeto Uma cópia global
Acesso $obj->prop Class::$prop

4. self:: vs static:: (Ligação estática tardia)

Este é um dos conceitos mais sutis — e que mais geram confusão — do PHP:

PHP
<?php
class ParentClass {
    public static function who(): string {
        return 'Parent';
    }
    
    public static function test(): string {
        return self::who();   // self:: always points to the defining class (compile-time binding)
    }
    
    public static function testLate(): string {
        return static::who(); // static:: points to the actual calling class (runtime binding)
    }
}

class ChildClass extends ParentClass {
    public static function who(): string {
        return 'Child';
    }
}

echo ChildClass::test();     // "Parent"  ← self:: bound to ParentClass
echo ChildClass::testLate(); // "Child"   ← static:: bound to ChildClass
?>
self:: static::
Tempo de ligação Tempo de compilação Tempo de execução
Aponta para A classe em que o método está definido A classe que, de fato, chama o método
Ideal para Métodos de utilidade, constantes Métodos estáticos que as subclasses devem sobrescrever

▶ Exemplo: Ligação estática tardia na prática — Um ORM simples

PHP
<?php
abstract class Model {
    // static:: lets subclasses return their own table name
    public static function table(): string {
        // Default: class name → snake_case → plural
        return strtolower(static::class) . 's';
    }
    
    public static function find(int $id): string {
        return "SELECT * FROM " . static::table() . " WHERE id = {$id}";
    }
}

class User extends Model {}
class Product extends Model {
    public static function table(): string {
        return 'products';  // Custom table name
    }
}

echo User::find(1);     // SELECT * FROM users WHERE id = 1
echo Product::find(5);  // SELECT * FROM products WHERE id = 5
// static::table() returns each subclass's own table name
?>
▶ Experimente

5. Constantes de classe (const)

As constantes de classe não variam entre objetos — elas são perfeitas para valores de configuração e convenções:

PHP
<?php
class HttpStatus {
    public const OK = 200;
    public const NOT_FOUND = 404;
    public const INTERNAL_ERROR = 500;
    
    public static function getMessage(int $code): string {
        return match($code) {
            self::OK            => 'OK',
            self::NOT_FOUND     => 'Not Found',
            self::INTERNAL_ERROR => 'Internal Server Error',
            default             => 'Unknown',
        };
    }
}

echo HttpStatus::OK;            // 200
echo HttpStatus::NOT_FOUND;     // 404
echo HttpStatus::getMessage(404); // Not Found

// PHP 8.1+ final const
class Config {
    final public const APP_NAME = 'MyBlog';
    // Subclasses can't override final constants
}
?>
💡 Dica: As constantes de classe são melhores do que define() — elas oferecem namespacing, autocompletar e não poluem o escopo global. Se um grupo de valores pertencer ao mesmo conceito (códigos de status HTTP, funções de usuário), organize-os usando constantes de classe.

❓ Perguntas Frequentes

P: Como faço para escolher entre um trait e uma classe abstrata? R: Classes não relacionadas que compartilham código → trait. Relação “é um” + propriedades compartilhadas → classe abstrata. Uma classe pode use vários traits, mas só pode estender uma classe abstrata.

P: Quando a distinção entre self:: e static:: realmente importa? R: Quando você escreve métodos estáticos que serão herdados. Por exemplo, a classe base Model de um framework precisa saber “qual subclasse específica está chamando esse método estático”. Para classes de utilidade (métodos estáticos que nunca são herdados), self:: é suficiente.

P: Os traits podem definir propriedades? E constantes? R: Os traits podem definir propriedades e métodos, mas não constantes (o PHP ainda não oferece suporte a isso). Vários traits não podem definir propriedades com o mesmo nome — isso gera um erro fatal.

📖 Resumo

📝 Exercícios

  1. Escreva uma característica Loggable com um método log($message) (que adiciona automaticamente um carimbo de data e hora). Crie duas classes não relacionadas, User e Product, e aplique use a elas.
  2. Escreva uma classe pai Database; use static:: para implementar um método table(), de modo que as subclasses User e Order retornem users e orders, respectivamente. Escreva um método estático all() que retorne SELECT * FROM [table].
  3. Crie uma classe AppConfig e use constantes de classe para definir APP_NAME, MAX_UPLOAD_SIZE e DEFAULT_LANGUAGE. Escreva um método estático display() que exiba todos os valores de configuração.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%