Menu
Coddy logo textTech

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

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

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

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

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

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