Late Static Binding (позднее статическое связывание) в PHP — это механизм, позволяющий в статических методах ссылаться на класс, который был вызван, а не на тот, где метод был определен. Он решает проблему наследования, сохраняя имя класса из последнего «неперенаправленного вызова» и используя ключевое слово static:: вместо self::.
Также из этой темы:
Основные аспекты:
self::используют, когда поведение должно быть жёстко привязано к классу, где метод объявлен. Когда ты пишешьself::, PHP подставляет класс в момент компиляции. То есть буквально тот класс, внутри которого написан код.static::используют, когда код должен корректно работать в наследниках.static::определяется в момент выполнения, а не в момент компиляции. PHP смотрит: «Кто реально вызвал метод?»- Использование: Применяется для статических методов, свойств и констант, обеспечивая правильный полиморфизм при наследовании.
Поэтому:
self::= раннее связывание. Раннее – решение принимается заранее.static::= позднее связывание. Позднее – решение принимается в момент вызова.
Late static binding работает не только для методов, но и для статических свойств:
class A {
protected static $name = 'A';
public static function getName() {
return static::$name;
}
}
class B extends A {
protected static $name = 'B';
}
echo B::getName(); // B
Если бы тут был self::$name, всегда возвращалось бы A.
То есть:
self::говорит: «Мне плевать, кто меня вызвал, я работаю строго как класс A».static::говорит: «Я уважаю иерархию. Если меня вызвал B, значит работаю как B».
технический пример именно для методов:
class Animal {
public static function speak() {
echo "Animal sound";
}
public static function makeSound() {
static::speak();
}
}
class Dog extends Animal {
public static function speak() {
echo "Woof";
}
}
// Call
Dog::makeSound();
// Output
Woof
Почему?
Потому что внутри makeSound() используется static::speak().
PHP во время выполнения смотрит: метод вызван через Dog, значит static = Dog.
Вот это и есть late static binding для методов.
static как возвращаемый тип
class A {
public static function create(): static {
return new static();
}
}
Тут new static() создаёт экземпляр того класса, который реально вызвал метод.
Если вызвать B::create(), создастся объект B, а не A.
Почему это вообще понадобилось?
Потому что наследование без этого ведёт себя странно.
Если бы существовал только self::, ты бы не мог корректно переопределять статические методы в потомках.
Late static binding позволяет родительскому классу “уважать” потомков.
Использование
Late static binding особенно важен, когда родительский класс реализует общий алгоритм, а детали должны переопределяться в потомках.
class Report {
public static function generate() {
static::header();
static::body();
static::footer();
}
protected static function header() {
echo "Base header\n";
}
protected static function body() {
echo "Base body\n";
}
protected static function footer() {
echo "Base footer\n";
}
}
class PdfReport extends Report {
protected static function body() {
echo "PDF body\n";
}
}
PdfReport::generate();
Выведет:
Base header
PDF body
Base footer
Если заменить static:: на self::, переопределение работать не будет.
Примеры с плагина CPM
Класс Plugin общий
// Plugin.php
final public static function getInstance(): static
{
$class = static::class; // ← НЕ 'Plugin', а тот класс на котором вызвали
if (!isset(self::$instances[$class])) {
self::$instances[$class] = new static(); // ← создаёт экземпляр наследника
}
return self::$instances[$class];
}
// Use, create plugin CpmExtraFeatures
class CpmExtraFeatures extends Plugin
CpmExtraFeatures::getInstance()
→ static::class = 'CpmExtraFeatures' (не 'Plugin')
→ new static() = new CpmExtraFeatures() (не new Plugin())
Без static:: — singleton всегда создавал бы Plugin, а не наследника.
Класс Block общий
// Block.php (базовый класс)
public static function registerHooks(): void
{
add_action('acf/init', [static::class, 'registerBlock']);
// ↑
// static::class — конкретный блок
}
// Use
class CardsBlock extends Block
CardsBlock::registerHooks()
→ static::class = 'CardsBlock'
→ add_action('acf/init', [CardsBlock::class, 'registerBlock'])
class ProsConsBlock extends Block
ProsConsBlock::registerHooks()
→ static::class = 'ProsConsBlock'
→ add_action('acf/init', [ProsConsBlock::class, 'registerBlock'])
Код написан один раз в Block.php, но каждый наследник регистрирует свой callback.
Или в том же классе , блоки — static::name() и static::$blockName.
// Block.php
public static function name(): string
{
return static::$blockName; // ← берёт свойство из наследника
}
public static function enqueueScripts(): void
{
$block = static::name(); // 'cpm-cards' или 'cpm-pros-cons'
apply_filters("cpm_extra_features/blocks/$block/style", []);
}
// CardsBlock.php
class CardsBlock extends Block {
protected static string $blockName = 'cpm-cards';
}
// ProsConsBlock.php
class ProsConsBlock extends Block {
protected static string $blockName = 'cpm-pros-cons';
}
CardsBlock::enqueueScripts()
→ static::name() → static::$blockName → 'cpm-cards'
ProsConsBlock::enqueueScripts()
→ static::name() → static::$blockName → 'cpm-pros-cons'
Или pluginPath():
// Plugin.php
public static function pluginPath(string $path = ''): string
{
return plugin_dir_path(static::getInstance()->pluginFile());
// ↑
// static:: → CpmExtraFeatures::getInstance()
// → $this->plugin_file (из наследника)
}
Наглядно: что было бы без static::
С self:: (неправильно): С static:: (правильно):
───────────────────────── ─────────────────────────
CardsBlock::registerHooks() CardsBlock::registerHooks()
self::class = 'Block' static::class = 'CardsBlock'
self::$blockName = ошибка static::$blockName = 'cpm-cards'
new self() = new Block() new static() = new CardsBlock()
Все блоки были бы одинаковые ❌ Каждый блок — свой ✓
Итого
Late static binding – это механизм, который позволяет static:: определяться динамически во время выполнения, а не жёстко ссылаться на текущий класс как self::.
Это тонкая вещь. Но без неё наследование в PHP было бы гораздо менее гибким.