HardТеория4 min

Магические методы

__get, __set, __call, __toString, __invoke, __clone

Магические методы начинаются с __ и вызываются PHP автоматически при определённых действиях с объектом.

__construct и __destruct

<?php
declare(strict_types=1);

class Connection
{
    private mixed $resource;

    public function __construct(private string $dsn)
    {
        $this->resource = fopen('/tmp/conn.log', 'a');
        fwrite($this->resource, "Connected to {$dsn}\n");
    }

    public function __destruct()
    {
        // Called when object is garbage collected
        if (is_resource($this->resource)) {
            fwrite($this->resource, "Disconnected from {$this->dsn}\n");
            fclose($this->resource);
        }
    }
}

__get, __set, __isset, __unset

<?php
declare(strict_types=1);

class DynamicObject
{
    private array $data = [];

    // Called when accessing undefined/inaccessible property
    public function __get(string $name): mixed
    {
        return $this->data[$name] ?? throw new RuntimeException("Property {$name} not found");
    }

    // Called when setting undefined/inaccessible property
    public function __set(string $name, mixed $value): void
    {
        $this->data[$name] = $value;
    }

    // Called by isset() and empty() on undefined/inaccessible property
    public function __isset(string $name): bool
    {
        return isset($this->data[$name]);
    }

    // Called by unset() on undefined/inaccessible property
    public function __unset(string $name): void
    {
        unset($this->data[$name]);
    }
}

$obj = new DynamicObject();
$obj->name = 'Alice';         // __set
echo $obj->name;              // __get => 'Alice'
echo isset($obj->name);       // __isset => true
unset($obj->name);            // __unset
echo isset($obj->name);       // __isset => false

Ловушка экзамена: __get и __set вызываются ТОЛЬКО для недоступных или несуществующих свойств. Если свойство объявлено как public, магические методы НЕ вызываются. Для protected/private свойств — вызываются при обращении извне.

__call и __callStatic

<?php
declare(strict_types=1);

class QueryBuilder
{
    private array $conditions = [];

    // Called when undefined instance method is called
    public function __call(string $name, array $arguments): static
    {
        if (str_starts_with($name, 'where')) {
            $field = lcfirst(substr($name, 5));
            $this->conditions[$field] = $arguments[0];
            return $this;
        }
        throw new BadMethodCallException("Method {$name} does not exist");
    }

    // Called when undefined STATIC method is called
    public static function __callStatic(string $name, array $arguments): mixed
    {
        if ($name === 'create') {
            return new static();
        }
        throw new BadMethodCallException("Static method {$name} does not exist");
    }

    public function getConditions(): array
    {
        return $this->conditions;
    }
}

$query = QueryBuilder::create()     // __callStatic
    ->whereName('Alice')             // __call
    ->whereAge(25)                   // __call
    ->whereCity('Moscow');           // __call

print_r($query->getConditions());
// ['name' => 'Alice', 'age' => 25, 'city' => 'Moscow']

__toString

<?php
declare(strict_types=1);

class Money
{
    public function __construct(
        private float $amount,
        private string $currency,
    ) {
    }

    public function __toString(): string
    {
        return number_format($this->amount, 2) . ' ' . $this->currency;
    }
}

$price = new Money(42.50, 'USD');
echo $price;              // 42.50 USD
echo "Price: {$price}";   // Price: 42.50 USD
echo (string)$price;      // 42.50 USD

Запомни: __toString() ДОЛЖЕН возвращать string. Без реализации — echo $object вызовет Fatal Error. С PHP 8.0 __toString может выбрасывать исключения.

__invoke

<?php
declare(strict_types=1);

class Multiplier
{
    public function __construct(
        private float $factor,
    ) {
    }

    // Called when object is used as function
    public function __invoke(float $value): float
    {
        return $value * $this->factor;
    }
}

$double = new Multiplier(2);
echo $double(5);     // 10.0
echo $double(3.14);  // 6.28

// Useful in callbacks
$numbers = [1, 2, 3, 4, 5];
$tripled = array_map(new Multiplier(3), $numbers);
// [3, 6, 9, 12, 15]

// Check if callable
var_dump(is_callable($double));  // true

__clone

<?php
declare(strict_types=1);

class Document
{
    private array $sections = [];

    public function __construct(
        private string $title,
        private DateTimeImmutable $createdAt,
    ) {
    }

    public function addSection(object $section): void
    {
        $this->sections[] = $section;
    }

    // Called after clone to customize the copy
    public function __clone(): void
    {
        // Deep clone nested objects
        foreach ($this->sections as $key => $section) {
            $this->sections[$key] = clone $section;
        }
        // Reset creation date for clone
        $this->createdAt = new DateTimeImmutable();
    }
}

__serialize / __unserialize (PHP 7.4+)

<?php
declare(strict_types=1);

class SecureUser
{
    public function __construct(
        private string $name,
        private string $email,
        private string $password,  // Should NOT be serialized
    ) {
    }

    // Modern serialization (PHP 7.4+)
    public function __serialize(): array
    {
        return [
            'name' => $this->name,
            'email' => $this->email,
            // Intentionally omit password
        ];
    }

    public function __unserialize(array $data): void
    {
        $this->name = $data['name'];
        $this->email = $data['email'];
        $this->password = '';  // Reset
    }
}

// Replaces old __sleep() / __wakeup()

__debugInfo

<?php
declare(strict_types=1);

class DatabaseConnection
{
    public function __construct(
        private string $host,
        private string $username,
        private string $password,
    ) {
    }

    // Customize var_dump output
    public function __debugInfo(): array
    {
        return [
            'host' => $this->host,
            'username' => $this->username,
            'password' => '***HIDDEN***',
        ];
    }
}

$db = new DatabaseConnection('localhost', 'admin', 'secret123');
var_dump($db);
// object(DatabaseConnection) { host => "localhost", username => "admin", password => "***HIDDEN***" }

Полный список магических методов

Метод Когда вызывается
__construct() При создании объекта
__destruct() При уничтожении объекта
__get($name) Чтение недоступного свойства
__set($name, $val) Запись в недоступное свойство
__isset($name) isset() / empty() на недоступном свойстве
__unset($name) unset() на недоступном свойстве
__call($name, $args) Вызов недоступного метода
__callStatic($name, $args) Вызов недоступного статического метода
__toString() Приведение к строке
__invoke($args) Использование как функции
__clone() После clone
__serialize() При serialize()
__unserialize($data) При unserialize()
__debugInfo() При var_dump()

Вопросы с экзамена ZCE

Проверь себя

5 из 15

Какой метод вызывается автоматически при сериализации объекта, позволяя закрыть ресурсы?

`__serialize` заменяет...

Какие утверждения о клонировании объектов в PHP верны?

Какой метод позволяет задать строковое представление объекта?

Когда вызывается `__get`?