
Reg.ru: домены и хостинг
Крупнейший регистратор и хостинг-провайдер в России.
Более 2 миллионов доменных имен на обслуживании.
Продвижение, почта для домена, решения для бизнеса.
Более 700 тыс. клиентов по всему миру уже сделали свой выбор.

Бесплатный Курс "Практика HTML5 и CSS3"
Освойте бесплатно пошаговый видеокурс
по основам адаптивной верстки
на HTML5 и CSS3 с полного нуля.

Фреймворк Bootstrap: быстрая адаптивная вёрстка
Пошаговый видеокурс по основам адаптивной верстки в фреймворке Bootstrap.
Научитесь верстать просто, быстро и качественно, используя мощный и практичный инструмент.
Верстайте на заказ и получайте деньги.
*Наведите курсор мыши для приостановки прокрутки.
Позднее статическое связывание: ключевое слово static
Перед изучением данной статьи вы можете прочитать предыдущую статью из этой серии - "Абстрактные классы и интерфейсы в PHP".
После того, как мы рассмотрели абстрактные классы и интерфейсы, самое время снова обратиться к статическим методам.
Вы уже знаете, что статический метод можно использовать в качестве фабрики, создающей экземпляры объектов того класса, в котором содержится этот метод. Если вы такой же ленивый программист, как и я, то вас должны раздражать повторы кода наподобие тех, что приведены ниже.
abstract class DomainObject { } class User extends DomainObject { public static function create() { return new User(); } } class Document extends DomainObject { public static function create() { return new Document(); } }
Сначала я создал суперкласс под именем DomainObject. Само собой разумеется, что в реальном проекте в нем будет находиться функциональность, общая для всех дочерних классов.
После этого я создал два дочерних класса User и Document. Я хотел, чтобы в каждом из моих конкретных классов находился метод create().
* Примечание
Почему для создания конкретного объекта я воспользовался статическим методом-фабрикой, а не оператором new и конструктором объекта?
Дело в том, что существует шаблон под названием Identity Map (материал по нему скоро будет доступен) и он создает и инициализирует новый объект только в том случае, если объект с аналогичными отличительными особенностями еще не создан. Если таковой объект существует, то возвращается просто ссылка на него.
Статический метод-фабрика наподобие рассмотренного выше метода create() является отличными кандидатом для реализации подобной функциональности.
Созданный мною код прекрасно работает, но в нем есть досадный недостаток - дублирование. Мне совсем не нравится повторять однотипный код наподобие того, что приведен выше, для каждого создаваемого дочернего объекта, расширяющего класс DomainObject. Как насчет того, чтобы переместить метод create() в суперкласс?
abstract class DomainObject { public static function create() { return new self(); } } class User extends DomainObject { } class Document extends DomainObject { } Document::create();
Ну что ж, все выглядит круто! Теперь весь общий код сосредоточен в одном месте, и чтобы обратиться к текущему классу, я воспользуюсь ключевым словом self.
Однако насчет ключевого слова self я сделал допущение, что оно должно так работать. На самом деле оно не работает для классов так же, как псевдопеременная $this для объектов.
С помощью ключевого слова self нельзя сослаться на вызывающий объект. Оно используется только для разрешения ссылок на содержащий класс, в контексте которого вызывается метод. Поэтому при попытке запуска приведенного выше примера получим следующее сообщение об ошибке:
PHP Fatal error: Cannot instantiate abstract class DomainObject in...
Переводится это так: невозможно создать экземпляр абстрактного класса.
Таким образом, ключевое слово self трансформируется в ссылку на класс DomainObject, в котором определен метод create(), а не на класс Document, для которого этот метод должен быть вызван.
До появления PHP 5.3 это было серьезным ограничением языка, которое породило массу неуклюжих обходных решений.
В PHP 5.3 впервые введена концепция позднего статического связывания (late static bindings). Самым заметным ее проявлением является введение нового (в данном контексте) ключевого слова static. Оно аналогично ключевому слову self, за исключением того, что относится к вызывающему, а не содержащему классу.
Итак, теперь я смогу воспользоваться всеми преимуществами наследования в статическом контексте.
abstract class DomainObject { public static function create() { return new static(); } } class User extends DomainObject { } class Document extends DomainObject { } print_r(Document::create());
В результате будет выведено следующее:
Document Object ( )
Ключевое слово static можно использовать не только для создания объектов. Так же, как и self и parent, его можно использовать как идентификатор для вызова статических методов даже из нестатического контекста.
Например, я хочу реализовать идею группировки моих классов DomainObject. По умолчанию все классы попадают в категорию "default". Но для некоторых веток иерархии наследования моих классов мне нужно это переопределить.
abstract class DomainObject { private $group; public function __construct() { $this->group = static::getGroup(); } public static function create() { return new static(); } static function getGroup() { return "default"; } } class User extends DomainObject { } class Document extends DomainObject { static function getGroup() { return "document"; } } class SpreadSheet extends Document { } print_r(User::create()); print_r(SpreadSheet::create());
Здесь в класс DomainObject я ввел конструктор, в котором используется ключевое слово static для вызова метода getGroup(). Стандартное значение группы сосредоточено в классе DomainObject, но оно переопределяется в классе Document.
Я также создал новый класс SpreadSheet, расширяющий класс Document. Вот что получим в результате.
User Object ( [group: DomainObject:private] => default ) SpreadSheet Object ( [group: DomainObject:private] => document )
Все происходящее с классом User не настолько очевидно, поэтому требует пояснений.
В конструкторе класса DomainObject вызывается метод getGroup(), который интерпретатор находит в текущем классе. Несмотря на это, в случае с классом SpreadSheet поиск метода getGroup() начинается не с класса DomainObject, а с класса SpreadSheet, для которого из метода create() был вызван стандартный конструктор.
Поскольку в классе SpreadSheet реализиция метода getGroup() не предусмотрена, интерпретатор вызывает аналогичный метод класса Document (т.е. идет вверх по иерархии объектов).
Таким образом, до появления PHP 5.3 и позднего статического связывания, здесь у нас возникала проблема из-за использования ключевого слова self, которое находило метод getGroup() только в классе DomainObject. Теперь же этот вопрос можно считать решенным.
На этом я завершаю данную статью. В следующем материале мы поговорим об обработке ошибок в PHP.
Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!
Смотрите также: