Трейты

28 февраля 2016
Трейты — это конструкция, похожая на класс (содержит методы и свойства, области доступа, может иметь абстрактные методы).

Особенности трейтов:
  • Не могут вызываться напрямую, но могут множественно наследоваться классами и другими трейтами;
  • Объявляется с помощью ключевого слова trair;
  • Подключаются к классу при помощи оператора use;
  • Методы трейта переопределяют методы родительских классов.

Пример двух трейтов, используемых в одном классе:
//Трейт Лицо
trait sp_person
{
	public $name = '';
	public $surname = '';

	//Возврашает заголовок лица (абстрактная функция)
	abstract public function get_person_caption();
}

//Трейт Контакты
trait sp_contacts
{
	private $contacts = array();

	// Устанавливает значение контакта
	// @param string $code - код контакта
	// @param string $value - значение контакта
	// @return bool
	public function set_contact($code, $value)
	{
		$code = trim($code);
		if($code=='')
		{
			return false;
		}
		else
		{
			$this->contacts[$code] = $value;
			return true;
		}
	}

	// Возвращает значение контакта
	// @param string $code - код контакта
	// @return string
	public function get_contact($code)
	{
		$code = trim($code);
		if($code!='' && isset($this->contacts[$code])) return $this->contacts[$code];
		else return NULL;
	}

	// Возвращает массив с контактными данными
	// @return array
	public function get_contacts()
	{
		return $this->contacts;
	}
}

//Работник
class worker
{
	use sp_person, sp_contacts;

	// Возврашает заголовок лица
	// @return string
	public function get_person_caption()
	{
		$caption = array();
		if($this->name!='') $caption[] = $this->name;
		if($this->surname!='') $caption[] = $this->surname;
		if(count($caption)==0) $caption[] = 'Аноним';

		return implode(' ', $caption);
	}
}


$w = new worker();
$w->name = 'Иван';
$w->surname = 'Иванов';
echo $w->get_person_caption(); //Выведет 'Иван Иванов'

$w->set_contact('telephone', '+77777777777');
$w->set_contact('email', 'test@test.ru');
$w->set_contact('country', 'Россия');


/*
Будет выведен массив:
Array
(
    [telephone] => +77777777777
    [email] => test@test.ru
    [country] => Россия
)
*/
echo '<pre>'.print_r($w->get_contacts(), true).'</pre>';

Совпадение свойств

При подключении к классу трейтов, у которых есть свойства с одинаковым именем, возникнет ошибка ТРЕЙТ_1 and ТРЕЙТ_2 define the same property ($ИМЯ_СВОЙСТВА) in the composition of words. However, the definition differs and is considered incompatible. Но ошибка не возникнет, если совпадающие свойства имеют одинаковое начальное значение и область видимости.

Совпадение методов

Если в трейтах совпадают имена методов, то при подключении этих трейтов к классу возникнет фатальная ошибка Trait method ИМЯ_МЕТОДА_ТРЕЙТА has not been applied, because there are collisions with other trait methods on ИМЯ_КЛАССА. Для обхода такой ситуации в конструкции use с помощью оператора insteadof указывают какой метод из трейта должен использоваться вместо дублирующего метода.
trait word_ru
{
	public static function hello()
	{
		return 'Привет';
	}
}

trait word_en
{
	public static function hello()
	{
		return 'Hello';
	}
}

class words
{
	use word_ru, word_en
	{
		word_ru::hello insteadof word_en; //Использовать word_ru::hello вместо такого же метода в word_en
	}
}

echo words::hello(); //Выведет 'Привет'

Изменение области видимости метода и создание синонима

Для изменения области видимости метода, а так же для создания синонима (псевдонима) метода используется оператор as, который указывается в теле use при подключении трейтов. Пример кода:
trait lang_ru
{
	private static function get_hello()
	{
		return 'Привет';
	}
}

class words
{
	use lang_ru
	{
		get_hello as protected;	//Делаем метод защищённым
		get_hello as public hello;	//Создаём открытый синоним функции
	}
}

echo words::hello(); //Выведет 'Привет'
echo words::get_hello(); //Выведет ошибку 'Fatal error: Uncaught Error: Call to protected method words::get_hello() from context