[PHP] こんなときどうする?trait編
こんなときどうしよう?という話をまとめようかと思い立ちました。
まずtrait編です。
続きがあるかはわかりません。
環境
環境です。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.
アレ!?
海王星からのメッセージがどこかへいってしまいました。
ラブレターが届くはずなのに
解決策
残念ながらありません。
届くはずだったラブレターは闇の中。
これが元となって別れ話に。。。
解決しない解決策
コーディング規約を整備しましょうか。
- trait内ではプロパティを定義しない
- 外部由来のtraitは使わない