Зарегистрируйтесь, чтобы продолжить обучение

Позднее статическое связывание PHP: Погружаясь в классы

В отличие от $this, обращение к статическим свойствам и методам через self работает по принципу раннего связывания (обратное позднему). Другими словами, self указывает на тот класс, в котором идёт обращение:

<?php

class A
{
    public static function who()
    {
        echo 'A';
    }
    public static function test()
    {
        self::who();
    }
}

class B extends A
{
    public static function who()
    {
        echo 'B';
    }
}

B::test();
// 'A'

В этом примере статический метод test() вызывает self::who(), который находится внутри класса A. Поэтому self указывает на сам A. Никакая иерархия наследования не может изменить это поведение. Такой код равносилен прямому указанию класса:

<?php

A::who(); // 'A'

Подобное поведение можно рассматривать как ограничение. Фактически оно игнорирует факт наследования. Позднее связывание же открывает доступ к некоторым возможностям, которые широко эксплуатируются в некоторых других языках, таких как Ruby. Прежде чем вдаваться в технические детали, нужно немного разобраться с тем, какую роль играют статические свойства и методы в классах.

Данные в статических свойствах относятся к классу в целом. Они "описывают" его. Самый распространённый пример из веба — это связь сущности с таблицей в базе данных, где она хранится:

<?php

class User
{
    // Очень важно делать их неизменяемыми!
    // protected, чтобы было доступно из родителя
    protected static $table = 'users';
}

Почему эту информацию нужно хранить в статическом свойстве? Потому что она не принадлежит конкретному объекту. Представьте, что вы просто хотите узнать из кода, в какую таблицу будут сохраняться пользователи. Если бы эта информация не была статической, то пришлось бы создавать объект только ради того, чтобы узнать ответ на наш вопрос. Это бессмысленно.

Для сохранения сущности в базу данных обычно используются библиотеки ORM (Object-Relationship Mapping), которые, в том числе, знают, как сохранять сущности в базу и как извлечь их. Большинство из них построено на наследовании. Любая сущность должна наследоваться от специального базового класса, который содержит в себе общую логику для работы с базой данных:

<?php

class User extends BaseEntity
{
    // Код
}

$user = new User();
// Сохранение в базу
$user->save();

Для сохранения пользователя в базу, недостаточно знать, что сохранять, нужно ещё понимать, куда сохранять. И эта информация записана в статическое свойство.

<?php

class User extends BaseEntity
{
    protected static $table = 'users';
}

Возникает вопрос, можно ли добраться до неё из базового класса BaseEntity ? Гипотетический код:

<?php

class BaseEntity
{

    public static function getTable()
    {
        return self::$table;
    }

    public function save()
    {
        // Валидация данных

        // Подготовка запроса
        // сработает ли этот код?
        self::getTable();
    }
}

User::getTable(); // Error

Проблема в том, что такой код не сработает. self сошлётся на BaseEntity и вызов save() завершится с ошибкой, так как в этом классе нет статического свойства $table. Из этой ситуации есть два выхода. Первый — отказаться от статического свойства. Это не правильно с точки зрения семантики, но хотя бы будет работать. Второй — добавить в язык позднее связывание и для статики.

Позднее статическое связывание было добавлено в PHP 5.3.0. Но как это часто бывает, разработчикам языка пришлось найти компромисс. Просто так поменять self было нельзя, слишком много кода могло сломаться, поэтому они добавили новое ключевое слово static. Его поведение идентично self, за исключением связывания.

<?php

class BaseEntity
{
    public static function getTable()
    {
        return static::$table;
    }

    public function save()
    {
        // Какой-то код
        self::getTable();
        // Дальнейшая обработка
    }
}

Теперь вызов self::getTable() вернёт значение статического свойства $table, определённого в том классе, с объектом которого идёт работа прямо сейчас.

<?php

User::getTable(); // 'users'

Обратите внимание на то, что сам вызов self::getTable() остался старым. В данном случае static не обязателен, так как сам метод определён в базовом классе. Но если бы мы хотели дать возможность переопределять его в подклассах, то было бы логично поменять вызов на static::getTable().


Дополнительные материалы

  1. Позднее связывание (Wiki)

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка веб-приложений на Laravel
10 месяцев
с нуля
Старт 26 декабря

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»