The Basics

Оновлено: 12.05.2023

Базові визначення класів починаються з ключового слова class, за яким слідує ім'я класу, за яким слідує пара фігурних дужок, що містять визначення властивостей і методів, які належать класу.

Ім'я класу може бути будь-якою допустимою міткою, якщо вона не є зарезервованим словом PHP. Допустиме ім'я класу починається з літери або символу підкреслення, за яким слідує будь-яка кількість літер, цифр або символів підкреслення. У вигляді регулярного виразу це буде виглядати так: ^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$.

Клас може містити власні константи, змінні (так звані "властивості") та функції (так звані "методи").

Приклад #1 Просте визначення класу

<?php
class SimpleClass
{
    // property declaration
    public $var = 'a default value';

    // method declaration
    public function displayVar() {
        echo $this->var;
    }
}
?>

Псевдо-змінна $this доступна, коли метод викликається з контексту об'єкта. $this - це значення об'єкту, що викликає метод.

Виклик нестатичного методу статично призводить до виникнення помилки. До версії PHP 8.0.0 це призводило б до повідомлення про застарілість, а $this був невизначеним.

Приклад #2 Деякі приклади псевдозмінної $this

<?php
class A
{
    function foo()
    {
        if (isset($this)) {
            echo '$this is defined (';
            echo get_class($this);
            echo ")\n";
        } else {
            echo "\$this is not defined.\n";
        }
    }
}

class B
{
    function bar()
    {
        A::foo();
    }
}

$a = new A();
$a->foo();

A::foo();

$b = new B();
$b->bar();

B::bar();
?>

Вивід вищенаведеного прикладу на PHP 7:

Вивід вищенаведеного прикладу на PHP 8:

Починаючи з версії PHP 8.2.0, клас можна позначити модифікатором readonly. Позначення класу як readonly додасть модифікатор readonly до кожної оголошеної властивості і унеможливить створення динамічних властивостей. Крім того, неможливо додати їх підтримку за допомогою атрибуту AllowDynamicProperties. Спроба зробити це призведе до помилки під час компіляції.

<?php
#[\AllowDynamicProperties]
readonly class Foo {
}

// Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class Foo
?>

Оскільки ні нетипізовані, ні статичні властивості не можуть бути позначені модифікатором readonly, класи readonly також не можуть їх оголошувати:

<?php
readonly class Foo
{
    public $bar;
}

// Fatal error: Readonly property Foo::$bar must have type
?>
<?php
readonly class Foo
{
    public static int $bar;
}

// Fatal error: Readonly class Foo cannot declare static properties
?>

Клас, доступний тільки для читання, може бути розширений тоді і тільки тоді, коли дочірній клас також є класом, доступним тільки для читання.

Щоб створити екземпляр класу, слід використовувати ключове слово new. Об'єкт буде створено завжди, якщо тільки для нього не визначено конструктор, який генерує виключення у випадку помилки. Класи слід визначати до створення екземплярів (а в деяких випадках це є обов'язковою вимогою).

Якщо рядок, що містить ім'я класу, використовується з new, буде створено новий екземпляр цього класу. Якщо клас знаходиться у просторі імен, при цьому слід використовувати його повне ім'я.

Зауважте:

Якщо конструктору класу не потрібно передавати жодних аргументів, дужки після імені класу можна опустити.

Приклад #3 Створення екземпляра

<?php
$instance = new SimpleClass();

// This can also be done with a variable:
$className = 'SimpleClass';
$instance = new $className(); // new SimpleClass()
?>

Починаючи з версії PHP 8.0.0, підтримується використання new з довільними виразами. Це дозволяє створювати більш складні інстанції, якщо вираз створює рядок. Вирази слід брати у круглі дужки.

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

У наведеному прикладі показано декілька прикладів правильних довільних виразів, які створюють ім'я класу. Тут показано виклик функції, конкатенацію рядків та константу ::class.

<?php

class ClassA extends \stdClass {}
class ClassB extends \stdClass {}
class ClassC extends ClassB {}
class ClassD extends ClassA {}

function getSomeClass(): string
{
    return 'ClassA';
}

var_dump(new (getSomeClass()));
var_dump(new ('Class' . 'B'));
var_dump(new ('Class' . 'C'));
var_dump(new (ClassD::class));
?>

Вивід вищенаведеного прикладу на PHP 8:

У контексті класу можна створити новий об'єкт за допомогою new self та new parent.

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

Приклад #5 Присвоєння об'єкта

<?php

$instance = new SimpleClass();

$assigned   =  $instance;
$reference  =& $instance;

$instance->var = '$assigned will have this value';

$instance = null; // $instance and $reference become null

var_dump($instance);
var_dump($reference);
var_dump($assigned);
?>

Створити екземпляри об'єкта можна кількома способами:

Приклад #6 Створення нових об'єктів

<?php
class Test
{
    static public function getNew()
    {
        return new static;
    }
}

class Child extends Test
{}

$obj1 = new Test();
$obj2 = new $obj1;
var_dump($obj1 !== $obj2);

$obj3 = Test::getNew();
var_dump($obj3 instanceof Test);

$obj4 = Child::getNew();
var_dump($obj4 instanceof Child);
?>

Можна отримати доступ до члена новоствореного об'єкту за допомогою одного виразу:

Приклад #7 Доступ до члена новоствореного об'єкта

<?php
echo (new DateTime())->format('Y');
?>

Зауваження: До версії PHP 7.1 аргументи не обчислюються, якщо не визначено жодної функції-конструктора.

Властивості та методи класу живуть в окремих "просторах імен", тому можна мати властивість і метод з однаковими іменами. Звернення до властивості та методу має однакові позначення, і те, чи буде здійснено доступ до властивості або виклик методу, залежить виключно від контексту, тобто від того, чи це буде доступ до змінної або виклик функції.

