self против static, enum и readonly
Три темы, которыми отличают тех, кто пишет на современном PHP, от тех, кто застрял в 5.6.
Late static binding (позднее статическое связывание) — механизм, при котором ключевое слово
staticвнутри метода означает «класс, у которого вызвали метод», а не «класс, где метод написан».
Вопрос 1: «Чем self отличается от static?»
Что проверяет интервьюер
Этот вопрос — про понимание того, как PHP разрешает статический контекст. self связывается на этапе компиляции с тем классом, в теле которого написан. static связывается во время вызова с реальным классом-получателем. Разница видна сразу, как только появляется наследование.
<?php
class Model {
public static function create(): static {
return new static();
}
public static function createSelf(): self {
return new self();
}
}
class User extends Model {}
echo get_class(User::create()), "\n";
echo get_class(User::createSelf()), "\n";
Вывод:
User Model
Именно на этом механизме держатся фабричные методы ORM: User::query() из Eloquent возвращает объект нужного класса, а не базового. Тот же принцип применим к static:: при вызове методов и констант — он ищет реализацию у класса-получателя, позволяя потомку переопределить поведение.
Полезно упомянуть и третье ключевое слово — parent::, а также то, что static с PHP 8.0 можно использовать как тип возвращаемого значения, что делает fluent-интерфейсы типобезопасными.
Вопрос 2: «Зачем нужны enum и чем они лучше констант?»
До PHP 8.1 набор допустимых значений описывали константами класса — и ничто не мешало передать в метод произвольную строку. enum делает недопустимое состояние непредставимым: тип-подсказка Status примет только один из объявленных вариантов.
<?php
enum Status: string {
case Draft = 'draft';
case Published = 'published';
public function label(): string {
return match($this) {
Status::Draft => 'Черновик',
Status::Published => 'Опубликовано',
};
}
}
$s = Status::from('draft');
echo $s->name, " ", $s->value, " ", $s->label(), "\n";
var_dump(Status::tryFrom('deleted'));
var_dump($s === Status::Draft);
echo count(Status::cases()), "\n";
Вывод:
Draft draft Черновик NULL bool(true) 2
Что важно назвать в ответе:
- Enum бывают чистые (без значений) и backed (с типом
stringилиint) — последние удобно хранить в базе. from()бросаетValueErrorдля неизвестного значения,tryFrom()возвращаетnull. Для данных извне берутtryFrom().- Кейсы — синглтоны, поэтому их корректно сравнивать через
===. - Enum может иметь методы, константы и реализовывать интерфейсы, но не может иметь состояния: свойств у него нет и
newдля него запрещён.
Вопрос 3: «Что даёт readonly?»
readonly-свойство (PHP 8.1) можно инициализировать ровно один раз — из области видимости класса — и после этого любая попытка записи бросает Error. Это дешёвый способ получить неизменяемые объекты-значения.
<?php
final class Money {
public function __construct(
public readonly int $amount,
public readonly string $currency,
) {}
public function add(int $delta): static {
return new static($this->amount + $delta, $this->currency);
}
}
$m = new Money(100, 'RUB');
echo $m->amount, " ", $m->currency, "\n";
try {
$m->amount = 500;
} catch (Error $e) {
echo "Error: ", $e->getMessage(), "\n";
}
echo $m->add(50)->amount, "\n";
Вывод:
100 RUB Error: Cannot modify readonly property Money::$amount 150
Обратите внимание на приём: метод add() не меняет объект, а возвращает новый — так работают все неизменяемые типы. Уточнения, которые отличают сильного кандидата: readonly требует объявленного типа свойства; свойство нельзя переинициализировать даже внутри класса; а сама «неизменяемость» поверхностная — если внутри лежит массив, его нельзя заменить, но объект по ссылке внутри может менять своё состояние. В PHP 8.2 появилась возможность помечать readonly класс целиком.
Типичные ошибки кандидатов
- Считают
selfиstaticсинонимами — до первого наследника фабричного метода. - Думают, что enum — это просто набор констант, и пытаются создать его через
new. - Используют
from()для данных из запроса и получают необработанныйValueErrorвместо 400-й ошибки. - Ожидают, что
readonlyсделает объект глубоко неизменяемым. - Пытаются присвоить
readonly-свойству значение по умолчанию в объявлении — это запрещено.
Как ответить кратко
«
selfжёстко указывает на класс, где написан код,static— на класс, у которого метод вызвали: это late static binding, на нём строятся фабрики в ORM.enumиз 8.1 задаёт закрытый набор значений, backed-варианты умеютfrom/tryFrom, кейсы — синглтоны и сравниваются через===.readonlyразрешает одну инициализацию и превращает объект в неизменяемое значение — изменения возвращаются новым экземпляром».