Основы

class

Каждое определение класса начинается с ключевого слова class, затем идёт имя класса, а потом пара фигурных скобок, в которых определяют свойства и методы класса.

Для класса разрешается выбирать произвольное название, при условии, что это не зарезервированное слово языка PHP. Начиная с PHP 8.4.0 объявление названия класса, которое состоит из одного символа подчёркивания _, устарело. Допустимое название класса начинается с буквы или подчеркивания, за которым идёт произвольное количество букв, цифр или символов подчеркивания. В виде регулярного выражения правило именования классов выглядят так: ^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$.

Классы содержат константы, переменные, которые в классах называют свойствами, и функции, которые в классах называют методами.

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

<?php

class SimpleClass
{
// Объявление свойства
public $var = 'значение по умолчанию';

// Объявление метода
public function displayVar() {
echo
$this->var;
}
}

?>

Псевдопеременная $this доступна, когда метод вызывается из контекста объекта. Переменная $this — значение вызывающего объекта.

Внимание

Вызов нестатического метода статически выбрасывает исключение Error. До PHP 8.0.0 это приводило к уведомлению об устаревании, а переменная $this оставалась неопределённой.

Пример #2 Примеры псевдопеременной $this

<?php

class A
{
function
foo()
{
if (isset(
$this)) {
echo
'PHP определил переменную $this (';
echo
get_class($this);
echo
")\n";
} else {
echo
"PHP не определил переменную \$this.\n";
}
}
}

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

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

A::foo();

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

B::bar();

?>

Результат выполнения приведённого примера в PHP 7:

PHP определил переменную $this (A)

Deprecated: Non-static method A::foo() should not be called statically in %s  on line 27
PHP не определил переменную $this.

Deprecated: Non-static method A::foo() should not be called statically in %s  on line 20
PHP не определил переменную $this.

Deprecated: Non-static method B::bar() should not be called statically in %s  on line 32

Deprecated: Non-static method A::foo() should not be called statically in %s  on line 20
PHP не определил переменную $this.

Результат выполнения приведённого примера в PHP 8:

PHP определил переменную $this (A)

Fatal error: Uncaught Error: Non-static method A::foo() cannot be called statically in %s :27
Stack trace:
#0 {main}
  thrown in %s  on line 27

Классы только для чтения

Начиная с PHP 8.2.0 класс можно пометить модификатором readonly. Пометка класса как readonly добавит модификатор readonly к каждому объявленному свойству и не разрешит создавать динамические свойства. Поддержку динамических свойств невозможно добавить даже атрибутом AllowDynamicProperties. Попытка это сделать вызовет ошибку компиляции.

<?php

#[\AllowDynamicProperties]
readonly class
Foo {}

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

?>

В классах только для чтения нельзя объявлять нетипизированные и статические свойства, поскольку такие свойства нельзя помечать модификатором 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

?>

Класс readonly получится расширить тогда и только тогда, когда дочерний класс тоже readonly-класс.

Ключевое слово new

Экземпляр класса создаётся директивой new. Новый объект создаётся, если только конструктор объекта во время ошибки не выбрасывает исключение. Класс рекомендуют определять перед тем, как создавать экземпляр класса; иногда это обязательное требование.

PHP создаст новый экземпляр класса, если с ключевым словом new указали переменную, которая содержит строку (string) с названием класса. Чтобы создать экземпляр класса, который определили в пространстве имён, требуется указывать абсолютное имя класса.

Замечание:

Круглые скобки после названия класса разрешается опускать, если нет аргументов, которые требуется передать в конструктор класса.

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

<?php

class SimpleClass {}

$instance = new SimpleClass();
var_dump($instance);

// Или создаём экземпляр класса через переменную:
$className = 'SimpleClass';
$instance = new $className(); // Аналогично new SimpleClass()
var_dump($instance);

?>

С PHP 8.0.0 поддерживается ключевое слово new с произвольными выражениями. Это разрешает создавать более сложные экземпляры, если выражение создаёт строку (string). Выражения берут в круглые скобки.

Пример #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:

object(ClassA)#1 (0) {
}
object(ClassB)#1 (0) {
}
object(ClassC)#1 (0) {
}
object(ClassD)#1 (0) {
}

В контексте класса возможно создать новый объект выражениями new self и new parent.

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

Пример #5 Присваивание объекта

<?php

class SimpleClass
{
public
string $var;
}

$instance = new SimpleClass();

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

$instance->var = 'У экземпляра класса, на который ссылается переменная $assigned, тоже будет это значение';

$instance = null; // Значения переменных $instance и $reference станут равны null

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

?>

Результат выполнения приведённого примера:

NULL
NULL
object(SimpleClass)#1 (1) {
  ["var"]=>
  string(152) "У экземпляра класса, на который ссылается переменная $assigned, тоже будет это значение"
}

Экземпляры объектов создают несколькими способами:

Пример #6 Новые объекты

<?php

class Test
{
public static 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);

?>

Результат выполнения приведённого примера:

bool(true)
bool(true)
bool(true)

К свойству или методу только что созданного объекта получится обратиться одним выражением:

Пример #7 Доступ к свойствам и методам только что созданного объекта

<?php

echo (new DateTime())->format('Y'), PHP_EOL;

// Начиная с PHP 8.4.0 брать в скобки выражение new не обязательно
echo new DateTime()->format('Y'), PHP_EOL;

?>

Вывод приведённого примера будет похож на:

2025
2025

Замечание: До PHP 7.1 язык не вычислял значения аргументов в круглых скобках выражения, которым инстанциировали объект класса, если в классе не было метода-конструктора.

Свойства и методы

