Это вторая часть нашего учебника по объектам в PHP. Первая часть находится тут: объекты в PHP .

Что такое наследование в PHP

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

Дочерний класс является расширением родительского класса, так как кроме унаследованных свойств и методов он содержит собственные.

Наследование устанавливается использованием слова extends при создании класса.

Простой пример:

name, возраст: $this->age, "; } } class Son extends Dad // Son по американски — сын, наследник класса Dad (отец) { public $user = "Nicer", $password = "secretNic"; public function printUserInfo() { echo "логин: $this->user, пароль: $this->password."; } } $obj = new Son(); $obj->printInfo(); $obj->printUserInfo();

Как видите, мы работаем только с экзампларом дочернего класса Son, но получаем через него доступ к методу printInfo() родительского класса Dad. В этом смысл наследования в PHP.

Слово extends дословно переводится с английского как расширять. Так что записть " class Son extends Dad " очень легко понятна, а именно: "класс Son расширяет {класс} Dad".

Пример наследования классов

Прошлый урок мы закончили на примере класса:

id = $id; $this->type = "книга"; $this->name = "Война и мир"; $this->description = "Толстая книга из нескольких томов"; $this->price = "543.26"; } function printGoods() { echo " ID товара: $this->id. Тип товара: $this->type. Название: \"$this->name\". Описание: $this->description. Цена: $this->price."; } } $Goods = new Goods(124); $Goods->printGoods();

В этом примере у нас всё работает. Класс хранит информацию о товаре и может печатать её на экран методом printGoods(). Но проект будет развиваться, и информацию о каком-то товаре понадобиться представить в виде HTML-таблицы, а о каком-то отправить на почту. Один класс не должен делать всё это, для этих целей создаются разные классы.

Наследование в PHP id = $id; $this->type = "книга"; $this->name = "Война и мир"; $this->description = "Толстая книга из нескольких томов"; $this->price = "543.26"; } } class GoodsInfo extends Goods { function printGoods() { echo "ID товара: $this->id. Тип товара: $this->type. Название: \"$this->name\". Описание: $this->description. Цена: $this->price."; } } $Goods = new GoodsInfo(124); $Goods->printGoods();

В этом примере второй класс GoodsInfo объявлен с использование ключевого слова extends , что обозначает что он является наследником класса, имя которого указано далее, в нашем случае это класс Goods.

Класс GoodsInfo называется дочерним, а класс Goods родительским. Такие названия указывают на наследственную связь этих классов.

В дочернем классе GoodsInfo мы имеем доступ ко всем свойствам и методам родительского класса.

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

Ещё раз напишу: фактически, дочерний класс представляет из себя расширение родительского класса. Ключевое слово extends , которым указывается родственная связь, так и переводится с английского - расширение.

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

Уточнение типов объектов

Теперь вот ещё о чём следует написать, о проверке типов объектов.

Разобъём наши классы на два отдельных класса:

Наследование в PHP id = $id; $this->type = "книга"; $this->name = "Война и мир"; $this->description = "Толстая книга из нескольких томов"; $this->price = "543.26"; } } class GoodsInfo // Этот класс уже не дочерний { function printGoods($Goods) // метод должен получать объект { echo "ID товара: $Goods->id. Тип товара: $Goods->type. Название: \"$Goods->name\". Описание: $Goods->description. Цена: $Goods->price."; } } $Goods = new Goods(124); $GoodsInfo = new GoodsInfo(); $GoodsInfo->printGoods($Goods); ?>

Пример работает точно так же, но на что тут нужно обратить внимание. Во-первых, область видимости свойств класса Goods стала public , иначе мы не получим к ним доступ из класса GoodsInfo, так как это уже не дочерний класс, а область видимости protected делает свойства доступными только дочерним классам.

Также обратите внимание, что область видимости была определена для метода printGoods() в классе GoodsInfo, это тоже можно делать.

Метод printGoods() на вход должен получать только объект класса Goods, и нам очень желательно проверить это. Проверим мы это так:

Public function printGoods(Goods $Goods) // метод должен получать объект { echo " ID товара: $Goods->id. Тип товара: $Goods->type. Название: \"$Goods->name\". Описание: $Goods->description. Цена: $Goods->price. "; }

Строка printGoods(Goods $Goods) , сначала идёт имя класса, которому должен соответствовать объект-аргумент, а потом уже аргумент. Если методу будет передан аргумент элементарного типа (string , integer , float , boolean) или объект другого типа, то будет сгенерирована ошибка.

Использование такого приёма очень правильно с точки зрения безопасности сценариев.

Таким образом можно проверять типы объектов и массивы, вот синтаксис для проверки массивов:

