Каждое определение класса начинается с ключевого слова class
, затем идёт имя класса, а потом пара фигурных скобок, в которых определяют свойства и методы класса.
Для имени класса разрешается выбирать любое слово, при условии, что слово не входит в список зарезервированных слов PHP, начинается с буквы или символа подчёркивания и за которым следует любое количество букв, цифр или символов подчёркивания. Если задать эти правила в виде регулярного выражения, то получится следующее выражение: ^[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 'Переменная $this определена (';
echo get_class($this);
echo ")\n";
} else {
echo "Переменная \$this не определена.\n";
}
}
}
class B
{
function bar()
{
A::foo();
}
}
$a = new A();
$a->foo();
A::foo();
$b = new B();
$b->bar();
B::bar();
?>
Результат выполнения приведённого примера в PHP 7:
Переменная $this определена (A) Deprecated: Non-static method A::foo() should not be called statically in %s on line 27 Переменная $this не определена. Deprecated: Non-static method A::foo() should not be called statically in %s on line 20 Переменная $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 Переменная $this не определена.
Результат выполнения приведённого примера в PHP 8:
Переменная $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(30) "$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);
?>
Результат выполнения приведённого примера:
bool(true) bool(true) bool(true)
К свойству или методу только что созданного объекта получится обратиться одним выражением:
Пример #7 Доступ к свойствам и методам только что созданного объекта
<?php
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
Начиная с PHP 8.0.0 к свойствам и методам можно также обращаться через оператор nullsafe: ?->
. Оператор nullsafe работает так же, как доступ к свойству или методу, как описание рассказывало выше, за исключением того, что если значение разыменовываемого объекта равно 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 рассматривается как допустимое значение, которое, как ожидается, вернут свойство или метод. Для указания на ошибку лучше выбрасывать исключение.