遅延静的束縛はアノテーションでも使える！PhpStorm での開発を助けるひと工夫

主に PhpStorm7 で開発してらっしゃる方が対象です。
ほかの IDE では試していませんが、可能性はあるかもしれません。

ここでの「アノテーション」は、ドキュメントコメントで IDE にメタ情報を提供するための注釈を指しています。Web Application Framework への情報提供ではありません。
また、PSR に準拠しているか否かについては確認しておりませんのでご了承ください。

# 遅延静的束縛の static はアノテーションでも使える

たとえば、DB からとある Model(Entity) を取得する共通メソッドが親クラスで定義されていたとします。

```php:Model.php
class Model {

    /*
     * モデル取得メソッド
     *
     * @param array|string|integer $parameters
     * @return Model
     */
    public static function findFirst($parameters=null) { }
}
```

これを継承して具象クラスを定義すると...

![non-static.png](https://qiita-image-store.s3.amazonaws.com/0/25145/ca768fa3-3a88-16cd-fad4-af18d92a4a59.png)

基底クラスで定義されている `findFirst` の戻り値は `Model` なので、最後の一文 `$entry->date` で `Model` にプロパティ `$date` が定義されていない旨の警告が表示されます。
（実際には magic method としてアクセスされるよというメッセージです）

**つまり、 `$entry` は `Entry` クラスのインスタンスとして扱いたい場面なのに、 `Model` クラスのインスタンスとして認識されてしまう、ということです。**

そこで、 `Model::findFirst` のアノテーションを次のように修正します。

```php:Model.php
class Model {

    /*
     * モデル取得メソッド
     *
     * @param array|string|integer $parameters
     * @return static
     */
    public static function findFirst($parameters=null) { }
}
```

`@return` に続く型注釈を `Model` ではなく `static` に変更しました。

![static.png](https://qiita-image-store.s3.amazonaws.com/0/25145/b1047e23-97c2-7598-b050-35ad33a1fac5.png)

これで警告は解除され、呼び出し元である `Entry` クラスのインスタンスとして認識されました。

例ではクラスのフィールドを参照しましたが、例えばせっかくメソッドチェーンを繋げられるように setter メソッドで `$this` を返却していても、具象クラスのインスタンスとして認識されなければ警告だらけになるわ入力補完が効かないわで残念な気持ちになりますよね。
返却されるインスタンスが具象クラスのものとなることがわかっている場合、アノテーションに static を指定するとそんなこともなくなります。

遅延静的束縛(static) について詳しいことは [公式マニュアル:遅延静的束縛](http://www.php.net/manual/ja/language.oop5.late-static-bindings.php) をご参照ください。

# Phalcon でのお話

PhpStorm + Phalcon で開発されている方はもうお気づきかとは思いますが、上記の例をそのまま Phalcon の  [ドキュメントライブラリ](https://github.com/phalcon/phalcon-devtools) に当てはめることで開発を加速できます。

[ide/1.2.5/Phalcon/Mvc/Model.php#L388](https://github.com/phalcon/phalcon-devtools/blob/master/ide/1.2.5/Phalcon/Mvc/Model.php#L388)

```php:Phalcon/Mvc/Model.php一部抜粋
<?php 

namespace Phalcon\Mvc {
	
	abstract class Model implements \Phalcon\Mvc\ModelInterface, \Phalcon\Mvc\Model\ResultInterface, \Phalcon\DI\InjectionAwareInterface, \Serializable {

		/* 省略 */

		/**
		 * Allows to query a set of records that match the specified conditions
		 *
		 * <code>
		 *
		 * //How many robots are there?
		 * $robots = Robots::find();
		 * echo "There are ", count($robots), "\n";
		 *
		 * //How many mechanical robots are there?
		 * $robots = Robots::find("type='mechanical'");
		 * echo "There are ", count($robots), "\n";
		 *
		 * //Get and print virtual robots ordered by name
		 * $robots = Robots::find(array("type='virtual'", "order" => "name"));
		 * foreach ($robots as $robot) {
		 *	   echo $robot->name, "\n";
		 * }
		 *
		 * //Get first 100 virtual robots ordered by name
		 * $robots = Robots::find(array("type='virtual'", "order" => "name", "limit" => 100));
		 * foreach ($robots as $robot) {
		 *	   echo $robot->name, "\n";
		 * }
		 * </code>
		 *
		 * @param array|string $parameters
		 *              ...配列だけじゃなく文字列も渡せるので修正
		 * @return \Phalcon\Mvc\Model\ResultsetInterface|static[]
		 *              ...static[] を追加して具象クラスの配列として認識させる
		 */
		public static function find($parameters=null){ }


		/**
		 * Allows to query the first record that match the specified conditions
		 *
		 * <code>
		 *
		 * //What's the first robot in robots table?
		 * $robot = Robots::findFirst();
		 * echo "The robot name is ", $robot->name;
		 *
		 * //What's the first mechanical robot in robots table?
		 * $robot = Robots::findFirst("type='mechanical'");
		 * echo "The first mechanical robot name is ", $robot->name;
		 *
		 * //Get first virtual robot ordered by name
		 * $robot = Robots::findFirst(array("type='virtual'", "order" => "name"));
		 * echo "The first virtual robot name is ", $robot->name;
		 *
		 * </code>
		 *
		 * @param array|string|integer $parameters
		 *              ...文字列も、実は数値（主キー値）も渡せる
		 * @return static
		 *              ...こちらも同様に具象クラスのインスタンスとして認識させる
		 */
		public static function findFirst($parameters=null){ }


		/* 省略 */

	}
}
```

上記のように、アノテーションを４行書き換えます。
これに気づくまでは具象クラス側で `find` `findFirst` をオーバライドしてアノテーションを振り当てていましたが、これでその必要もなくなりました！

ふと Pull Request を送ろうかとも思ったのですが、Phalcon 全体としてのコーディングスタイルや PSR との衝突も考えられるので、あくまで個人的に調整する範囲であると考えこちらに投稿いたしました。