Magic Methods
PHP reserves method names beginning with two underscores as 'magic methods' — hooks the engine calls automatically in response to specific events on an object, rather than being called explicitly by name. __construct() and __destruct() are the most familiar examples, firing on object creation and garbage collection. Beyond those, PHP defines magic methods for intercepting property access on undefined or inaccessible properties (__get, __set, __isset, __unset), calling undefined or inaccessible methods (__call, __callStatic), converting an object to a string (__toString), controlling serialization (__sleep, __wakeup, __serialize, __unserialize), cloning (__clone), and invoking an object as if it were a function (__invoke). Used well, magic methods enable elegant APIs like fluent builders and transparent proxies; used carelessly, they make code behave unpredictably and are notoriously hard to debug.
Cricket analogy: A stadium's automatic floodlight system triggers itself the instant the umpire signals bad light, without anyone flipping a switch by name — just as PHP's magic methods fire automatically on events like object creation, not by explicit call.
Property interception: __get, __set, __isset, __unset
__get(string $name) and __set(string $name, $value) fire only when code accesses a property that is either undeclared or declared private/protected from outside the class's visibility scope — they never intercept normal access to a public, declared property. __isset($name) backs isset()/empty() checks on those same inaccessible properties, and __unset($name) backs unset(). This quartet is commonly used to implement virtual properties, lazy-loaded attributes, or to log/validate access to a data structure.
Cricket analogy: A player's private training diary is off-limits to teammates by default, but a team manager checking in via an authorized liaison (like __get) can request a peek without directly opening the diary themselves, since direct access to a protected page is denied.
<?php
declare(strict_types=1);
final class Config
{
private array $data = [];
public function __get(string $name): mixed
{
return $this->data[$name] ?? throw new \OutOfBoundsException("Unknown config key: $name");
}
public function __set(string $name, mixed $value): void
{
$this->data[$name] = $value;
}
public function __isset(string $name): bool
{
return isset($this->data[$name]);
}
}
$config = new Config();
$config->timeout = 30; // triggers __set
echo $config->timeout; // triggers __get -> 30
var_dump(isset($config->retries)); // triggers __isset -> false__call, __toString, and __invoke
__call(string $name, array $arguments) intercepts calls to inaccessible or non-existent instance methods, and its static twin __callStatic does the same for static calls — both are the backbone of fluent query builders and mock objects that need to respond to arbitrary method names. __toString() lets an object be used anywhere a string is expected, such as string interpolation or echo, and must return a string. __invoke(...$args) lets an object be called directly like $obj(...), which is useful for building single-purpose callable objects that carry configuration as instance state.
Cricket analogy: A commentary team's flexible 'ask the analyst anything' segment intercepts any question thrown at it and generates a relevant answer on the fly, much like __call intercepting arbitrary method names to build a fluent response, rather than having a fixed script for every possible question.
<?php
declare(strict_types=1);
final class Money
{
public function __construct(private readonly int $cents, private readonly string $currency = 'USD') {}
public function __toString(): string
{
return sprintf('%.2f %s', $this->cents / 100, $this->currency);
}
}
final class Multiplier
{
public function __construct(private readonly int $factor) {}
public function __invoke(int $value): int
{
return $value * $this->factor;
}
}
echo "Total: " . new Money(4599); // Total: 45.99 USD (via __toString)
$double = new Multiplier(2);
echo $double(21); // 42 (via __invoke)__toString() is what allows an exception object to be printed directly, and it's why PHP lets you do echo $someObject; for value objects like Money or DateTime-like wrappers without calling an explicit ->format() or ->toString() method every time.
__get/__set and __call are easy to overuse. Because they intercept *any* undefined property or method name, typos silently succeed instead of triggering a clear 'undefined property' error — a call to $config->timout (misspelled) may quietly return null or throw a vague exception deep inside __get rather than an immediate, obvious PHP error. Prefer explicit, declared, typed properties and methods whenever the full set of names is known ahead of time.
- Magic methods are invoked automatically by PHP in response to object lifecycle or access events, not called directly by name.
__get/__set/__isset/__unsetonly fire for undefined or inaccessible properties, never for normal public property access.__call/__callStaticintercept calls to undefined or inaccessible methods and are the basis of fluent builders and proxies.__toString()must return a string and allows objects to be used in string contexts likeechoand interpolation.__invoke()lets an object be called as a function, useful for encapsulating configured, reusable callables.- Overusing magic methods trades explicitness for flexibility and can mask typos and bugs — use them deliberately, not by default.
Practice what you learned
1. When does `__get()` get invoked on an object?
2. What must `__toString()` return?
3. Which magic method allows an object instance to be used with function-call syntax like `$obj(1, 2)`?
4. What is the primary risk of relying heavily on `__call()` for a class's public API?
5. Which magic method pair specifically backs `isset()` and `unset()` behaviour for inaccessible properties?
Was this page helpful?
You May Also Like
Classes and Objects
Explore PHP's object-oriented foundations: declaring classes, constructing objects, typed properties, visibility, and constructor promotion.
Traits in PHP
Traits let you share method implementations across unrelated classes without using inheritance, solving PHP's single-inheritance limitation for horizontal code reuse.
Abstract Classes and Polymorphism
Abstract classes define a partial implementation and a contract that subclasses must complete, enabling polymorphic code that treats different concrete types uniformly.
Working with JSON in PHP
Learn how to encode PHP data to JSON and decode JSON back into PHP values using json_encode and json_decode, plus common pitfalls and error handling.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics