Разбор механизма позднего статического связывания в PHP, различий между self:: и static::, а также практических примеров использования в ORM и фабриках.

Позднее статическое связывание в PHP

Позднее статическое связывание (Late Static Binding, LSB) — это механизм, позволяющий обращаться к классу, который был фактически вызван во время выполнения, а не к тому, в котором физически написан метод.

До появления PHP 5.3 статические методы и свойства в языке работали по принципу «раннего связывания». Это означало, что при наследовании статические вызовы всегда разрешались в пользу того класса, где был написан код, полностью игнорируя класс-наследник. Это создавало серьезные ограничения при проектировании иерархий классов и вынуждало разработчиков дублировать код.

Ключевое слово static, введенное для решения этой проблемы, изменило правила игры. Оно позволяет создавать гибкие базовые классы, которые корректно работают с наследниками, что критически важно для реализации паттернов Active Record (ORM), Factory и Fluent Interface.

Проблема раннего связывания и self::

Когда вы используете ключевое слово self:: или магическую константу __CLASS__, PHP всегда ссылается на класс, в котором этот код написан. Даже если метод вызывается через наследника, self «помнит» только родителя.

class BaseModel {
    protected static $tableName = 'base_table';

    public static function getTableName() {
        return self::$tableName; // self всегда ссылается на BaseModel
    }
}

class UserModel extends BaseModel {
    protected static $tableName = 'users';
}

echo UserModel::getTableName(); // Выведет 'base_table', а не 'users'!

Это происходит потому, что self разрешается на этапе компиляции (раннее связывание). Для решения этой задачи и было введено позднее связывание.

Решение: ключевое слово static::

Ключевое слово static:: использует информацию о классе, который был фактически вызван в момент выполнения скрипта (позднее связывание). Если метод вызван через наследника, static будет указывать на наследника.

class BaseModel {
    protected static $tableName = 'base_table';

    public static function getTableName() {
        return static::$tableName; // static ссылается на вызвавший класс
    }
}

class UserModel extends BaseModel {
    protected static $tableName = 'users';
}

class PostModel extends BaseModel {
    protected static $tableName = 'posts';
}

echo UserModel::getTableName(); // Выведет 'users'
echo PostModel::getTableName(); // Выведет 'posts'

Сравнение self::, static:: и new

Позднее связывание работает не только со статическими свойствами, но и с методами, константами классов и созданием экземпляров. Различия между ними фундаментальны:

  • self:: vs static::: self всегда указывает на класс, где написан код. static указывает на класс, от имени которого был вызван метод.
  • __CLASS__ vs static::class: Магическая константа __CLASS__ всегда возвращает имя текущего класса. static::class вернет имя фактического вызвавшего класса.
  • new self() vs new static(): Это одно из самых частых применений LSB. new self() создаст экземпляр базового класса, а new static() создаст экземпляр наследника.
class Factory {
    public static function create() {
        return new static(); // Создаст экземпляр конкретного наследника
    }
}

class ConcreteFactory extends Factory {
    public function doSomething() {
        return "Done!";
    }
}

$obj = ConcreteFactory::create();
echo $obj->doSomething(); // Выведет "Done!", так как создан объект ConcreteFactory, а не Factory

Практическое применение: паттерн Active Record

Самый яркий пример использования позднего статического связывания — ORM (например, Eloquent в Laravel). LSB позволяет базовому классу модели знать, с какой таблицей и каким классом он работает, без дублирования кода в каждой модели.

class ActiveRecord {
    protected static $table;

    public static function find($id) {
        // static::$table подставит таблицу наследника
        // static::class можно использовать для маппинга данных в нужный класс
        $sql = "SELECT * FROM " . static::$table . " WHERE id = " . (int)$id;

        return "Выполнен запрос: " . $sql;
    }
}

class User extends ActiveRecord {
    protected static $table = 'users';
}

class Order extends ActiveRecord {
    protected static $table = 'orders';
}

echo User::find(1);  // Выполнен запрос: SELECT * FROM users WHERE id = 1
echo Order::find(5); // Выполнен запрос: SELECT * FROM orders WHERE id = 5

Что запомнить

  • Позднее статическое связывание (LSB) появилось в PHP 5.3 для решения проблем наследования статических методов.
  • Ключевое слово self:: всегда ссылается на класс, в котором написан код (раннее связывание).
  • Ключевое слово static:: ссылается на класс, который был фактически вызван во время выполнения (позднее связывание).
  • new static() создает экземпляр класса-наследника, а new self() — экземпляр класса, где написан код.
  • LSB критически важен для реализации паттернов Active Record, Factory и Fluent Interface в базовых классах.
  • Позднее связывание работает со статическими свойствами, методами, константами и созданием объектов.