Трейты в 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 для повторного использования кода вне иерархии классов.

Написать комментарий