public function printGoods(array $Goods) {...}

А строки и другие элементарные типы нужно проверять специальными функциями.

Также можно требовать, чтобы аргумент был или определённого типа, либо нулевым значением (NULL).

public function printGoods(array $Goods=null) {...}

Область видимости в PHP

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

  • public - доступно всем.
  • protected - доступно этому классу и дочерним, а также их объектам.
  • private - недоступно даже дочерним классам.

Это всё про области видимости.

Работа с наследованием в PHP

Из дочернего класса можно обратиться к методу родительского класса. Для этого используется дескриптор (по английски - родитель).

Наследование в PHP id = $id; $this->type = "книга"; $this->name = "Война и мир"; $this->description = "Толстая книга из нескольких томов"; $this->price = "543.26"; } } class GoodsInfo extends Goods { protected $discount = "скидка"; function __construct($id) { parent::__construct($id); $this->discount = 10; // скидка в процентах } function discountGoods() { echo "Размер скидки: ".($this->price * ($this->discount / 100)); } } $Goods = new GoodsInfo(124); $Goods->discountGoods();

Обратите внимание, что для доступа к родительскому классу мы использовали двойное двоеточие " :: ". Обращаясь к методу к контексте класса используем " :: ", а в контексте объекта используем " -> ".

Ключевое слово parent в PHP используется при переопределении методов в дочерних классах, подробнее вы можете прочитать по ссылке.

К методу класса можно обратиться напрямую, используя двойное двоеточие " :: ". Вот пример синтакиса:

Наследование в PHP

Сначала пишем имя класса, потом двойное двоеточие, потом метод.

Резюме

Итак, мы изучили механизм наследования. Конечно, ещё много придётся изучать, данная информация лишь вводная.

Продолжим, следующий урок:

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

Классы, которые расширяют оригинальный называют "дочерними ", а родительский - "суперклассом ". У суперкласса может быть множество дочерних классов.

extends

Рассмотрим основные принципы наследования классов:

  • С помощью наследования мы можем создавать классы, которые будут содержать в себе все свойства и методы родительского класса, т.е. дочерние классы наследуют public и protected свойства и методы родительского класса.
  • Дочерний класс является расширением родительского класса, так как кроме унаследованных свойств и методов он может содержать собственные.
  • Наследование устанавливается использованием слова extends при создании класса.
  • Если у наследника нет своего конструктора (о нем писал ранее), то автоматически запустится конструктор родителя. Если в дочернем классе есть конструктор, то родительский конструктор не будет запускаться.
  • В дочерних классах могут переопределяться свойства и методы суперкласса. Определяя подкласс, мы гарантируем, что его экземпляр определяется характеристиками сначала дочернего, а затем родительского класса.

Давайте рассмотрим простой пример. Пусть у нас есть класс Ploshad , который считает площадь поверхности. Но теперь нам понадобилось рассчитывать ее объем. Оригинальный класс изменять нельзя. Создадим дочерний класс Obyem , который будет наследовать все свойства и методы Ploshad , и добавим функционал для расчета объема:

class Ploshad {
protected $width;
protected $height;
public function __construct($x, $y) {
$this->width = $x;
$this->height = $y; }
public function Square() { return $this->width * $this->height; }
}

class Obyem extends Ploshad {
protected $glybina;
public function Obyemss($g) {
$this->glybina = $g;
return $this->Square()* $this->glybina;
}
}

За счет записи class Obyem extends Ploshad мы объявили, что класс Obyem наследует свойства и методы класса Ploshad и может ими управлять. Внутри Obyem мы прописали новой свойство $glybina и метод public function Obyemss($g) . Давайте теперь рассчитаем объем и площадь:

$obj = new Obyem(2,12); //создаем объект от наследника
echo "Площадь: ".$obj->Square(); //старая функция родителяя
echo "Объем: ".$obj->Obyemss(2); //новая функция наследника

Как видно из комментариев $obj теперь может использовать как старые методы класса Ploshad, так и новые дочернего класса Obyem.

При рассчете площади интерпретатор PHP ищет конструктор в Obyem классе, если его не находи, то запускает конструктор в Ploshad. Он переопределяет значения переданных свойств(Obyem(2,12)). Затем запускается метод Square() и использует эти данные.

В случае с объемом, также запускается конструктор в Ploshad, а затем метод Obyemss($g), в которой передается высота. Внутри метода используем рассчет площади умноженный на высоту и получаем результат.

Наследование используют, когда функционал внутри одного класса становится слишком сложным для понимания и работы с ним, а также для создания исключений или разграничения типов данных. Например, у вас есть класс для выгрузки книжных товаров с множеством полей (автор, год издания, число страниц). Теперь, если вам понадобилось выводить товар с другим типом, например: DVD игры, то у него будут другие параметры (системные требования). Поэтому если класс стал напоминать два класса, лучше сделать наследование.

Интерпретатор PHP всегда сначала смотрит на дочерний класс:

  1. Есть ли конструктор. Если есть, то игнорирует конструктор родителя
  2. Есть ли свойства и методы. Если в дочернем и родительском они совпадают, то используются дочерние. Таким образом, можно полностью переписать функционал методов родительского класса.

extends parent::

parent:: - используется если в дочернем классе нужно изменить функционал родительского метода

Пример:
class PloshadProcent extends Ploshad {
function Square() {
$str = parent::Square();
$str = $str*0.1;
return $str;
}
}

Теперь при вызове, мы получим 10% от нашей площади:
$obj2 = new PloshadProcent(2,12);
echo "Площадь 10%: ".$obj2->Square(); //выдаст 2.4

Мы переопредилили метод суперкласса Square() внутри PloshadProcent

Второй и очень важный способ применения parent:: заключается в переопределении функционала конструктора внутри дочернего класса. Это можно использовать, если в суперклассе нет каких-то новых свойств, для работы:

Пусть есть класс book и в нем конструктор принимает всего 2 параметра: $title, $price.

class book {
public $title;
public $price;
function __construct($title, $price) {
$this->title = $title;
$this->price = $price;
}
}

Вдруг нам понадобилось вывести новый параметр $pages. Создадим экземпляр класса new_book и сделаем в нем свой конструктор, в котором уже будут нужные данные: $title, $price, $pages

Когда в дочернем классе определяется свой конструктор, PHP не вызывает конструктор родительского класса автоматически. Это необходимо сделать вручную в конструкторе подкласса.

Дочерний класс в своем конструкторе вызывает конструктор своего родительского класса, передавая нужные аргументы для инициализации, исполняет его, а затем выполняется код, который реализует дополнительную функциональность, в данном случае инициализирует свойство подкласса.

class new_book extends book {
public $pages;
function __construct($title, $price, $pages) {
parent::__construct($title, $price); // вызываем метод-конструктор родительского класса
$this->pages = $pages; // инициализируем свойство определенное в подклассе
}
}
$obj = new new_book("азбука", 35, 500);
echo "Книга: $obj->title | Цена: $obj->price | Страниц: $obj->pages";

Таким образом, мы можем расширять функционал суперклассов, не изменяя их самих. В следующем уроке вы узнаете

Наследование - это механизм объектно ориентированного программирования, который позволяет описать новый класс на основе уже существующего (родительского).

Класс, который получается в результате наследования от другого, называется подклассом. Эту связь обычно описывают с помощью терминов «родительский» и «дочерний». Дочерний класс происходит от родительского и наследует его характеристики: свойства и методы. Обычно в подклассе к функциональности родительского класса (который также называют суперклассом) добавляются новые функциональные возможности.

Чтобы создать подкласс, необходимо использовать в объявлении класса ключевое слово extends , и после него указать имя класса, от которого выполняется наследование:

age = $age; } function add_age () { $this->age++; } } // объявляем наследуемый класс class my_Cat extends Cat { // определяем собственный метод подкласса function sleep() { echo "
Zzzzz..."; } } $kitty = new my_Cat(10); // вызываем наследуемый метод $kitty->add_age(); // считываем значение наследуемого свойства echo $kitty->age; // вызываем собственный метод подкласса $kitty->sleep(); ?>

