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
// 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>";
?>
2. Resolução de conflitos entre traços
Quando várias características possuem métodos com o mesmo nome:
<?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
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']));
?>
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
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
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
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
?>
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
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
}
?>
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
usevários traits, mas só pode estender uma classe abstrata.
P: Quando a distinção entre
self::estatic::realmente importa? R: Quando você escreve métodos estáticos que serão herdados. Por exemplo, a classe baseModelde 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
- As características resolvem a limitação da herança única, permitindo que classes não relacionadas compartilhem código
use Trait1, Trait2 { ... }resolve conflitos cominsteadof+asself::$propacessa propriedades/métodos estáticos (que pertencem à classe e são compartilhados por todas as instâncias)self::é vinculado em tempo de compilação (aponta para onde está definido),static::é vinculado em tempo de execução (aponta para o chamador)- As constantes de classe
public const KEY = valuesão melhores do que as globaisdefine()
📝 Exercícios
- Escreva uma característica
Loggablecom um métodolog($message)(que adiciona automaticamente um carimbo de data e hora). Crie duas classes não relacionadas,UsereProduct, e apliqueusea elas. - Escreva uma classe pai
Database; usestatic::para implementar um métodotable(), de modo que as subclassesUsereOrderretornemuserseorders, respectivamente. Escreva um método estáticoall()que retorneSELECT * FROM [table]. - Crie uma classe
AppConfige use constantes de classe para definir APP_NAME, MAX_UPLOAD_SIZE e DEFAULT_LANGUAGE. Escreva um método estáticodisplay()que exiba todos os valores de configuração.



