Traits

Оновлено: 11.05.2023

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;
    /* ... */
}
?>

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

Приклад #2 Приклад порядку пріоритету

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

<?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();
?>

У клас можна вставити декілька трейтів, перерахувавши їх в інструкції використання, розділивши комами.

Приклад #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 $different1 = false;
    public bool $different2;
    public bool $different3;
}

class PropertiesExample {
    use PropertiesTrait;
    public $same = true;
    public $different1 = true; // Fatal error
    public string $different2; // Fatal error
    readonly public bool $different3; // Fatal error
}
?>

Починаючи з версії PHP 8.2.0, риси можуть також визначати константи.

Приклад #14 Визначення констант

<?php
trait ConstantsTrait {
    public const FLAG_MUTABLE = 1;
    final public const FLAG_IMMUTABLE = 5;
}

class ConstantsExample {
    use ConstantsTrait;
}

$example = new ConstantsExample;
echo $example::FLAG_MUTABLE; // 1
?>

Якщо риса визначає константу, то клас не може визначати константу з таким самим іменем, якщо тільки вони не є сумісними (однакова видимість, початкове значення та скінченність), інакше буде видано фатальну помилку.

Приклад #15 Вирішення конфліктів

<?php
trait ConstantsTrait {
    public const FLAG_MUTABLE = 1;
    final public const FLAG_IMMUTABLE = 5;
}

class ConstantsExample {
    use ConstantsTrait;
    public const FLAG_IMMUTABLE = 5; // Fatal error
}
?>