Métodos mágicos e enums

O PHP possui um conjunto de “métodos mágicos” que começam com __ — você nunca os chama explicitamente; o PHP os aciona automaticamente em momentos específicos. Eles fazem com que os objetos se comportem de maneira mais inteligente, assim como os tipos embutidos. Adicione as enums do PHP 8.1 a essa combinação, e esta lição levará suas habilidades em PHP a um novo patamar.

1. __toString() — Convertendo um objeto em uma string

PHP
<?php
class User {
    public function __construct(
        public string $name,
        public string $email
    ) {}
    
    // Automatically called when the object is echoed
    public function __toString(): string {
        return "{$this->name} <{$this->email}>";
    }
}

$user = new User("John", "john@example.com");
echo $user;  // John <john@example.com>
// No need to write $user->name—just echo the object directly
?>
💡 Dica: __toString() permite definir “como esse objeto deve ser representado como uma string”. Use-o para registro em log, saída de depuração e renderização de modelos.


2. __get() e __set() — Acesso a propriedades inexistentes

PHP
<?php
class UserModel {
    private array $data = [];
    private array $changes = [];
    
    // Called when reading a non-existent property
    public function __get(string $name): mixed {
        return $this->data[$name] ?? null;
    }
    
    // Called when assigning to a non-existent property
    public function __set(string $name, mixed $value): void {
        $this->changes[$name] = true;
        $this->data[$name] = $value;
    }
    
    public function getChanges(): array {
        return array_keys($this->changes);
    }
}

$user = new UserModel();
$user->name = "John";         // Triggers __set()
$user->email = "j@j.com";     // Triggers __set()
echo $user->name;             // John — triggers __get()
echo $user->name;             // John
print_r($user->getChanges()); // ['name', 'email'] — track which fields were modified
?>

(1) __isset() e __unset()

PHP
<?php
class Config {
    private array $data = ['app_name' => 'MyApp'];
    
    public function __isset(string $name): bool {
        return isset($this->data[$name]);
    }
    
    public function __unset(string $name): void {
        unset($this->data[$name]);
    }
}

$c = new Config();
var_dump(isset($c->app_name));  // true
var_dump(isset($c->missing));   // false
unset($c->app_name);
var_dump(isset($c->app_name));  // false
?>

3. __call() — Chamada de métodos inexistentes

▶ Exemplo: Construtor de consultas dinâmicas

PHP
<?php
class QueryBuilder {
    private array $where = [];
    private ?string $orderBy = null;
    
    // Triggered when calling a non-existent method
    public function __call(string $name, array $arguments): static {
        if (str_starts_with($name, 'whereBy')) {
            // whereByName("John") → WHERE name = "John"
            $field = lcfirst(substr($name, 7));
            $this->where[] = "{$field} = '{$arguments[0]}'";
        } elseif ($name === 'orderByDesc') {
            $this->orderBy = "{$arguments[0]} DESC";
        }
        return $this;
    }
    
    public function toSQL(): string {
        $sql = "SELECT * FROM table";
        if ($this->where) {
            $sql .= " WHERE " . implode(" AND ", $this->where);
        }
        if ($this->orderBy) {
            $sql .= " ORDER BY {$this->orderBy}";
        }
        return $sql;
    }
}

$q = new QueryBuilder();
$q->whereByName("John")
  ->whereByStatus("active")
  ->orderByDesc("created_at");

echo $q->toSQL();
// SELECT * FROM table WHERE name = 'John' AND status = 'active' ORDER BY created_at DESC
?>
▶ Experimente
💡 Dica: Frameworks como o Eloquent do Laravel fazem uso intenso de __call() para implementar métodos dinâmicos. Métodos como whereByName(), que parecem existir, mas na verdade não estão definidos, funcionam graças a ele.


4. __clone() — Quando um objeto é clonado

PHP
<?php
class ShoppingCart {
    private array $items = [];
    
    public function __construct(
        public string $owner
    ) {}
    
    public function addItem(string $name, int $qty): void {
        $this->items[] = compact('name', 'qty');
    }
    
    public function getItems(): array {
        return $this->items;
    }
    
    // Automatically called when cloned
    public function __clone(): void {
        $this->owner = "Copy of {$this->owner}";
        // The items array is shallow-copied by default, but you can do deep copies here
    }
}

$cart1 = new ShoppingCart("John");
$cart1->addItem("PHP Tutorial", 2);

$cart2 = clone $cart1;
echo $cart2->owner;  // Copy of John
// items are also copied
print_r($cart2->getItems()); // Has the PHP Tutorial item
?>

