Привет всем во втором эпизоде сериала “Сущности ООП”. Здесь я расскажу о магических методах классов. Несмотря на то, что приводимые здесь названия методов(и код) будет о пхп, данные методы есть и в других ЯП; конечно, если он поддерживает ООП, ну и, вероятно, под другим именем…
Магические методы
Это методы класса, которые по умолчанию создаются(и, по умолчанию, делают ничего!) для каждого класса. Их отличие от обычных методов в том, что они могут быть переписаны в этом же классе(как и в наследниках). Теперь главное – почему они магические? Связано это с тем, каким образом они вызываются: они работают не потому, что их явно вызвали в коде, а по срабатыванию определенного события. Эти методы нужно просто знать, ибо фреймворки часто их используют. Да, вероятно, для написания более понятного кода стоит использовать явные вызовы, но все-же – они есть и используются…
Касательно конкретно PHP – название этих методов начинается с двойного нижнего подчеркивания. Также, они могут иметь все три(описаны в предыдущей серии) модификатора области видимости; при этом: public ведет открыто и наследуется при событиях с наследником, protected – закрыто, но наследование сохраняется; private – события с наследниками их не дергают.
Создание и разрушение объекта
Магический метод , отслеживающий создание объекта с класса – наиболее часто используемый метод из всех – __construct
. Немного о передаче переменных в конструктор: до PHP 8.0 было так
<?php
class Page
{
public $name;
public $id;
public function __construct($name,$id)
{
$this->id = $id;
$this->name = $name;
}
}
$page = new Page('Главная', 1);
после(включая ее) cтал возможен такой синтаксис(помимо первого)
<?php
class Page
{
public function __construct(public $name, public $id)
{
}
}
$page = new Page('Главная', 1);
Он единственный кто кладет болт на правило совместимости сигнатуры при наследовании.
Антипод данного метода – __destruct
– срабатывает каждый раз когда объект разрушается(в том числе и при завершении работы скрипта)
<?php
class Page
{
public function __construct($name)
{
print_r('Объект создан'.PHP_EOL);
print_r('Название страницы: '.$name.PHP_EOL);
}
public function __destruct()
{
print_r('Объект уничтожен'.PHP_EOL);
}
}
$page = new Page('Главная');
unset($page);
echo 'Страница';
/*
Вывод:
Объект создан
Название страницы: Главная
Объект уничтожен
Страница
Перечитывал написанное – вспомнил… вспомнил, что не уточнил – в конструктор может быть передан любой тип переменных кроме callable(груб говоря – функции).
Перегрузка(обращение к недоступным активам класса)
Под “недоступными активами класса” я подразумеваю несуществующие или недоступные в текущей области видимости свойства/методы.
Свойства
Начну с работы с недоступными свойствами. Для манипуляции с ними есть методы __set
, __get
, __unset
и __isset
. Первые два – это динамические сеттеры и геттеры:
<?php
class Page
{
private $fields;
public function __set($name, $value)
{
$this->fields[$name] = $value;
print_r("Свойство [$name] установлено".PHP_EOL);
}
public function __get($name)
{
$value = $this->fields[$name];
print_r("Свойство [$name] равно $value".PHP_EOL);
}
}
$page = new Page();
$page->name = 'Главная';
$page->name;
/*
Вывод:
Свойство [name] установлено
Свойство [name] равно Главная
Вторые два метода срабатывают когда происходим проверка на существование/пустоту недоступного свойства, а также его удаления
<?php
class Page
{
private $fields;
public function __set($name, $value)
{
$this->fields[$name] = $value;
}
public function __isset($name)
{
print_r('Вызван метод '.__METHOD__.PHP_EOL);
}
public function __unset($name)
{
print_r('Вызван метод '.__METHOD__.PHP_EOL);
}
}
$page = new Page();
$page->name = 'Главная';
isset($page->name); // существует ли
empty($page->name); // пуст ли
unset($page->name); // уничтожить свойство
/*
Вывод:
Вызван метод Page::__isset
Вызван метод Page::__isset
Вызван метод Page::__unset
Методы
У методов все попроще, всего два один на обычные методы(__call
) и один на статические(__callStatic
)
<?php
class Page
{
public function __call($name, $args)
{
print_r('Вызван метод '.__METHOD__.PHP_EOL);
print_r($name.PHP_EOL);
print_r($args);
}
public static function __callStatic($name, $args)
{
print_r('Вызван метод '.__METHOD__.PHP_EOL);
print_r($name.PHP_EOL);
print_r($args);
}
}
$page = new Page();
$page->setTitle('Главная', 1);
Page::setTitle('Главная', 1);
/*
Вывод:
Вызван метод Page::__call
setTitle
Array
(
[0] => Главная
[1] => 1
)
Вызван метод Page::__callStatic
setTitle
Array
(
[0] => Главная
[1] => 1
)
Сериализация объектов
Сериализация — это процесс преобразования объекта в поток байтов для сохранения или передачи в память, базу данных или файл; вот такое определение дает Google этому процессу…
Прямой процесс – сериализация
В моем ЯП есть функция сериализации в поток байтов – serialize
. На выходе будет строка
<?php
class Page
{
public $id = 1;
public $name = 'Главная';
public function doSomeThink()
{}
}
$page = serialize(new Page());
echo $page;
/*
Вывод:
O:4:"Page":2:{s:2:"id";i:1;s:4:"name";s:14:"Главная";}
Здесь целых два магических метода будут проверены – __sleep
и __serialize
; Если первый метод в объекте присутствует – он должен вернуть массив с именами свойств для сериализованного объекта
<?php
class Page
{
public $id = 1;
public $name = 'Главная';
public function __sleep()
{
return ['id'];
}
}
$page = serialize(new Page());
echo $page;
/*
Вывод:
O:4:"Page":1:{s:2:"id";i:1;}
Вторая функция, доступная с PHP 7.4, будет перезаписывать первую(если присутствуют обе) и должна возвращать массив “ключ – значение”, который и будет сериализован
<?php
class Page
{
public $id = 1;
public $name = 'Главная';
public function __serialize()
{
return [
'id' => 2,
'name' => 'Карточка'
];
}
}
$page = serialize(new Page());
echo $page;
/*
Вывод:
O:4:"Page":2:{s:2:"id";i:2;s:4:"name";s:14:"Карточка";}
Зачем это нужно? Классический пример – одно из полей объекта – ссылка на соединение с БД. Поскольку сериализованный объект не может хранить его, это поле не нужно.
Десериализация
Это процесс восстановления последовательности байт(полученной при сериализации) в объект. Как и при сериализации, при десериализации срабатывают два метода – __wakeup и __unserialiize. Их отличие в том, что они получают восстановленный обьект и ничего не должны возвращать.
<?php
class Page
{
public $id = 1;
public $name = 'Главная';
public function __sleep()
{
return ['id'];
}
public function __wakeup()
{
$this->id = 5;
}
}
print_r(unserialize('O:4:"Page":1:{s:2:"id";i:2;}'));
/*
Вывод:
Page Object
(
[id] => 5
[name] => Главная
)
Cоответственно, здесь можно восстановить соединение с БД. Однако же, в любом случаи, для правильной десериализации объекта – должен быть доступ к классу, с которого он был создан.
Иная магия
К рассмотрению осталось пять методов: __toString
отрабатывает, когда объект пытаются представить в виде строки
<?php
class Page
{
public $id = 1;
public $name = 'Главная';
public function __toString()
{
return "это обьект Главная Страница";
}
}
echo new Page();
/*
Вывод:
это обьект Главная Страница
При экспорте свойств объекта – работает статический __set_state
. Я не понял(и, пока что, не понимаю), что она делает и к чему это – поэтому пропустим ее. Третья – __invoke – срабатывает когда объект пытаются запустить как функцию
<?php
class Page
{
public $id = 1;
public $name = 'Главная';
public function __invoke()
{
echo 'Главная страница';
}
}
call_user_func(new Page());
/*
Вывод:
Главная страница
Process finished with exit code 0
Cледующий метод – метод __clone
– срабатывает после того, как объект клонируют. Этот код увеличивает идентификатор объекта
<?php
class Page
{
public $id = 1;
public $name = 'Главная';
public function __clone()
{
$this->id ++;
}
}
print_r(clone new Page());
/*
Вывод:
Page Object
(
[id] => 2
[name] => Главная
)
Process finished with exit code 0
и последний – __debugInfo
– создан для вывода объекта через var_dump
. Эта функция показывает все поля объекта(публичные, скрытые…), но если объект имеет метод __debugInfo
– можно задать свой вывод.