Magic methods: __call, __callStatic (method overloading)
Concept
Method overloading magic methods intercept calls to inaccessible methods — methods that don't exist, or exist but are private/protected and called from outside. They enable dynamic dispatch and proxy patterns.
__call($name, $args): Called when an undefined or inaccessible instance method is called. $args is an array of all passed arguments. Returns whatever value the method call should evaluate to.
__callStatic($name, $args): Same, but for static method calls (ClassName::undefinedMethod(...)).
Laravel Facades use __callStatic to forward static calls to the underlying service container instance. When you call Cache::get('key'), it triggers __callStatic('get', ['key']) on the Cache facade, which resolves the CacheInterface implementation from the container and calls get on it.
Macros in Laravel also use __call: any class using the Macroable trait registers user-defined callables by name, and __call / __callStatic look up and invoke them. This lets packages add methods to Laravel's core classes without modifying their source.
Proxy pattern: A class wrapping another class can forward all method calls to the wrapped object using __call with call_user_func_array, transparently proxying behavior (adding logging, timing, auth checks) without knowing the wrapped class's full interface.
Code Example
<?php
declare(strict_types=1);
// Fluent query builder via __call
class QueryBuilder
{
private array $clauses = [];
private string $table = '';
public function table(string $t): static
{
$this->table = $t;
return $this;
}
// Dynamic where methods: whereStatus(), whereName(), whereEmail()...
public function __call(string $name, array $args): static
{
if (str_starts_with($name, 'where')) {
$column = lcfirst(substr($name, 5)); // "whereStatus" → "status"
$this->clauses[] = "$column = ?";
}
return $this;
}
// Static: QueryBuilder::forTable('users')
public static function __callStatic(string $name, array $args): static
{
if (str_starts_with($name, 'for')) {
$table = strtolower(substr($name, 3)); // "forUsers" → "users"
return (new static())->table($table);
}
throw new \BadMethodCallException("Unknown static method: $name");
}
}
$query = QueryBuilder::forUsers() // __callStatic
->whereStatus('active') // __call
->whereName('Alice'); // __call
// Proxy / decorator via __call
class TimingProxy
{
public function __construct(private object $target) {}
public function __call(string $name, array $args): mixed
{
$start = hrtime(true);
$result = $this->target->$name(...$args);
$ns = (hrtime(true) - $start) / 1_000_000;
printf("%s() took %.2fms\n", $name, $ns);
return $result;
}
}
class SlowService
{
public function process(string $data): string
{
usleep(10_000); // simulate work
return strtoupper($data);
}
}
$timed = new TimingProxy(new SlowService());
echo $timed->process('hello'); // "HELLO", prints timing
// Laravel Macroable trait pattern
trait Macroable
{
private static array $macros = [];
public static function macro(string $name, callable $fn): void
{
static::$macros[$name] = $fn;
}
public function __call(string $name, array $args): mixed
{
if (!isset(static::$macros[$name])) {
throw new \BadMethodCallException("Method $name not found");
}
return (static::$macros[$name])(...$args);
}
}