Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

まず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は使わない
sengoku
テスト原理主義者。 記事は日々の記録です。2020年は、Java。2019年は、Angular/TypeScript/ASP.NET Core/C#が多めです。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした