PHP :: Специальные виды классов
Абстрактные классы в PHP
В PHP поддерживается определение абстрактных классов и методов, т.е. некоторых наиболее общих шаблонов, которые в основном имеют описательный смысл и задают характерные особенности всех наследуемых классов-потомков. При этом, если в классе объявляется хотя бы один абстрактный метод, то и сам класс должен быть объявлен абстрактным. Также нельзя создавать экземпляры (объекты) абстрактного класса, а методы, объявленные абстрактными, не должны включать конкретной реализации. Объявляются абстрактные классы и методы при помощи служебного слова abstract (см. пример №1).
<?php //Объявили абстрактный класс abstract class abstract_class{ //Абстрактный, т.е. без реализации abstract protected function return_value(); //Абстрактный, т.е. 'пустой' abstract protected function get_name($name); //Обычный общий метод наследуемый public function common_method(){ //всеми классами-потомками return $this->return_value(); } } class concrete_class_1 extends abstract_class{ //Реализуем (заполняем) метод protected function return_value() { return 'Реализация метода в классе-потомке concrete_class_1'.'<br>'; } //Расширяем область видимости метода public function get_name($first_name){ return "{$first_name}".'<br>'; } } class concrete_class_2 extends abstract_class{ //Расширяем область видимости метода public function return_value(){ return 'Реализация метода в классе-потомке concrete_class_2'.'<br>'; } //Расширяем область видимости метода и добавляем 1 необязательный параметр public function get_name($first_name, $last_name=' Иванов '){ return "{$first_name} {$last_name}".'<br>'; } } //Создаем экземпляр первого класса-потомка $obj_1 = new concrete_class_1; echo $obj_1->common_method(); echo $obj_1->get_name(' Иван '); //Создаем экземпляр второго класса-потомка $obj_2 = new concrete_class_2; echo $obj_2->common_method(); //Используется значение по умолчанию echo $obj_2->get_name(' Петр '); echo $obj_2->get_name(' Петр ', ' Сидоров'); ?>
Пример №1. Использование абстрактных классов и методов
При наследовании от абстрактного класса все методы, объявленные абстрактными в родительском классе, должны быть определены и в классе-потомке, а их область видимости, как и в случае с обычными методами, должна оставаться той же или быть менее строгой. Например, если абстрактный метод был объявлен как protected, то область видимости реализации этого метода в классе-потомке должна быть либо protected либо public, но никак не private. Также следует помнить, что должны совпадать типы данных при использовании контроля типов и количество обязательных аргументов метода. И хотя разрешается добавлять необязательные аргументы (т.е. аргументы, которые используют значения по умолчанию), количество обязательных аргументов в любом случае должно совпадать (см. пример №1).
Интерфейсы в PHP
Еще одной разновидностью общих шаблонов, использующихся в PHP, являются интерфейсы. Они служат для указания методов без необходимости описывания их функционала. При этом все методы, определенные в интерфейсе, должны быть реализованы в наследующих данный интерфейс классах, иметь область видимости public (и в интерфейсе, и в классе), а их тела должны быть пустыми. Но самой важной особенностью интерфейсов является то, что классы могут реализовывать (наследовать) сразу несколько интерфейсов, перечисляемых при наследовании через запятую (соответственно, должны быть реализованы и все методы наследуемых интерфейсов).
Объявляются интерфейсы при помощи отдельного ключевого слова interface, но в случае необходимости расширения, могут быть унаследованы друг от друга также, как и обычные классы, т.е. при помощи оператора extends. Если же интерфейс наследуется (реализуется) обычным классом, а не другим интерфейсом, должен использоваться не оператор extends, а специально предназначенный для такого случая оператор implements (см. пример №2).
<?php //Объявляем первый интерфейс interface my_intf_1{ //Объявляем константу интерфейса const c_1="Константа интерфейса 1"; //Метод должен принимать 2 аргумента public function my_func_1($a, $b); } //Объявляем второй интерфейс interface my_intf_2{ //Метод должен принимать 1 аргумент public function my_func_2($d); } //Расширяем интерфейс interface my_intf_3 extends my_intf_1, my_intf_2{ //Метод без аргументов public function my_func_3(); } //Объявляем еще один интерфейс interface my_intf_4{ //Объявляем константу интерфейса const c_4="Константа интерфейса 4"; } //Реализуем расширенный интерфейс class my_class implements my_intf_3, my_intf_4{ //Объявляем константу класса const c_2="Константа класса"; //Реализуем первый метод public function my_func_1($a, $b){ return $a+$b; } //Реализуем второй метод public function my_func_2($d){ echo $this->my_func_1($d,5).'<br>'; } //Реализуем третий метод public function my_func_3(){ $this->my_func_4(); } //Объявляем закрытый метод класса private function my_func_4(){ echo 'Строка из my_func_4'.'<br>'; } } $obj= new my_class(); //Выведет '10' $obj->my_func_2(5); //Выведет 'Строка из my_func_4' $obj->my_func_3(); //Выведет все константы echo $obj::c_1.'<br>'.$obj::c_4.'<br>'.$obj::c_2; ?>
Пример №2. Использование интерфейсов
Реализовывать в одном классе два интерфейса, содержащих одноименную функцию, запрещено, т.к. это повлечет за собой неоднозначность. Кроме того, сигнатуры методов в классе, реализующем интерфейс, должны точно совпадать с сигнатурами, используемыми в интерфейсе, иначе будет вызвана ошибка.
Отметим, что интерфейсы могут также содержать константы, которые работают так же, как и константы обычных классов, за исключением того, что они не могут быть переопределены наследующим классом или интерфейсом.
Трейты в PHP
Для уменьшения некоторых ограничений единого наследования в PHP используются специальные конструкции, называемые трейтами, которые представляют собой наборы свойств и методов доступных для последующего свободного и многократного использования сразу в нескольких независимых классах. Трейты очень похожи на классы, но не могут иметь собственных объектов и дают возможность использовать свои свойства и методы классами свободно без необходимости наследования (см. пример №3).
<?php //Трейты объявляются при помощи служебного слова trait trait tr_1{ //Объявили свойство трейта public $var_tr_1='Свойство 1-го трейта'; //Объявили метод трейта public function func_tr_1(){ echo 'Метод 1-го трейта'.'<br>'; } } //Объявили второй трейт trait tr_2{ static $var_tr_2='Статическое свойство 2-го трейта '; //Объявили статический метод трейта public static function func_tr_2(){ echo 'Статический метод 2-го трейта'.'<br>'; } } //Объявили третий трейт trait tr_3{ //Подключаем первый и второй трейты use tr_1, tr_2; //Объявили абстрактный метод трейта public abstract function func_tr_3(); } //Объявили четвертый трейт trait tr_4{ //Объявили абстрактный метод трейта public abstract function func_tr_4(); } //Объявили первый класс class base_class_1{ //Подключаем первый трейт use tr_1; //Объявили метод первого класса public function func_bs_cl_1(){ echo 'Метод класса base_class_1'.'<br>'; } } //Объявили класс-потомок первого класса class child_class_1 extends base_class_1{ //Подключаем второй трейт use tr_2; //Объявили метод класса public function func_cld_cl_1(){ echo 'Метод класса-потомка child_class_1'.'<br>'; } } //Объявили второй класс class base_class_2{ //Объявили свойство второго класса public $var_bs_cl_2='Св-во 2-го класса'; //Подключаем третий и четвертый трейты use tr_3,tr_4; //Абстрактный метод должен быть реализован public function func_tr_3(){ echo 'Реализация абстр. метода func_tr_3'.'<br>'; } //Абстрактный метод должен быть реализован public function func_tr_4(){ echo 'Реализация абстр. метода func_tr_4'.'<br>'; } } //Создали объект класса-потомка child_class_1 $obj_cld_cl_1=new child_class_1(); //Выведет 'Метод 1-го трейта' $obj_cld_cl_1->func_tr_1(); //Выведет 'Статический метод 2-го трейта' $obj_cld_cl_1::func_tr_2(); //Выведет 'Метод класса base_class_1' $obj_cld_cl_1->func_bs_cl_1(); //Выведет 'Метод класса-потомка child_class_1' $obj_cld_cl_1->func_cld_cl_1(); //Выведет 'Свойство 1-го трейта' echo $obj_cld_cl_1->var_tr_1.'<br>'; //Выведет 'Статическое свойство 2-го трейта' echo $obj_cld_cl_1::$var_tr_2.'<br>'; //Создали объект класса base_class_2 $obj_bs_cl_2=new base_class_2(); //Выведет 'Реализация абстр. метода func_tr_3' $obj_bs_cl_2->func_tr_3(); //Выведет 'Реализация абстр. метода func_tr_4' $obj_bs_cl_2->func_tr_4(); //Выведет 'Св-во 2-го класса' echo $obj_bs_cl_2->var_bs_cl_2.'<br>'; //Плюс свойства и методы всех подключенных ко второму классу трейтов //Выведет 'Метод 1-го трейта $obj_bs_cl_2->func_tr_1(); ' //Выведет 'Статический метод 2-го трейта' $obj_bs_cl_2::func_tr_2(); //Выведет 'Свойство 1-го трейта' echo $obj_bs_cl_2->var_tr_1.'<br>'; //Выведет 'Статическое свойство 2-го трейта' echo $obj_bs_cl_2::$var_tr_2; ?>
Пример №3. Использование трейтов
Как было показано в примере, трейты объявляются и подключаются, соответственно, при помощи ключевых слов trait и use. В случае подключения нескольких трейтов сразу, они перечисляются через запятую. При этом разрешается использовать трейты не только в классах, но и подключать их к другим трейтам, при помощи того же оператора use.
При формировании трейтов разрешается использовать абстрактные методы, а также статические свойства и методы, но объявлять константы в трейтах нельзя.
Следует помнить, что область видимости всех методов и свойств в трейтах должна быть определена как public, а далее, при использовании в классах, она может быть изменена в зависимости от конкретных потребностей класса.
В случае объявления в классе свойства с именем, совпадающим с именем свойства присутствующего в подключаемом трейте, возникнет ошибка. Тоже самое произойдет и при подключении к классу двух трейтов, имеющих методы с одинаковыми именами. Однако в этом случае ошибки можно избежать, использовав оператор insteadof, который позволяет выбрать из конфликтных методов лишь один. Если же необходимо включить в класс сразу оба метода, следует использовать один из них под другим именем при помощи оператора as, который к тому же позволяет изменить и область видимости подключаемого метода (см. пример №4).
<?php trait tr_1{ //Объявили свойство трейта public $var_tr_1='Св-во трейта'; //Объявили метод трейта public function func_a(){ echo 'Метод func_a 1-го трейта'.'<br>'; } //Объявили метод трейта public function func_b(){ echo 'Метод func_b 1-го трейта'.'<br>'; } } //Объявили второй трейт trait tr_2{ //Имя используется и в tr_1 public function func_a(){ echo 'Метод func_a 2-го трейта'.'<br>'; } //Имя используется и в tr_1 public function func_b(){ echo 'Метод func_b 2-го трейта'.'<br>'; } //Еще один метод 2-го трейта public function func_c(){ echo 'Метод func_c 2-го трейта'.'<br>'; } } //Объявили класс class base_class{ //Ошибка, свойство уже определено в трейте // public $var_tr_1='Свойство класса'; use tr_1, tr_2{ //Будем использовать метод первого трейта tr_1::func_a insteadof tr_2; //Переопределяем его на protected tr_1::func_a as protected; //Будем использовать метод второго трейта tr_2::func_b insteadof tr_1; //Метод 2-го трейта используем под именем //func_a_2 с областью видимости protected tr_2::func_a as protected func_a_2; } //Объявили собственный метод класса,который переопределит метод 2-го трейта public function func_c(){ echo 'Метод класса base_class'.'<br>'; } } //Объявили класс-потомок class child_class extends base_class{ //Объявили собственный метод класса-потомка public function func_d(){ //Вызываем метод родительского класса parent::func_a(); } //Объявили еще один метод класса-потомка public function func_e(){ //Вызываем метод родительского класса parent::func_a_2(); } //Переопределяем метод родительского класса public function func_b(){ echo 'Метод класса child_class'.'<br>'; } } //Создали объект класса base_class $obj_bs_cl=new base_class(); //Выведет 'Метод func_b 2-го трейта ' $obj_bs_cl->func_b(); //Выведет 'Метод класса base_class ' $obj_bs_cl->func_c(); //Создали объект класса-потомка $obj_cld_cl=new child_class(); //Выведет 'Метод класса child_class ' $obj_cld_cl->func_b(); //Выведет 'Метод func_a 1-го трейта ' $obj_cld_cl->func_d(); //Выведет 'Метод func_a 2-го трейта ' $obj_cld_cl->func_e(); //Выведет 'Метод класса base_class ' $obj_cld_cl->func_c(); ?>
Пример №4. Разрешение конфликтов в трейтах
Обратите внимание, что в примере трейт, который был использован в родительском классе, не переопределяет одноименный метод этого класса. Если же трейт вставляется в класс-потомок, то методы трейта, наоборот, имеют больший приоритет и переопределяют одноименные унаследованные методы родительского класса, хотя собственные методы текущего класса-потомка они по-прежнему не переопределяют.
Анонимные классы в PHP
В PHP 7 была добавлена поддержка анонимных классов, которые могут быть полезны в случае необходимости одноразового использования, например, для возврата функцией значения в виде определенного объекта с требуемым набором свойств и методов. При этом анонимные классы могут иметь конструкторы, расширять другие классы, реализовывать интерфейсы или подключать трейты точно также, как и обычные классы (см. пример №5).
<?php //Создаем трейт trait tr_5_minus{ //Определяем один метод public function _5_minus($n){ echo $this->m-$n.'<br>'; } } //Создаем интерфейс interface int_5_mult{ //Задаем шаблон метода public function _5_plus($n); } //Создаем объект анонимного класса $obj_5=new class(5) implements int_5_mult{ //Подключаем интерфейс use tr_5_minus; //Объявляем свойство класса public $m; //Объявляем конструктор класса function __construct($arg){ //Устанавливает первый член операций $this->m=$arg; } //Реализуем интерфейс public function _5_plus($n){ echo $this->m+$n.'<br>'; } //Создаем собственный метод public function _5_mult($n){ echo $this->m*$n.'<br>'; } }; //Не забываем про точку с запятой!!! //Выведет '-1' $obj_5->_5_minus(6); //Выведет '11' $obj_5->_5_plus(6); //Выведет '30' $obj_5->_5_mult(6); ?>
Пример №5. Анонимные классы
Анонимные классы могут использоваться также внутри других классов. При этом в общем случае у них отсутствует доступ к свойствам и методам внешнего класса, которые имеют область видимости private или protected. Поэтому, чтобы получить доступ к ним, анонимный класс должен расширять внешний класс. В этом случае закрытые и защищенные методы становятся доступны напрямую, а свойства должны передаваться анонимному классу через его конструктор (см. пример №6).
<?php //Создаем внешний класс class outer_class{ //Объявили свойство внешнего класса protected $n_out=2; //Определяем первый метод outer_class public function func_ins_1(){ //Здесь $this представляет внешний класс echo (new class($this->n_out) extends outer_class{ //Объявили свойство анонимного класса public $n_ins; //Конструктор function __construct($arg){ //А здесь $this уже относится к анонимному классу $this->n_ins=$arg*$arg; } //И сразу же обращаемся к свойству созданного //объекта анонимного класса })->n_ins;.'<br>' } //Определяем второй метод outer_class public function func_ins_2(){ //Здесь $this представляет внешний класс return new class($this->n_out) extends outer_class{ //Объявили константу анонимного класса const a=5; //Объявили свойство анонимного класса public $n_ins; //Конструктор function __construct($arg){ //А здесь $this уже относится к анонимному классу $this->n_ins=$arg*$arg; } }; //Возвращаем готовый анонимный объект } } $obj=new outer_class(); //Выведет '4' $obj->func_ins_1(); //Выведет '4' echo $obj->func_ins_2()->n_ins.'<br>'; //Выведет '5' echo $obj->func_ins_2()::a; ?>
Пример №6. Использование анонимных классов внутри других классов
Таким образом, конструкция (new class(){}); представляет собой неименованный объект с определенным набором свойств и методов, который может быть использован как сразу в паре с различными операторами, например, echo или return, так и сохранен в обычной переменной для доступа к его свойствам и методам позднее.
Быстрый переход к другим страницам
- Определение области видимости свойств и методов в PHP
- Специальные виды классов в PHP
- Операции с объектами в PHP
- Вернуться к оглавлению учебника