PhpStormで幸せになれるPHPの書き方


(PhpStormと銘打ってますがIDEを使ったPHP開発全般に通じる話です)

PhpStorm使ってますか？僕はもうテキストエディタでPHP書きたくない程度には毒されています。
以前[PhpStormでまず覚えるべきショートカット](http://qiita.com/crifff/items/bbdf850f0d90ad16415a)で効率化のためのショートカットを紹介しました。
今回はもう一歩踏み込んで、PhpStormが力を最大限発揮するためのPHPの書き方についてです。
端的に言ってしまえば **PHPDocで表現可能な形でしか書かない** というだけです。


## 返り値の型を混ぜない
PHPだと関数の返り値で様々なデータを配列にまとめて返す、ということがよくありますが配列はほとんど補完ができないのでここでは避けるべきパターンです。

```php:悪いパターン：配列でまとめて返す
<?php
/**
 * @param String $url
 * @return Array
 */
function fetchPage($url)
{
	$ch = curl_init($url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	$body = curl_exec($ch);
	$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
	curl_close($ch);
	return array($status, $body);
}
```

多少面倒ではありますが、ひとつの解決として返却用のクラスを定義することで補完が可能になります。

```php:良いパターン：返却用のクラスを作る
<?php
class HogeResponse
{
	/** @var Integer $status */
	public $status;
	/** @var String $body */
	public $body;

	function __construct($status, $body)
	{
		$this->status = $status;
		$this->body = $body;
	}
}

/**
 * @param String $url
 * @return HogeResponse
 */
function fetchPage($url)
{
	$ch = curl_init($url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	$body = curl_exec($ch);
	$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
	curl_close($ch);
	return new HogeResponse($status, $body);
}
```

複雑な結果を返す場合、補完が可能であることの恩恵は非常に大きいです。返却されたオブジェクトから必要なデータを探すのに長大なドキュメントを探す手間が省けます。

また、返却値が配列である場合、PHPDocで`@return Array`と表現することは可能ですが、配列の要素の型までは不明なので補完が効きません。PHPDocでは、型の後ろに`[]`をつけるとその型の配列であることを表現することができます。

```php:良いパターン：配列を返す関数
<?php
/**
 * @param Array $condition
 * @return User[]
 */
function getUsers()
{
  $users = User::find('all'); //検索結果が0であればfalseを返すような関数
  if($users === false){
    return array();
  }
  reutrn $users;
}

foreach(getUsers() as $user){
  echo $user->name; // Userの配列であれば各要素はUserであると判別され警告が出ない
}
```

返り値が特定クラスだけの配列であることを明示するとループ内でも変数の型が解決され補完が可能になります。型が入り混じった配列をPHPDocでは表現できないため、配列を返す関数は単一の型の配列を返すことが望ましいです。

配列でなくとも、複数の型を返す関数を書くべきではありません。

```php:悪いパターン：様々な型を返す
<?php
/**
 * @param String $path
 * @return NumberObject | StringObject | false
 */
function getData($path)
{
  $data = file_get_contents($path);
  if(!$data)
  {
    return false;
  }
  if(is_numeric($data))
  {
    return new NumberObject($data);
  }
  return new StringObject($data);
}
```

PHPDocでは`|`でOR表現ができ、返しうる値を列挙することができます。ただしこの場合、返り値の型は動作時にしか決定できないので、IDE上では返しうる型すべての可能性を扱います。上記の例では`NumberObject`と`StringObject`がそれぞれ持っているメンバーを変換候補に出してしまうので、それだけを信用してしまうとエラーを起こす可能性が有ります。

というわけで関数の返り値は単一の型であるべきです。ただしエラーハンドリングでnullやbooleanを返す場合は許容されます。

## 動的なプロパティ・メソッドはPHPDocで表現する

さてマジックメソッドなどで呼出可能なメンバーはコード上に記述されません。なので呼出可能なすべてのメンバをPHPDocで記述するべきです。

```php:良いパターン：PHPDocで表現する
<?php
/**
 * ActiveRecord\ModelがDBのカラムを見て勝手にプロパティとゲッタ・セッタを定義してくれるようなモデル
 * ここではidとnameというカラムが存在したとする
 * @property Integer $id
 * @property String  $name
 * @method Integer getId()
 * @method Void setId(Integer $id)
 * @method Integer getName()
 * @method Void setName(String $name)
 */
class User extends ActiveRecord\Model
{
}
```

`@property`や`@method`で実在していないメンバーを定義することができ、通常と同じように補完が行えます。数が少なければ自力で書いてもいいですが、大量のメンバをPHPDocで記述するのは労力も取られるしミスも出やすいのでその場合はコードジェネレータなどで自動生成するようにしましょう。


さてしれっとクソ面倒くさいルールを強いてしまいましたが、ここまでしないとIDEの静的解析は力を発揮できません。
そこまでコストをかけてやるべきなのかと問われると、開発規模が大きくなるほど、開発期間が長くなるほど効いてくるので傷が浅いうちにやるべきだと答えます。
PhpStormの出す警告は致命的なエラーにつながることが割とあるので(未定義の可能性がある変数とか)、警告を丁寧に潰していくだけで防げるエラーがいくつもあります。開発効率のためにも、安全性のためにも、IDEをフルに活用するのが良いですね。