Трейты в PHP

Содержание
В этой статье Вы узнаете, как использовать трейты PHP для разделения функциональности между независимыми классами, которые не находятся в одной иерархии наследования.
Знакомство с трейтами PHP
Повторное использование кода - один из важнейших аспектов объектно-ориентированного программирования. В PHP вы используете наследование, чтобы обеспечить повторное использование кода в различных классах с одинаковой иерархией наследования.
Чтобы добиться повторного использования кода нужно переносите общую функциональность классов в методы родительского класса. Однако наследование делает код очень тесно связанным. Поэтому чрезмерное использование наследования может привести к тому, что код будет очень трудно поддерживать.
Для решения этой проблемы в PHP 5.4 была введена новая многократно используемая единица кода, называемая trait
. Трейты позволяют свободно использовать различные методы во многих различных классах, которые не обязательно должны находиться в одной иерархии классов.
Наследование позволяет классам повторно использовать код по вертикали, а traits позволяют классам повторно использовать код по горизонтали.
Трейт очень похож на класс, но предназначен для группирования функционала хорошо структурированным и последовательным образом. Невозможно создать самостоятельный экземпляр трейта.
Пример PHP трейта
Чтобы объявить трейт используется ключевое слово trait, за которым следует имя, как показано ниже:
trait Logger { public function log($msg) { echo '<pre>'; echo date('Y-m-d h:i:s') . ':' . '(' . __CLASS__ . ') ' . $msg . '<br/>'; echo '</pre>'; } }
Чтобы использовать трейт в классе используется ключевое слово use
. Все методы трейта доступны в классе, в котором он используется. Вызов метода черты аналогичен вызову метода экземпляра.
Следующий пример демонстрирует, как использовать трейт Logger
в классе BankAccount
:
class BankAccount { use Logger; private $accountNumber; public function __construct($accountNumber) { $this->accountNumber = $accountNumber; $this->log("A new $accountNumber bank account created"); } }
Можете повторно использовать трейт Logger
в классе User
следующим образом:
class User { use Logger; public function __construct() { $this->log('A new user created'); } }
Классы BankAccount
и User повторно используют методы трейта Logger
, который является очень гибким.
Использование нескольких трейтов
Класс может использовать несколько трейтов. Следующий пример демонстрирует использование нескольких трейтов в классе IDE. Для наглядности он имитирует модель компиляции языка C в PHP.
trait Preprocessor { public function preprocess() { echo 'Preprocess...done' . '<br />'; } } trait Compiler { public function compile() { echo 'Compile code... done' . '<br />'; } } trait Assembler { public function createObjCode() { echo 'Create the object code files... done.' . '<br />'; } } trait Linker { public function createExec() { echo 'Create the executable file...done' . '<br />'; } } class IDE { use Preprocessor, Compiler, Assembler, Linker; public function run() { $this->preprocess(); $this->compile(); $this->createObjCode(); $this->createExec(); echo 'Execute the file...done' . '<br />'; } } $ide = new IDE(); $ide->run();
Сочетание нескольких трейтов
PHP позволяет объединить несколько трейтов в один трейт с помощью оператора use в объявлении трейта. Например:
trait Reader { public function read($source) { echo sprintf('Read from %s <br>', $source); } } trait Writer { public function write($destination) { echo sprintf('Write to %s <br>', $destination); } } trait Copier { use Reader, Writer; public function copy($source, $destination) { $this->read($source); $this->write($destination); } } class FileUtil { use Copier; public function copyFile($source, $destination) { $this->copy($source, $destination); } }
Как это работает.
- Во-первых, объявляем трейты
Reader
иWriter
. - Во-вторых, обявляем новый трейт
Copier
, который состоит из трейтовReader
иWriter
. В методеcopy()
трейтаCopier
вызовем методыread()
иwrite()
трейтовReader
иWriter
. - В-третьих, используем трейт
Copier
в методеcopyFile()
классаFileUtil
для имитации копирования файла.
Переопределение трейта (конфликты)
Если класс использует несколько трейтов с одинаковым именем метода, PHP выдаст фатальную ошибку.
К счастью, можно указать PHP использовать этот метод с помощью ключевого слова inteadof
. Например:
trait FileLogger { public function log($msg) { echo 'File Logger ' . date('Y-m-d h:i:s') . ':' . $msg . '<br/>'; } } trait DatabaseLogger { public function log($msg) { echo 'Database Logger ' . date('Y-m-d h:i:s') . ':' . $msg . '<br/>'; } } class Logger { use FileLogger, DatabaseLogger { FileLogger::log insteadof DatabaseLogger; } } $logger = new Logger(); $logger->log('this is a test message #1'); $logger->log('this is a test message #2');
Оба трейта FileLogger
и DatabaseLogger
имеют один и тот же метод log()
.
В классе Logger
мы разрешили конфликт имен методов, указав, что метод log()
трейта FileLogger
будет использоваться вместо метода DatabaseLogger
.
Что если нужно использовать оба метода log() из трейтов FileLogger
и DatabaseLogger
? В этом случае можно использовать псевдоним для метода трейта в классе, который использует этот трейт.
Псевдонимы для трейтов
Используя псевдонимы для одного и того же имени метода в нескольких трейтах, можно повторно использовать все методы в этих трейтах.
Исрользуя ключевое слово as
, чтобы присвоить методу трейт другое имя в классе, использующем трейт.
В следующем примере показано, как псевдоним метода trait разрешить конфликт имен методов:
class Logger { use FileLogger, DatabaseLogger { DatabaseLogger::log as logToDatabase; FileLogger::log insteadof DatabaseLogger; } } $logger = new Logger(); $logger->log('this is a test message #1'); $logger->logToDatabase('this is a test message #2');
Метод log()
класса DatabaseLogger
имеет новое имя ( logToDatabase
) в контексте класса Logger
.
В этой статье Вы узнали, как использовать трейты PHP для повторного использования кода вне иерархии классов.