LoginSignup
1
0

More than 3 years have passed since last update.

PHPのtrait上で任意のモデルを`$this`として参照する

Last updated at Posted at 2020-11-14

そもそもtraitとは

PHPは仕様として多重継承をサポートしていません。そのためある基底クラスを既に継承している場合に共通の振る舞いを拡張するため継承を追加する、といったことが行えないのです。。
しかし、traitを利用することで共通の振る舞いを切り出し、再利用することが可能となります。

PHP 5.4.0 以降では、コードを再利用するための「トレイト」という仕組みが導入されました。

トレイトは、PHP のような単一継承言語でコードを再利用するための仕組みのひとつです。 トレイトは、単一継承の制約を減らすために作られたもので、 いくつかのメソッド群を異なるクラス階層にある独立したクラスで再利用できるようにします。 トレイトとクラスを組み合わせた構文は複雑さを軽減させてくれ、 多重継承や Mixin に関連するありがちな問題を回避することもできます。

トレイトはクラスと似ていますが、トレイトは単にいくつかの機能をまとめるためだけのものです。 トレイト自身のインスタンスを作成することはできません。 昔ながらの継承に機能を加えて、振る舞いを水平方向で構成できるようになります。 つまり、継承しなくてもクラスのメンバーに追加できるようになります。

PHP: トレイト - Manual

使い方

通常の継承のように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)として引き渡されている$thisHogeTraitとして認識されてしまう結果、「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);
  }
  .....

$thisHogeModel型であることをassertで明示的に保証することで、trait上でも警告なしで呼び出し元のモデルを直接参照することができます。

PHPでもやりようで型とは上手に付き合える(はず)

インタプリタ言語であるPHPですがdeclare(strict_types=1)の設定をすることで型指定に基づいた実装を行うことができます。より適切な型の扱いや実装を行えるよう、精進していきたいと思います(静的言語も業務でやりたい。。)。

お読みいただき、ありがとうございました。

1
0
4

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
1
0