この記事はPHPStan開発者のOndřej Mirtesによって2024年12月31日にPHPStan Blogに書かれた記事を翻訳したものです。
このリリースは私の過去4ヶ月にわたる作業の集大成です。9月初めに1.12.xから2.0.xブランチを分岐しました。PHP 8.4構文をサポートするためにはPHP-Parser v5にアップグレードしなければいけませんでしたが、そのためには一度PHPStan 2.0としてリリースする必要がありました。私は11月11日に長いこと待ち望んでいたキュートなPHPStan Elephpantと同時に発表しました。ElephpantはPHPStanのファンによって750個注文され、生産地の中国からヨーロッパまで船で運ばれるには時間がかかります。注文確認メールで約束した通り、2025年の5月から6月に幸せな飼い主の手に渡ることを願っています。
本日リリースしたPHPStan 2.1では、プロパティフック、非対称の可視性、#[Deprecated]
などのPHP 8.4の主要機能を完全に解釈できます。いつものように、PHPStanでこれらの言語機能をサポートするためにどのような検討事項があったかを説明します。
プロパティフック
これが大規模で複雑な新しい言語機能であることはプロパティフックのRFCを見れば明白です。これを読んで、私はPHPStanのコードベースを適切にサポートするために何をするべきか、80個ほどのTODOリストにまとめました😅 リリースした今でもまだ32個ほどのTODOが残っていますが、これらは初期リリースに含めるほど重要ではありませんが、あった方がよいものです。
ひとことでいうと、プロパティフックを使えば、独自コードでプロパティの読み取りと書き込みに処理を割り込みできます。また、PHPで初めてインターフェイス上でプロパティ宣言できるようにもなりました。仮想プロパティを「バッキング値」(実際にオブジェクトに格納される値)なしで持つことができます。最後に重要なこととして、フック本体は長い構文でも短い構文でも記述できます。
プロパティフックはクラスのメソッドによく似ていますが、別のものでもあります。つまり、メソッドについての多くの既存ルールを調整、あるいはコピーする必要がありました:
return
の不足- 返される型
- パラメータ型が存在するか
- PHPDocとパラメータ型の互換性
- PHPStanの構文エラー
- PHPDoc内の未知の
@phpstan-
タグ - 実装本体で送出されたチェック例外を処理するかPHPDocで
@throws
として文書化する必要がある - 使用されないプライベートプロパティのルールでは、フック内のプロパティアクセスを無視する必要がある
- プロパティフックの属性(アトリビュート)
さらに、プロパティフックのための特別なルールがあります:
- 非仮想プロパティの
get
フックは、バッキング値を読み取らなければいけません - 非仮想プロパティの
set
フックは常にバッキング値に書き込まなればいけません
set
フックのパラメータは実際のプロパティの型よりも広くなる可能性があるため、PHPStanはフック内外のプロパティに代入できる型を認識する必要もあります:
<?php class Foo { public int $i { get { return $this->i - 10; } set (int|string $value) { $this->i = $value; // string not allowed } } } $f = new Foo(); $f->i = rand(0,1) ? '10' : 'foo'; // string allowed
プロパティフックによって、読み取りのみ、あるいは書き込みのみが可能なプロパティが言語に実現されます。
<?php interface Foo { public int $i { get; } public int $j { set; } } function (Foo $f): void { $f->i = 42; // invalid echo $f->j; // invalid };
そして最後に、プロパティへのアクセスでは例外がスローされることが予期されるようになったことも述べておきます:
<?php interface Foo { public int $i { get; } } function (Foo $f): void { try { echo $f->i; } catch (MyCustomException) { // PHP 8.3では無駄なcatchだが、8.4以上ではそうではない } };
フックされていないプロパティもサブクラスでフックによって上書きされる可能性があるため、この対象になります。プロパティアクセスは例外をスローしないとPHPStanに力説したければ、プロパティをprivate
またはfinal
にしてください。
フックも @throws
PHPDocタグによって文書化できます。これによって関数やメソッドと同じようにtry-catch
ブロックの解析が強化されます。
また、カスタムルールでプロパティフックをサポートするには何をすればいいのかを説明したコミュニティアップデートも投稿しました。
BetterReflectionへの最初のプロパティフックのサポートと、その後に 見つかった 多くの バグの修正についてJarda Hanslík に大いに感謝を申し上げます 😅
また、PHP-Parser全般の作業だけでなく、私が行なったいくつかの修正をマージし、いくつもの報告した問題を修正してくれたNikita Popovにも非常に感謝いたします。
非対称の可視性
PHPでは、プロパティの読み取りと書き込み(代入)にそれぞれ別の可視性を許可できるようになりました。たとえば、パブリックに読み取り可能だがプライベートにのみ書き込み可能なプロパティ、といったものが実現できます。
この機能はプロパティフックよりもはるかに理解しやすいものですが、注意点もあります。
public
で読み取り可能もしくはprotected
で読み取り可能で、プライベート書き込みのみ可能なプロパティは、暗黙的にfinalになりオーバーライドできません。- プロパティへの参照の取得は読み取りではなく書き込みの可視性に従うため、それを検出するためのルールを用意しました。
プロパティフックと同じように、後々実現できれば役に立つTODOリストを残してあります。
#[Deprecated]
属性
この属性によって、PHP処理系が非推奨の警告をトリガーできるようになりました。
関数、メソッド、クラス定数の上でのみ許可されているため、実際にはあまり役に立たないと思います。最も注目すべき点は、クラス全体を非推奨としてマークするためには利用できないことです。また、プロパティには機能しません。この問題は将来的にPHP側で対応されるかもしれません。
ですが、PHPStan 2.1ではphpstan-deprecation-rulesを組み合わせることで、非推奨のコードをマークして使用している箇所を報告させることもできます。
PHPStanが好きで、毎日使用していますか? GitHub SponsorsでPHPStanのさらなる開発をサポートし、PHPStan Proに登録してください。本当にありがとう!
(翻訳ここまで)
訳者によるまとめ
PHPStan 2.0ではPHP 8.4でのPHPStan実行はサポートされていましたがPHP 8.4の新しい機能はサポートされておらず、公約通り(!)2024年中の12月後半 (の暮れも暮れ…) に8.4サポートのPHPStan 2.1公開されました。PHP 8.4の機能追加、特にプロパティ回りはシンプルに見えて表面通りではないのは今回の記事通りです。
PHP 8.4サポートといえばBcMath
サポートも必要なのですが… がんばります。