Разбор механизма позднего статического связывания в 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::vsstatic:::selfвсегда указывает на класс, где написан код.staticуказывает на класс, от имени которого был вызван метод.__CLASS__vsstatic::class: Магическая константа__CLASS__всегда возвращает имя текущего класса.static::classвернет имя фактического вызвавшего класса.new self()vsnew 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 в базовых классах.
- Позднее связывание работает со статическими свойствами, методами, константами и созданием объектов.