超PHPerになろう

Enjoy PHP Programming

PHPStan 2.1: PHP 8.4のプロパティフックなどのサポート!

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

phpstan.org

このリリースは私の過去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で初めてインターフェイス上でプロパティ宣言できるようにもなりました。仮想プロパティを「バッキング値」(実際にオブジェクトに格納される値)なしで持つことができます。最後に重要なこととして、フック本体は長い構文でも短い構文でも記述できます。

プロパティフックはクラスのメソッドによく似ていますが、別のものでもあります。つまり、メソッドについての多くの既存ルールを調整、あるいはコピーする必要がありました:

さらに、プロパティフックのための特別なルールがあります:

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ブロックの解析が強化されます

また、カスタムルールでプロパティフックをサポートするには何をすればいいのかを説明したコミュニティアップデートも投稿しました。

github.com

BetterReflectionへの最初のプロパティフックのサポートと、その後に 見つかった 多くの バグの修正についてJarda Hanslík に大いに感謝を申し上げます 😅

また、PHP-Parser全般の作業だけでなく、私が行なったいくつか修正をマージし、いくつもの報告した問題を修正してくれたNikita Popovにも非常に感謝いたします。

非対称の可視性

PHPでは、プロパティの読み取りと書き込み(代入)にそれぞれ別の可視性を許可できるようになりました。たとえば、パブリックに読み取り可能だがプライベートにのみ書き込み可能なプロパティ、といったものが実現できます。

この機能はプロパティフックよりもはるかに理解しやすいものですが、注意点もあります。

プロパティフックと同じように、後々実現できれば役に立つ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の機能追加、特にプロパティ回りはシンプルに見えて表面通りではないのは今回の記事通りです。

www.php.net

PHP 8.4サポートといえばBcMathサポートも必要なのですが… がんばります。