超PHPerになろう

Enjoy PHP Programming

PHPStan 0.12.0がリリースされました

この記事はPHPStan開発者のOndřej Mirtesによって2019年12月4日に書かれた記事を翻訳したものです。

medium.com


これは6ヶ月にわたって開発された大規模なリリースです。この新しいメジャーバージョンの開発中も私たちは多くのマイナーバージョンをリリースできました。この継続的なワークフローはフィーチャートグル(bleedingEdge)によって可能になりました。ユーザーは安定版を使用していてもオプトインすることで新機能を試してフィードバックすることができました。

この新しいバージョンはあまりにも素晴らしいので、誰もが新しいバージョンのインストールを待ちきれないことでしょう。PHPStanのアップグレードは誰でもすぐに行うことができるように設計されています。

PHPStanはPHPに慣れていなくてもコードのバグを発見することにフォーカスした静的解析ツールです。アプリを実行しなくても全クラスからバグを拾い上げます。基本について知りたい方は入門記事を読んでください。

PHPStan 0.12はGábor Hojtsy (@gaborhojtsy)の提供によってお届けしました

PHPStanはDrupal9の準備にあたって、drupal-check・アップグレード状況・drupalci deprecation testingのバックボーンとしてDrupalのエコシステム全体に多大な支援を提供してくれました。廃止予定のAPIについて一般的なパターンを特定することが可能になり、クリティカルなツールを決定し、コミュニティを団結させました。ありがとう!


それでは、PHPStan 0.12.0の新機能は何でしょうか?

ジェネリクス

これは新バージョンの目玉機能です。それは個別の記事(訳注:後日こちらも訳します)に詳しく書きました。こちらの記事で型安全とコードの文書化のために何ができるか確認してください。

いかにしてPHPStanでジェネリクスが実現したのでしょうか。それは今年の五月にArnaud Le Blancから「ジェネリクスの実装を手伝ってほしい」というEメールが届いたことから始まりました。それは印刷して額に入れて飾りたくなるメールです。それは私が長い間に行ってきた共同作業の中でも最高のコラボレーションになったからです。我々は50以上のプルリクエストと数百のコミットを往復し、互いにフィードバックし、コードをテストすることで洗練されていきました。まだ将来的に実装したいアイディアはいくつかありますが、現段階でも非常に満足のいくものなので、多くのひとが0.12.0で利用できるようにします。

機能の一部(ジェネリック型の正しい使い方をチェックする規則)は10月にブリュッセルで開催されたEU-FOSSAハッカソンで実装されました。これについては後ほどまた触れます。

新しいレベル

いままでは0〜7のレベルがありましたが、PHPStanには今や新しいレベルがあります。それは8ではありません。

PHPStanのレベルはツールをコードベースに段階的に導入するために機能します。レベル0は開発者が修正すべき最も重大なエラーのみを報告します。より厳密な高いレベルに進むにはコードの全体的な品質を改善する必要があります。

0.11.x以前のレベルの概要は次のとおりでした。

0: 基本的なチェック、未知のクラス、未知の関数、`$this`の未知のメソッド呼び出し、関数およびメソッドの間違った数の引数、確実に定義されていない変数
1: 未定義のおそれがある変数、`__call` および `__get` の未知のマジックメソッド・マジックプロパティ
2: 全ての式での(`$this`以外の)未知のメソッド、PHPDocのバリデーション
3: 戻り値型検査、プロパティ代入の型検査
4: 基本的なデッドコード検出 - 常に`false`になる`instanceof`検査、通らない`else`節、`return`の後に書かれた到達できないコード
5: 関数およびメソッド呼び出しの引数の型検査
6: 部分的に間違ったユニオン型の検査 - どちらかの型にしかないメソッドが呼び出されていると報告
7: nullable型のメソッド呼び出しとプロパティアクセスを報告

ある程度の妥当な時間があれば、どんなプロジェクトでもレベル5までは達成できるでしょう。しかしレベル6と7は更地のプロジェクトから達成する方がはるかに簡単です。それはオブジェクトの設計に大きく依存します。

PHPStan 0.12.0で達成したかったのは分析したコードに欠落している型ヒントを報告することでした。PHPStanが変数の型を知らなければ、どんなエラーも検出できません。型ヒントが完全に欠落していることだけでなく、配列やその他のiterableな値の内容の型の欠落も検出します。

それをレベル8として追加するのは簡単ですが、達成するための難易度を反映していません。レベル6と7を達成するよりも、欠落した型ヒントを加える方が簡単です。

そのため新しいレベルは以下のように再設計されます。

0: 基本的なチェック
1: 未定義のおそれがある変数、…
2: 全ての式での(`$this`以外の)未知のメソッド
3: 戻り値型検査、プロパティ代入の型検査
4: 基本的なデッドコード検出
5: 関数およびメソッド呼び出しの引数の型検査
**6: 欠落した型ヒント**
7: 部分的に間違ったユニオン型の検査 - どちらかの型にしかないメソッドが呼び出されていると報告
8: nullable型のメソッド呼び出しとプロパティアクセスを報告

つまり、以前レベル5以上だったユーザーは、すべてのコードに型ヒントを追加すれば次のレベルにステップアップできます。

デフォルトでのPhar配布

