В отличие от $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()
.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.