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