トレイト

PHP は、コードを再利用するための「トレイト」という仕組みを実装しています。

トレイトは、PHP のような単一継承言語でコードを再利用するための仕組みのひとつです。 トレイトは、単一継承の制約を減らすために作られたもので、 いくつかのメソッド群を異なるクラス階層にある独立したクラスで再利用できるようにします。 トレイトとクラスを組み合わせた構文は複雑さを軽減させてくれ、 多重継承や Mixin に関連するありがちな問題を回避することもできます。

トレイトはクラスと似ていますが、トレイトは単にいくつかの機能をまとめるためだけのものです。 トレイト自身のインスタンスを作成することはできません。 昔ながらの継承に機能を加えて、振る舞いを水平方向で構成できるようになります。 つまり、継承しなくてもクラスのメンバーに追加できるようになります。

例1 トレイトの例

<?php
trait ezcReflectionReturnInfo {
function
getReturnType() { }
function
getReturnDescription() { }
}

class
ezcReflectionMethod extends ReflectionMethod {
use
ezcReflectionReturnInfo;

}

class
ezcReflectionFunction extends ReflectionFunction {
use
ezcReflectionReturnInfo;

}
?>

優先順位

基底クラスから継承したメンバーよりも、トレイトで追加したメンバーのほうが優先されます。 優先順位は現在のクラスのメンバーが最高で、その次がトレイトのメソッド、 そしてその次にくるのが継承したメソッドとなります。

例2 優先順位の例

基底クラスから継承したメソッドは、MyHelloWorld に SayWorld トレイトから追加されたメソッドで上書きされます。 この挙動は、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();
?>

上の例の出力は以下となります。

 Hello World! 

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

上の例の出力は以下となります。

 Hello Universe! 

複数のトレイト

複数のトレイトをひとつのクラスに追加するには、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();
?>

上の例の出力は以下となります。

 Hello World! 

衝突の解決

同じ名前のメンバーを含む複数のトレイトを追加するときには、 衝突を明示的に解決しておかないと fatal エラーが発生します。

同一クラス内での複数のトレイト間の名前の衝突を解決するには、 insteadof 演算子を使って そのうちのひとつを選ばなければなりません。

この方法はひとつのメソッドだけしか使えませんが、 as 演算子を使うと、 メソッドのいずれかにエイリアスを追加できます。 as 演算子はメソッドをリネームするわけではないので、 その他のメソッドにも何も影響を及ぼさないことに注意しましょう。

例5 衝突の解決

この例では、Talker がトレイト A と B を使います。 A と B には同じ名前のメソッドがあるので、 smallTalk はトレイト B を使って bigTalk はトレイト A を使うように定義します。

Aliased_Talker は、as 演算子を使って B の bigTalk の実装に 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!';
}
}

// sayHello のアクセス権を変更します
class MyClass1 {
use
HelloWorld { sayHello as protected; }
}

// アクセス権を変更したエイリアスメソッドを作ります
// sayHello 自体のアクセス権は変わりません
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();
?>

上の例の出力は以下となります。

 Hello World! 

トレイトのメンバーの抽象化

トレイトでは、抽象メソッドを使ってクラスの要件を指定できます。 アクセス権は public, protected, private をサポートしています。 PHP 8.0.0 より前のバージョンでは、 public と protected な抽象メソッドだけがサポートされていました。

警告

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;
}
}
?>

トレイトの static メンバー

トレイトでは、static 変数、static メソッド、static プロパティを定義できます。

注意:

PHP 8.1.0 以降では、トレイトにある static メソッドや、 static プロパティに直接アクセスすることは、 推奨されなくなりました。 これらは、トレイトを使っているクラスからのみアクセスすべきものです。

例9 static変数

<?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(); // 1 と表示
$p = new C2(); $p->inc(); // 1 と表示
?>

例10 staticメソッド

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

class
Example {
use
StaticExample;
}

Example::doSomething();
?>

例11 staticプロパティ

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

トレイトでプロパティを定義したときは、 クラスではそれと互換性 (公開範囲と型とreadonlyの有無、そして初期値が同じ) がない同じ名前のプロパティを定義できません。 互換性がない名前を定義すると、致命的なエラーが発生します。

例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 protected 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
?>

トレイトで定数を定義したときは、 クラスではそれと互換性 (公開範囲と初期値、そして final の有無が同じ) がない同じ名前の定数を定義できません。 互換性がない名前を定義すると、致命的なエラーが発生します。

例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
}
?>
To Top