0

Magic methods: __construct, __destruct, __toString, __invoke

Intermediate5 min read·php-07-011
interviewlaravel-src

Concept

PHP's magic methods are predefined method names with special semantics — the engine calls them automatically in response to specific language events. They allow classes to customize fundamental behaviors: construction, destruction, string conversion, and invocation.

__construct(): Called by new ClassName(). Initializes the object. If a parent class has __construct, the child must explicitly call parent::__construct() — constructors are NOT automatically chained.

__destruct(): Called when the object is about to be garbage collected (when refcount drops to zero or at end of request). Use for cleanup: closing file handles, releasing non-memory resources. Don't rely on it for critical cleanup — throw exceptions from __construct instead, as destructors are called even if the constructor threw.

__toString(): Called when an object is used in a string context (echo $obj, "Hello $obj", explicit (string)$obj). Since PHP 8.0, classes implementing __toString implicitly implement the Stringable interface. Must return a string; throwing from __toString was allowed from PHP 7.4+.

__invoke(): Called when an object is called as a function ($obj(...args)). Makes the object a "callable." Common in middleware, command handlers, and Laravel jobs/listeners that are also callables. Checked by is_callable().

Code Example

php
<?php
declare(strict_types=1);

class TempFile
{
    private string $path;

    public function __construct(string $prefix = 'php')
    {
        $this->path = tempnam(sys_get_temp_dir(), $prefix);
        echo "__construct: created {$this->path}\n";
    }

    public function write(string $data): void
    {
        file_put_contents($this->path, $data);
    }

    public function __toString(): string
    {
        return $this->path;
    }

    public function __destruct()
    {
        if (file_exists($this->path)) {
            unlink($this->path);
            echo "__destruct: deleted {$this->path}\n";
        }
    }
}

$file = new TempFile();
$file->write("hello");
echo "File path: $file\n"; // __toString called
// When $file goes out of scope: __destruct deletes the file

// __invoke — callable object
class Multiplier
{
    public function __construct(private float $factor) {}

    public function __invoke(float $value): float
    {
        return $value * $this->factor;
    }
}
$double  = new Multiplier(2.0);
$taxRate = new Multiplier(1.2);

echo $double(5.0);    // 10.0 — __invoke called
echo $taxRate(100.0); // 120.0

// As callable
$prices  = [10.0, 20.0, 50.0];
$doubled = array_map($double, $prices); // [20.0, 40.0, 100.0]

// Combining — Stringable + callable
class Formatter
{
    public function __construct(private string $template) {}
    public function __invoke(array $data): string
    {
        return preg_replace_callback('/\{(\w+)\}/', fn($m) => $data[$m[1]] ?? '', $this->template);
    }
    public function __toString(): string { return $this->template; }
}
$fmt = new Formatter('Hello, {name}!');
echo $fmt(['name' => 'Codrut']); // "Hello, Codrut!"
echo $fmt;                        // "Hello, {name}!" (template itself)