Psalm(サーム)は、PHPコードを実行せずに型の矛盾やnull参照、ユーザー入力の危険な流れを見つける静的解析ツールです。現行の安定版は6.20.1(2026年10月20日リリース)で、次の7系は10月5日にリリース候補(RC)へ入り、22日のrc8まで進んでいます。この記事では、PHP 8.5.8の環境でPsalm 6.20.1・7.0.0-rc8・PHPStan 2.3.1に同じコードを通した結果を示します。psalm --initが選ぶレベルでnull参照が消える理由と、SQLインジェクション検出が設定ひとつで空振りする罠も扱います。7系の変化も、同じコードの実行結果で比べました。
まとめ|Psalm 6.20と7 RCの導入判断と設定の要点
- 本番のCIには6.20.1を入れます。対応PHPは8.1.31以上の8.1系から8.5系までで、系列ごとにパッチ版の下限があります。7.0.0はRCの段階で、プラグインの一部はすでに7を要求しています。
psalm --initはレベルを固定せず、既存コードを走査して決めます。検証コードではlevel 4が選ばれ、null参照の3件がエラーから外れました。nullを拾いたいなら、level 3以下を指定してからbaselineを作ります。- PHPStan 2.3.1は同じコードのnull参照をlevel 8で初めて報告し、
strpos()のfalseを足し算に使う経路と未使用のローカル変数はlevel 10でも報告しませんでした。Psalmはどちらも拾います。 - Psalmにしかない機能はtaint解析(入力汚染の追跡)です。
composer.jsonでext-pdoを要求していないと、6でも7でもPDOへのSQLインジェクションを見逃します。 - 7.0.0-rc8は、6.20.1が見逃した
php://inputやリクエストヘッダー由来の汚染を検出しました。LLMを呼ぶ関数に@psalm-taint-sink llm_promptを付ければ、プロンプト注入の経路も追えます。taint解析は既定で有効になり、通常の1回の実行で型チェックと同時に走ります。 - Laravel中心ならPHPStan+Larastanが無難です。READMEはPsalmの保守者を「唯一のアクティブメンテナ」と書いており、月間ダウンロード数もPHPStanの約7分の1です。
Psalmの実体|開発元・読み方と6.20・7 RCの位置づけ
PsalmはVimeo社内で生まれたOSSで、リポジトリは今もvimeo/psalmです。作者はMatt Brown氏で、現在の保守はDaniil Gentili氏が担っています。GitHubのREADMEは同氏を「the only active maintainer of Psalm」と明記し、過去のメンテナとしてOrklah氏とBruce Weirdan氏を挙げています。
名前の「psalm」は英語で聖書の「詩篇」を指す単語で、pは発音しないため「サーム」と読みます。2026年10月23日時点で、GitHubのスター数は5,899、Packagistの月間ダウンロード数は2,312,640件でした。同じ日のPHPStanはスター14,134、月間16,741,383件です。
Psalm 6で変わった既定値|未使用コード検出が最初から有効
6系で見落としやすいのは既定値の変更です。UPGRADING.mdによると、find_unused_codeとfind_unused_baseline_entryの既定値がfalseからtrueに変わりました。--initで生成されるpsalm.xmlにもfindUnusedCode="true"が入ります。外から呼ばれるだけのクラスを持つライブラリでは、UnusedClassやPossiblyUnusedMethodが大量に出ます。公開APIを持つパッケージでは、最初にこの属性を見直してください。
PHPの要件は、6.20.1のcomposer.jsonで~8.1.31 || ~8.2.27 || ~8.3.16 || ~8.4.3 || ~8.5.0です。系列ごとにパッチ版の下限があるため、「PHP 8.1なら動く」とは限りません。7.0.0-rc8はここに~8.6.0を加えています。
LaravelとSymfonyのプラグインの版の組み合わせ
困るのは、プラグインが本体より先に7系へ移っている点です。最新版をそのまま指定すると6系の環境には入りません。Psalm 6.20.1の環境でcomposer require --devを試すと、Composerは次の表の「6系の環境で入る版」を選びました。
| パッケージ | 最新版 | 最新版が要求するPsalm | 6系の環境で入る版 |
|---|---|---|---|
| psalm/plugin-laravel | v4.17.0 | ^7.0.0-beta23 | v3.17.0 |
| psalm/plugin-symfony | v5.6.0 | ^7.0.0-rc3 | v5.3.0 |
plugin-laravelのv3.17.0が対応するLaravelは、11系が11.35以上、12系が12.14以上、13系が13.3以上です。v3・v4ともPHP 8.2以上が必要です。7系への移行後、プラグインは別パッケージpsalm/psalm-plugin-apiの版で互換性を示す方式に変わります。
インストールとpsalm.xml|initコマンドが選ぶレベルの落とし穴
導入はComposerの開発依存として入れ、--initで設定ファイルを作ります(出力は空行を詰めて抜粋)。
composer require --dev vimeo/psalm
vendor/bin/psalm --init
Calculating best config level based on project files
Detected level 4 as a suitable initial default
Config file created successfully. Please re-run psalm.
生成されたpsalm.xmlは次のとおりです。errorLevelが解析の厳しさで、projectFilesが対象ディレクトリです。
<?xml version="1.0"?>
<psalm
errorLevel="4"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
findUnusedBaselineEntry="true"
findUnusedCode="true"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>
公式のエラーレベル解説は「レベルを明示しなければlevel 2」と書いています。ところが--initはコードを走査して「最初に通しやすいレベル」を算出し、それを書き込みます。次章のコードではlevel 4でしたが、後半のtaint解析で使うファイルを足すとlevel 7に変わりました。level 4以上ではnull参照の可能性を示すPossiblyNull系の指摘がエラーから外れます。--initの結果をそのまま採用すると、Psalmの強みを切った状態で運用が始まります。
PhpStormでPsalmを動かす設定
PhpStormはPsalmをインスペクションとして組み込めます。JetBrainsのPhpStorm 2026.2ヘルプによると、手順は2段階です。
- 設定(Windows・LinuxはCtrl+Alt+S、macOSは⌘,)の「PHP | 品質ツール | Psalm」で実行ファイルを指定します。Composerで入れた場合は
vendor/binのPsalmが自動で検出されます。 - 「エディター | インスペクション」で「PHP | 品質ツール」の「Psalm 検証」を有効にします。設定ファイルを指定しなければ、プロジェクトルートの
psalm.xmlかpsalm.xml.distが使われます。
エディター上の指摘はpsalm.xmlのerrorLevelに従うため、CIとエディターで結果を揃えるには同じ設定ファイルを共有します。PhpStorm自体の導入やPHPDoc補完はPhpStormの使い方で扱っています。
エラーレベル1〜8の違い|level 4でnull参照がエラーから外れる実測
Psalmのレベルは1が最も厳しく、8が最も緩い設計です。検証には次のファイル(src/UserRepo.php)を使いました。find()がnullを返しうるのに、その戻り値のプロパティへ直接触れています。ほかに未使用変数、戻り値型のないメソッド、$_GETの直接利用を含めています。
<?php
declare(strict_types=1);
namespace App;
final class User
{
public function __construct(public readonly int $id, public readonly ?string $email) {}
}
final class UserRepo
{
/** @var array<int, User> */
private array $users = [];
public function find(int $id): ?User
{
return $this->users[$id] ?? null;
}
public function emailDomain(int $id): string
{
$user = $this->find($id);
return strtolower(substr($user->email, strpos($user->email, '@') + 1));
}
public function label(int $id): string
{
$unused = 'x';
return 'user-' . $id;
}
public function count()
{
return count($this->users);
}
}
function search(\PDO $pdo): void
{
$name = $_GET['name'];
$pdo->query("SELECT * FROM users WHERE name = '" . $name . "'");
echo $name;
}
環境はmacOS(x86_64)・PHP 8.5.8です。composer.jsonにはext-pdoと、開発依存のPsalm・PHPStanだけを入れました。Psalmは前章のpsalm.xmlを使い、vendor/bin/psalm --no-cache --error-level=Nでレベルだけを切り替えています。
| レベル | 6.20.1の件数 | 7.0.0-rc8の件数(ERRORのみ) | null参照・false混入 | 戻り値型の欠落 |
|---|---|---|---|---|
| 1・2 | 11件 | 18件 | エラー | エラー |
| 3 | 10件 | 17件 | エラー | infoへ格下げ |
| 4〜8 | 4件 | 11件 | infoへ格下げ | infoへ格下げ |
6.20.1のlevel 4〜8で残った4件は、未使用のクラス・メソッド・プロパティ・変数でした。null関連のPossiblyNullPropertyFetchとPossiblyNullArgument、strpos()のfalseを足し算に使うPossiblyFalseOperandは、level 3まではエラーです。level 4では一覧から消え、--show-info=trueを付けたときだけinfoとして表示されます。infoは終了コードに影響しません。6.x系の公式説明も、level 4は「起こりうる(possible)問題」を無視すると書いています。7.0.0-rc8はこのほかに、通常の実行でもSECURITYとしてTaintedSql・TaintedHtml・TaintedTextWithQuotesの3件を全レベルで出します。
level 1と2の差は、主にMixed*系とLessSpecificReturnTypeの扱いです。検証コードには該当する指摘が無いため、件数は同じでした。既存コードへ入れるなら、level 3かlevel 2で始めて既存のエラーをbaselineへ退避します。level 4で始めるより、拾えるバグが多くなります。7.0.0-rc8のERRORが全レベルで7件多い理由は、後半の「Psalm 7 RCで変わる点」で説明します。
PsalmとPHPStanの違い|同じコードで比べた検出範囲
同じファイルをPHPStan 2.3.1のlevel 0〜10でも解析しました。設定ファイルも拡張も使わず、vendor/bin/phpstan analyse src --level=Nで実行しています。PHPStanは0が最も緩く10が最も厳しい、Psalmとは逆向きの11段階です。
| 検出内容 | Psalm 6.20.1 | PHPStan 2.3.1 |
|---|---|---|
| nullの可能性があるプロパティ参照 | level 3以下 | level 8以上 |
| strposのfalseを加算に使う | level 3以下 | level 10でも出ない |
| 戻り値型の欠落 | level 2以下 | level 6以上 |
| $_GETの値を文字列連結 | level 3以下 | level 9以上(mixed) |
| 未使用のローカル変数 | 全レベル | level 10でも出ない |
| SQL・HTMLへの入力汚染 | taint解析で検出 | 本体に機能なし |
| 自動修正 | Psalterで修正 | –fixは0件 |
PHPStanがstrpos()の件を報告しないのは、PHPではfalseに1を足すと1になり、実行時エラーにならないからです。ただしこの例では、@を含まないメールアドレスを渡すと先頭1文字を落とした値が返ります。Psalmはこの種の「動くが意図と違う」経路まで型で追います。
逆方向の差もあります。PHPStanは拡張の生態系が厚く、Laravel用のLarastan(v3.13.1はPHPStan ^2.3.0を要求)が事実上の標準です。PHPStanのレベル設計とbaseline運用はPHPStanのレベル0〜10の違いと導入で詳しく扱っています。
PHPの静的解析ツールの位置づけ|PHPStan・Psalm・Phan・Mago・PHPMD
「PHPの静的解析ツール」で比較されるのは、主に次の5つです。型チェックの主力はPHPStanとPsalmで、残りは速度や別の観点で補う位置にあります(版は2026年10月23日時点のPackagist)。
| ツール | 最新版 | 得意な領域 |
|---|---|---|
| PHPStan | 2.3.1 | 型チェック・拡張の多さ |
| Psalm | 6.20.1 | 型チェック・taint解析・自動修正 |
| Phan | 6.0.11 | 型チェック |
| Mago | 1.58.2 | Rust製のリンター・整形・解析 |
| PHPMD | 3.0.0 | 複雑度・命名などの設計指標 |
速度を優先するRust製の選択肢はMago(PHPのRust製リンター・静的解析)、複雑度や未使用パラメータの検出はPHPMDの使い方で解説しています。
PsalmとPHPStanの選び方|判断を分ける3条件
次の条件のどれかに当てはまれば、Psalmを入れる価値があります。どれにも当てはまらなければPHPStanを選びます。
- SQL・HTML・シェルコマンドへの入力汚染を静的に追いたい。PHPStan本体にtaint解析は無く、ここがPsalmの最大の差です。
- レガシーコードで「型は合っているがfalseやnullが紛れ込む」バグが多い。上の表のとおり、level 3以下のPsalmは同じコードからより多くを拾います。
- 型の欠落や未使用コードを機械的に直したい。Psalterは戻り値型の追加や未使用変数の削除をコードへ書き戻します。
メンテナンス体制の差は、採用前に織り込んでください。Psalmのアクティブメンテナは1人です。長期保守で両者を併用するなら、PHPStanに型チェック、Psalmにtaint解析を割り当てます。そのうえで、二重に管理する設定と警告の範囲を決めます。@psalm-typeなどの注釈は両者で共有できます(FAQで後述)。
taint解析|SQLインジェクション検出が空振りする拡張スタブの罠
Psalmの--taint-analysisは、$_GETなどの入力(ソース)がechoやSQL実行(シンク)へ届く経路を追跡します。代入、関数呼び出し、配列やプロパティを経由しても追えます。前章のsearch()関数に対して実行すると、6.20.1では次の結果になりました(抜粋・末尾のURLは省略)。
$ vendor/bin/psalm --taint-analysis --no-cache
ERROR: TaintedHtml - src/UserRepo.php:42:10 - Detected tainted HTML
ERROR: TaintedTextWithQuotes - src/UserRepo.php:42:10 - Detected tainted text with possible quotes
これはcomposer.jsonからext-pdoを外した状態の結果です。echoによるXSSは検出されましたが、query()の行のSQLインジェクションは報告されません。原因は、PDOの定義ファイル(スタブ)が読み込まれていないことです。Psalm同梱のpdo.phpstubはPDO::query()をSQLのシンクとして定義しています。しかし設定リファレンスは「既定ではcomposer.jsonが要求する拡張だけを有効にする」と書いています。PHP本体にPDOが入っていても、composer.jsonに書かれていなければPDOはシンクとして扱われません。7.0.0-rc8でも結果は同じでした。
対処は2通りあり、どちらでもTaintedSqlが出るようになります。1つ目はcomposer.jsonで拡張を要求する方法です。
"require": {
"ext-pdo": "*"
}
2つ目はpsalm.xmlで明示的に有効化する方法です。
<enableExtensions>
<extension name="pdo"/>
</enableExtensions>
ERROR: TaintedSql - src/UserRepo.php:41:17 - Detected tainted SQL
ERROR: TaintedHtml - src/UserRepo.php:42:10 - Detected tainted HTML
ERROR: TaintedTextWithQuotes - src/UserRepo.php:42:10 - Detected tainted text with possible quotes
6系にはもう1つ制約があります。6.x系のSecurity Analysisの説明は「taint解析を有効にすると、それ以外の解析は行わない」と明記しています。6.20.1で試すと、level 1の通常の実行で出た型エラー16件が、同じレベルのtaint解析の実行では1件も出ませんでした。6系では型チェックとtaint解析をCIの別ジョブに分けます。
独自の関数は、中身を解析できれば自動で追跡されます。追跡が切れるのは、実装が解析対象外にあり、スタブやプラグインにもシンクの定義が無い場合です。そのときは@psalm-taint-sinkで自分で印を付けます。SQLインジェクションそのものの仕組みと対策はSQLインジェクションの仕組みと対策、静的解析が原理的に拾えない領域は静的コード解析の誤検知と限界で整理しています。
Psalm 7 RCで変わる点|taint解析の同時実行とLLMプロンプト検出
7系の最初のベータ(2025年3月20日)のリリースノートは、taint解析の内部表現を作り直し、最大10倍速くなったと告知しています。速度以外にも、検出範囲と実行方法が変わりました。次のファイルを足し、6.20.1と7.0.0-rc8に同じ--taint-analysisを実行して比べました。
<?php
declare(strict_types=1);
namespace App;
/**
* @psalm-taint-sink llm_prompt $prompt
*/
function askLlm(string $prompt): string
{
return strlen($prompt) > 0 ? 'ok' : '';
}
function fromBody(\PDO $pdo): void
{
$body = file_get_contents('php://input');
$pdo->query("SELECT * FROM logs WHERE body = '" . $body . "'");
}
function fromHeader(): void
{
echo $_SERVER['HTTP_X_FORWARDED_HOST'];
}
function fromRequest(): void
{
echo $_REQUEST['q'];
}
function prompt(): string
{
return askLlm('次の質問に答えてください: ' . $_GET['q']);
}
function typeError(): int
{
return 'not int';
}
6.20.1が見逃した入力元とLLMプロンプト
| 入力元 → 出口 | 6.20.1 | 7.0.0-rc8 |
|---|---|---|
| $_GET → PDO::query | TaintedSql | TaintedSql |
| $_REQUEST → echo | TaintedHtml | TaintedHtml |
| php://input → PDO::query | 出ない | TaintedSql |
| リクエストヘッダー → echo | 出ない | TaintedHtml |
| $_GET → llm_prompt | 出ない | TaintedLlmPrompt |
| 型エラー(戻り値の不一致) | 出ない | 同時に出る |
JSONのAPIは本文をphp://inputから読むことが多く、6.20.1ではその経路のSQLインジェクションが素通りしました。7(masterブランチ)のSecurity Analysisの説明は、既定のソースに、クライアントが送る$_SERVERの項目(HTTP_*のヘッダーやREQUEST_URIなど)、php://inputの読み取りを加えています。taintの種類にも、llm_prompt(プロンプトインジェクション)、nosql、xpath、sleepなどが加わりました。同梱のスタブにはllm_promptのシンクが無いため、LLM SDKを呼ぶ自前の関数に注釈を付けます。LLMへ渡す文字列に利用者の入力が混ざる経路を、SQLやHTMLと同じ仕組みで追えます。攻撃の型と対策はプロンプトインジェクションの攻撃例と対策で解説しています。
7.0.0-rc8の出力では、セキュリティ系の指摘がERRORではなくSECURITYの見出しで表示されます。7.0.0-rc8では設定のrunTaintAnalysisが既定で有効になり、フラグを付けない通常の実行でもtaint解析が走ります。InvalidReturnStatementなどの型エラーとセキュリティの指摘が、同じ1回の実行で出ました(抜粋・末尾のURLは省略)。
SECURITY: TaintedLlmPrompt - src/Taint.php:8:17 - Detected tainted LLM prompt
SECURITY: TaintedSql - src/Taint.php:16:17 - Detected tainted SQL
SECURITY: TaintedHtml - src/Taint.php:21:5 - Detected tainted HTML
ERROR: InvalidReturnStatement - src/Taint.php:36:12 - The inferred type ''not int'' does not match the declared return type 'int' for App\typeError
7で増えるMissingPureAnnotationと自動修正
エラーレベルの表で7.0.0-rc8のERRORが7件多かった内訳は、MissingPureAnnotation4件、MissingImmutableAnnotation2件、未使用プロパティ1件です。7は副作用の有無を「capabilities」という単位で細かく扱うようになりました。そのため、純粋にできる関数やクラスに注釈が無いと、taint解析の精度を上げる目的で指摘します。この6件はlevel 8でも消えません。
手で書く必要はなく、Psalterが注釈を足します。適用すると、注釈に関する6件はすべて消えました(差分は抜粋)。
vendor/bin/psalm --alter --issues=MissingPureAnnotation,MissingImmutableAnnotation --dry-run
+/**
+ * @psalm-immutable
+ */
+ /**
+ * @psalm-capabilities read-props
+ */
7へ上げる判断は、taint解析を使うかどうかで分けます。taint解析をCIに入れているなら、php://inputとヘッダーの検出漏れが無くなる利点が大きいため、RCの段階から別ブランチで試す価値があります。型チェックだけに使っている場合も、7では既定でtaint解析とMissingPureAnnotationなどの指摘が加わります。正式版とプラグインの対応を待ってから、runTaintAnalysisの扱いを決めます。
既存プロジェクトへの段階導入|baselineとPsalterの自動修正
既存コードに厳しいレベルを当てると大量のエラーが出ます。その場合は現状をbaselineファイルへ記録し、新しく増えたエラーだけを止める運用にします。
vendor/bin/psalm --set-baseline=psalm-baseline.xml # 現在のエラーを記録
vendor/bin/psalm # 以降は新規分だけが出る
vendor/bin/psalm --update-baseline # 直した分をbaselineから外す
baselineは、運用したいレベルをpsalm.xmlに設定してから作ります。緩いレベル(数字の大きいレベル)で作ってから厳しいレベルへ下げると、そのレベルで初めてエラーになる既存の指摘はbaselineに入っていないため、一度に表に出ます。6系ではfindUnusedBaselineEntryが既定で有効なので、直したのにbaselineに残っている項目も指摘されます。6.20.1では、--set-baselineと--use-baselineを同時に指定したときに設定ファイルを書き換えない修正が入りました。
個別に抑止するなら、該当行の直前に/** @psalm-suppress PossiblyNullArgument */を書きます。公開APIのように外部から呼ばれるクラスには、公式のUnusedClassの説明どおり@psalm-apiを付け、抑止ではなく「公開APIである」という意図をコードに残します。
型の書き足しはPsalter(psalm --alter)に任せられます。--dry-runで差分を確認してから適用します。
$ vendor/bin/psalm --alter --issues=MissingReturnType,UnusedVariable --dry-run
- $unused = 'x';
- public function count()
+ /**
+ * @psalm-return int<0, max>
+ */
+ public function count(): int
戻り値型はintに加えて、PHPDocにint<0, max>という範囲付きの型まで書き足されます。同じファイルにPHPStan 2.3.1の--fixを当てると「No fixable errors found」で、何も変わりませんでした。型の穴埋めを自動化したいなら、現状はPsalmに分があります。
CIへの組み込み|GitHub Actionsの設定と終了コード
Psalmの終了コードは、0が問題なし、1がPsalm自体の実行失敗、2が問題の検出です。|| trueで握りつぶすと設定ミスによる1まで隠れるため、使いません。--output-format=githubを指定すると、プルリクエストの該当行に注釈が付きます。
name: psalm
on: [pull_request]
jobs:
types:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
- run: composer install --no-interaction --no-progress
- run: vendor/bin/psalm --output-format=github --threads=4
taint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
- run: composer install --no-interaction --no-progress
- run: vendor/bin/psalm --taint-analysis --output-format=github
6系では、taint解析が他の解析を止めるため、ジョブを2つに分けています。7.0.0-rc8は通常の実行でtaint解析まで行う(runTaintAnalysisの既定値がtrue)ため、7へ移った時点でtaintジョブを削除し、typesジョブだけにします。taint解析を止めたい場合はpsalm.xmlにrunTaintAnalysis="false"を書きます。出力形式はgithubのほかにsarif・checkstyle・junit・sonarqube・codeclimateなどがあります。SARIFで出せばGitHub Code Scanningの画面に取り込めます。
静的解析とテストの役割分担は静的テストと動的テストの違いで、同じコードを使って比べています。PHPで業務システムを新しく組む段階から、静的解析をCIに入れた開発体制を用意したい場合は、フルスクラッチ開発でCI設計から支援しています。
よくある質問
Psalmの読み方と名前の意味は何ですか?
「サーム」と読みます。psalmは英語で聖書の「詩篇」を意味する単語で、先頭のpは発音しません。リポジトリ名がvimeo/psalmなのは、Vimeo社内で開発が始まったためです。
PsalmとPHPStanは併用できますか?
併用できます。両方を開発依存に入れ、CIで別ステップとして走らせるのが一般的です。@psalm-typeで定義した型エイリアスは、PHPStan 2.3.1もそのまま解釈しました。存在しない配列キーの参照は、PsalmがInvalidArrayOffset、PHPStanがoffsetAccess.notFoundとして、どちらも検出しました。注釈を二重に書く必要はありません。
LaravelでPsalmを使うには何を入れればよいですか?
composer require --dev psalm/plugin-laravelのあと、vendor/bin/psalm-plugin enable psalm/plugin-laravelで有効化します。Psalm 6.20.1の環境ではv3.17.0が入ります。最新のv4系はPsalm 7を要求するため、6系のままでは入りません。Laravel中心で型チェックだけが目的なら、PHPStan+Larastanの方が情報も利用者も多い選択です。
@psalm-typeとは何ですか?
クラスのPHPDocで配列の形などに名前を付ける注釈です。@psalm-type UserRow = array{id: int, email: string}と書けば、同じクラスの@return UserRowで再利用できます。別クラスから使うときは@psalm-import-typeで読み込みます。
Psalmの開発は続いていますか?Psalm 7はいつ出ますか?
続いています。2026年10月だけでも、安定版の6.19.2・6.20.0・6.20.1と、7.0.0のrc1からrc8が公開されました。10月にはmasterと6.xのブランチを、テストが通れば1時間ごとにリリースする仕組みも入っています。7.0.0の正式版の日付は、10月23日時点で告知されていません。READMEは保守者を1人と明記しているため、長期案件ではPHPStanを型チェックの主軸に置き、Psalmはtaint解析のような代わりの無い用途に絞る構成が安全です。