PHPで多重継承をする(PHP5.4以降 trait利用)
こんにちは、株式会社ライオンハートの鵜飼です。
コチラのブログも書いてくれる人が増えてきたので賑やかになってきました。最初に書かせてもらっていた自分としては嬉しい限りです。細々と続けていますが、誰かの目に止まり、その方にとって役に立つ情報となっていれば幸いです。
それでは今回ですが、また技術寄りの話になりまして、PHP5.4で追加されていたようなのですが、最近まで知らなかったtrait
という機能についての内容です。ちゃんと最新情報をおってないとダメですね…。
継承について
同じような機能を持つオブジェクトが沢山ある時に、継承を利用すると非常に便利なのですが、2つのオブジェクトを継承しようとすると、PHPのextends
は1つしか指定することが出来ません。
<?php abstract class ClassA { public function hoge() { echo 'HOGE'; } } abstract class ClassB { public function fuga() { echo 'FUGA'; } } class ClassAB extends ClassA, ClassB // <- こう書きたいけどエラーが発生する { }
interface
では宣言しか出来ないですし、どうにか2つのオブジェクトの処理を継承させたい…こういった場合に、trait
を利用すると解決することが出来ます。(厳密には通常の継承とは異なりますが…)
traitを利用する
一見に如かず、ということで、実際に記述した場合はこのようになります。
<?php trait ClassA { public function hoge() { echo 'HOGE'; } } trait ClassB { public function fuga() { echo 'FUGA'; } } class ClassAB { use ClassA, ClassB; } $classAB = new ClassAB; $classAB->hoge(); $classAB->fuga();
または、extends
と併用することも可能です。
<?php abstract class ClassA { public function hoge() { echo 'HOGE'; } } trait ClassB { public function fuga() { echo 'FUGA'; } } class ClassAB extends ClassA { use ClassB; } $classAB = new ClassAB; $classAB->hoge(); $classAB->fuga();
優先順位について
extends
とtrait
extends
とtrait
で継承したオブジェクトに、同じメソッドが存在した場合、trait
で継承したメソッドが優先され、parent
でextends
のメソッドを呼び出すことが可能です。
<?php abstract class ClassA { public function sameFunction() { echo 'foo'; } } trait ClassB { public function sameFunction() { parent::sameFunction(); echo 'bar'; } } class ClassAB extends ClassA { use ClassB; } $classAB = new ClassAB; $classAB->sameFunction(); // 'foobar'
trait
と継承先オブジェクト
trait
で宣言したメソッドを継承先オブジェクトでも宣言した場合、継承先オブジェクトのメソッドが優先されます。ただし、一度上書きした場合extends
とは異なり、trait
で宣言したメソッドを呼び出すことはできません。
この辺り、厳密には継承ではないところが分かりますね。
<?php trait TraitClass { public function sameFunction() { echo 'foo'; } } class ChildClass { use TraitClass; public function sameFunction() { echo 'bar'; } } $childClass = new ChildClass; $childClass->sameFunction(); // 'bar'
先ほどのextends
との優先順位とコチラの処理の流れを見ていると、継承とは異なることがわかると思います。個人的にはtrait
については「テンプレート」みたいな印象を持っています。
(use
と記述しているところに実際に記述をしてあるような印象です、伝わりますでしょうか…)
衝突と解決
上述のような、extends
とtrait
と継承先のメソッドは同じ名称で記述しても、優先順位だけ気をつければ問題ありませんが、trait
同士で同じメソッドを記述してしまうとfatalエラーが発生してしまいます。
<?php trait ClassA { public function sameFunction() { echo 'HOGE'; } } trait ClassB { public function sameFunction() { echo 'FUGA'; } } class ClassAB { use ClassA, ClassB; // fatalエラー }
こういった場合には、insteadof
演算子を利用してどちらを優先するか明示することで解決することが可能です。
また、insteadof
だけではどちらか一方のメソッドしか利用することが出来ませんが、as
演算子を利用して別名に変更することで、双方のメソッドを利用することも可能です。
下記例の場合、functionAはClassAを、functionBはClassBを優先し、ClassBのfunctionAはdifferentFunctionAという名称で宣言しています。
<?php trait ClassA { public function sameFunctionA() { echo 'ClassA function A'; } public function sameFunctionB() { echo 'ClassA function B'; } } trait ClassB { public function sameFunctionA() { echo 'ClassB function A'; } public function sameFunctionB() { echo 'ClassB function B'; } } class ClassAB { use ClassA, ClassB { ClassA::sameFunctionA insteadof ClassB; ClassB::sameFunctionB insteadof ClassA; ClassB::sameFunctionA as differentFunctionA; } } $classAB = new ClassAB; $classAB->sameFunctionA(); // 'ClassA function A' $classAB->sameFunctionB(); // 'ClassB function B' $classAB->differentFunctionA(); // 'ClassB function A'
ややこしいですが、柔軟に対応が可能そうな印象ですね。
雑感
個人的には、既存のフレームワークを利用する際に、もう少しこうしたいけど、多重継承するのも微妙…と言った場合に、フレームワークのクラスを継承しつつ、自分で作ったtrai
tを追加する、といった形で利用する際に重宝しています。
その他、静的メソッドやプロパティも宣言出来たり、抽象化も出来たりするので、知っておいて便利な機能だと感じました。
あまり追加された機能について追いかけれていませんでしたが、他にも見落としている機能がありそうなきがしてきました。再度見なおしてみようと思います。
参考
おまけ
文中で、上書きしたtrait
のメソッドを呼び出すことは出来ない、と記述しましたが、強引に実現することは可能です。
強引に上書きしたtrait
のメソッドを呼び出す
use
の際にメソッドのエイリアスを生成することが可能なので、一旦trait
のメソッドを逃がしておき、上書きしたメソッド内からそのメソッドを呼び出すことで、擬似的なparentメソッドを実現することが可能です。
<?php trait TraitClass { public function sameFunction() { echo 'foo'; } } class ChildClass { use TraitClass { // 一旦traitのメソッドを逃がしておく sameFunction as traitFunction; } public function sameFunction() { // 逃がしておいたメソッドを実行する $this->traitFunction(); echo 'bar'; } } $childClass = new ChildClass; $childClass->sameFunction(); // 'foobar'
ただ、本来の使い方から離れてしまっていることと、プログラム自体が読みづらくなってしまうことを考えると、利用するのは控えたほうが良いでしょう。