Menu
Coddy logo textTech

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

Часть раздела Объектно-ориентированное программирование путешествия по 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::, если хочешь, чтобы дочерние классы правильно наследовали и настраивали статическое поведение.

challenge icon

Задание

Легко

Давайте создадим систему реестра моделей, демонстрирующую возможности позднего статического связывания. Вы создадите базовый класс 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()

?>
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор PHP