PHPStanはphpstan/phpstan-shimとして依存関係競合のリスクがないPharファイルによるパッケージを提供していました。このパッケージを提供してから私はメインストリームのphpstan/phpstanパッケージとphpstan/phpstan-shimの差異を取り除いてきました。面倒が少なく多くの人(最新のPHPStanと依存がコンフリクトしていた人)が最新バージョンにアップグレードできるようになったので、phpstan/phpstanパッケージが唯一の配布方法になりました。

PHPStan 0.12.0ではphpstan-shimは更新されなくなります。全てのユーザーはphpstan/phpstanに移動してください。PHPStan拡張はそのまま機能します。

プルリクエストを含むPHPStanの開発はphpstan/phpstan-srcで行われています。

整数範囲型

PHPStanは指定された境界の整数を推論できるようになりました。次の例のとおりです:

<?php

function foo(int $i)
{
    if ($i > 2 && $i < 5) {
        if ($i === 5) { } // error: this is always false
    }
    if ($i >= 2 && $i < 3) {
        // PHPStan knows that $i is 2
    }
}

この機能によってPHPStanはさらにコードについての理解を深め、常にtrueまたはfalseになる条件を指摘できます。

整数範囲型はブリュッセルで開催されたEU-FOSSAハッカソンJan Tvrdíkが実装しました。

Lukáš Unger (@lookyman_), Ondřej Mirtes (@OndrejMirtes), Jan Tvrdík (@jantvrdik)

イベントに招待されたことを光栄に思います。常に中断と行ったりきたりを繰り返す日々の中で、オープンソースの仕事に丸二日間取り組む絶好の機会でした。

Doctrine拡張: プロパティ型と @ORM\Column の比較

Lukáš Ungerハッカソンでエンティティのマッピングの不整合チェックを実装しました。これらのエラーは、いやらしく目立たないバグにつながるおそれがあります。これらはPHP 7.4で利用可能になったネイティブのプロパティ型宣言にも関連があります。

<?php

/**
 * @ORM\Column(type="string")
 * @var string|null
 */
private $text; // "Property $text type mapping mismatch: property can contain string|null but database expects string."

/**
 * @ORM\Column(type="string", nullable=true)
 */
private string $title; // "Property $one type mapping mismatch: database can contain string|null but property expects string."

この目的のために独自の型を記述することもできます。詳細はREADMEをお読みください

“class-string” 擬似型

実在するクラス名の文字列だと仮定して関数がすべての文字列を受け入れるのは危険です。そのため、PHPStanは適用可能な全てのPHPDocにclass-string擬似型を導入します。リテラル文字列または::class定数のみを引数を渡すことができます。または呼び出しチェーン全体でclass-stringを要求できます。class_exists()関数および関連する機能でstringからclass-stringに絞り込み(narrow)ができます。これはレベル7以上でのみ報告されます。

副作用がない関数呼び出し

副作用がない関数・メソッドを単独行で呼び出すことには意味がありません。戻り値が欲しいときにだけ、その関数・メソッドを呼び出すべきです。void戻り値型の正反対と言えます。PHPStanは戻り値を変数に代入もせず、引数にも渡さない、「戻り値の使い忘れ」を検出します。

そのような関数は、たとえば count()sprintf()DateTimeImmutableクラスの多くのメソッドなどです。

$date = new \DateTimeImmutable();

// "Call to method DateTimeImmutable::setTime() on a separate line has no effect."
$date->setTime(0, 0, 0);

// OK
$midnight = $date->setTime(0, 0, 0);

Baseline to the rescue!

この記事は新機能の完全なリストではありません。簡単な内容まで含めると圧倒的な量になってしまいます。そのためにリリースノートがあります。

最新の検査を使えるようにするために、どなたにもアップグレードをお勧めします。修正しなければならない新しいエラーの数が不安なら、最近リリースされたベースライン機能で、すぐにビルドを「グリーン」にして、時間のあるときに修正することができます。ただし、変更された行と新しいファイルは新しいPHPStanによる基準が保持されます。


PHPStanが好きで毎日使っていますか? PatreonでPHPStanの開発をサポートしてください。本当に感謝しています!

(翻訳ここまで)

訳者あとがき

PHPStanは活発に開発されている静的解析ツールのひとつです。Phan, Psalmなど競合ツールとの比較については、訳者(@tadsan)が先日勉強会で話しました(「PHPプロジェクトに静的解析を新規導入する」 #phperkaigi)。

そのなかでもPHPStanは定義情報を動的にロードすることで高速に検査できる特徴を持ちますが、それはPhanやPsalmと比べて検査項目が劣るということではなく、単にそれぞれの実装ごとに別のアプローチで問題を検出してくれるということです。現状では三種類のツールのどれかでしか検出してくれない問題というものもあって、併用できるものです。現在開発中のCakePHP 4.0.0は静的解析が強化されていますが、これはPHPStanとPsalmを併用しているそうです。

Phan, PHPStan, Psalmはどれもハイペースで開発されているので、可能な限り最新のリリースを使うようにしましょう。今回の記事中で言及されているGenerics in PHP using PHPDocs - Ondřej Mirtes - MediumPHPStan: Find Bugs In Your Code Without Writing Tests!についても近日中に翻訳したいと思います。

最後になりましたが、ありがとうOndřej! 私たちは毎日毎秒PHPStanを使っています。読者の皆様も感謝の気持ちはPatreonで具体的に送りましょう!