`@return $this` と `@return self` と `@return static` の違い

# `@return $this` と `@return self` と `@return static` の違い

もしかしたら phpDocumentor 由来なのかもしれませんが、とりあえず手元の phpstorm 2017.2.4 で下記のような挙動を示したので備忘録として書き記します。

2019/11/02 追記・修正

- マジックメソッドの場合を追記
- phpstorm 2017.2.4 での結果

追記ここまで。

2018/02/17 修正

- phpstorm 2019.2.4 で挙動が変わってたので修正

追記ここまで。

親クラスの返り値の型記述が原因で、「継承したらメソッドチェーンでメソッドが消えちゃった」というシチュエーションを想定しています。

下記のコードを考えます。

```php
/**
 * @method static returnMagicStatic()
 * @method self returnMagicSelf()
 * @method $this returnMagicThis()
 */
class P
{
    /**
     * @return static
     */
    public function returnStatic() { return $this; }

    /**
     * @return self
     */
    public function returnSelf() { return $this; }

    /**
     * @return $this
     */
    public function returnThis() { return $this; }

    public function __toString()
    {
        return "";
    }
}

class C extends P
{
    public function func() { /* 何か処理 */ }
}
```

クラス `P` に `@return` アノテーションをそれぞれ

- `static`
- `self`
- `$this`

を指定した3つのメソッド＋3つのマジックメソッド（用アノテーション）を用意します（ `__toString` は後で触れます）。

この状態で下記のコードを記述するといくつか phpstorm が警告を発します（コメント参照）。

```php
// パターン1. returnStatic は static を指定しているので戻り値は C として振る舞う（警告が出ない）
(new C())->returnStatic()->func();

// パターン2. returnSelf は self を指定しているので戻り値は P として振る舞う…かと思いきや C として振る舞う（警告が出ない。しかし補完にも出ない）
(new C())->returnSelf()->func();

// パターン3. returnThis は $this を指定しているので戻り値は P として振る舞う…かと思いきや C として振る舞う（警告が出ない）
(new C())->returnThis()->func();

// パターン4. returnMagicStatic は static を指定しているので戻り値は C として振る舞う…かと思いきや色々ダメになる（補完に出ないし警告がでる）
(new C())->returnMagicStatic()->func();

// パターン5. returnMagicSelf は self を指定しているので戻り値は P として振る舞う…かと思いきや C として振る舞う（警告が出ない。しかし補完にも出ない）
(new C())->returnMagicSelf()->func();

// パターン6. returnMagicThis は $this を指定しているので戻り値は P として振る舞う…かと思いきや C として振る舞う（警告が出ない）
(new C())->returnMagicThis()->func();
```

※「振る舞う」という記述は IDE が思い込んでいる型のことであって、実際の型はもちろんすべて `C` です。実行自体はエラー無くできます

`static` と `self` は予想通りでしたが、 `$this` がちょっと意外でした。
アノテーションのコンテキストではなく、実際の呼び出し側コンテキストでの `$this` になるみたいですね。すごい。

が、マジックメソッドになると毛色が変わっていました。
具体的には `self` でも `C` のコンテキストになりました（パターン5）。
ただし、コード補完には出ないという謎挙動でした（手動で入力すればジャンプなどもできる）。
また、 `static` であらゆる補完が消えていました（パターン4）。

`self` はなぜかよくわかりませんが、 `static` の方は多分 static メソッドだと判断されていそうです。 `@method static returnMagicStatic()` って記法は「static なマジックメソッド」の記法そのままなので。
つまり、結果として `@method` では static を返り型の明示としては使えません。
（ただ、 `@method static static returnMagicStatic()` なら行けました。キモい）。

一覧でまとめると下記のようになります。

|指定             |Cが呼び出せるか（普通のメソッド）|Cが呼び出せるか（マジックメソッド）|Cが補完に出るか（普通のメソッド）|Cが補完に出るか（マジックメソッド）|
|:----------------|:-------------------------------:|:---------------------------------:|:-------------------------------:|:---------------------------------:|
|static           |○                                |×（静的なら可）                    |○                                |×                                  |
|self             |○                                |○                                  |×                                |×                                  |
|$this            |○                                |○                                  |○                                |○                                  |


とりあえず `$this` が万能のように見えるので、インターフェースや抽象クラスなどの、再実装前提のメソッドには `$this` を指定するのが良さそうです。
（CakePHP は `$this` を推奨してるっぽいです。[参考](http://book.cakephp.org/2.0/ja/contributing/cakephp-coding-conventions.html#id35)）。

※ **下記の現象は phpstorm 2017.2.4 で直っていました**

と、思ったんですが、下記の状況で妙な警告が出ました。

```php
class O
{
    public function hoge()
    {
        // Method __toString is not implemented for 'O'
        echo (new C())->returnStatic();

        // 警告なし
        echo (new C())->returnSelf();

        // Method __toString is not implemented for 'O'
        echo (new C())->returnThis();
    }
}
```

他のクラス内部で `__toString` が実行される処理（この場合 `echo` ）で上記の警告が出ました。
**いや、そのオブジェクト、 `O` じゃないから！**
`static` や `$this` がなにか悪さして記述中のコンテキストを $this だと思い込んでる？
と思ったんですが上記の警告を google 翻訳にかましたところ

> メソッド__toStringは、「O」のために実装されていません

「ために」ってなんじゃらほい？ `__toString` 使うのに、なんかオブジェクト同士の依存って必要だっけか？

なんだかよく分かりませんが、そう高い頻度で発生する事象でもないので、再実装前提のメソッドには `static` か `$this` を指定するのが良さそう、という結論は変わりません。