Свойства и методы класса живут в отдельных «пространствах имён», поэтому свойства и методы допустимо называть одинаково. У ссылок на свойства и методы одинаковая нотация, и получит ли код доступ к свойству или вызовет метод — зависит только от контекста, т. е. обращаются ли к переменной или вызывают функцию.

Пример #8 Сравнение доступа к свойству и вызова метода

<?php

class Foo
{
public
$bar = 'свойство';

public function
bar()
{
return
'метод';
}
}

$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;

?>

Результат выполнения приведённого примера:

42

Ключевое слово extends

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

Константы, методы и свойства, которые унаследовал класс, разрешается переопределять в дочернем классе путём повторного объявления с таким же именем, которое определили в родительском классе. Метод или константу нельзя переопределить, если в родительском классе метод или константу определили окончательными — с ключевым словом final. Доступ к переопределённым методам или статическим свойствам родительского класса получают, когда ссылаются на них через parent::.

Замечание: С PHP 8.1.0 разрешается объявлять константы окончательными.

Пример #10 Простое наследование классов

<?php

class SimpleClass
{
function
displayVar()
{
echo
"Parent class\n";
}
}

class
ExtendClass extends SimpleClass
{
// Переопределение родительского метода
function displayVar()
{
echo
"Расширенный класс\n";
parent::displayVar();
}
}

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

?>

Результат выполнения приведённого примера:

Расширенный класс
значение по умолчанию

Правила совместимости сигнатур

При переопределении метода сигнатура метода должна быть совместима с родительским методом. В противном случае PHP выдаст фатальную ошибку или, до PHP 8.0.0, сгенерирует ошибку уровня E_WARNING. Сигнатура совместима, если она соответствует правилам вариантности, делает обязательный параметр необязательным, добавляет только необязательные новые параметры и не ограничивает, а только ослабляет видимость. Эти правила называются принципом подстановки Барбары Лисков, или сокращённо LSP. Правила совместимости не распространяются на конструктор и сигнатуру private-методов, и поэтому они не выдадут фатальную ошибку, если сигнатура не соответствует.

Пример #11 Совместимость дочерних методов

<?php

class Base
{
public function
foo(int $a)
{
echo
"Допустимо\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
"Допустимо\n";
}
}

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

?>

Результат выполнения приведённого примера в PHP 8 аналогичен:

Fatal error: Declaration of Extend::foo() must be compatible with Base::foo(int $a = 5) in /in/evtlq on line 13

Пример #13 Фатальная ошибка, когда дочерний метод делает необязательный параметр обязательным

<?php

class Base
{
public function
foo(int $a = 5)
{
echo
"Допустимо\n";
}
}

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

?>

Результат выполнения приведённого примера в PHP 8 аналогичен:

Fatal error: Declaration of Extend::foo(int $a) must be compatible with Base::foo(int $a = 5) in /in/qJXVC on line 13
Внимание

Переименование параметра метода в дочернем классе не относится к несовместимости сигнатуры. Однако это не рекомендуется, поскольку приведёт к исключению Error во время выполнения, если передавать именованные аргументы.

Пример #14 Ошибка передачи именованных аргументов в параметры, которые переименовали в дочернем классе

<?php

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

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

$obj = new B();

// Передача параметров согласно контракту метода A::test()
$obj->test(foo: "foo", bar: "bar"); // ОШИБКА!

?>

Вывод приведённого примера будет похож на:

Fatal error: Uncaught Error: Unknown named parameter $foo in /in/XaaeN:14
Stack trace:
#0 {main}
  thrown in /in/XaaeN on line 14

Константа ::class

Ключевым словом class также разрешают имя класса. Запись ClassName::class вернёт абсолютное имя класса ClassName. Это полезно при работе с классами, которые определили в пространстве имён.

Пример #15 Разрешение имени класса

<?php

namespace NS {
class
ClassName {}

echo
ClassName::class;
}

?>

Результат выполнения приведённого примера:

NS\ClassName

Замечание:

Разрешение имени класса через конструкцию ::class — преобразование во время компиляции. Это означает, что в момент, когда компилятор создаёт строку с именем класса, PHP ещё не выполнил автозагрузку класса. Следствием этого становится то, что имена классов разворачиваются, даже если класс не существует. При этом ошибку PHP не выдаёт.

Пример #16 Разрешение имени несуществующего класса

<?php

print Does\Not\Exist::class;

?>

Результат выполнения приведённого примера:

Does\Not\Exist

Начиная с PHP 8.0.0 константа ::class научилась разрешать имена объектов. Это разрешение константа совершает при выполнении кода, а компиляции. Результат аналогичен вызову функции get_class() на объекте.

Пример #17 Разрешение имени объекта

<?php

namespace NS {
class
ClassName {}

$c = new ClassName();
print
$c::class;
}

?>

Результат выполнения приведённого примера:

NS\ClassName

Null-безопасные методы и свойства

Оператор nullsafe появился в PHP 8.0.0 и разрешил безопасно обращаться к свойствам и методам: ?->. Null-безопасный оператор работает аналогично доступу к свойству или методу, как описывали предыдущие параграфы, за исключением возврата значения null вместо генерации исключения, когда разыменовываемый объект равен null; PHP пропустит остаток цепочки обращений, если разыменование в цепочке получит значение null.

Результат аналогичен предварительному оборачиванию каждого обращения в функцию is_null(), но более компактный.

Пример #18 Пример работы Nullsafe-оператора

<?php

// Начиная с PHP 8.0.0 эта строка:
$result = $repository?->getUser(5)?->name;

// эквивалентна следующему блоку кода:
if (is_null($repository)) {
$result = null;
} else {
$user = $repository->getUser(5);

if (
is_null($user)) {
$result = null;
} else {
$result = $user->name;
}
}

?>

Замечание:

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