PHP реализует способ повторного использования кода под названием Traits.

Трейты — это механизм повторного использования кода в языках одиночного наследования, таких как PHP. Черта предназначена для уменьшения некоторых ограничений одиночного наследования, позволяя разработчику свободно повторно использовать наборы методов в нескольких независимых классах, живущих в разных иерархиях классов. Семантика комбинации трейтов и классов определяется таким образом, чтобы уменьшить сложность и избежать типичных проблем, связанных с множественным наследованием и примесями.

Черта аналогична классу, но предназначена только для группировки функций в мелкозернистой и согласованной форме. Невозможно создать экземпляр черты сам по себе. Это дополнение к традиционному наследованию, которое обеспечивает горизонтальную композицию поведения; то есть применение членов класса без необходимости наследования.

Пример #1 Пример признака

<?php
trait ezcReflectionReturnInfo {
    function getReturnType() { /*1*/ }
    function getReturnDescription() { /*2*/ }
}

class ezcReflectionMethod extends ReflectionMethod {
    use ezcReflectionReturnInfo;
    /* ... */
}

class ezcReflectionFunction extends ReflectionFunction {
    use ezcReflectionReturnInfo;
    /* ... */
}
?>

Приоритет

Унаследованный член от базового класса переопределяется членом, вставленным признаком. Порядок приоритета заключается в том, что члены текущего класса переопределяют методы Trait, которые, в свою очередь, переопределяют унаследованные методы.

Пример #2 Пример приоритетного порядка

Унаследованный метод от базового класса переопределяется методом, вставленным в MyHelloWorld из черты SayWorld. Поведение такое же для методов, определенных в классе MyHelloWorld. Порядок приоритета заключается в том, что методы текущего класса переопределяют методы Trait, которые, в свою очередь, переопределяют методы базового класса.

<?php
class Base {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait SayWorld {
    public function sayHello() {
        parent::sayHello();
        echo 'World!';
    }
}

class MyHelloWorld extends Base {
    use SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
?>

Приведенный выше пример выведет:

Привет, мир!

Пример #3 Пример альтернативного порядка приоритета

<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

class TheWorldIsNotEnough {
    use HelloWorld;
    public function sayHello() {
        echo 'Hello Universe!';
    }
}

$o = new TheWorldIsNotEnough();
$o->sayHello();
?>

Приведенный выше пример выведет:

Здравствуй Вселенная!

Несколько признаков

В класс можно вставить несколько признаков, перечислив их в use операторе, разделенных запятыми.

Пример #4 Использование нескольких трейтов

<?php
trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World';
    }
}

class MyHelloWorld {
    use Hello, World;
    public function sayExclamationMark() {
        echo '!';
    }
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
?>

Приведенный выше пример выведет:

Привет, мир!

Разрешение конфликтов

Если два трейта вставляют метод с одинаковым именем, возникает фатальная ошибка, если конфликт явно не разрешен.

Чтобы разрешить конфликты имен между трейтами, используемыми в одном и том же классе, insteadofнеобходимо использовать оператор для выбора ровно одного из конфликтующих методов.

Поскольку это позволяет исключать только методы, as оператор можно использовать для добавления псевдонима к одному из методов. Обратите внимание, что asоператор не переименовывает метод и не влияет ни на какой другой метод.

Пример #5 Разрешение конфликта

В этом примере Talker использует черты A и B. Поскольку A и B имеют конфликтующие методы, он определяет использование варианта smallTalk из черты B и варианта bigTalk из черты A.

Aliased_Talker использует asоператор, чтобы иметь возможность использовать реализацию bigTalk B под дополнительным псевдонимом talk.

<?php
trait A {
    public function smallTalk() {
        echo 'a';
    }
    public function bigTalk() {
        echo 'A';
    }
}

trait B {
    public function smallTalk() {
        echo 'b';
    }
    public function bigTalk() {
        echo 'B';
    }
}

class Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
    }
}

class Aliased_Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
        B::bigTalk as talk;
    }
}
?>

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

Используя asсинтаксис, можно также настроить видимость метода в экспонирующем классе.

Пример #6 Изменение видимости метода

<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

// Change visibility of sayHello
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

// Alias method with changed visibility
// sayHello visibility not changed
class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}
?>

Трейты, составленные из трейтов

Точно так же, как классы могут использовать черты, так же могут использовать и другие черты. Используя один или несколько признаков в определении признака, он может быть частично или полностью составлен из членов, определенных в этих других признаках.

Пример #7 Трейты, составленные из трейтов

<?php
trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World!';
    }
}

trait HelloWorld {
    use Hello, World;
}

class MyHelloWorld {
    use HelloWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
?>

Приведенный выше пример выведет:

Привет, мир!

Члены абстрактных признаков

Трейты поддерживают использование абстрактных методов для наложения требований на демонстрирующий класс. Поддерживаются общедоступные, защищенные и частные методы. До PHP 8.0.0 поддерживались только общедоступные и защищенные абстрактные методы.

Осторожность

Конкретный класс выполняет это требование, определяя конкретный метод с тем же именем; его подпись может быть другой.

Пример #8. Экспресс-требования с помощью абстрактных методов

<?php
trait Hello {
    public function sayHelloWorld() {
        echo 'Hello'.$this->getWorld();
    }
    abstract public function getWorld();
}

class MyHelloWorld {
    private $world;
    use Hello;
    public function getWorld() {
        return $this->world;
    }
    public function setWorld($val) {
        $this->world = $val;
    }
}
?>

Статические трейты-члены

Черты могут определять статические переменные, статические методы и статические свойства.

Примечание :

Начиная с PHP 8.1.0, вызов статического метода или доступ к статическому свойству непосредственно в трейте устарели. К статическим методам и свойствам следует обращаться только в классе, используя трейт.

Пример #9 Статические переменные

<?php
trait Counter {
    public function inc() {
        static $c = 0;
        $c = $c + 1;
        echo "$c\n";
    }
}

class C1 {
    use Counter;
}

class C2 {
    use Counter;
}

$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1
?>

Пример #10 Статические методы

<?php
trait StaticExample {
    public static function doSomething() {
        return 'Doing something';
    }
}

class Example {
    use StaticExample;
}

Example::doSomething();
?>

Пример #11 Статические свойства

<?php
trait StaticExample {
    public static $static = 'foo';
}

class Example {
    use StaticExample;
}

echo Example::$static;
?>

Свойства

Черты также могут определять свойства.

Пример #12 Определение свойств

<?php
trait PropertiesTrait {
    public $x = 1;
}

class PropertiesExample {
    use PropertiesTrait;
}

$example = new PropertiesExample;
$example->x;
?>

Если трейт определяет свойство, то класс не может определить свойство с тем же именем, если оно не совместимо (такая же видимость и начальное значение), в противном случае выдается фатальная ошибка.

Пример #13 Разрешение конфликтов

<?php
trait PropertiesTrait {
    public $same = true;
    public $different = false;
}

class PropertiesExample {
    use PropertiesTrait;
    public $same = true;
    public $different = true; // Fatal error
}
?>