Конструкторы и деструкторы

Конструктор

__construct(mixed...$values = ""): void

PHP разрешает разработчикам объявлять для классов методы-конструкторы. Класс с методом-конструктором вызовет этот метод на каждом вновь созданном объекте класса. Поэтому объявление метода-конструктора удобно для инициализации того, что может потребоваться объекту в начале работы.

Замечание: Класс не будет неявно вызывать конструкторы, которые определили в классах-родителях, если дочерний класс определяет свой конструктор. Внутри конструктора дочернего класса требуется вызвать parent::__construct(), чтобы запустить конструктор родительского класса. Дочерний класс унаследует конструктор родительского класса как обычный метод, если в дочернем классе конструктор не определили и родительский конструктор не закрытый.

Пример #1 Конструкторы при наследовании

<?php

class BaseClass
{
function
__construct()
{
print
"Конструктор класса BaseClass\n";
}
}

class
SubClass extends BaseClass
{
function
__construct()
{
parent::__construct();
print
"Конструктор класса SubClass\n";
}
}

class
OtherSubClass extends BaseClass
{
// Наследует конструктор класса BaseClass
}

// Конструктор класса BaseClass
$obj = new BaseClass();

// Конструктор класса BaseClass
// Конструктор класса SubClass
$obj = new SubClass();

// Конструктор класса BaseClass
$obj = new OtherSubClass();

?>

В отличие от других методов, метод __construct() освобождается от правил совместимости сигнатуры при наследовании.

Конструкторы — обыкновенные методы, которые вызываются при инстанциировании объектов, которые содержат эти конструкторы, или объектов дочерних классов без конструктора. Поэтому в конструкторах определяют произвольное количество аргументов, которые разрешено объявлять обязательными, типизированными, со значением по умолчанию. Аргументы конструктора указываются в круглых скобках после имени класса.

Пример #2 Объявление аргументов в конструкторах

<?php

class Point
{
protected
int $x;
protected
int $y;

public function
__construct(int $x, int $y = 0)
{
$this->x = $x;
$this->y = $y;
}
}

// Передаём оба параметра
$p1 = new Point(4, 5);

// Передаём только обязательные параметры. Переменная $y содержит значение по умолчанию 0
$p2 = new Point(4);

// Вызываем с именованными параметрами (начиная с PHP 8.0):
$p3 = new Point(y: 5, x: 4);

?>

Скобки после имени класса необязательны, если у класса нет конструктора, или конструктор класса не содержит обязательных параметров.

Конструкторы в старом стиле

До PHP 8.0.0 классы в глобальном пространстве имён будут интерпретировать названный именем класса метод как конструктор старого стиля. Этот синтаксис устарел и будет вызывать ошибку уровня E_DEPRECATED, но всё равно вызовет этот метод как конструктор. PHP вызовет как конструктор метод __construct(), если в классе определили и метод __construct(), и метод с именем класса.

У метода, название которого совпадает с именем класса, нет особого значения в классах внутри пространства имён, а с PHP 8.0.0 — в любых классах.

В новом коде определяют только метод __construct().

Продвижение свойств в конструкторе

С PHP 8.0.0 параметры конструктора можно продвинуть до свойств объекта. Это распространённая практика — присваивать свойствам объекта значения только за счёт переданных в конструктор аргументов. Определение свойств класса в конструкторе значительно сокращает количество шаблонного кода для такого случая. Пример выше можно будет переписать вот так:

Пример #3 Продвижение параметров конструктора до свойств

<?php

class Point
{
public function
__construct(protected int $x, protected int $y = 0) {}
}

?>

PHP интерпретирует аргумент одновременно и как параметр конструктора, и как свойство объекта, и устанавливает значение аргумента свойству, когда параметр конструктора содержит модификатор. Тогда тело конструктора оставляют пустым или добавляют другие инструкции. Конструктор выполнит дополнительные инструкции после присваивания значений аргументов свойствам.

Не каждый аргумент обязан продвигать параметр конструктора до свойства объекта. Допустимо смешивать продвигаемые и непродвигаемые параметры в произвольном порядке, и давать обыкновенным параметрам конструктора имена, которые совпадают с именами свойств класса. Аргументы, которые продвигают параметры до свойств, не влияют на вызывающий конструктор код.

Замечание:

Указать модификатор области видимостиpublic, protected или private — наиболее вероятный способ применить продвинутую установку свойств, но любой другой модификатор, например readonly, даст такой же эффект.

Замечание:

Нельзя указывать свойствам объекта тип callable. Это связано с неоднозначностью, которую они представляют для движка PHP. Поэтому и для параметров конструктора, которые устанавливают классу свойства, также нельзя указывать тип callable. Любые другие декларации типов допустимы.

Замечание:

PHP применяет ограничения на именование параметров конструктора как к свойствам класса, так и к параметрам функции, поскольку при разборе кода PHP преобразовывает синтаксический сахар продвинутых свойств в декларирование свойств класса с теми же модификаторами видимости и типом данных, которые указали в продвинутом параметре, и присваивает значение аргумента и параметру функции, и свойству класса.

Замечание:

