Если видео недоступно для просмотра, попробуйте выключить блокировщик рекламы.

В отличие от $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)
Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →