Métodos mágicos
Nesta aula, exploramos os métodos mágicos do PHP: __get, __set, __call, __toString e __invoke, que permitem interceptar ações em objetos. Aprendemos como implementá-los, seus usos práticos e os cuidados necessários para evitar problemas de desempenho e manutenção.
Os métodos mágicos no PHP são métodos especiais que começam com dois underscores (__) e são automaticamente invocados em determinadas situações. Eles permitem que você defina comportamentos personalizados para operações como acesso a propriedades inacessíveis, chamada de métodos inexistentes, conversão para string e uso de objetos como funções. Esses recursos são poderosos, mas exigem cuidado para não comprometer a clareza e o desempenho do código.
Nesta aula, vamos nos aprofundar nos métodos mágicos mais comuns: __get, __set, __call, __toString e __invoke. Cada um será explicado com exemplos práticos, mostrando como e quando utilizá-los. Também discutiremos boas práticas e armadilhas a evitar.
__get, __set, __call
Os métodos __get e __set são invocados quando você tenta ler ou escrever em uma propriedade inacessível (protegida, privada ou inexistente) de um objeto. Eles permitem implementar acesso dinâmico a dados, como em objetos que encapsulam arrays ou usam lazy loading. Já o método __call é chamado quando um método inacessível é invocado, possibilitando a criação de métodos dinâmicos ou a delegação de chamadas.
Exemplo prático: uma classe que armazena dados em um array interno e permite acesso via propriedades dinâmicas.
class DynamicProperties {
private $data = [];
public function __get($name) {
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
return null;
}
public function __set($name, $value) {
$this->data[$name] = $value;
}
public function __call($name, $arguments) {
if (method_exists($this, $name)) {
return call_user_func_array([$this, $name], $arguments);
}
// Exemplo: se o método começar com 'get', retorna propriedade
if (strpos($name, 'get') === 0) {
$prop = lcfirst(substr($name, 3));
return $this->__get($prop);
}
throw new \BadMethodCallException("Method $name not found");
}
}
$obj = new DynamicProperties();
$obj->name = 'João';
echo $obj->name; // João
$obj->setName('Maria'); // Chamada dinâmica? Na verdade, __call não é invocado se o método existe.
// Para testar __call, chame um método inexistente:
echo $obj->getName(); // 'Maria' (usando __call)
Note que __call só é acionado para métodos que não existem ou não são acessíveis. No exemplo, getName não existe, então __call o trata como um getter dinâmico.
__toString
O método __toString permite que um objeto seja tratado como string, sendo invocado automaticamente em contextos como echo, print ou concatenação. Ele deve sempre retornar uma string. É útil para fornecer uma representação legível do objeto, como em logs ou depuração.
Exemplo:
class User {
public $name;
public $email;
public function __construct($name, $email) {
$this->name = $name;
$this->email = $email;
}
public function __toString() {
return "User: {$this->name} ({$this->email})";
}
}
$user = new User('Ana', 'ana@example.com');
echo $user; // User: Ana (ana@example.com)
É importante lembrar que a partir do PHP 7.4, você pode declarar o tipo de retorno como string para garantir que o método sempre retorne uma string.
__invoke
O método __invoke permite que um objeto seja chamado como uma função. Quando você coloca parênteses após o objeto, o PHP executa este método. Isso é útil para criar objetos que se comportam como callables, como closures ou funções de callback.
Exemplo:
class Adder {
private $amount;
public function __construct($amount) {
$this->amount = $amount;
}
public function __invoke($value) {
return $value + $this->amount;
}
}
$addFive = new Adder(5);
echo $addFive(10); // 15
// Pode ser usado como callback:
$numbers = [1, 2, 3];
$result = array_map($addFive, $numbers);
print_r($result); // [6, 7, 8]
Isso é especialmente útil em padrões como Strategy ou quando você precisa passar comportamento como argumento.
Cuidados
Embora os métodos mágicos sejam flexíveis, seu uso excessivo pode tornar o código difícil de entender e depurar. Eles podem esconder erros (como propriedades ou métodos que não existem) e impactar o desempenho, pois são mais lentos que acessos diretos. Além disso, podem quebrar a introspecção de IDEs e ferramentas de análise estática.
Recomendações:
- Use métodos mágicos apenas quando necessário, como em frameworks ou bibliotecas que precisam de flexibilidade.
- Documente bem o comportamento dinâmico.
- Considere alternativas como
ArrayAccesspara acesso a propriedades dinâmicas. - Evite
__callpara implementar proxies ou delegates; prefira técnicas mais explícitas. - Não abuse de
__gete__setpara criar propriedades públicas dinâmicas; isso pode prejudicar a manutenção.
Lembre-se: poder não significa que você deve usar. Sempre avalie se a simplicidade e clareza não são mais importantes.
Boas Práticas
Ao implementar métodos mágicos, siga estas boas práticas:
- Declare os métodos com visibilidade pública, pois eles são chamados externamente.
- Respeite as assinaturas:
__get(string $name): mixed,__set(string $name, mixed $value): void,__call(string $name, array $arguments): mixed,__toString(): string,__invoke(mixed ...$args): mixed. - Para
__toString, sempre retorne uma string; caso contrário, o PHP emitirá um erro fatal. - Use type hints sempre que possível para melhorar a previsibilidade.
- Evite efeitos colaterais em
__toString, pois pode ser chamado em contextos inesperados (como depuração).
Referências
- PHP Manual: Métodos Mágicos
- PHP Manual: Sobrecarga
- PHP Manual: __toString
- PHP Manual: __invoke
- PSR-1: Basic Coding Standard
Exercícios
Crie uma classe
Configque armazena configurações em um array privado e usa__gete__setpara acessar e modificar propriedades dinamicamente. Implemente também__toStringpara retornar uma string JSON das configurações.✓ Resposta:class Config { private $settings = []; public function __get($name) { return $this->settings[$name] ?? null; } public function __set($name, $value) { $this->settings[$name] = $value; } public function __toString() { return json_encode($this->settings); } } $config = new Config(); $config->host = 'localhost'; $config->port = 3306; echo $config; // {"host":"localhost","port":3306}Implemente uma classe
Calculatorque usa__callpara tratar métodos comoadd,subtract, etc. Cada método deve receber dois números e retornar o resultado. Se o método não for reconhecido, lance uma exceção.✓ Resposta:class Calculator { public function __call($name, $arguments) { if (count($arguments) != 2) { throw new \InvalidArgumentException('Two arguments required'); } switch ($name) { case 'add': return $arguments[0] + $arguments[1]; case 'subtract': return $arguments[0] - $arguments[1]; case 'multiply': return $arguments[0] * $arguments[1]; case 'divide': return $arguments[0] / $arguments[1]; default: throw new \BadMethodCallException("Method $name not supported"); } } } $calc = new Calculator(); echo $calc->add(5, 3); // 8 echo $calc->divide(10, 2); // 5Crie uma classe
Multiplierque recebe um fator no construtor e usa__invokepara multiplicar o argumento por esse fator. Use-a comarray_mappara dobrar uma lista de números.✓ Resposta:class Multiplier { private $factor; public function __construct($factor) { $this->factor = $factor; } public function __invoke($value) { return $value * $this->factor; } } $double = new Multiplier(2); $numbers = [1, 2, 3, 4]; $result = array_map($double, $numbers); print_r($result); // [2, 4, 6, 8]Explique por que o uso excessivo de métodos mágicos pode ser prejudicial. Dê pelo menos três razões.
✓ Resposta:- Dificulta a depuração: erros podem ser mascarados, pois acessos a propriedades ou métodos inexistentes não geram erros imediatos.
- Impacto no desempenho: os métodos mágicos são mais lentos que chamadas diretas, pois exigem resolução dinâmica.
- Quebra de ferramentas: IDEs e analisadores estáticos perdem a capacidade de autocompletar e verificar tipos, reduzindo a produtividade.
Implemente uma classe
Usercom propriedades privadasnameeemail, e um método__toStringque retorna o nome e email formatados. Crie um objeto e exiba-o comecho.✓ Resposta:class User { private $name; private $email; public function __construct($name, $email) { $this->name = $name; $this->email = $email; } public function __toString() { return "{$this->name} <{$this->email}>"; } } $user = new User('Carlos', 'carlos@example.com'); echo $user; // Carlos