PHP Magic Methods

PHP fires special "magic" methods when certain events happen - construction, property access, method calls, string conversion. Master all 14 and when to use each.

Intermediate 10 min read 11 examples

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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
<?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
<?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) }
Magic = harder to read

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.

Next Steps

Frequently Asked Questions

Usually no. Explicit properties with explicit getters/setters give better performance, IDE autocomplete, static analysis, and type safety. Use __get/__set only for dynamic data containers (config bags, ORM rows).

Yes - PHP 8.0+ enforces specific signatures. __toString(): string, __isset($name): bool, __call($name, $args): mixed. Violating these is a fatal error.

__call handles undefined method calls ($obj->missing()). __invoke makes the object itself callable ($obj()). Different mechanisms for different scenarios.

Yes - measurably. __get calls add ~20% overhead vs a regular property read. Fine for occasional use, painful in tight loops. If perf matters, use explicit properties.

No - PHP's default clone is shallow. Nested object properties stay referenced. Implement __clone to deep-clone them: $this->nested = clone $this->nested;