トレイト
PHP は、コードを再利用するための「トレイト」という仕組みを実装しています。
トレイトは、PHP のような単一継承言語でコードを再利用するための仕組みのひとつです。 トレイトは、単一継承の制約を減らすために作られたもので、 いくつかのメソッド群を異なるクラス階層にある独立したクラスで再利用できるようにします。 トレイトとクラスを組み合わせた構文は複雑さを軽減させてくれ、 多重継承や Mixin に関連するありがちな問題を回避することもできます。
トレイトはクラスと似ていますが、トレイトは単にいくつかの機能をまとめるためだけのものです。 トレイト自身のインスタンスを作成することはできません。 昔ながらの継承に機能を加えて、振る舞いを水平方向で構成できるようになります。 つまり、継承しなくてもクラスのメンバーに追加できるようになります。
例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 トレイトから追加されたメソッドで上書きされます。 この挙動は、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
}
?>
User Contributed Notes 25 notes
Unlike inheritance; if a trait has static properties, each class using that trait has independent instances of those properties.
Example using parent class:
<?php
class TestClass {
public static $_bar;
}
class Foo1 extends TestClass { }
class Foo2 extends TestClass { }
Foo1::$_bar = 'Hello';
Foo2::$_bar = 'World';
echo Foo1::$_bar . ' ' . Foo2::$_bar; // Prints: World World
?>
Example using trait:
<?php
trait TestTrait {
public static $_bar;
}
class Foo1 {
use TestTrait;
}
class Foo2 {
use TestTrait;
}
Foo1::$_bar = 'Hello';
Foo2::$_bar = 'World';
echo Foo1::$_bar . ' ' . Foo2::$_bar; // Prints: Hello World
?>
The best way to understand what traits are and how to use them is to look at them for what they essentially are: language assisted copy and paste.
If you can copy and paste the code from one class to another (and we've all done this, even though we try not to because its code duplication) then you have a candidate for a trait.
Note that the "use" operator for traits (inside a class) and the "use" operator for namespaces (outside the class) resolve names differently. "use" for namespaces always sees its arguments as absolute (starting at the global namespace):
<?php
namespace Foo\Bar;
use Foo\Test; // means \Foo\Test - the initial \ is optional
?>
On the other hand, "use" for traits respects the current namespace:
<?php
namespace Foo\Bar;
class SomeClass {
use Foo\Test; // means \Foo\Bar\Foo\Test
}
?>
Together with "use" for closures, there are now three different "use" operators. They all mean different things and behave differently.
I have not seen this specific use case:
"Wanting to preserve action of parent class method, the trait one calling ::parent & also the child class mehod action".
// Child class.
use SuperTrait {
initialize as initializeOr;
}
public function initialize(array &$element) {
...
$this->initializeOr($element);
}
// Trait.
public function initialize(array &$element) {
...
parent::initialize($element);
}
// Parent class.
public function initialize(array &$element) {
...
}
Another difference with traits vs inheritance is that methods defined in traits can access methods and properties of the class they're used in, including private ones.
For example:
<?php
trait MyTrait
{
protected function accessVar()
{
return $this->var;
}
}
class TraitUser
{
use MyTrait;
private $var = 'var';
public function getVar()
{
return $this->accessVar();
}
}
$t = new TraitUser();
echo $t->getVar(); // -> 'var'
?>
It may be worth noting here that the magic constant __CLASS__ becomes even more magical - __CLASS__ will return the name of the class in which the trait is being used.
for example
<?php
trait sayWhere {
public function whereAmI() {
echo __CLASS__;
}
}
class Hello {
use sayWHere;
}
class World {
use sayWHere;
}
$a = new Hello;
$a->whereAmI(); //Hello
$b = new World;
$b->whereAmI(); //World
?>
The magic constant __TRAIT__ will giev you the name of the trait
Keep in mind; "final" keyword is useless in traits when directly using them, unlike extending classes / abstract classes.
<?php
trait Foo {
final public function hello($s) { print "$s, hello!"; }
}
class Bar {
use Foo;
// Overwrite, no error
final public function hello($s) { print "hello, $s!"; }
}
abstract class Foo {
final public function hello($s) { print "$s, hello!"; }
}
class Bar extends Foo {
// Fatal error: Cannot override final method Foo::hello() in ..
final public function hello($s) { print "hello, $s!"; }
}
?>
But this way will finalize trait methods as expected;
<?php
trait FooTrait {
final public function hello($s) { print "$s, hello!"; }
}
abstract class Foo {
use FooTrait;
}
class Bar extends Foo {
// Fatal error: Cannot override final method Foo::hello() in ..
final public function hello($s) { print "hello, $s!"; }
}
?>
Here is an example how to work with visiblity and conflicts.
<?php
trait A
{
private function smallTalk()
{
echo 'a';
}
private function bigTalk()
{
echo 'A';
}
}
trait B
{
private function smallTalk()
{
echo 'b';
}
private function bigTalk()
{
echo 'B';
}
}
trait C
{
public function smallTalk()
{
echo 'c';
}
public function bigTalk()
{
echo 'C';
}
}
class Talker
{
use A, B, C {
//visibility for methods that will be involved in conflict resolution
B::smallTalk as public;
A::bigTalk as public;
//conflict resolution
B::smallTalk insteadof A, C;
A::bigTalk insteadof B, C;
//aliases with visibility change
B::bigTalk as public Btalk;
A::smallTalk as public asmalltalk;
//aliases only, methods already defined as public
C::bigTalk as Ctalk;
C::smallTalk as cmallstalk;
}
}
(new Talker)->bigTalk();//A
(new Talker)->Btalk();//B
(new Talker)->Ctalk();//C
(new Talker)->asmalltalk();//a
(new Talker)->smallTalk();//b
(new Talker)->cmallstalk();//c