そもそもtraitとは
PHPは仕様として多重継承をサポートしていません。そのためある基底クラスを既に継承している場合に共通の振る舞いを拡張するため継承を追加する、といったことが行えないのです。。
しかし、traitを利用することで共通の振る舞いを切り出し、再利用することが可能となります。
PHP 5.4.0 以降では、コードを再利用するための「トレイト」という仕組みが導入されました。
トレイトは、PHP のような単一継承言語でコードを再利用するための仕組みのひとつです。 トレイトは、単一継承の制約を減らすために作られたもので、 いくつかのメソッド群を異なるクラス階層にある独立したクラスで再利用できるようにします。 トレイトとクラスを組み合わせた構文は複雑さを軽減させてくれ、 多重継承や Mixin に関連するありがちな問題を回避することもできます。
トレイトはクラスと似ていますが、トレイトは単にいくつかの機能をまとめるためだけのものです。 トレイト自身のインスタンスを作成することはできません。 昔ながらの継承に機能を加えて、振る舞いを水平方向で構成できるようになります。 つまり、継承しなくてもクラスのメンバーに追加できるようになります。
使い方
通常の継承のようにextend
するのではなく、traitを利用したいクラス内でuse
して参照します。
class HogeModel() {
use HogeTrait;
.....
}
traitから$this
で呼び出し元を参照できない!?
traitはその性質上、共通処理の重複を防ぐなどの理由で複数のクラスから利用されることを想定されていることも多いと思います。
しかし、あるtraitが特定のクラスでのみ利用される設計のこともあると思います。
例えば弊社では設計にDDD(ドメイン駆動設計)を採用しているのですが、モデルの責務を超えてくるような責務についてはtraitに切り出し、モデル内から参照するようにしています(例えばFactoryパターンの実装などはtraitで行っています)。
そのような場合には$this
として呼び出し元のモデルを参照する処理を実装したい場合もあると思いますが、それをtrait上で行ってみると。。。
class HogeModel() {
use HogeTrait;
.....
public function getString(): string {
return 'echo me';
}
}
trait HogeTrait {
private function hogeFunction(): void {
$this->echoHogeModelString($this);
}
private function echoHogeModelString(HogeModel $model): void {
echo $model->getString();
}
}
Expected parameter of type 'HogeModel', 'HogeTrait' provided
hogeFunction
内でechoHogeModelString($this)
として引き渡されている$this
がHogeTrait
として認識されてしまう結果、「echoHogeModelString
の引数にはHogeModelを渡してね!」と注意されてしまう訳です。当然といえば当然ですね。
しかし、設計上単一のモデルからのみ参照されるtraitなのだから、$this
で呼び出し元モデルを参照したい!ということもあると思います(僕はありました。。)。
ここでは二つの対応方法をご紹介します。
2020/11/16追記
PHPの言語使用ではそもそもトレイのインスタンスは作れないので、上記のようにtrait上で$this
として呼び出し元のモデルを参照した場合でも PHP自体ではエラーや警告になりません。
今回の警告は静的解析でType compatabilityに対する警告が発出されていたために生じていたものでした。
内部仕様をきちんと理解してコーディングを行う重要性が身に染みました。。。
ご指摘いただいた@rana_kualuさん、@sj-iさん、ご指導ご鞭撻をいただきまして、ありがとうございました。
以下の解説はPHPの言語そのものによる警告ではなく、IDE上での静的解析による警告の回避方法の一例としてご参照いただければと思います。
1:phpdocの@var
コメントで変数の型を指定する
下記のように@var
コメントで型を指定することで$this
参照を実現することができます。
trait HogeTrait {
private function hogeFunction(): void {
/** @var HogeModel $this */
$this->echoHogeModelString($model);
}
.....
$model
変数をHogeModel
型であることを明示的に宣言することで、trait上でも警告なしで呼び出し元のモデルを直接参照することができます。
2:assert
することで$this
の型を保証する
下記のようにtrait上で型を保証するassert
を記述することで$this
参照を実現することができます。
trait HogeTrait {
private function hogeFunction(): void {
assert($this instanceof HogeModel)
$this->echoHogeModelString($this);
}
.....
$this
をHogeModel
型であることをassert
で明示的に保証することで、trait上でも警告なしで呼び出し元のモデルを直接参照することができます。
PHPでもやりようで型とは上手に付き合える(はず)
インタプリタ言語であるPHPですがdeclare(strict_types=1)
の設定をすることで型指定に基づいた実装を行うことができます。より適切な型の扱いや実装を行えるよう、精進していきたいと思います(静的言語も業務でやりたい。。)。
お読みいただき、ありがとうございました。