Подкласс наследует доступ ко всем методам и свойствам родительского класса, так как они имеют тип public . Это означает, что для экземпляров класса my_Cat мы можем вызывать метод add_age() и обращаться к свойству $age не смотря на то, что они определены в классе cat . Также в приведенном примере подкласс не имеет своего конструктора. Если в подклассе не объявлен свой конструктор, то при создании экземпляров подкласса будет автоматически вызываться конструктор суперкласса.

Обратите внимание на то, что в подклассах могут переопределяться свойства и методы. Определяя подкласс, мы гарантируем, что его экземпляр определяется характеристиками сначала дочернего, а затем родительского класса. Чтобы лучше это понять рассмотрим пример:

age"; } } class my_Cat extends Cat { public $age = 10; } $kitty = new my_Cat; $kitty->foo(); ?>

При вызове $kitty->foo() интерпретатор PHP не может найти такой метод в классе my_Cat , поэтому используется реализация этого метода заданная в классе Cat . Однако в подклассе определено собственное свойство $age , поэтому при обращении к нему в методе $kitty->foo() , интерпретатор PHP находит это свойство в классе my_Cat и использует его.

Так как мы уже рассмотрели тему про указание типа аргументов, осталось сказать о том, что если в качестве типа указан родительский класс, то все потомки для метода будут так же доступны для использования, посмотрите на следующий пример:

