Constructors and Destructors

Оновлено: 12.05.2023

PHP дозволяє розробникам оголошувати методи-конструктори для класів. Класи, які мають метод конструктора, викликають цей метод для кожного новостворюваного об'єкта, тому він підходить для будь-якої ініціалізації, яка може знадобитися об'єкту перед його використанням.

Зауваження: Батьківські конструктори не викликаються неявно, якщо у дочірньому класі визначено конструктор. Для того, щоб запустити батьківський конструктор, потрібно викликати parent::__construct() всередині дочірнього конструктора. Якщо у дочірньому класі не визначено конструктора, то він може бути успадкований від батьківського класу так само, як звичайний метод класу (якщо він не був оголошений як приватний).

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

<?php
class BaseClass {
    function __construct() {
        print "In BaseClass constructor\n";
    }
}

class SubClass extends BaseClass {
    function __construct() {
        parent::__construct();
        print "In SubClass constructor\n";
    }
}

class OtherSubClass extends BaseClass {
    // inherits BaseClass's constructor
}

// In BaseClass constructor
$obj = new BaseClass();

// In BaseClass constructor
// In SubClass constructor
$obj = new SubClass();

// In BaseClass constructor
$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;
    }
}

// Pass both parameters.
$p1 = new Point(4, 5);
// Pass only the required parameter. $y will take its default value of 0.
$p2 = new Point(4);
// With named parameters (as of 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 інтерпретує його і як властивість об'єкта, і як аргумент конструктора, і присвоює значення аргументу властивості. Тіло конструктора може бути порожнім або містити інші оператори. Будь-які додаткові оператори будуть виконані після того, як значення аргументів будуть присвоєні відповідним властивостям.

Не всі аргументи потрібно просувати. Ви можете змішувати і поєднувати просунуті і непросунуті аргументи у будь-якому порядку. Розкручені аргументи не впливають на код, що викликає конструктор.

Зауважте:

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

Зауважте:

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

Починаючи з версії PHP 8.1.0, об'єкти можна використовувати як значення параметрів за замовчуванням, статичні змінні та глобальні константи, а також як аргументи атрибутів. Об'єкти також можна передавати у функцію define().

Зауважте:

Не допускається використання динамічного або нестрокового імені класу або анонімного класу. Не допускається використання розпакування аргументів. Не допускається використання непідтримуваних виразів в якості аргументів.

Приклад #4 Використання new в ініціалізаторах

<?php

// All allowed:
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,
    ) {}
}

// All not allowed (compile-time error):
function test(
    $a = new (CLASS_NAME_CONSTANT)(), // dynamic class name
    $b = new class {}, // anonymous class
    $c = new A(...[]), // argument unpacking
    $d = new B($abc), // unsupported constant expression
) {}
?>

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);
        return new static($data['id'], $data['name']);
    }

    public static function fromXml(string $xml): static {
        // Custom logic here.
        $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 транслюється в назву класу, в якому знаходиться код. У цьому випадку Product.

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() у деструкторі не дозволить виконатися решті процедур завершення роботи.

Зауважте:

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

Зауважте:

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