5. __sleep() e __wakeup() — Serialização

PHP
<?php
class DatabaseConnection {
    private $connection;
    
    public function __construct(
        private string $dsn,
        private string $user,
        private string $pass
    ) {
        $this->connect();
    }
    
    private function connect(): void {
        $this->connection = "connected({$this->dsn})";
    }
    
    // Called on serialize(): return which property names to serialize
    public function __sleep(): array {
        // Don't serialize $connection (resource types can't be serialized)
        return ['dsn', 'user', 'pass'];
    }
    
    // Called on unserialize(): re-establish the connection
    public function __wakeup(): void {
        $this->connect();
    }
}
?>

6. Enums (PHP 8.1)

Escrever "pending" "approved" "rejected" nas funções pode gerar erros e dificulta o rastreamento. As enums são suas melhores aliadas:

PHP
<?php
enum OrderStatus: string {
    case PENDING   = 'pending';
    case APPROVED  = 'approved';
    case SHIPPED   = 'shipped';
    case DELIVERED = 'delivered';
    case CANCELLED = 'cancelled';
}

// Using enums
function updateOrder(int $id, OrderStatus $status): void {
    echo "Order {$id} updated to {$status->value}<br>";
}

updateOrder(1, OrderStatus::APPROVED);  // ✅
// updateOrder(1, "approved");          // ❌ TypeError!
// updateOrder(1, "approvved");         // ❌ Hard to even typo this
?>

▶ Exemplo: Enums + match — A dupla poderosa

PHP
<?php
enum UserRole: string {
    case ADMIN  = 'admin';
    case EDITOR = 'editor';
    case MEMBER = 'member';
    case GUEST  = 'guest';
    
    // Enum methods
    public function label(): string {
        return match($this) {
            self::ADMIN  => 'Administrator',
            self::EDITOR => 'Editor',
            self::MEMBER => 'Member',
            self::GUEST  => 'Guest',
        };
    }
    
    public function canEdit(): bool {
        return match($this) {
            self::ADMIN, self::EDITOR => true,
            default => false,
        };
    }
}

$role = UserRole::ADMIN;
echo $role->label();  // Administrator
echo $role->canEdit() ? 'Can edit' : 'Cannot edit';  // Can edit
echo $role->value;    // admin
?>
▶ Experimente

(1) Métodos de enumeração

PHP
<?php
// UserRole::cases() — get all enum values
print_r(array_map(fn($r) => $r->value, UserRole::cases()));
// ['admin', 'editor', 'member', 'guest']

// UserRole::from() — create enum from value (throws exception on mismatch)
$role = UserRole::from('admin');   // UserRole::ADMIN
// $role = UserRole::from('super'); // ValueError

// UserRole::tryFrom() — safe creation (returns null on mismatch)
$role = UserRole::tryFrom('super'); // null
?>
💡 Dica: PHP 8.1+: use enums sempre que seus dados tiverem um conjunto fixo de valores possíveis. OrderStatus::class tem autocompletar no IDE — "pending" não tem. Essa é a diferença.

❓ Perguntas Frequentes

P: __get e __set prejudicam o desempenho? R: Há uma pequena sobrecarga, já que cada acesso passa por uma chamada de função. Para operações com grandes volumes de dados (milhões de registros), propriedades públicas/privadas explícitas são mais rápidas. Para cenários flexíveis, como ORMs/modelos, __get/__set é perfeito.

P: Quando devo usar enums em vez de constantes de classe? R: PHP 8.1+: prefira enums — elas oferecem segurança de tipos (passar o valor errado gera um erro), autocompletar e verificação de exaustividade compatível com match. As constantes de classe são mais indicadas para “apenas um conjunto de valores de configuração relacionados”.

P: As classes enum podem definir métodos? R: Com certeza! Os exemplos label() e canEdit() acima comprovam isso. As enums elevam o nível do seu código, passando da “passagem de strings” para a “passagem de tipos com comportamento”.

📖 Resumo

📝 Exercícios

  1. Escreva uma classe DynamicConfig que utilize __get/__set/__isset/__unset para permitir que você “opere em um array de configuração como se ele tivesse propriedades”.
  2. Crie uma enumeração OrderStatus (Pendente → Pago → Enviado → Entregue → Cancelado). Atribua a cada status um método canTransitionTo(OrderStatus $target) que defina as transições de estado permitidas.
  3. Escreva uma classe ORM simples que utilize __call() para implementar métodos de consulta dinâmica, como whereByFieldName($value).
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%