Объектно-ориентированное программирование в WordPress – Наследование. Часть II
Объектно-ориентированное программирование в WordPress – Наследование. Часть I
Объектно-ориентированное программирование в WordPress – Наследование. Часть II
В предыдущей статье мы представили концепт объектно-ориентированного наследования, попытались описать его с точки зрения не профессионалов, а затем рассмотрели высокоуровневую концептуальную модель того, как наследование работает в контексте программирования.
Да, мы уже охватили многое, но для того, чтобы заложить основу для начинающих — прочный фундамент, отталкиваясь от которого можно было бы начать писать объектно-ориентированный код на PHP, нужно многое изучить.
Наследование — это предмет обсуждения, в котором мы начинаем погружаться уже во вспомогательные темы, поэтому данная статья является заключительным обзором базовых концепций, после которого мы завершим нашу серию обобщающей статьей.
Обзор наследования
Напомним, что мы определили наследование следующим образом:
Наследование — это механизм, при котором один класс служит в качестве родительского для дочернего класса, что обеспечивает ряд свойств и методов, общих для родителя и для ребенка; при этом ребенок обладает способностью вводить свои собственные свойства.
Это определение немного менее формально, чем то, которое вы можете найти в учебниках, или даже в Википедии, но оно объясняет идею в терминах, которые иллюстрируют суть наследования.
В этой статье, мы рассмотрим весь необходимый код, функции и зарезервированные слова, относящиеся к теме; мы обсудим то, как можно реализовать наследование на PHP очень-очень простым платформо-независимым способом, а затем мы рассмотрим фактическую реализацию наследования в WordPress.
Итак, план действий намечен, давайте приступим.
Возможности PHP
Для того чтобы реализовать наследование в объектно-ориентированном PHP, существует ряд зарезервированных слов, с которыми нам нужно познакомиться. К счастью, большинство слов мы уже рассмотрели, а те, что остались, достаточно понятны, чтобы легко их запомнить.
Поэтому прежде чем мы погрузимся в разбор кода, давайте рассмотрим все зарезервированные слова языка, которые нам нужно знать, чтобы мы могли действительно приступить к созданию чего-либо:
- extends — это зарезервированное слово, которое показывает, что один класс является ребенком другого класса. Например, в нашей предыдущей статье, класс Post расширяет Content. Мы скоро увидим это на деле;
- private — это атрибут, который применяется к свойствам и функциям, и означает, что они доступны только в контексте класса, в котором они объявлены;
- protected похоже на ключевое слово private за тем исключением, что свойства и методы, объявленные таким образом, будут доступны данному классу и любым дочерним классам;
- public имеет значение, противоположное private — любой класс: данный класс, подкласс и любой сторонний класс, могут получить доступ к этому свойству или методу для того, чтобы изменить содержащуюся в нем информацию или вызывать функцию.
Вам также нужно знать оператор ::, но мы обсудим его в этой статье немного позже, когда начнем разбирать код.
Вот и все — ничего ужасно сложного, не так ли? И к тому же, если вы следили вместе с нами за происходящим в этой серии статей, то вероятно, вы знакомы со всеми словами за исключением extends.
Как бы то ни было, давайте начнем разбирать пример.
Некоторые примеры кода
Для того чтобы начать писать код, нам необходимо точно выяснить что же мы собираемся моделировать (и, в конце концов, что код делает, не так ли?).
Чтобы оставаться последовательным и поддержать связь со всей серией статей — особенно с последней — у нас будет родительский класс Content и два подкласса, которые мы назовем Comment и Post соответственно.
Это позволит нам посмотреть, как свойства и методы функционируют в рамках отдельного класса, и как дочерние классы могут получить доступ к атрибутам их родителей, а также как родители могут защитить свои свойства и функции от своих подклассов.
Однако практическая реализация может продемонстрировать гораздо больше, чем просто слова, поэтому давайте начнем писать код.
Родительский класс
В нашем примере родительский класс — это класс Content, так как оба его подкласса, Post и Comment, являются типами контента, которые имеют уникальную информацию, относящуюся к ним, но не связанную с классом Content.
Главное в наследовании — это выявить все свойства и методы, общие для всех классов и поместить их определение в родительском классе, в нашем случае, в классе Content.
Хотя данная часть может варьироваться в зависимости от того, как к этому подойти, мы зададим класс Content так, что он будет содержать в себе следующую информацию:
- дата опубликования контента;
- автор;
- метод сохранения контента;
- метод форматирования контента;
- метод извлечения контента;
- метод получения автора.
Сначала, давайте посмотрим код, а затем разберем все, что в нем происходит:
<?php
class Content {
protected $publish_date;
protected $author;
private $content;
public function __construct() {
date_default_timezone_set( 'GMT' );
$date = new DateTime();
$this->publish_date = $date->format( 'Y-m-d H:i:s' );
$this->author = '';
}
public function save( $author, $content ) {
$this->author = $author;
$this->content = $this->format_content( $content );
$this->content;
}
public function read() {
return $this->content;
}
private function format_content( $content ) {
return strip_tags( stripslashes( $content ) );
}
public function get_author() {
return $this->author;
}
}
Как отмечалось выше, у нас есть два атрибута типа protected и один private атрибут. Напомню, что это значит, что все подклассы могут получить доступ к переменным $publish_date и $author, но к атрибуту $content может обращаться только экземпляр класса Content.
Также прошу заметить, что большая часть кода, которую вы видите в классе, приведенном выше, является базовым объектно-ориентированным кодом, написанным на PHP. Там нет ничего, что имело бы прямое отношение к наследованию, кроме некоторых модификаторов доступа, которые мы объявили. То есть можно сказать, что этот код похож на тот, что мы уже видели в уроках.
Стоит отметить, что private функция используется, чтобы продемонстрировать две вещи:
- К private функциям можно обращаться только в контексте класса, где они были объявлены.
- Все теги и слеши будут удалены из контента, так что разметка не сохраниться вместе с контентом.
Конечно, этот код не подключается к базе данных или файловой системе или чему-либо еще, но суть от этого не меняется.
Обратите внимание, что в коде, приведенном выше, есть пара вещей, которые нам пришлось добавить, чтобы соблюсти требования PHP. Это выходит за рамки данной статьи, но стоит отметить следующее:
- Код date_default_time_set необходим, чтобы установить часовой пояс, для которого будет извлекаться время.
- Конструктор ответственен за начальную установку даты публикации контента, кроме того, свойство author в нем инициализируется пустой строкой. Это сделано потому, что объекты классов Post и Comment могут иметь своих собственных авторов. И как мы увидим далее, экземпляр Comment может даже переопределить дату публикации, заданную по умолчанию.
Отмечу также, что мы можем извлечь контент с помощью метода read и получить автора посредством функции get_author.
Первый ребенок
Давайте продолжим. Создадим подкласс Post. Сначала взглянем на код, а потом посмотрим, как класс Post взаимодействует с классом Content, который мы только что создали:
<?php
class Post extends Content {
public function __construct() {
parent::__construct();
$this->author = 'Tom McFarlin';
}
public function post( $content ) {
$this->format_content( $content );
}
}
Класс оказался небольшим, правда? В нем нет свойств, потому что он наследует их от класса Content, и есть только две функции, одна из которых является уникальной для класса — функция post.
Обратите внимание, что в конструкторе мы сначала вызываем родительский конструктор с помощью оператора «::». Вы можете найти более подробную информацию об этом в руководстве, но достаточно сказать, что оператор зарезервирован для ссылки на разные вещи вне класса, в котором он определен. В нашем случае, это вызов родительского конструктора.
Затем, я решил установить свое имя в качестве автора поста. Заметьте, что я использую ключевое слово $this. Так как подкласс наследует свойства от своего родителя, он может обращаться и к этим свойствам, и к свойствам, определенным с тем же именем внутри него самого.
Отмечу, что это возможно не только потому, что класс Post расширяет Content, но и также потому, что свойство отмечено, как protected в классе Content. Если бы оно имело идентификатор private, это было бы невозможно.
Второй ребенок
Теперь, когда мы создали класс Post, у нас остался класс Comment, который, напомню, представляет кого-то, кто оставил комментарий к посту.
Если бы это был код реального проекта, то его было бы намного больше: нам нужно было бы соотнести комментарий с постом, определить, является ли комментарий ответом на существующий комментарий, отметить статус комментария и так далее.
Однако чтобы показать, как работает наследование, мы не останавливаемся на всех этих деталях, а фокусируемся только на том, что демонстрирует основные идеи:
<?php
class Comment extends Content {
public function __construct() {
parent::__construct();
}
public function add( $comment ) {
$this->save( 'John Doe', $comment );
}
}
Как вы можете увидеть, код класса Comment не сильно отличается от кода класса Post. В какой-то степени это хорошо, потому что показывает, что мы сделали верное абстрагирование соответствующих частей в нашем базовом классе.
Как бы то ни было, обратите внимание, что после создания объекта класса Comment, мы вызываем наш родительский конструктор. Затем мы определяем метод add, который ответственен за принятие входящего комментария и его сохранения путем передачи автора комментария и содержимого в метод save.
Примечательно, что метод save уже определен в базовом классе, который также обрабатывает все форматирование с помощью private функции. Таким образом, мы получили необходимый функционал, создав наш дочерний класс.
Примеры
Когда все готово, давайте выполним пару примеров, чтобы показать, как отдельные части функционируют друг с другом. Для того чтобы убедиться, что код выполняется, вам потребуется веб-сервер, каталог, из которого будут запускаться PHP скрипты и текстовый редактор.
Сначала мы создадим экземпляр класса Content, а затем обратимся к отладочной информации, чтобы увидеть, что собой представляет экземпляр класса:
$content = new Content();
var_dump( $content );
Если все работает правильно, вы должны увидеть то, что приведено выше.
Двигаемся дальше. Создадим экземпляр класса Post. Поскольку мы задаем всю информацию внутри самого класса, все, что нам действительно нужно сделать, это вызвать функцию класса для отображения информации.
Например:
$post = new Post();
echo 'The post author is: ' . $post->get_author();
Опять же, так как мы задали все в самом коде, вызов метода просто демонстрирует концепцию.
Наконец, мы можем создать экземпляр класса Comment, вызвать для него метод add, попытаться передать подозрительный код (только чтобы посмотреть как он будет обработан нашей программой). Если все идет как надо, вы должны увидеть следующее:
$comment = new Comment();
$comment->add( '<script type="text/javascript">alert("This is my comment.");</script>' );
echo 'The comment reads: ' . $comment->read();
Вот и все: наша простая демонстрация наследования завершена.
Наследование в WordPress
Когда речь заходит о наследовании в WordPress, первая мысль, которая приходит мне в голову (и вероятно, другим разработчикам) — это Widgets API. И причиной этому служит то, что API функционирует на основе наследования.
Безусловно, виджеты могут быть созданы и без использования API, но я убежден, что это ошибка в разработке. Зачем все усложнять, если уже создано основание для работы? Но я отвлекся.
Особенностью этого конкретного API является то, что он демонстрирует все основные моменты объектно-ориентированного подхода и наследования в работе. К примеру, вот отрывок кода, взятого непосредственно из кодекса WordPress:
<?php
class My_Widget extends WP_Widget {
/**
* Установка имени виджетов
*/
public function __construct() {
// widget actual processes
}
/**
* Вывод содержимого виджета
*
* @param array $args
* @param array $instance
*/
public function widget( $args, $instance ) {
// вывод содержимого виджета
}
/**
* Вывод параметров для администратора
*
* @param array $instance параметры виджета
*/
public function form( $instance ) {
// вывод параметров для администратора
}
/**
* Обработка параметров виджета при сохранении
*
* @param array $new_instance Новые параметры
* @param array $old_instance Предыдущие параметры
*/
public function update( $new_instance, $old_instance ) {
// обработка параметров виджета для сохранения
}
}
Теперь, когда мы обсудили концептуальную модель, рассмотрели ключевые слова и методологию, написали собственный код и создали свой пример, в остальном будет относительно легче разобраться.
Но вот в чем дело: один из лучших способов совершенствоваться в написании любого типа кода — непрерывно практиковаться в концепциях. То есть изучать идеи, реализованные другими людьми, которые сделали более сложные вещи, чем вы в предыдущих работах.
В качестве примера посмотрите первый пример из кодекса WordPress. А если вы работаете с последней версией PHP, которая поддерживает такие функции, как пространства имен (немного более сложная тема), тогда обратитесь также ко второму примеру.
Чем больше вы просматриваете код и копаетесь в нем, тем больше вы узнаете. Но если мы углубимся дальше, то выйдем за рамки всей серии статей.