対象読者: WSL2 + Docker で PHP 開発中で、静的解析を初めて導入する方
既存記事への依存を置かず、WSL2 + Docker 環境で PHPStan を最小導入する手順をまとめます。
ゴールは「最初のエラー1件を確認し、修正して解析0件まで通す」ことです。
主線コマンドは ./vendor/bin/phpstan analyse に固定します。
composer analyse は最後に任意ショートカットとして扱います。
前提環境
- Windows 11
- WSL2(Ubuntu)
- VS Code(Remote - WSL)
- Docker Desktop(WSL連携有効)
以降のコマンドは、特記がない限り WSL 側ターミナルで実行します。
サービス名は app に固定しています。
1. ゴールと非対象
この記事で到達する状態:
- PHPStan を
phpstan/phpstan:^2.1で導入できる - 最初のエラー1件を確認し、修正して解析0件まで通せる
./vendor/bin/phpstan analyseを主線に継続できる
この記事で扱わない内容:
- baseline や
ignoreErrorsの本格運用 phpstan-strict-rulesなどの拡張導入- Laravel / Symfony / Slim 向けの専用設定
- GitHub Actions での自動実行
- Psalm など他ツールとの比較
2. 新規デモ環境を作成して起動する
WSL 側のシェルで作業します。
Windows 側から始める場合は wsl で Ubuntu に入り、以下を実行してください。
# Windows側から始める場合のみ実行
# wsl
mkdir -p ~/projects/phpstan-first-error-fix-demo
cd ~/projects/phpstan-first-error-fix-demo
mkdir -p docker/php src
code .
compose.yml を作成します。
services:
app:
build:
context: .
dockerfile: docker/php/Dockerfile
working_dir: /workspace
volumes:
- ./:/workspace
command: ["sleep", "infinity"]
docker/php/Dockerfile を作成します。
Composer が配布アーカイブを展開できるように、ここでは unzip も入れておきます。
FROM php:8.5-cli
RUN apt-get update \
&& apt-get install -y --no-install-recommends unzip \
&& rm -rf /var/lib/apt/lists/*
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /workspace
composer.json を作成します。
この時点で autoload.psr-4 に src/ を登録しておくと、後続の GreetingBuilder を素直に読み込めます。
{
"name": "example/phpstan-first-error-fix-demo",
"type": "project",
"require": {},
"require-dev": {},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
起動して確認します。
docker compose up -d --build
docker compose exec app php -v
docker compose exec app composer --version
詰まり時:
- コンテナが起動しない:
docker compose logs app - 依存導入で zip 展開エラーが出る: Dockerfile に
unzipが入っているか確認し、docker compose up -d --buildを再実行
3. PHPStanを導入してバージョンを確認する
PHPStan を導入します。
バージョンを固定しておくと、環境ごとの差分が少なくなり、再現しやすくなります。
docker compose exec app composer require --dev phpstan/phpstan:^2.1
docker compose exec app ./vendor/bin/phpstan --version
docker compose exec では実行パスを明示するほうが安定するため、以後は ./vendor/bin/phpstan を主線にします。
composer require --dev ... を実行すると、composer.json の require-dev には依存が自動追記されます。
詰まり時:
phpstanが見つからない:docker compose exec app ls -la vendor/binを確認- 依存解決に失敗する:
docker compose exec app php -vとdocker compose exec app composer --versionを再確認
4. 最小設定と失敗するサンプルを作る
まずは最小設定だけで始めます。
phpstan.neon を作成します。
parameters:
level: 5
paths:
- src
今回は、GreetingBuilder を「入力値から挨拶文を返す最小クラス」として置きます。
ただし最初は、わざと型が食い違う失敗版にします。
src/GreetingBuilder.php を作成します(失敗版)。
<?php
declare(strict_types=1);
namespace App;
final class GreetingBuilder
{
public function build(int $name): string
{
$trimmed = $this->normalizeName($name);
if ($trimmed === '') {
return 'Hello, guest!';
}
return "Hello, {$trimmed}!";
}
private function normalizeName(string $name): string
{
return trim($name);
}
}
ここでは build() が int を受け取るのに、normalizeName() は string を要求しています。
次章で、この食い違いを PHPStan に見つけてもらいます。
詰まり時:
src/が解析対象に入らない:phpstan.neonのpathsを確認- クラスの読込で迷う:
composer.jsonのautoload.psr-4がApp\\=>src/になっているか確認
5. 解析を実行して最初のエラー1件を読む
解析を実行します。
docker compose exec app ./vendor/bin/phpstan analyse
実行結果例:
------ -------------------------------------------------------------------
Line GreetingBuilder.php
------ -------------------------------------------------------------------
10 Parameter #1 $name of method App\GreetingBuilder::normalizeName()
expects string, int given.
argument.type
------ -------------------------------------------------------------------
[ERROR] Found 1 error
確認する点は4つです。
- 問題が起きている行番号は
10 - 対象メソッドは
normalizeName() - 期待されている型は
string - 実際に渡している型は
int
つまり、PHPStan は「string が必要なメソッドへ int を渡している」と教えてくれています。
今回は build() の引数型が原因なので、次章でそこを直します。
詰まり時:
- エラーが0件になる: 4章の失敗版を保存し直して再実行
- 想定外に複数件出る:
phpstan.neonのpathsがsrcだけか確認
6. 引数型を修正して解析0件にする
今回は「名前を受け取って挨拶文を作る」クラスなので、build() の引数は string にそろえます。
src/GreetingBuilder.php を修正版に更新します。
<?php
declare(strict_types=1);
namespace App;
final class GreetingBuilder
{
public function build(string $name): string
{
$trimmed = $this->normalizeName($name);
if ($trimmed === '') {
return 'Hello, guest!';
}
return "Hello, {$trimmed}!";
}
private function normalizeName(string $name): string
{
return trim($name);
}
}
再実行して確認します。
docker compose exec app ./vendor/bin/phpstan analyse
成功時は次のように表示されます。
[OK] No errors
今回は int を string に変えるだけの最小修正で済みました。
静的解析は、実行前にこうした型の食い違いを見つけるための道具です。
詰まり時:
- まだ失敗する:
normalizeName()に渡す値の型を見直す - 変更が反映されない: 保存漏れと実行場所を確認する
7. 任意: composer analyse で実行を短縮する
主線は ./vendor/bin/phpstan analyse のままですが、任意でショートカットを追加できます。
2章の composer.json を次の最終版に更新します。
{
"name": "example/phpstan-first-error-fix-demo",
"type": "project",
"require-dev": {
"phpstan/phpstan": "^2.1"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"scripts": {
"analyse": "phpstan analyse"
}
}
実行例:
docker compose exec app composer analyse
composer analyse が動くのは、Composer scripts 実行時に vendor/bin が PATH に追加されるためです。
詰まり時:
composer analyseが失敗する:scripts.analyseのキー名とコマンド文字列を確認
8. まとめと次の一歩
PHPStan 2.1 を最小構成で導入し、型の食い違いを解析で検出・修正して 0 件まで通す一連を確認できました。
実行不能になった場合は3章へ、エラーの読み直しは5章へ、修正の見直しは6章へ戻って確認してください。
次の一歩:
- 解析対象を
srcからtestsやapp全体へ広げる - ルールレベルを1段ずつ引き上げて、増えた指摘を整理する
- GitHub Actions で
composer analyseを自動実行する記事へ進む