2018年のPHPDoc事情とPSR-5

PHPDocとは、クラスや関数などのブロックに記述できるDocComment内に記述する書式の通称です。この書式の情報源として時折PSR-5が参照されることがあるので簡単に状況をまとめます。

## TL;DR

* PSR-5の標準化ステータスは<del>昨年10月に**ABANDONED** (放棄・議論停滞)になりました</del><br><ins>2018年9月26日にPSR-5とPSR-19が分割され、ともにDRAFT状態に入りました :tada:</ins>
* PHPDocを型注釈として利用する各処理系の実装にはばらつきがあり、**PSR-5とは相違点があります**
  * PhpStormは現在のところ(2018.1 EAP)**PSR-5と互換性がありません**
  * 特にチーム開発では、対応する型記述の書式について*注意が必要*です
* PhpStormに配慮して書く場合、phpDocumentorの仕様を参照する方が安全です
  * [PHPDoc リファレンス — phpDocumentor](https://zonuexe.github.io/phpDocumentor2-ja/references/phpdoc/index.html)

**この記事は2018年3月11日現在の進行中の話題を扱ってるので、<del>遠からず時代遅れになるおそれがあります。</del>**

* <ins><a href="#2018%E5%B9%B49%E6%9C%8826%E6%97%A5%E8%BF%BD%E8%A8%98">2018年9月26日追記</a> PSR-5とPSR-19に分割して新体制で再始動することが決定しました。</ins>

--------

続き⇒ **2018年11月現在での状態とPhpStormの実装状況について<a href="https://qiita.com/tadsan/items/c9685be8e8297bd74cb6">PhpStorm 2018.3でPSR-5ジェネリクス記法はサポートされない</a>に書きました**

## PSRって何？

[PHP-FIG]((https://www.php-fig.org/)) (*PHP Framework Interop Group*、**PHPフレームワーク相互運用グループ**)が策定する、PHPの共通仕様です。

**PSR** (*PHP Standards Recommendations*、**PHP標準勧告**)は若干権威を帯びた言葉ですが、「PHP開発者全員が知っておかなければモダンなPHPを書けない」といった性質のものでは**ありません**。策定プロセスはPHP本体([The PHP Group](http://php.net/credits.php))とは直接関係なく、飽くまでPHPを使って構築された[フレームワークやCMSなどの開発者](https://www.php-fig.org/personnel/)間での合意に過ぎません。

ただし、**PSR-7**(HTTP Message Interface)をはじめとする有用なクラスの仕様が定義されてますので、もし自分で同じようなクラスの車輪の再発明をするならばPSRで定義されたインターフェイスに準拠することも**選択肢のひとつ**です。

## PSR-5の状態

私は[WEB+DB PRESS Vol.87](http://gihyo.jp/magazine/wdpress/archive/2015/vol87)で、PHPDocを紹介しました。これは8ページの連載記事ですが、2018年現在においてもPHPDocについて、おそらく最も網羅的にまとめたものです。 

この記事において、私はPSR-5について以下のように言及しました。

<img width="527" alt="スクリーンショット 2018-03-11 午後4.09.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/4121/8de3090d-e98f-8ab1-9b40-0f02522cd775.png">

残念ながらこの状況は改善せず、断続的な提案はあるもののコーディネイターの不在により議論はまとまらず、[2015年末頃を最後に一切の作業が停滞しました](https://github.com/phpDocumentor/fig-standards/commits/master)。その後2017年10月頃に**ABANDONED**のステータスとして再分類されました。誰かがコーディネイターとして名乗り出て議論を再開させないと話が進まない状況です。

## デファクトにおける型表記の仕様

この記事では、主に**型表記**の記述方法に絞ってPSR-5と各種実装の現状を比較します。

一般に知られた用語と、この記事で使用する用語の一覧です。

| 呼び名                               | 説明                                          | 例                               |
|--------------------------------------+-----------------------------------------------+----------------------------------|
| **型宣言**                           | 関数定義の引数や返り値に書ける型の書式        | `function sum(int ...$num): int` |
| **型ヒント**<br>(タイプヒンティング) | PHP5時代の型宣言の旧称 (型宣言より制約が多い) | `function hoge(array $option)`   |
| **型注釈**<br>(型アノテーション)     | DocCommentに書く変数や引数・返り値に書くタグ<br>`@param`, `@return`, `@var`, `@property`など  | `/** @var string $foo `          |
| **型表記**                           | 型注釈の中に書ける型の書式のこと              | `DateTimeInterface[]`            |

**型表記(type notation)**の用語は、今回この記事を書くためにでっちあげたものなので、一般に広く受け入れられた用語ではないことをご承知おきください。

### phpDocumentorの型表記 (基本)

[型の定義 — phpDocumentor](https://zonuexe.github.io/phpDocumentor2-ja/references/phpdoc/types.html#arrays)に定義があります。特に配列の書式については以下の種類があると説明されます。

 1. 内容の型の記述なし `@return array`
 2. 内容の単一型を記述 `@return int[]`
 3. 内容の複数型を記述 `@return (int|string)[]`

そのほか、**2.**と普通の複合型(`int|string`)を組み合せて`int[]|string[]`と書くこともできます。(これは**3.**とは別です)

> Note
> 多くのIDEは、おそらくまだこの表記をサポートしません。

この記述の通り、現在でも**3.**の形式をサポートしないツールはいくつかあります。それ以外の形式はおそらく事実上の標準としてさまざまなツールでサポートされてます。

[Annotating Types via PHP Doc Comments - Documentation](https://scrutinizer-ci.com/docs/tools/php/php-analyzer/guides/annotating_code)

### PSR-5の型表記

基本的にはPHPDoc形式の型表記を踏襲しつつ、コレクションのジェネリクスをサポートしました。しかし、開発が停滞した時期の都合で[`iterable`](https://github.com/phpDocumentor/fig-standards/issues/141)や[`?`(nullable)](https://github.com/phpDocumentor/fig-standards/issues/153)が含まれません。

```php
/**
 * @return \ArrayObject<int, \DateTimeInterface>
 */
function hoge() {
    return new ArrayObject([
        new DateTime("2003-04-07"),
        new DateTime("2008-04-01"),
        new DateTime("2112-09-03"),
    ]);
}

/**
 * @return \ArrayObject|\DateTimeInterface[]
 */
function hoge2() {
    return new ArrayObject([
        new DateTime("2003-04-07"),
        new DateTime("2008-04-01"),
        new DateTime("2112-09-03"),
    ]);
}
```

この形式は[phpDocumentor/TypeResolver](https://github.com/phpDocumentor/TypeResolver)が2017年12月末にリリースされた[0.5.0でようやく対応した](https://github.com/phpDocumentor/TypeResolver/releases/tag/0.5.0)ほか、PHPStan、Phanなどがサポートします。その一方、PhpStormこの表記を2018.1 EAP(Early Access Program、次期リリースの先行評価版)においても解釈しません。

つまり、PhpStormとの互換性を保つためには、PSR-5形式のジェネリクス記法で書くことを諦める(あるいは冗長に書く…)しかありません。PhpStormを使ってるみなさんは[PHPDoc PSR-5 <generic> collection type hints formatting : WI-31960](https://youtrack.jetbrains.com/issue/WI-31960)に投票してください。

### Phanの型表記

Phan独自のタグは[Annotating Your Source Code · phan/phan Wiki](https://github.com/phan/phan/wiki/Annotating-Your-Source-Code)で定義されます。PhpDocumentorやPSR-5形式の型表記は基本的に受理されます。

強烈な特徴は、つい最近2018年2月にリリースされた[Phan 0.11.2](https://github.com/phan/phan/blob/0.12.2/NEWS.md#11-feb-2018-phan-0112)で導入された`array shapes`記法です。これは、配列のキーごとに型を定義できるようになった**新たな配列記法**です。

この記法は以下のようなパターンに対応できます。

```php
list($id, $name, $prices) = hoge();

/**
 * @return array{0:int,1:string,2:Money[]}
 */
function hoge()
{
    return [1, 'hoge', [new Money(120), new Money(110)]];
}
```

また、当然連想配列に対しても型検査ができます。

```php
$vocaloids = fuga();

foreach ($vocaloids as $v) {
    printItem($v['item']);
}

/**
 * @return array{name:string,age:int,item:?Item}
 */
function fuga()
{
    return [
        [
            'name' => "初音ミク",
            'age' => 16,
            'item' => new Item("ネギ"),
        ],
        [
            'name' => "重音ミク",
            'age' => 31,
            'item' => new Item("フランスパン"),
        ],
    ];
}
```

こうなるとPhpStormなどのツールとの互換性が破滅するので、`@phan-var`, `@phan-param`, `@phan-return`, `@phan-property`, `@phan-method`などの新しいタグが新設されました。`array shapes`記法を利用する場合は複数の記法を両立することができます。…………おう。

```php
/**
 * @return array[]
 * @phan-return array{name:string,age:int,item:?Item}[]
 */
function fuga()
{
```

### PHPStanの型表記

PHPStanについては昨日[PHPerKaigi 2018](https://phperkaigi.jp/2018/)での[@Hirakuの発表](https://speakerdeck.com/hirak/php-static-analysis)で知りました。で、実装を読んでみて驚いたのですが、PhpDocumentorとPSR-5の型表記に対応してるだけでなく`&`記法で[IntersectionType](https://github.com/phpstan/phpstan/blob/master/src/Type/IntersectionType.php)を実装してます。

型の話をしようと思ったら頭が痛くなるので、TypeScriptについて言及された、こちらの[高等な型 | TypeScript 日本語ハンドブック | js STUDIO](http://js.studio-kingdom.com/typescript/handbook/advanced_types)を読んでいただけると雰囲気掴める気がします。要は`|`(または)の逆で`&`(かつ)ってだけなのですが。

開発者本人が書いた記事: [Union Types vs. Intersection Types – Ondřej Mirtes – Medium](https://medium.com/@ondrejmirtes/union-types-vs-intersection-types-fd44a8eacbb)

たぶん以下のような型を書いて、引数の型チェックができるはずです。

```php
/**
 * @param Countable&Iterator $xs
 */
function hoge($xs)
{
```

これも強力でべんりな機能ではあるのですが、いつものようにPhpStormと互換性がありません。

> Someone has to drive the innovation 😊 If I had to make a choice of sacrificing either PhpStorm or PHPStan, I'd sacrifice PhpStorm 😊 So the fact that the code is understood by PHPStan is more important to me.

いいぞもっとやれ。例によってPhpStormを急かしたければ[Support for intersection types : WI-39419](https://youtrack.jetbrains.com/issue/WI-39419)に投票してください。

### PSR-5への個人的な不満

機能として欲しいものはいくつかあるのですが、PhpDocumentorの仕様にはあるタグ`@property-read`と`@property-write`を返してほしいです……。そもそも現在のプロポーザルでは型については“Appendix A. Types”(**付録**)なので、勧告の中での位置付けがさっぱりわからない。

## 結局チームではどう書けばいいの？

PhpStormに配慮して書く場合、現段階では**PSR-5を参照せずphpDocumentorの仕様を参照にする**方が安全です。

### PhpStormのためのバッドノウハウ

上記にも部分的に書いてありますが、以下のように定義すると`\ArrayObject<\DateTimeInterface>`と同様のジェネリクスのようなものを実現すること**は**できます。しかしこれは甘い罠です。

```php
/**
 * @return \ArrayObject|\DateTimeInterface[]
 */
function hoge2() {
    return new ArrayObject([
        new DateTime("2003-04-07"),
        new DateTime("2008-04-01"),
        new DateTime("2112-09-03"),
    ]);
}

/**
 * @return \DateTimeInterface[]
 */
function hoge4(): ArrayObject {
    return new ArrayObject([
        new DateTime("2003-04-07"),
        new DateTime("2008-04-01"),
        new DateTime("2112-09-03"),
    ]);
}
// ↑ この定義では**地味な警告**が出ます
```

<img width="333" alt="スクリーンショット 2018-03-13 午後7.14.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/4121/6d7b0d41-30f7-dfd9-b3e2-ccf4797f6744.png">

型注釈で`|`を書くか、地味な警告を無視しつつ型宣言と型注釈を書くことで、ジェネリクスっぽいものは再現できます。ループ内では`ArrayObject`の補完が、ループ内では`\DateTimeInterface`の補完が機能するようになります。

<img width="506" alt="スクリーンショット 2018-03-13 午後7.04.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/4121/95922b60-be26-5095-7837-ab7148e5d9c8.png">

<img width="554" alt="スクリーンショット 2018-03-13 午後7.09.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/4121/83de82cc-251f-b31c-58dd-57d08190d1fc.png">

問題は、`\DateTimeInterface[]`は`iterable`なものの要素が`\DateTimeInterface`である、と思ってほしいのですが、残念なことに[phpDocumentorの仕様](https://docs.phpdoc.org/guides/types.html#arrays)では “The brackets inform you, and several tools, that this is an array of that Type.” (`[]`はその型の配列だと通知する) と定義されてるので、その返り値を`array_`系の関数に投げてもエラーは通知されなくなります。

<img width="280" alt="スクリーンショット 2018-03-13 午後7.47.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/4121/3bc7d89b-8a99-2ee0-95f7-0d62353533da.png">

これは補完したさに安易にUnionTypeに頼った罰です。PhpStormは一刻も早くリストのジェネリクスを実装してください。

<img width="389" alt="スクリーンショット 2018-03-13 午後7.49.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/4121/dbe5b53c-7f85-b349-5822-9b09b0e96f5c.png">

↑ 本当はこうやって警告してほしい。

## 結局どうなの

この記事の初稿の段階では「PSR-5全然実装されてないしだめだろ」みたいな論調で書こうと思ってたのですが、調べてみたらPhpStorm以外では結構実装されちゃってる感じでした。2016年11月あたりに[Phan静的解析がもたらす大PHP型検査時代](https://devpixiv.hatenablog.com/entry/2016/11/11/202656)を書いたときにはPSR-5形式の型表記はサポートされてるツールが全然なくて絵に描いた餅だったのですが、気がついたら[PHPStan](https://github.com/phpstan/phpstan)やら[phpDocumentor/TypeResolver](https://github.com/phpDocumentor/TypeResolver)が対応してくれてたので、あとはPhpStormだけが対応してくれればPSR-5の型が実用できるのでは、といった状況です。

とは言ってもまだ足りない感じがする上にPHPStanが導入してIntersectionType(交差型)を見せられてしまっては、機能としてはともかく、型表記の書式の標準化はがんばってほしいところではあります。

JetBrainsがスポンサーになってPhpStormの関係者がしゃしゃり出てコーディネートしてくれないと話が進みそうにないんですが……。PhanもPHPStanもコア開発者が一人で猛然と開発していくタイプっぽいので、議論をまとめるとかそんな感じではなさげ。うーん。

**PhpStormはPHPDocの利用率を押し上げましたが、その書式については[PhpStormのヘルプにもまともな記述もなく](https://www.jetbrains.com/help/phpstorm/phpdoc-comments.html)ユーザーに混乱をもたらしていることについて、JetBrains関係者にもはっきりと認識していただきたいです。**

ちなみにPSR-5が割り当てられた**ABANDONED**は、PHP版[ジョークRFC](http://www.wdic.org/w/WDIC/%E3%82%B8%E3%83%A7%E3%83%BC%E3%82%AFRFC)として名高い (?) [PSR-8 Hug (日本語訳)](https://qiita.com/tanakahisateru/items/3f24014a91744c2b04fe)と同じ分類です。~~PHPDoc=Huggable~~

## おまけ: 資料

* [PHPStan](https://github.com/phpstan/phpstan)
    * [PHPStanで始める継続的静的解析 #phperkaigi /php-static-analysis // Speaker Deck](https://speakerdeck.com/hirak/php-static-analysis)
    * [Union Types vs. Intersection Types – Ondřej Mirtes – Medium](https://medium.com/@ondrejmirtes/union-types-vs-intersection-types-fd44a8eacbb)
    * [phpstan/TypeCombinatorTest.php at 0.9.2 · phpstan/phpstan](https://github.com/phpstan/phpstan/blob/0.9.2/tests/PHPStan/Type/TypeCombinatorTest.php)
* [Phan](https://github.com/phan/phan)
    * [Annotating Your Source Code · phan/phan Wiki](https://github.com/phan/phan/wiki/Annotating-Your-Source-Code)
    * [About Union Types · phan/phan Wiki](https://github.com/phan/phan/wiki/About-Union-Types)
    * [Phan静的解析がもたらす大PHP型検査時代 - pixiv inside [archive]](https://devpixiv.hatenablog.com/entry/2016/11/11/202656)
* [phpDocumentor/TypeResolver](https://github.com/phpDocumentor/TypeResolver)
* [PSR-5](https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md) (提案)
    * [Issues · phpDocumentor/fig-standards](https://github.com/phpDocumentor/fig-standards/issues)
* [Scrutinizer CI](https://scrutinizer-ci.com/)
    * [Annotating Types via PHP Doc Comments - Documentation](https://scrutinizer-ci.com/docs/tools/php/php-analyzer/guides/annotating_code)
* `|`と`&`の話
    * [高等な型 | TypeScript 日本語ハンドブック | js STUDIO](http://js.studio-kingdom.com/typescript/handbook/advanced_types)
    * [型システムの勉強 | 雑記帳](https://blog.miz-ar.info/2017/07/type-system/#_intersection_type__union_type)
    * [Scalaの合併型と全称型](http://halcat.org/scala/curryhoward/index.html)

### 2018年9月26日追記

[メーリングリストでの投票](https://groups.google.com/forum/?hl=ja#!topic/php-fig/5Yd0XGd349Q)により、phpDocumentorとPEARプロジェクトメンバーのChuck Burgess([@ashnazg](https://github.com/ashnazg))がエディターとなって、PHPDoc書式定義の**PSR-5**とPHPDocタグカタログの**PSR-19**に分割して仕様策定が再開されることが決まりました。

<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">We brought back PSR-5 to draft!<br><br>The CC voted unanimously to bring it back, and we split the PHPDoc Tags part into a new, separate PSR-19!<a href="https://t.co/ANDWKsA9gS">https://t.co/ANDWKsA9gS</a></p>&mdash; PHP-FIG (@phpfig) <a href="https://twitter.com/phpfig/status/1044850002760282112?ref_src=twsrc%5Etfw">September 26, 2018</a></blockquote>


今回はで[phpDocumentor/TypeResolver](https://github.com/phpDocumentor/TypeResolver)の開発にも携る[@ashnazg](https://github.com/ashnazg)に加え、実際にPHPの静的解析ツールの開発者たちがワーキンググループメンバーとして入ってるので安心ではありますね。

> **5.3 Working group members**
>
>   * Alexey Gopachenko - [PhpStorm](https://www.jetbrains.com/phpstorm)
>   * Matthew Brown - [Psalm](https://github.com/vimeo/psalm)
>   * Ondrej Mirtes - [PHPStan](https://github.com/phpstan/phpstan)

一方で、現時点で俗にPSR-5と呼ばれる型表記の定義[PSR-5 Appendix A. Types](https://github.com/php-fig/fig-standards/blob/7285e602f433744e3754e239d63e35f54bcd8c5e/proposed/phpdoc.md#appendix-a-types)がどちらに含まれるのか不透明ではあります。

## 2018年11月22日追記

2018年11月現在での状態とPhpStormの実装状況について<a href="https://qiita.com/tadsan/items/c9685be8e8297bd74cb6">PhpStorm 2018.3でPSR-5ジェネリクス記法はサポートされない</a>に書きました。