https://d226lax1qjow5r.cloudfront.net/blog/blogposts/scrub-up-cleaning-your-php-application-with-phpstan/scrub-up_phpstan.png

スクラブアップPHPStanでPHPアプリケーションをクリーニング

最終更新日 November 29, 2021

所要時間:5 分

私がPHP開発者として働いている間に、コードを書いて出荷する方法は劇的に変化した。初期の symfonyZend Frameworkアプリケーションでは PHP-FIGは存在せず、コーディング標準はそれを書く人の裁量に任されていました。この数年で PSR 標準この数年間、PSR 標準が広く採用されるようになったものの、 堅牢な静的解析ツールはほとんど提供されていませんでした。しかし、この度 のバージョン1.0がリリースされた。.この機会に、PHPStanの機能をいくつか紹介しよう!

コンパイル済み言語、あなたの先制バグ潰しツール

のようなコンパイル言語を使うことの大きな利点のひとつは、次のようなことだ。 JavaC#のようなコンパイル言語を使うことの大きな利点のひとつは、コンパイル時にコードが タイプセーフ標準を強制することである。PHPでは PHPはインタプリタ言語であるため、そのような余裕はありません。

コンパイル済みと解釈:CI + ツーリング

最近のウェブ開発と静的解析では、膨大な量のDevOpsツールが利用できる。 私たちは同じツールを使っているが、異なる手段を使っている。.そうである以上 どの程度これから紹介する環境と同じようなものを用意することをお勧めする。では、なぜこのようなツールが必要なのでしょうか?例を見てみよう。

シナリオ

このようなツールに関しては、楽しいテーマを選んだり、自分が書いていることに当てはまるテーマを選んだりするのが一般的だ。しかし、この記事では、私が個人的にエージェンシー環境で何度も何度も遭遇してきたシナリオを紹介しようと思う:

"助けてください!X/Y/Zの機能が必要なのに、A/B/Cの機能がうまく動かないんだ。"

他人のコードベースやプロジェクトを引き継ぐのは、常に完全な宝くじだ。もし新機能が必要で、すでに技術的な負債で混乱しているから引き継ぐのであれば、他のことに手をつける前にそれを解決しなければならないのはわかるだろう。さらに悪いことに、こういったプロジェクトの多くは(私の経験では)、コードを自己文書化するためのテストがまったくない状態でやってくる傾向がある。私が何度も見てきた典型的な例を考えてみよう:

$someData = \MyNamespace\MyORM\MyRepository::findAllBySomething(SOMETHING);

foreach ($someData as $myEntity) {
	$myEntity->doTheThing();
}

そのエンティティクラスやリポジトリメソッドを書いたのはあなたではありません。もともと PHP5.3 で書かれたものであるため、型ヒントはありません。ORM が同じエンティティの配列を返すのはいいのですが、 バグがひとつ、NULL がひとつ findAllBySomething()doTheThing()は致命的なエラーをスローします。

今こそ PHPStanをセットする時だ。

戦略を知る

PHPStanを使いなさい」と言うのは簡単だが、レガシーなアプリケーションや技術的な負債が多いアプリケーションの場合は、ただ放り投げて何が起こるか見るのではなく、戦略が必要だろう。まず、「ルールレベル」について知っておく必要がある。

ルール・レベル

PHPStanは、0から9までのルールレベルで実行されるように構成されている:

  1. 基本的なチェック、未知のクラス、未知の関数、呼び出された未知のメソッド $thisメソッドや関数に渡される引数の数が間違っている。

  2. を持つクラス上の未定義の変数、未知のマジックメソッド、およびプロパティの可能性があります。 __call__get

  3. 未知のメソッドをすべての式(だけでなく $this)、PHPDocs

  4. 戻り値の型、プロパティに割り当てられた型

  5. 基本的なデッドコード・チェック - 常に偽 instanceofなどの型チェック、デッド else分岐、リターン後の到達不能コードなど。

  6. メソッドや関数に渡される引数の種類のチェック

  7. タイプヒントの欠落を報告する

  8. 部分的に間違ったユニオン・タイプを報告する - ユニオン・タイプ内のいくつかのタイプにのみ存在するメソッドを呼び出すと、レベル7はそのことを報告し始める。

  9. null可能な型に対するメソッドの呼び出しとプロパティへのアクセスを報告する。

  10. 型については厳格である。 mixed型に対して厳格である。 mixed

これが、あなたの戦略が重要な理由である。他の人が書いたレガシープロジェクトで、PHPStanタスクランナーをレベル9で起動すると、その結果に圧倒されるかもしれない。すべてが壊れている!リファクタリングするには、次のようにするのがいいだろう:

  • それぞれのレベルにマイルストーンを設定し、小さなことから始める。

  • 長期的な投資はいずれ報われるだろうが(パイプラインについては追って説明する)、"技術的負債の固定化 "を自分なりの "完了の定義 "で分類する際に、どのレベルまでなら行っても構わないかを設定する。

  • レガシープロジェクトの事実上の目標としては、ルールレベル6に合格するのがよい。この時点で、コードベースは「危険」な状態から「正しい」状態に移行できる可能性が高い。これは、ルールレベル6を があなたのベースライン).

  • これは超重要を修正するための時間(スプリント、マゾヒストのためのJiraチケット)を割り当てること。 修正PHPStanが各ルールレベルでフラグを立てているものを修正する時間(スプリント、マゾヒストはJiraチケットに分割)を確保すること。技術的負債を修正するのは 簡単ではない多くの場合、技術的負債を修正するのは簡単ではない。

  • ルール・レベルのインクリメンタル・ターゲットを設定する際、必ず パイプラインリファクタリング中に新たなコード臭が発生しないように、変更をコミットする前にパイプラインを設定してください。パイプラインをセットアップするには、以下を確立する必要がある。 ベースラインを確立する必要がある。

パイプライン

DevOpsの世界では、問題を解決するために利用可能なツールの選択肢が、やや圧倒的に多い。この例では、私は1つのアプローチだけを提供するが、利用可能な他のオプションよりも複雑ではない。戦略を確立したら、パイプラインをセットアップして、最初にPHPStanを経由していない新しいコードをコミットしないようにしよう。

守備の壁:ローカルとサーバーサイド

私は、単一障害点の可能性を排除するためにツールを導入するのが好きで、その皮肉な結果として、静的解析をローカル開発者のマシンだけでなく サーバーサイドのCIチェックの両方で実行することを強くお勧めします。

ローカル
  • コンポーザー+PHPStan

まず、プロジェクト内にPHPStanをインストールします。ここでは composerを使うことにします。うまくいけば、レガシーコードでもパッケージ管理が使えるかもしれません。そうでない場合は composerをインストールしてをインストールして composer initを使って新しいプロジェクトを作ることができる。

PHPStanをインストールするには、以下を実行する:

$composer require --dev phpstan/phpstan

我々は --devを追加している(理論上は!)。

  • コンフィギュレーション:ベースラインの確立

これはPHPStanの非常に優れた機能です。ベースラインは、アプリの「グラウンドゼロ」を確立し、選択したルールレベル内に存在する現在のエラーは無視されます。 あなたが対処すると決めるまで同時に 同時に、コミットされた新しい変更に対してルールレベルを強制することができます。.戦略で説明されているような賢明なアプローチは、ルール・レベル6にベースラインを設定することだろう:

  • プロジェクトにコミットされるすべての新しいコードは、ルール・レベル6である必要がある。

  • そして、戦略目標で特定された下位レベルの技術負債目標を設定することができる。

ベースラインを作成するには、以下を実行する:

vendor/bin/phpstan analyse --level 6 \ --configuration phpstan.neon \ src/ tests/ --generate-baseline

これで、指定したファイル(phpstan.neon)に設定され、ファイルごとにエラーの詳細な概要が保存されます。

ここで、PHPStanにリポジトリへのコミットを防止させたい場合は、次のようにします。 へのコミットをへのコミットを防ぐようにします。そのために Git フックを使います。

  • Gitフック

の新しい git リポジトリにフックが標準でインストールされることに気づくのに何年もかかりました。 git init.フックについては git フックについての詳細は.これから pre-commitフックを編集します。プロジェクトのルートからこのコマンドを実行します:

mv ./.git/hooks/pre-commit.sample ./.git/hooks/pre-commit

そのファイルを開いて中身を削除し、次のようにコピーする:

#!/bin/sh # exec < /dev/tty if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi gitDiffFiles=$(git diff --name-only --diff-filter=d $against | grep \.php) if [ "$gitDiffFiles" != "" ]; then analysisResult=$(vendor/bin/phpstan analyse $gitDiffFiles) if [ "$analysisResult" = "" ]; then echo 'PHPStan pass' else echo "$analysisResult" exit 1; fi fi # Redirect output to stderr. exec 1>&2 # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against --

これで pre-commitを有効にすると、PHPStan は各コミットの前に起動し、ベースラインと照らし合わせて分析します。 に対して分析を行います。.もうコミットしたコードが臭くなることはありません!

レベルを上げたときにコマンドラインのトリガーを調整したいかもしれないので、変更が必要なとき(あるいは他のPHPStan機能を有効にしたいとき)は analysisResult=$(vendor/bin/phpstan analyse $gitDiffFiles)ライン引数を変更する。

サーバーサイド

コードに対する防御は、多ければ多いほど良い。継続的インテグレーションの一環として、コードをプッシュした後にPHPStanをサーバーサイドで実行することは 必須です。.この例では、Github Actionsを使用しますが、同じレベルの機能で次のように設定できることを覚えておいてください。 CircleCI, Bitbucket Pipelines, Gitlab CI/CDまたは ジェンキンス.ここでは、アクションのワークフローの例を示します。 Ubuntuコンテナでコードをビルドします:

---  
name: build  
  
on: [ push, pull_request ]  
  
jobs:  
 build:  
 runs-on: ubuntu-latest  
    strategy:  
 matrix:  
 name: Build example
    steps:  
 - name: Checkout  
        uses: actions/checkout@v2  
  
      - name: Setup PHP  
        uses: shivammathur/setup-php@v2  
        with:  
 php-version: 8.0  
          extensions: json, mbstring  
          coverage: pcov  
        env:  
 COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}  
  
      - name: Run PHPStan
        run: vendor/bin/phpstan analyse .

Run PHPStan "のコマンドは、ローカルでPHPStanを実行するときと同じように、要件に合わせて設定することができる。このワークフローは、プロジェクト内のすべてのファイルに対して、デフォルトレベルでPHPStanを実行するように書きました(このワークフローはまだ起動していません。 composerこのワークフローはまだ実行されていないので、自分のフォルダで実行するという不要で非効率なステップはない。 vendorこのワークフローはまだ実行されていないので、プロジェクト全体のルールレベルを設定する設定を取り込むことをお勧めする。

あなたのレガシープロジェクトには、コードをスクラップアップするための戦略や、コミット時に新たなバグが発生しないようにするためのパイプラインができた。このようなセットアップを行うことで、技術的負債を取り除くためにリファクタリングが必要になりそうな箇所を洞察しながら、プロジェクトにコミットすることにはるかに自信を持つことができる。

最後に、静的解析とテストの比較。

特に後ろにいる人たちのために、大きな声で言います:PHPStan やその他の静的解析ツールは、テストの代わりにはなりません!PHPStan やその他の静的解析ツールは、テストの代わりにはなりません。 は互いに補完し合うは、コードの品質を評価する上で、お互いを補完し合うものだ。

テスト・スイートはほとんど必要ない、と考えるのは誤解だ。ここで最も重要なことは 静的解析ではドメインロジックをテストできない.当たり前のことのように思えるかもしれませんが、PHPStan では は特定のテストの必要性を排除します。この例としては instanceOfテストは、作成されるクラスがプロセスの最終結果であることを保証します。PHPStanは、この潜在的なバグを除去するために必要な解析を行うので、この要求を取り除くことができます。 ドメインロジックを事前に知ることはできません。 これこそをテストする必要があります。

そして、代替案があることを忘れないでほしい!

やってみた?あまりお気に召しませんでしたか?人それぞれ好みがある。 オンドゥジェイPHPStanを開発したOndřejを賞賛したいが、PHPStanと同じ機能を持つ、あるいは併用できるツールは他にもいくつかある:

ありがとう

オンドゥジェイ・ミルテスには、アドバイスとこの素晴らしいツールのリリースに尽力してもらった。

シェア:

https://a.storyblok.com/f/270183/400x385/12b3020c69/james-seconde.png
James SecondeシニアPHPデベロッパー

スタンダップ・コメディーの学位論文を持つ俳優の訓練を受け、ミートアップ・シーンを経てPHP開発に携わるようになった。技術について話したり書いたり、レコード・コレクションから変わったレコードを再生したり買ったりしています。