foo(new my_Cat); ?>

Мы можем обращаться с экземпляром класса my_Cat так, как будто это объект типа Cat , т.е. мы можем передать объект типа my_Cat методу foo() класса Cat , и все будет работать, как надо.

Оператор parent

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

Чтобы вызвать нужный метод из родительского класса, вам понадобится обратиться к самому этому классу через дескриптор. Для этой цели в PHP предусмотрено ключевое слово parent . Оператор parent позволяет подклассам обращаться к методам (и конструкторам) родительского класса и дополнять их существующую функциональность. Чтобы обратиться к методу в контексте класса, используются символы " :: " (два двоеточия). Синтаксис оператора parent:

Parent::метод_родительского_класа

Эта конструкция вызовет метод, определенный в суперклассе. Вслед за таким вызовом можно поместить свой программный код, который добавит новую функциональность:

title = $title; $this->price = $price; } } class new_book extends book { public $pages; function __construct($title, $price, $pages) { // вызываем метод-конструктор родительского класса parent::__construct($title, $price); // инициализируем свойство определенное в подклассе $this->pages = $pages; } } $obj = new new_book("азбука", 35, 500); echo "Книга: $obj->title
Цена: $obj->price
Страниц: $obj->pages"; ?>

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

Ключевое слово parent можно использовать не только в конструкторах, но и в любом другом методе, функциональность которого вы хотите расширить, достигнуть этого можно, вызвав метод родительского класса:

name}."; return $str; } } class my_Cat extends Cat { public $age = 5; function getstr() { $str = parent::getstr(); $str .= "
Возраст: {$this->age} лет."; return $str; } } $obj = new my_Cat; echo $obj->getstr(); ?>

Здесь сначала вызывается метод getstr() из суперкласса, значение которого присваивается переменной, а после этого выполняется остальной код определенный в методе подкласса.

Теперь, когда мы познакомились с основами наследования, можно, наконец, рассмотреть вопрос видимости свойств и методов.

public, protected и private: управление доступом

До этого момента мы явно объявляли все свойства как public (общедоступные). И такой тип доступа задан по умолчанию для всех методов.

Элементы класса можно объявлять как public (общедоступные), protected (защищенные) и private (закрытые). Рассмотрим разницу между ними:

  • К public (общедоступным) свойствам и методам, можно получить доступ из любого контекста.
  • К protected (защищенным) свойствам и методам можно получить доступ либо из содержащего их класса, либо из его подкласса. Никакому внешнему коду доступ к ним не предоставляется.
  • Вы можете сделать данные класса недоступными для вызывающей программы с помощью ключевого слова private (закрытые). К таким свойствам и методам можно получить доступ только из того класса, в котором они объявлены. Даже подклассы данного класса не имеют доступа к таким данным.

public - открытый доступ:

hello"; } } $obj = new human; // доступ из вызывающей программы echo "$obj->age"; // Допустимо $obj->say(); // Допустимо?>

private - доступ только из методов класса:

age"; } } $obj = new human; // напрямую из вызывающей программы доступа к закрытым данным нет echo "$obj->age"; // Ошибка! доступ закрыт! // однако с помощью метода можно выводить закрытые данные $obj->say(); // Допустимо?>

protected - защищенный доступ:

Модификатор protected с точки зрения вызывающей программы выглядит точно так же, как и private: он запрещает доступ к данным объекта извне. Однако в отличие от private он позволяет обращаться к данным не только из методов своего класса, но также и из методов подкласса.

В этом уроке мы поговорим о наследовании в ООП и о том, как оно работает в PHP. С помощью наследования можно сделать классы намного сильнее и гибче, а также сэкономить уйму времени на написание скриптов.

Мы рассмотрим следующее:

  • Концепцию наследования, и почему его полезно использовать;
  • Как один PHP класс может наследоваться от другого;
  • Как один из “детей” класса может перегружать функциональность методов своего “родителя”;
  • Работа с методами и классами final;
  • Использование абстрактных классов;
  • Работа с интерфейсами.

Готовы? Тогда вперед!

Как осуществляется наследование?

Наследование базируется на понятиях класс-родитель и класс-наследник. Используя определенный синтаксис, вы можете создать класс, который будет наследовать другой класс (становится его наследником).

На заметку: классы-родители также называют базовыми классами или супер-классами. Классы-наследники, в свою очередь, можно назвать дочерними классами или подклассами.

Когда вы создаете дочерний класс, он наследует все поля и методы своего базового класса. В дочерний класс можно добавлять дополнительные поля и методы, тем самым расширяя функциональность класса-родителя.

К примеру, в веб-приложении форума есть класс Member, у которого есть методы createPost(), editProfile(), showProfile() и др. Так как администраторы форума также являются его членами, то вы можете создать класс Administrator - дочерний класса Member. Класс Administrator наследует все поля и методы класса Member, а значит, объект класса Administrator будет вести себя точно так же, как объект Member.

Вы можете расширить функциональность класса Administrator, добавив в него такие методы, как createForum(), deleteForm() и banMember(). А если хотите назначать роли еще и администраторам, то добавьте в данный дочерний класс поле, например, $adminLevel.

Действуя таким образом, вы не захламляете класс Member методами и полями, которые не подходят для обычных участников форума, а только для администраторов. Вам также не придется копировать и вставлять методы и поля из класса Member в дочерний класс. Так что наследование подойдет вам как нельзя лучше.

На заметку: вы можете создать столько дочерних классов, сколько вам понадобится, от одного единственного супер-класса, и в каждый из них можно добавлять всё новые и новые методы и поля.

Создание дочерних классов

Итак, как же создать класс, который станет наследником другого класса, в PHP? Для этого существует ключевое слово extends:

Class ParentClass { // описание полей и методов } class ChildClass extends ParentClass { // описание дополнительных полей и методов }

Мы создали класс ParentClass, а затем класса ChildClass, который наследуется от ParentClass. ChildClass наследует все поля и методы класса ParentClass, и в него также могут быть добавлены свои поля и методы.

А теперь - пример. Создадим класс Member для воображаемого веб-форума, а затем класс Administrator - дочерний от Member:

Class Member { public $username = ""; private $loggedIn = false; public function login() { $this->loggedIn = true; } public function logout() { $this->loggedIn = false; } public function isLoggedIn() { return $this->loggedIn; } } class Administrator extends Member { public function createForum($forumName) { echo "$this->username created a new forum: $forumName
"; } public function banMember($member) { echo "$this->username banned the member: $member->username
"; } }

Как видите, класс Member содержит поле public $username и поле private $loggedIn, а также методы для входа-выхода из форума и метод для определения того, зашел ли пользователь или нет.

  • createForum($forumName) для создания нового форума с названием $forumName;
  • banMember($member) для бана пользователя $member.

На заметку: естественно, эти методы ничего не делают, так как форум наш - воображаемый. В них на страницу просто выводятся какие-то сообщения.

Давайте посмотрим на наши классы в действии. Создадим по одному объекту обоих классов, а затем вызовем некоторые из их методов:

// создаем участника форума и логиним его $member = new Member(); $member->username = "Fred"; $member->login(); echo $member->username . " is " . ($member->
"; // создаем администратора и логиним его $admin = new Administrator(); $admin->username = "Mary"; $admin->login(); echo $admin->username . " is " . ($member->isLoggedIn() ? "logged in" : "logged out") . "
"; // отобразит "Mary created a new forum: Teddy Bears" $admin->createForum("Teddy Bears"); // отобразит "Mary banned the member: Fred" $admin->banMember($member);

На странице отобразится:

Fred is logged in Mary is logged in Mary created a new forum: Teddy Bears Mary banned the member: Fred

Вот, как это работает:

  1. Сначала мы создали объект класса Member, задали имя пользователя “Fred”, залогинили его и отобразили на странице сообщение о том, что он вошел на форум.
  2. Затем создаем объект класса Administrator. Так как данный класс наследуется от Member, мы можем пользоваться всеми методами и полями этого класса для объектов класса Administrator. Мы даем имя администратору - Mary - и логиним ее, после чего отображаем сообщение о том, что она вошла.
  3. Теперь вызываем метод класса Administrator createForum(), передав в него название форума - “Teddy Bears”.
  4. Наконец, вызываем метод banMember() от объекта - админа, передав имя пользователя Fred.

В этом и заключается суть наследования в ООП. Дальше в уроке мы рассмотрим различные способы манипулирования наследованием, включая перегрузку, классы и методы final, абстрактные классы и интерфейсы.

Перегрузка родительских методов

Как вы уже увидели, при создании дочернего класса, он наследует все поля и методы своего родительского класса. Тем не менее, может возникнуть необходимость изменить функциональность методов супер-класса в дочернем классе.

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

Перегрузкой метода login() в дочернем классе, вы можете изменить данный метод по своему усмотрению.

Чтобы перегрузить метод супер-класса в дочернем классе, просто создайте в нем метод с таким же названием. Тогда при вызове метода для объектов дочернего класса, будет вызываться именно перегруженный метод, а не метод супер-класса:

Class ParentClass { public function myMethod() { // (действия) } } class ChildClass extends ParentClass { public function myMethod() { // вызывется для объекта класса ChildClass // вместо метода супер-класса MyMethod() } }

Давайте перегрузим метод login() для класса Administrator так, чтобы в файл записывались логи:

Class Member { public $username = ""; private $loggedIn = false; public function login() { $this->loggedIn = true; } public function logout() { $this->loggedIn = false; } } class Administrator extends Member { public function login() { $this->loggedIn = true; echo "Log entry: $this->username logged in
"; } } // создаем нового пользователя и логиним его $member = new Member(); $member->username = "Fred"; $member->login(); $member->logout(); // создаем нового администратора и логиним его $admin = new Administrator(); $admin->username = "Mary"; $admin->login(); // отобразит "Log entry: Mary logged in" $admin->logout();

Как видите, мы перегрузили метод login() класса Administrator, чтобы он отображал сообщения, как в файлах - логах.

Затем мы создали обычного пользователя (Fred) и администратора (Mary). При вызове метода login() от Фреда вызывается метод Member::login(). А когда мы вызываем метод от администратора Mary, вызовется метод Administrator::login(), так как PHP видит, что мы перегрузили этот метод для данного класса. На странице отобразится строка "Log entry: Mary logged in".

С другой стороны, мы не перегрузили метод logout() в дочернем классе, поэтому Member:logout() вызывается и для админов и для обычных пользователей.

Вызов метода супер-класса из дочернего класса

Когда вы перегружаете метод в дочернем классе, вам обычно не требуется изменять его полностью. Вам нужно будет использовать функциональность метода супер-класса и добавить кое-что в метод дочернего класса.

В примере, приведенном в прошлом параграфе, мы перегрузили метод login(). Но мы продублировали часть метода Member::login() в Administrator::login():

Class Administrator extends Member { public function login() { $this->loggedIn = true; echo "Log entry: $this->username logged in
"; } }

Вместо того, чтобы дублировать код, лучше вызвать метод Member::login() из Administrator::login().

Чтобы получить доступ к методу супер-класса из класса дочернего, воспользуйтесь ключевым словом parent:

Parent::myMethod();

Давайте теперь перепишем метод login() в дочернем классе так, чтобы из него вызывался тот же метод из класса-родителя, а затем добавим в него что-то новое:

Class Administrator extends Member { public function login() { parent::login(); echo "Log entry: $this->username logged in
"; } }

Это не только сокращает код, но также обеспечивает легкость его будущих корректировок. Если вам позже понадобится изменить способ, по которому логинится любой пользователь, вам потребуется подкорректировать метод login() только в классе Member, и в классе Administrator будет вызываться уже измененный метод.

Предотвращение наследования с помощью методов и классов final

В большинстве случаев, давать возможность классам расширять свою функциональность посредством наследования - хорошая практика. Это ведь часть силы ООП.

Однако бывает такое, что перегрузка методов супер-классов может привести к неполадкам, снизить безопасность, усложнить код. В этих случаях, было бы полезно запретить наследование метода или даже всего класса.

Чтобы запретить дочерним классам перегружать методы супер-класса, добавьте перед его описанием ключевое слово final. Например, вы можете запретить перегрузку метода login() класса Member по причинам усиления безопасности:

Class Member { public $username = ""; private $loggedIn = false; public final function login() { $this->loggedIn = true; } public function logout() { $this->loggedIn = false; } public function isLoggedIn() { return $this->loggedIn; } }

Если кто-то попытается наследовать класс и перегрузить данный метод:

Class NaughtyMember extends Member { public function login() { $this->loggedIn = true; // сделать что-то плохое } }

… PHP выведет сообщение об ошибке:

Fatal error: Cannot override final method Member::login()

Вы можете также запретить наследование от всего класса:

Final class Member { // от этого класса нельзя вообще наследовать }

Любая попытка создать дочерний класс данного класса, не увенчается успехом:

Fatal error: Class NaughtyMember may not inherit from final class (Member)

На заметку: несмотря на то что это больше касается Java, нежели PHP, приводит преимущества использования методов и классов final.

Работа с абстрактными классами

Абстрактный класс - это такой класс, который не может быть реализован, то есть, вы не сможете создать объект класса, если он абстрактный. Вместо этого вы создаете дочерние классы от него и спокойно создаете объекты от этих дочерних классов. Абстрактные классы представляют собой шаблоны для создания классов.

Абстрактный класс содержит один или несколько абстрактных методов. Когда вы создаете абстрактный метод в абстрактном классе, вы не добавляете в этот метод ничего. Вместо этого он должен быть описан в любом дочернем классе.

На заметку: как только вы создали хотя бы один абстрактный метод в классе, вы должны объявить этот класс как абстрактный.

Когда от абстрактного класса наследуется обычный класс, он должен реализовать все абстрактные методы класса-родителя. В противном случае, PHP сгенерирует ошибку. Так, абстрактный класс создает “правила поведения” для своих дочерних классов.

На заметку: вы можете добавлять в абстрактный класс и не абстрактные методы. Они будут обыкновенным образом наследоваться дочерними классами.

Давайте рассмотрим пример. Скажем, мы создаем веб-сайт, в котором есть как участники форума, так и покупатели онлай-магазина, который является частью нашего сайта. Так как и участники форума и покупатели - люди, мы можем создать абстрактный класс Person, в котором будут какие-то поля и методы, общие для всех пользователей сайта:

Abstract class Person { private $firstName = ""; private $lastName = ""; public function setName($firstName, $lastName) { $this->firstName = $firstName; $this->lastName = $lastName; } public function getName() { return "$this->firstName $this->lastName"; } abstract public function showWelcomeMessage(); }

Как видите, мы создали абстрактный класс, добавив в его описание ключевое слово abstract. В этом классе есть несколько свойств, общих для всех людей, - $frstName и $lastName - а также методы для инициализации и чтения значений этих полей.

В классе также есть абстрактный метод showWelcomeMessage(). Этот метод выводит приветствие, когда пользователь входит на сайт. Опять же, мы добавляем ключевое слово abstract в описание данного метода, чтобы сделать его абстрактным. Так как он абстрактный, в нем нет ни строчки кода, это просто его объявление. Тем не менее, любой дочерний класс обязан добавить и описать метод showWelcomeMessage().

Теперь давайте создадим пару классов от абстрактного класса Person:

  1. класс Member для участников форума;
  2. класс Shopper для покупателей онлайн-магазина.

Class Member extends Person { public function showWelcomeMessage() { echo "Hi " . $this->getName() . ", welcome to the forums!
"; } public function newTopic($subject) { echo "Creating new topic: $subject
"; } } class Shopper extends Person { public function showWelcomeMessage() { echo "Hi " . $this->getName() . ", welcome to our online store!
"; } public function addToCart($item) { echo "Adding $item to cart
"; } }

Как видите, каждый из них описывает метод showWelcomeMessage() из абстрактного супер-класса. Они имплементированы по-разному: в классе Member отображается сообщение "welcome to the forums", а в классе Shopper - "welcome to our online store", но это нормально. Главное то, что они оба описали данный метод.

Если бы один из них, например, Shopper, не описал метод, PHP выдал бы ошибку:

Наряду с имплементацией абстрактного метода, в каждом классе есть свои обычные методы. В Member есть метод newTopic() для создания новой темы в форуме, а в Shopper - метод addToCart() для добавления товаров в корзину.

Теперь мы можем создавать участников форума и покупателей на нашем сайте. Мы можем вызывать методы newTopic() и addToCart() от этих объектов, а также getName() и setName(), так как они наследуются от супер-класса Person.

Более того, зная, что классы Member и Shopper наследуются от Person, мы можем спокойно вызывать метод showWelcomeMessage() для обоих классов, так как он точно реализован и в том и в другом. Мы в этом уверены, так как знаем, что он был объявлен как абстрактный метод в классе Person.

Вот пример:

$aMember = new Member(); $aMember->setName("John", "Smith"); $aMember->showWelcomeMessage(); $aMember->newTopic("Teddy bears are great"); $aShopper = new Shopper(); $aShopper->setName("Mary", "Jones"); $aShopper->showWelcomeMessage(); $aShopper->addToCart("Ornate Table Lamp");

На странице отобразится:

Hi John Smith, welcome to the forums! Creating new topic: Teddy bears are great Hi Mary Jones, welcome to our online store! Adding Ornate Table Lamp to cart

Создание и использование интерфейсов

Интерфейсы во многом схожи с абстрактными классами. Интерфейс - это шаблон, который задает поведение одного или более классов.

Вот основные отличия между интерфейсами и абстрактными классами:

  1. Ни один метод не может быть описан в интерфейсе. Они все абстрактны. В абстрактном классе могут быть и не абстрактные методы.
  2. Интерфейс не может содержать полей - только методы.
  3. Класс имплементирует интерфейс, и класс наследует или расширяет другой класс.
  4. Класс может имплементировать несколько интерфейсов одновременно. Этот же класс может наследовать другой класс. Но у дочернего класса может быть только один супер-класс (абстрактный или нет).

Как и абстрактный класс, интерфейс объявляет несколько методов, которые должны быть реализованы в любом классе, который имплементирует данный интерфейс. Синтаксис выглядит так:

Interface MyInterface { public function aMethod(); public function anotherMethod(); }

Чтобы создать класс, который имплементирует тот или иной интерфейс, напишите так:

Class MyClass implements MyInterface { public function aMethod() { // (имплементация метода) } public function anotherMethod() { // (имплементация метода) } }

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

Например, веб-форум может содержать класс Member для участников форума и класс Topic для тем, создаваемых участниками форума. В отношении наследования, эти классы скорее всего не будут зависеть друг от друга, так как они выполняют совершенно разные функции.

Тем не менее, давайте предположим, что нам нужно будет доставать их и записывать в базу данных как объекты класса Member, так и объекты Topic. Для этого мы создадим интерфейс Persistable, в котором будут методы для сохранения объектов в БД и извлечения их оттуда:

Interface Persistable { public function save(); public function load(); public function delete(); }

Теперь давайте создадим класс Member и имплементируем для него интерфейс Persistable. Это значит, что в интерфейсе должны быть методы save(), load() и delete():

Class Member implements Persistable { private $username; private $location; private $homepage; public function __construct($username, $location, $homepage) { $this->username = $username; $this->location = $location; $this->homepage = $homepage; } public function getUsername() { return $this->username; } public function getLocation() { return $this->location; } public function getHomepage() { return $this->homepage; } public function save() { echo "Saving member to database
"; } public function load() { echo "Loading member from database
"; } public function delete () { echo "Deleting member from database
"; } }

Наш класс Topic также будет имплементировать данный интерфейс, поэтому в нем тоже должны быть эти три метода:

Class Topic implements Persistable { private $subject; private $author; private $createdTime; public function __construct($subject, $author) { $this->subject = $subject; $this->author = $author; $this->createdTime = time(); } public function showHeader() { $createdTimeString = date("l jS M Y h:i:s A", $this->createdTime); $authorName = $this->author->getUsername(); echo "$this->subject (created on $createdTimeString by $authorName)
"; } public function save() { echo "Saving topic to database
"; } public function load() { echo "Loading topic from database
"; } public function delete () { echo "Deleting topic from database
"; } }

На заметку: так как у нас форум - воображаемый, вместо взаимодействия с базой данных в методах save(), load() и delete() просто выводятся сообщения.

Теперь можем создать объекты классов Member и Topic, а затем вызвать их методы getUsername() и showHeader(). Более того, зная, что эти классы имплементируют интерфейс Persistable, мы можем вызывать такие методы, как save(), load() или delete():

$aMember = new Member("fred", "Chicago", "http://example.com/"); echo $aMember->getUsername() . " lives in " . $aMember->getLocation() ."
"; $aMember->save(); $aTopic = new Topic("Teddy Bears are Great", $aMember); $aTopic->showHeader(); $aTopic->save();

На странице отобразится:

Fred lives in Chicago Saving member to database Teddy Bears are Great (created on Wednesday 25th May 2011 02:19:14 AM by fred) Saving topic to database

Как видите, интерфейсы могут соединить классы, которые никак не относятся друг к другу, для определенных целей, например, записи в базу данных объектов класса. Не забудьте, что один класс может имплементировать несколько интерфейсов:

Class MyClass implements anInterface, anotherInterface { ... }

На заметку: интерфейсы - это мощное свойство ООП, и о них можно еще много чего сказать. Узнать о них больше можно в PHP документации.

Заключение

В этом уроке вы ознакомились с одним из самых мощных свойств ООП - наследованием. Вы узнали:

  1. Как работает наследование, и как его использовать для расширения классов;
  2. Как создавать дочерние классы в PHP;
  3. Почему вам может понадобиться перегружать методы в дочерних классах;
  4. Как получить доступ к методам супер-класса;
  5. Всё о методах и классах final, и почему полезно их использовать;
  6. Концепцию абстрактных классов для создания шаблонов дочерних классов;
  7. Как использовать интерфейсы, чтобы задать общую функциональность несвзянным между собой классам.

Если вы прошли все уроки из данной серии, то вы уже сможете писать сложные приложения на ООП. Поздравляю!

В следующем и последнем уроке я покажу вам супер-полезные ООП свойства, которые есть в PHP.

А пока удачного кодирования!