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

Конструктор

__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, проте досі дозволяє викликати цю функцію, як конструктор. Якщо оголошено і метод __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 через неоднозначність алгоритмів рушія. Отже це стосується і параметрів, що оголошені, як властивості. Однак, будь-яке інше оголошення типу — дозволене.

Зауваження:

Оскільки параметри, що оголошені як властивості, виконують подвійну роль, до них застосовуються обмеження щодо назв, які стосуються як параметрів, так і властивостей.

Зауваження:

Атрибут, встановлений для параметра-властивості, діє і на параметр, і на властивість. Початкове значення параметра, що оголошений, як властивість, буде надано тільки параметру.

Ключове слово 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-рядок, обробляє його, а потім створює чистий об'єкт. Метод викликає конструктор без параметрів, оскільки всі вони необов'язкові. Далі безпосередньо задає значення властивостям об'єкта та повертає цей об'єкт.

У всіх трьох випадках ключове слово static транслюється в назву класу, всередині якого воно застосовано. In this case, Product.

Деструктор

__destruct(): void

PHP має концепцію деструктора, схожу з тими, що є в інших об'єктно-орієнтованих мовах, як от C++. Метод деструктора об'єкта буде викликано одразу за відсутності посилань на об'єкт, або в довільному порядку під час процедури завершення скрипта.

Приклад #6 Використання деструктора

<?php

class MyDestructableClass
{
function
__construct() {
print
"In constructor\n";
}

function
__destruct() {
print
"Destroying " . __CLASS__ . "\n";
}
}

$obj = new MyDestructableClass();

Схоже з конструктором, батьківський деструктор не буде викликано автоматично. Щоб запустити батьківський деструктор, потрібно явно викликати parent::__destruct() в тілі дочірнього деструктора. Також, як і у випадку з конструктором, якщо в дочірньому класі не оголошено деструктор, то він може успадковувати деструктор батьківського класу.

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

Якщо деструктор створює нові посилання на свій об'єкт, його не буде викликано вдруге, коли кількість посилань знову досягне нуля чи під час завершення скрипта.

Починаючи з PHP 8.4.0, якщо збирання циклів вмикається під час виконання файбера, то заплановані деструктори об'єктів виконуються в окремому файбері з назвою gc_destructor_fiber. Якщо цей файбер призупинено, то створюється новий, щоб довиконати деструктори. Збирач сміття більше не посилатиметься на попередній файбер gc_destructor_fiber, який буде знищений під час наступного збирання сміття. Об'єкт, чий деструктор призупинено, не знищиться, допоки деструктор не буде виконано або не буде знищено файбер.

Зауваження:

Під час завершення скрипта деструктори викликаються після відправлення HTTP-заголовків. Робоча тека під час завершення скриптів може відрізнятись в деяких SAPI (наприклад в Apache).

Зауваження:

Спроба кинути виключення з деструктора, викликаного під час завершення скрипта, призведе до фатальної помилки.