LoginSignup
44
28

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-08-29

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

まず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は使わない
44
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
28