Приклад #8 Доступ до властивості проти виклику методу

<?php
class Foo
{
    public $bar = 'property';
    
    public function bar() {
        return 'method';
    }
}

$obj = new Foo();
echo $obj->bar, PHP_EOL, $obj->bar(), PHP_EOL;

Це означає, що виклик анонімної функції, якій присвоєно властивість, безпосередньо неможливий. Замість цього властивість має бути присвоєна, наприклад, змінній. Таку властивість можна викликати безпосередньо, взявши її в круглі дужки.

Приклад #9 Виклик анонімної функції, що зберігається у властивості

<?php
class Foo
{
    public $bar;
    
    public function __construct() {
        $this->bar = function() {
            return 42;
        };
    }
}

$obj = new Foo();

echo ($obj->bar)(), PHP_EOL;

Клас може успадковувати константи, методи та властивості іншого класу, використовуючи ключове слово extends в оголошенні класу. Неможливо розширити декілька класів; клас може успадковувати лише від одного базового класу.

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

примі: Починаючи з версії PHP 8.1.0, константи можуть бути оголошені як фінальні.

Приклад #10 Проста спадковість класів

<?php
class ExtendClass extends SimpleClass
{
    // Redefine the parent method
    function displayVar()
    {
        echo "Extending class\n";
        parent::displayVar();
    }
}

$extended = new ExtendClass();
$extended->displayVar();
?>

При перевизначенні методу його сигнатура повинна бути сумісною з батьківським методом. В іншому випадку буде видано фатальну помилку або, до версії PHP 8.0.0, буде згенеровано помилку рівня E_WARNING. Підпис є сумісним, якщо він дотримується правил відхилень, робить обов'язковий параметр необов'язковим, додає лише необов'язкові нові параметри і не обмежує, а лише послаблює видимість. Це називається принципом підстановки Ліскова, або скорочено LSP. Конструктор та закриті методи не підпадають під ці правила сумісності сигнатур, а отже, не видаватимуть фатальної помилки у випадку невідповідності сигнатур.

Приклад #11 Сумісні дочірні методи

<?php

class Base
{
    public function foo(int $a) {
        echo "Valid\n";
    }
}

class Extend1 extends Base
{
    function foo(int $a = 5)
    {
        parent::foo($a);
    }
}

class Extend2 extends Base
{
    function foo(int $a, $b = 5)
    {
        parent::foo($a);
    }
}

$extended1 = new Extend1();
$extended1->foo();
$extended2 = new Extend2();
$extended2->foo(1);

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

Приклад #12 Фатальна помилка, коли дочірній метод видаляє параметр

<?php

class Base
{
    public function foo(int $a = 5) {
        echo "Valid\n";
    }
}

class Extend extends Base
{
    function foo()
    {
        parent::foo(1);
    }
}

Вивід вищенаведеного прикладу в PHP 8 буде аналогічним:

Приклад #13 Фатальна помилка, коли дочірній метод робить необов'язковий параметр обов'язковим

<?php

class Base
{
    public function foo(int $a = 5) {
        echo "Valid\n";
    }
}

class Extend extends Base
{
    function foo(int $a)
    {
        parent::foo($a);
    }
}

Вивід вищенаведеного прикладу в PHP 8 буде аналогічним:

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

Приклад #14 Помилка при використанні іменованих аргументів та параметрів, що були перейменовані в дочірньому класі

<?php

class A {
    public function test($foo, $bar) {}
}

class B extends A {
    public function test($a, $b) {}
}

$obj = new B;

// Pass parameters according to A::test() contract
$obj->test(foo: "foo", bar: "bar"); // ERROR!

Ключове слово class також використовується для розпізнавання імен класів. Для отримання повного імені класу ClassName використовуйте ClassName::class. Це особливо корисно для класів з простором імен.

Приклад #15 Розв'язання імен класів

<?php
namespace NS {
    class ClassName {
    }
    
    echo ClassName::class;
}
?>

Зауважте:

Розпізнавання імен класів за допомогою ::class є перетворенням часу компіляції. Це означає, що під час створення рядка імені класу ще не відбулося автозавантаження. Як наслідок, імена класів розширюються, навіть якщо клас не існує. У цьому випадку помилки не буде видано.

Приклад #16 Відсутнє розпізнавання імен класів

<?php
print Does\Not\Exist::class;
?>

Починаючи з версії PHP 8.0.0, константа ::class також може використовуватися для об'єктів. Таке перетворення відбувається під час виконання, а не під час компіляції. Його ефект такий самий, як і при виклику get_class() для об'єкта.

Приклад #17 Розв'язання імен об'єктів

<?php
namespace NS {
    class ClassName {
    }
}
$c = new ClassName();
print $c::class;
?>

Починаючи з версії PHP 8.0.0, доступ до властивостей і методів також можна отримати за допомогою оператора "nullsafe": ?->. Оператор nullsafe працює так само, як і доступ до властивостей або методів, як описано вище, за винятком того, що якщо об'єкт, на який роз'єднується посилання, є нульовим, то замість виключення буде повернуто null. Якщо розіменування є частиною ланцюжка, решта ланцюжка пропускається.

Ефект подібний до того, якби кожне звернення спочатку оберталося перевіркою is_null(), але більш компактний.

Приклад #18 Оператор Nullsafe

<?php

// As of PHP 8.0.0, this line:
$result = $repository?->getUser(5)?->name;

// Is equivalent to the following code block:
if (is_null($repository)) {
    $result = null;
} else {
    $user = $repository->getUser(5);
    if (is_null($user)) {
        $result = null;
    } else {
        $result = $user->name;
    }
}
?>

Зауважте:

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