Каждое определение класса начинается с ключевого слова 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
.
Новый объект создаётся, если только
конструктор объекта
во время ошибки не выбрасывает исключение.
Класс рекомендуют определять перед тем, как создавать экземпляр класса;
иногда это обязательное требование.
PHP создаст новый экземпляр класса, если с ключевым словом
new
указали переменную, которая содержит строку (string)
с названием класса.
Чтобы создать экземпляр класса, который определили в пространстве имён,
требуется указывать абсолютное имя класса.
Замечание:
Круглые скобки после названия класса разрешается опускать, если нет аргументов, которые требуется передать в конструктор класса.
Пример #3 Создание экземпляра класса
<?php
$instance = new SimpleClass();
// Или создаём экземпляр класса через переменную:
$className = 'SimpleClass';
$instance = new $className(); // new SimpleClass()
?>
С 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
$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 8.4.0 брать в скобки выражение new не обязательно
echo new DateTime()->format('Y');
?>
Вывод приведённого примера будет похож на:
2016
Замечание: До 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
в объявлении класса.
Невозможно наследовать больше одного класса, одному классу разрешено
наследовать только один базовый класс.
Константы, методы и свойства, которые унаследовал класс, разрешается переопределять в дочернем классе путём повторного объявления с таким же именем, которое определили в родительском классе. Метод или константу нельзя переопределить, если в родительском классе метод или константу определили окончательными — с ключевым словом final. Доступ к переопределённым методам или статическим свойствам родительского класса получают, когда ссылаются на них через parent::.
Замечание: С PHP 8.1.0 разрешается объявлять константы окончательными.
Пример #10 Простое наследование классов
<?php
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
также разрешают имя класса.
Запись 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
Оператор nullsafe появился в PHP 8.0.0 и разрешил безопасно
обращаться к свойствам и методам, значение которых равно null: ?->
.
Null-безопасный оператор работает так же, как доступ к свойству или методу,
как описывалось выше,
за исключением того, что если значение разыменовываемого объекта равно null
,
PHP вернёт null
, а не выбросит исключение. PHP пропустит остаток цепочки,
если встретит разыменование в середине.
Результат аналогичен предварительному оборачиванию каждого обращения в функцию 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 рассматривается как допустимое значение, которое, как ожидается, вернут свойство или метод. Для указания на ошибку лучше выбрасывать исключение.