Позднее статическое связывание
Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 36 из 91.
Когда вы используете self:: в родительском классе, он всегда ссылается на класс, в котором он написан, а не на тот класс, который фактически используется. Это может привести к неожиданному поведению при наследовании. Позднее статическое связывание решает эту проблему с помощью ключевого слова static::.
Рассмотрим этот пример, в котором self:: не работает так, как ожидалось:
<?php
class ParentClass {
public static function create() {
return new self();
}
public static function getClassName() {
return self::class;
}
}
class ChildClass extends ParentClass {}
$obj = ChildClass::create();
echo get_class($obj) . "\n";
echo ChildClass::getClassName();
Вывод:
ParentClass
ParentClassНесмотря на то, что мы вызвали ChildClass::create(), мы получили объект ParentClass, потому что self:: разрешается во время компиляции. Теперь давайте используем static:: вместо этого:
<?php
class ParentClass {
public static function create() {
return new static();
}
public static function getClassName() {
return static::class;
}
}
class ChildClass extends ParentClass {}
$obj = ChildClass::create();
echo get_class($obj) . "\n";
echo ChildClass::getClassName();
Вывод:
ChildClass
ChildClassС static:: PHP ожидает времени выполнения, чтобы определить, какой класс использовать. Это называется «поздним» связыванием, так как принятие решения откладывается. Это необходимо для фабричных методов и любых статических методов, которые должны учитывать наследование.
Ключевой момент: Используйте static:: вместо self::, когда вы хотите, чтобы дочерние классы правильно наследовали и настраивали статическое поведение.
Задание
ЛегкоДавайте создадим систему реестра моделей, которая демонстрирует мощь позднего статического связывания. Вы создадите базовый класс Model с фабричным методом, который смогут наследовать дочерние классы — и благодаря static:: каждый дочерний класс будет корректно создавать экземпляры самого себя, а не родителя.
Вы организуете свой код в четырех файлах:
Model.php— Создайте базовый классModelс защищенным статическим свойством$tableName, установленным в"models". Добавьте статический методcreate(), который используетnew static()для возврата нового экземпляра того класса, который его вызвал. Добавьте еще один статический методgetTable(), который возвращаетstatic::$tableName. Наконец, добавьте методdescribe(), который возвращает"[ClassName] from table [tableName]", используяstatic::classиstatic::$tableName.User.php— Создайте классUser, который расширяетModel. Подключите родительский файл. Переопределите статическое свойство$tableNameна"users". Класс наследует все методы отModelбез каких-либо изменений.Product.php— Создайте классProduct, который расширяетModel. Подключите родительский файл. Переопределите статическое свойство$tableNameна"products". Опять же, класс наследует все методы от родителя.main.php— Подключите файлы User и Product. Используйте унаследованный фабричный методcreate()для создания экземпляров какUser, так иProduct. Для каждого экземпляра вызовитеdescribe()и выведите результат на отдельной строке. Затем выведите имя таблицы для каждого класса, используяUser::getTable()иProduct::getTable(), каждое на отдельной строке.
Магия здесь заключается в том, что методы create() и getTable() определены только в родительском классе Model, но они корректно работают с дочерними классами, потому что static:: откладывает связывание до момента выполнения. Когда вы вызываете User::create(), PHP создает экземпляр User — а не экземпляр Model — потому что static:: ссылается на класс, который был фактически вызван.
Ваш вывод должен содержать четыре строки: описание для экземпляра User, описание для экземпляра Product, имя таблицы User и имя таблицы Product.
Шпаргалка
Ключевое слово self:: в родительском классе всегда ссылается на класс, в котором оно написано, и разрешается во время компиляции. Позднее статическое связывание с использованием static:: разрешает ссылку во время выполнения, позволяя дочерним классам правильно наследовать и настраивать статическое поведение.
Использование self:: (связывание на этапе компиляции)
<?php
class ParentClass {
public static function create() {
return new self(); // Always creates ParentClass
}
public static function getClassName() {
return self::class; // Always returns "ParentClass"
}
}
class ChildClass extends ParentClass {}
$obj = ChildClass::create(); // Returns ParentClass instance
echo ChildClass::getClassName(); // Outputs: ParentClass
Использование static:: (связывание во время выполнения)
<?php
class ParentClass {
public static function create() {
return new static(); // Creates instance of calling class
}
public static function getClassName() {
return static::class; // Returns calling class name
}
}
class ChildClass extends ParentClass {}
$obj = ChildClass::create(); // Returns ChildClass instance
echo ChildClass::getClassName(); // Outputs: ChildClass
Ключевой момент: Используйте static:: вместо self::, если вы хотите, чтобы дочерние классы правильно наследовали и настраивали статическое поведение. Это важно для фабричных методов и любых статических методов, которые должны учитывать наследование.
Попробуйте сами
<?php
require_once 'User.php';
require_once 'Product.php';
// TODO: Используйте унаследованный фабричный метод create() для создания экземпляра User
// TODO: Используйте унаследованный фабричный метод create() для создания экземпляра Product
// TODO: Вызовите describe() для экземпляра User и выведите результат
// TODO: Вызовите describe() для экземпляра Product и выведите результат
// TODO: Выведите User::getTable()
// TODO: Выведите Product::getTable()
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово $thisМетодыСвойстваКонструктор (__construct)Деструктор (__destruct)Итоги — Простой калькулятор4Наследование
Основы наследованияКлючевое слово parent::Переопределение методовКлючевое слово finalАбстрактные классыИтоги — Иерархия сотрудников7Инкапсуляция
Public, Protected, PrivateПодробный разбор модификаторов доступаГеттеры и сеттерыСокрытие данныхConstructor Promotion (8.0)Итоги — Система учета студентов10Продвинутые концепции ООП
Композиция против наследованияВнедрение зависимостейАнонимные классыПеречисления (PHP 8.1)Файберы (PHP 8.1)Глубокое погружение в клонирование объектовГенераторы и итераторы13Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий2Пространства имен и автозагрузка
Введение в пространства именКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerИтоги: структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс против абстрактного классаType Hinting с интерфейсамиИтоги — Калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeИтоги — Кастомная коллекция11Система типов и обработка ошибок
Объявление типовNullable-типыТипы объединения и пересеченияКлассы исключенийИерархия пользовательских исключенийTry, Catch, FinallyИтоги — Валидатор форм14Проект: Управление библиотекой
Обзор проектаКлассы Book и User3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищенные свойстваСвойства Readonly (PHP 8.1)Итоги — Менеджер банковского счета6Полиморфизм
Снова о переопределении методовПолиморфизм через интерфейсыType Hinting и Union TypesПозднее статическое связываниеИтоги — Payment Processor9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy