Специальные виды классов в PHP
http://belarusweb.net
Основы создания сайтов

Специальные виды классов в PHP

Абстрактные классы в PHP

В PHP поддерживается определение абстрактных классов и методов, т.е. некоторых наиболее общих шаблонов, которые в основном имеют описательный смысл и задают характерные особенности всех наследуемых классов-потомков. При этом, если в классе объявляется хотя бы один абстрактный метод, то и сам класс должен быть объявлен абстрактным. Также нельзя создавать экземпляры (объекты) абстрактного класса, а методы, объявленные абстрактными, не должны включать конкретной реализации. Объявляются абстрактные классы и методы при помощи служебного слова abstract (см. пример №1).

PHP Результат BwCodes
<?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 Результат BwCodes
<?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 Результат BwCodes
<?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 Результат BwCodes
<?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 Результат BwCodes
<?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 Результат BwCodes
<?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, так и сохранен в обычной переменной для доступа к его свойствам и методам позднее.

Быстрый переход к другим страницам