こんなときどうしよう?という話をまとめようかと思い立ちました。

まずtrait編です。

続きがあるかはわかりません。 :sweat_smile:

環境

環境です。OSはUbuntu、PHPは7.0です。

% php -v
PHP 7.0.22-0ubuntu0.17.04.1 (cli) (built: Aug  8 2017 22:03:30) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.0.22-0ubuntu0.17.04.1, Copyright (c) 1999-2017, by Zend Technologies

同じ名前のメソッドが定義された複数のTraitを使いたい

前提

例えば、こんな風に同じ名前のsayメソッドを持つTraitがありました。

A.php

trait A
{
    public function say()
    {
        echo "I am A\n";
    }
}

B.php

trait B
{
    public function say()
    {
        echo "I am B\n";
    }
}

問題

2つのTraitを単純にuseすると……

Sayer.php

class Sayer
{
    use A, B;
}

Fatalエラーになります。

PHP Fatal error:  Trait method say has not been applied, because there are collisions with other trait methods on Sayer

解決策(1) - ひとつのTraitだけ使えればいい場合

insteadofを使って、「Bの代わりにA」を使うことを指示します。

Sayer.php

class Sayer
{
    use A, B {
        A::say insteadof B;
    }
}

usage

$sayer = new Sayer();
$sayer->say();
# 実行結果:
# I am A

解決策(2) - 全部のTraitを使いたい場合

asを使って別名にします。

Sayer.php

class Sayer
{
    use A, B {
        A::say as sayA;
        B::say as sayB;
    }

    public function say()
    {
        $this->sayA();
        $this->sayB();
    }
}

usage

$sayer = new Sayer();
$sayer->say();
# 実行結果:
# I am A
# I am B

基底クラスと同名のメソッドが定義されたTraitを使いたい

前提

こんな基底クラスとTraitがありました。どちらもsayメソッドを持っています。

Base.php

class Base
{
    public function say()
    {
        echo "I am Base\n";
    }
}

A.php

trait A
{
    public function say()
    {
        echo "I am A\n";
    }
}

継承とTraitを使いました。

Sayer.php

class Sayer extends Base
{
    use A;
}

問題

基底クラスではなく、Traitが呼ばれます。

usage

$sayer = new Sayer();
$sayer->say();
# 実行結果:
# I am A

解決策

Traitのほうは別名にしてオーバーライドします。

Sayer.php

class Sayer extends Base
{
    use A {
        A::say as sayA;
    }

    public function say()
    {
        parent::say();
    }
}

usage

$sayer = new Sayer();
$sayer->say();
# 実行結果:
# I am Base

間違った解決策

別名にするだけでは、Traitのほうが呼ばれます。

Sayer.php

class Sayer extends Base
{
    use A {
        A::say as sayA;
    }
}

usage

$sayer = new Sayer();
$sayer->say();
$sayer->sayA();
# 実行結果:
# I am A
# I am A

同じ名前のプロパティ(メンバ変数)が定義されたtraitを使いたい

前提

例えば、private $__cacheというプロパティを持つtraitが2つありました。

Mars.php

trait Mars
{
    private $__cache;

    /**
     * 火星からのメッセージを読み込みます(キャッシュ付き)
     */
    public function loadMessageFromMars()
    {
        if (!empty($this->__cache)) {
            return $this->__cache;
        }

        $message = 'Message from Mars.';
        $this->__cache = $message;

        return $this->__cache;
    }
}

Neptune.php

trait Neptune
{
    private $__cache;

    /**
     * 海王星からメッセージを読み込みます(キャッシュ付き)
     */
    public function loadMessageFromNeptune()
    {
        if (!empty($this->__cache)) {
            return $this->__cache;
        }

        $message = 'Love Letter from Neptune.';
        $this->__cache = $message;

        return $this->__cache;
    }
}

この2つのtraitを使うクラスを定義しました。

MessageAggregator.php

class MessageAggregator
{
    use Mars, Neptune;

    public function loadMessages() {
        yield $this->loadMessageFromMars();
        yield $this->loadMessageFromNeptune();
    }
}

問題

実行してみます。


$aggregator = new MessageAggregator();

foreach ($aggregator->loadMessages() as $i) {
    echo $i, "\n";
}

実行結果

Message from Mars.
Message from Mars.

アレ!?
海王星からのメッセージがどこかへいってしまいました。
ラブレターが届くはずなのに :cry:

解決策

残念ながらありません。 :scream:

届くはずだったラブレターは闇の中。
これが元となって別れ話に。。。 :sob:

解決しない解決策

コーディング規約を整備しましょうか。

  • trait内ではプロパティを定義しない
  • 外部由来のtraitは使わない