Атрибуты, которые установили для аргумента в продвинутом конструкторе, будут реплицированы как на аргумент, так и на свойство класса. Значение по умолчанию для аргумента в продвинутом конструкторе распространяется только на аргумент, а не свойство.

Ключевое слово new в инициализаторах

В PHP 8.1.0 разрешили присваивать объекты как значения по умолчанию для параметров, как значения статических переменных и глобальных констант, а также как значения аргументов в атрибутах. Объекты также допустимо передавать в функцию define().

Замечание:

При этом динамические или нестроковые имена классов или анонимных классов не разрешены. Использовать распаковку аргументов не разрешено. Неподдерживаемые выражения как аргументы не разрешены.

Пример #4 Пример ключевого слова new при инициализации класса

<?php

// Всё допустимо:
static $x = new Foo;
const
C = new Foo;

function
test($param = new Foo) {}

#[
AnAttribute(new Foo)]
class
Test
{
public function
__construct(
public
$prop = new Foo,
) {}
}

// Всё запрещено (ошибка времени компиляции):
function test(
$a = new (CLASS_NAME_CONSTANT)(), // Динамическое имя класса
$b = new class {}, // Анонимный класс
$c = new A(...[]), // Распаковка аргументов
$d = new B($abc), // Неподдерживаемое постоянное выражение
) {}

?>

Статические методы, которые создают объект класса

PHP поддерживает только один конструктор для класса. Однако бывает так, что нужно создавать разные объекты для разных входных данных. Рекомендуемый способ — использовать статические методы как обёртки над конструктором.

Пример #5 Использование статических методов для создания объектов

<?php

class Product
{
private ?
int $id;
private ?
string $name;

private function
__construct(?int $id = null, ?string $name = null)
{
$this->id = $id;
$this->name = $name;
}

public static function
fromBasicData(int $id, string $name): static
{
$new = new static($id, $name);
return
$new;
}

public static function
fromJson(string $json): static
{
$data = json_decode($json, true);
return new static(
$data['id'], $data['name']);
}

public static function
fromXml(string $xml): static
{
// Пользовательская логика
$data = convert_xml_to_array($xml);
$new = new static();
$new->id = $data['id'];
$new->name = $data['name'];
return
$new;
}
}

$p1 = Product::fromBasicData(5, 'Widget');
$p2 = Product::fromJson($some_json_string);
$p3 = Product::fromXml($some_xml_string);

?>

Конструктор разрешено делать закрытым или защищённым, чтобы исключить вызов конструктора извне. Тогда создать объект класса получится только через статический метод. Доступ к закрытым методам класса есть и у конструктора, и у статического метода, поскольку конструктор, статический и закрытый методы определили в одном и том же классе, даже если один экземпляр объекта вызывает закрытый метод другого. Закрытый конструктор необязателен, и будет ли в закрытом конструкторе смысл, определяет ситуация.

В примере выше три открытых статических метода показывают разные способы, которыми они создают экземпляр объекта.

  • Метод fromBasicData() принимает конкретные параметры, создаёт экземпляр класса через конструктор и возвращает результат.
  • Метод fromJson() принимает JSON-строку и выполняет над строкой предварительную обработку, чтобы преобразовать строку в формат, который требует конструктор. Затем метод возвращает новый объект.
  • Метод fromXml() принимает XML-строку, предварительно обрабатывает её, а затем создаёт пустой объект. При этом PHP вызывает конструктор, но поскольку параметры конструктора необязательны, метод пропускает их. Затем непосредственно перед возвратом результата метод присваивает значения свойствам объекта.

В каждом из трёх случаев ключевое слово static транслируется в имя класса, в котором вызвали код. В примере — в имя класса Product.

Деструкторы

__destruct(): void

Концепция деструктора PHP повторяет концепцию других объектно-ориентированных языков, например C++. PHP вызовет деструктор, как только не останется ссылок на конкретный объект, или в другом порядке в течение завершения работы.

Пример #6 Пример использования деструктора

<?php

class MyDestructableClass
{
function
__construct()
{
print
"Конструктор\n";
}

function
__destruct()
{
print
"Уничтожается " . __CLASS__ . "\n";
}
}

$obj = new MyDestructableClass();

?>

Как и конструкторы, движок PHP не будет неявно вызывать деструкторы, которые объявили в родительском классе. Необходимо вызвать parent::__destruct() в теле деструктора дочернего класса, чтобы запустить деструктор родительского класса. Аналогично конструкторам, дочерний класс, в котором не определен деструктор, наследует деструктор из родительского класса.

Движок вызовет деструктор, даже если выполнение скрипта остановила функция exit(). Вызов функции exit() в деструкторе предотвратит запуск остальных процедур завершения работы.

Замечание:

При завершении работы скрипта PHP отправляет HTTP-заголовки перед обработкой деструкторов. На этапе завершения работы скрипта рабочая директория, как её определяет PHP, иногда не совпадает с рабочей директорией, которую возвращают отдельные SAPI-интерфейсы (например, в Apache).

Замечание:

Попытка выбросить исключение из деструктора, который вызвали во время завершения работы скрипта, вызовет фатальную ошибку.

To Top