В чём разница между new self и new static в PHP? Позднее статическое связывание
Ключевая идея: время разрешения класса
Разница между new self() и new static() — это разница между ранним (early) и поздним (late) статическим связыванием. Грубо говоря, self «замораживает» класс на момент компиляции, а static определяет его во время выполнения.
Как работает new self()
self всегда ссылается на тот класс, в котором физически написан код с этим ключевым словом. Даже если метод вызывается из дочернего класса, self не изменится. Это решается на этапе парсинга скрипта.
class A {
public static function who() {
echo __CLASS__; // всегда 'A'
}
public static function test() {
self::who(); // всегда вызовет A::who()
}
}
class B extends A {
public static function who() {
echo __CLASS__; // 'B'
}
}
B::test(); // выведет 'A', а не 'B'
Как работает new static() (позднее статическое связывание)
static — это «тот класс, который был вызван в цепочке наследования». PHP вычисляет его в момент выполнения, используя класс, который фигурировал в вызове (так называемый «вызывающий класс»).
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
static::who(); // вызовет метод того класса, откуда вызвали test()
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test(); // выведет 'B' — позднее связывание сработало!
Решение вашей проблемы: замените self на static
Чтобы ChildClass::create() возвращал объект ChildClass, измените код родительского класса:
class ParentClass {
public static function create() {
// Было: return new self();
return new static(); // Теперь создаётся экземпляр вызванного класса
}
}
class ChildClass extends ParentClass {
// Дополнительные свойства или методы
}
$obj = ChildClass::create();
echo get_class($obj); // Теперь выведет 'ChildClass'
Важные нюансы и подводные камни
- Не все операции с
staticподдерживают позднее связывание. Например,static::$propertyиstatic::CONSTработают, аstatic::method()— да. Ноparent::всегда жёстко привязан к родителю. - Используйте
staticв фабричных методах. Это классический паттерн: статический метод, который должен возвращать экземпляр того класса, из которого его вызвали. - Будьте осторожны с переопределением. Если дочерний класс переопределит метод
create(), то поведениеnew static()может измениться. Всегда проверяйте, не нарушает ли наследование вашу логику. - Производительность.
staticработает чуть медленнееselfиз-за дополнительных проверок во время выполнения, но в 99% случаев это незаметно.
Когда использовать self, а когда static?
new self()— когда вам точно нужен объект текущего класса, независимо от того, кто вызвал метод. Например, в неизменяемых (final) классах.new static()— когда вы пишете библиотеку, трейт или абстрактный класс, который должен корректно работать с наследниками. Это основа для реализации паттернов «Строитель» и «Фабричный метод».
Запомните простое правило: self — это «где я написан», static — это «откуда меня вызвали». Надеюсь, теперь стало понятнее!
<<Дополнение: практический пример с трейтами
Часто путаница возникает при использовании трейтов. Трейт — это не класс, а набор методов. Если в трейте написать new self(), то self будет ссылаться на класс, который использует трейт (фактически на тот класс, куда трейт «вставлен»). Но если трейт используется в нескольких классах, self будет разным для каждого из них — это может сбивать с толку.
trait FactoryTrait {
public static function create() {
return new self(); // self — это класс, который использует трейт
}
}
class User {
use FactoryTrait;
}
class Admin extends User {
// create() унаследован
}
$admin = Admin::create();
echo get_class($admin); // User, а не Admin — снова та же проблема!
Решение то же самое — используйте new static() в трейте:
trait FactoryTrait {
public static function create() {
return new static(); // теперь всё работает как ожидается
}
}
$admin = Admin::create();
echo get_class($admin); // Admin
Вывод: В трейтах и базовых классах, которые предполагают наследование, всегда отдавайте предпочтение static вместо self, если вам нужно создавать экземпляры дочерних классов. Это сделает ваш код гибким и предсказуемым для всех наследников.
Ответов пока нет.
Войдите чтобы оставить ответ