Позднее статическое связывание
Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy. Урок 36 из 91.
Когда вы используете self:: в родительском class, оно всегда ссылается на class, в котором записано, а не на class, который фактически используется. Это может привести к неожиданному поведению при наследовании. Позднее статическое связывание решает эту проблему с помощью ключевого слова 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 ждёт до выполнения, чтобы определить, какой class использовать. Это называется «поздним» связыванием, потому что решение откладывается. Это необходимо для factory methods и любого static method, который должен учитывать наследование.
Ключевой момент: Используй 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()и выведите result в отдельной строке. Затем выведите имя таблицы для каждого класса с помощьюUser::getTable()иProduct::getTable(), каждое в отдельной строке.
Суть заключается в том, что методы create() и getTable() определены только в родительском классе Model, но при этом корректно работают с дочерними классами, поскольку static:: откладывает связывание до момента выполнения. Когда вы вызываете User::create(), PHP создаёт экземпляр User, а не экземпляр Model, потому что static:: ссылается на класс, который был фактически вызван.
Ваш результат должен содержать четыре строки: описание экземпляра User, описание экземпляра Product, имя таблицы User и имя таблицы Product.
Попробуйте сами
<?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Модификаторы доступа подробноГеттеры и сеттерыСокрытие информацииПродвижение конструктора (8.0)Повторение — система учёта студентов2Пространства имён и автозагрузка
Введение в пространства имёнКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerПовторение — структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс и абстрактный классАннотация типов с интерфейсамиПовторение — калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeПовторение — пользовательская коллекция3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищённые свойстваСвойства только для чтения (PHP 8.1)Повторение — менеджер банковского счёта6Полиморфизм
Повторение переопределения методовПолиморфизм через интерфейсыАннотации типов и объединённые типыПозднее статическое связываниеПовторение — обработчик платежей9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования, часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн StrategyПотренируйтесь самостоятельно: Онлайн-компилятор PHP