[PHP] こんなときどうする？trait編

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

まずtrait編です。

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

# 環境

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

```shell-session
% 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がありました。

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

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

## 問題

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

```php: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」を使うことを指示します。

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

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

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

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

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

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

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

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

## 前提

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

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

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

継承とTraitを使いました。

```php:Sayer.php
class Sayer extends Base
{
    use A;
}
```

## 問題

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

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

## 解決策

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

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

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

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


## 間違った解決策

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

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

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

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

## 前提

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

```php: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;
    }
}
```

```php: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を使うクラスを定義しました。

```php:MessageAggregator.php
class MessageAggregator
{
    use Mars, Neptune;

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

## 問題

実行してみます。

```php:
$aggregator = new MessageAggregator();

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

```text:実行結果
Message from Mars.
Message from Mars.
```

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

# 解決策

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

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

## 解決しない解決策

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

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