What are Magic Methods?
Magic methods are special class methods (always prefixed with __) that PHP calls automatically in response to specific events. You implement them - PHP triggers them.
__construct & __destruct
<?php
class Connection {
public function __construct(public string $dsn) {
echo "Connecting to $dsn\n";
}
public function __destruct() {
echo "Disconnecting {$this->dsn}\n";
}
}
$c = new Connection("mysql:host=localhost");
// __destruct fires when last reference is dropped (end of scope, unset, etc)
__get & __set
Intercept access to undefined or inaccessible properties. Great for dynamic property bags:
<?php
class Config {
private array $data = [];
public function __get(string $name): mixed {
return $this->data[$name] ?? null;
}
public function __set(string $name, mixed $value): void {
$this->data[$name] = $value;
}
}
$config = new Config();
$config->host = "localhost";
$config->port = 3306;
echo $config->host; // "localhost"
echo $config->missing; // null (no notice)
__isset & __unset
<?php
class Config {
private array $data = [];
public function __isset(string $name): bool {
return isset($this->data[$name]);
}
public function __unset(string $name): void {
unset($this->data[$name]);
}
}
// Now isset($config->host) and unset($config->host) work
__call & __callStatic
Intercept calls to undefined methods. Used by fluent query builders and proxies:
<?php
class Query {
private array $wheres = [];
public function __call(string $name, array $args): self {
// whereName("Alice") -> name = Alice
if (str_starts_with($name, "where")) {
$field = lcfirst(substr($name, 5));
$this->wheres[$field] = $args[0];
}
return $this;
}
public function __callStatic(string $name, array $args): self {
return (new self())->$name(...$args);
}
}
$q = Query::whereName("Alice")->whereAge(30);
__toString
Define what happens when your object is used as a string:
<?php
class Money implements Stringable {
public function __construct(
public readonly int $cents,
public readonly string $currency,
) {}
public function __toString(): string {
return number_format($this->cents / 100, 2) . " " . $this->currency;
}
}
$m = new Money(1999, "USD");
echo $m; // "19.99 USD"
echo "Price: $m"; // works in interpolation
$str = (string) $m; // explicit cast
__invoke
Makes the object itself callable like a function:
<?php
class Multiplier {
public function __construct(private int $factor) {}
public function __invoke(int $n): int {
return $n * $this->factor;
}
}
$double = new Multiplier(2);
$triple = new Multiplier(3);
echo $double(5); // 10
echo $triple(5); // 15
// Works with array_map etc.
$out = array_map($double, [1, 2, 3]); // [2, 4, 6]
__clone
Customize what happens during clone $obj (default is a shallow copy):
<?php
class Order {
public Address $shipTo;
public function __clone(): void {
// Deep-clone nested object so copies don't share state
$this->shipTo = clone $this->shipTo;
}
}
__serialize & __unserialize (PHP 7.4+)
<?php
class User {
public function __construct(
public string $name,
private string $password, // dont serialize secrets
) {}
public function __serialize(): array {
return ["name" => $this->name]; // omit password
}
public function __unserialize(array $data): void {
$this->name = $data["name"];
$this->password = "";
}
}
$s = serialize(new User("Ruban", "secret"));
// no "secret" in $s
__debugInfo
Customize what var_dump() shows - useful to hide noisy fields:
<?php
class HttpClient {
private string $apiKey;
public string $host;
private array $hugeCache = [];
public function __debugInfo(): array {
return ["host" => $this->host, "cacheSize" => count($this->hugeCache)];
}
}
var_dump(new HttpClient());
// object(HttpClient)#1 (2) { ["host"]=> string(...), ["cacheSize"]=> int(0) }
Every magic method makes your class less predictable. Use them when the use case genuinely justifies it (ORMs, query builders, dynamic config), not as a shortcut to avoid writing explicit getters.