Skip to content

PHPStan拡張クイックマスター

公開日:

東京都渋谷区渋谷スクランブルスクエア40F DeNAラウンジで開催された『Lint Night #2』で20分枠として発表しました。

Download PDF

スライドテキスト

Page 1

PHPStan拡張
クイックマスター

Fast-learning PHPStan extension

pixiv Inc.
USAMI Kenta

2023-08-04 Lint Night #2

#dena_lint_night

Page 2

お前誰よ

  • うさみけんた (@tadsan) / Zonu.EXE / にゃんだーすわん
  • ピクシブ株式会社 pixiv事業本部 Webエンジニアリングチーム PHPer
    • 2012年末から現職、APIとかCIとかいろいろなところを見つめてきました
    • 最近はピクシブ百科事典(dic.pixiv.net)も開発しています
  • Emacs PHP Modeを開発しています (2017年-)
  • プログラミング言語にちょっとこだわりのある素人 (spcamp2010)

Page 3

emacs-php

Page 4

PHPカンファレンス沖縄2023

Page 5

LLイベント

Page 6

LLイベント

Page 7

LLイベント

Page 8

PHPって
どんな言語?

Page 9

PHPの型にどのような 印象を持っていますか?

Page 10

P H P の原作者
ラスマスかく語りき

Page 11

“We have things like protected properties. We have abstract methods. We have all this stuff that your computer science teacher told
you you should be using.
I don't care about this crap at all.”

–Rasmus Lerdorf
https://en.wikiquote.org/wiki/Rasmus_Lerdorf

Page 12

には プロパティも
“PHP protected
抽象メソッドもありますよ。計算機科学の
教授が「使え」と言ってるものは全部。

そんなことはクソ興味ないですけど

–Rasmus Lerdorf

Page 13

脆弱性 ゆるふわ 弱い型 動的

クソザコ 型なし 意味不明 弱い 自動変換 貧弱 Perlっぽい 適当
PHPについてのパブリックイメージ

Page 14

脆弱性 ゆるふわ 弱い型 動的

クソザコ 型なし 意味不明 弱い 自動変換 貧弱 Perlっぽい 適当
PHPについての認識は概ね間違い

Page 15

PHPの進化は 型宣言の進化

Page 16

型がついていない関数(PHP5)

function add($a, $b) {
return $a + $b;
}

Page 17

スカラー型宣言(PHP7)

int + intって
本当にintなの?

function add(int $a, int $b): int {
return $a + $b;
}

Page 18

広い値をとるにはfloatが必要

ひとつの解決策では
あるが… 不必要に(cid:152)oat
を強制するのか

function add(float $a, float $b): float {
return $a + $b;
}

Page 19

PHPDocの型注釈

(アノテーション)

/**
* @param int|float $a

あえて型宣言を省略する

* @param int|float $b
* @return int|float
*/

function add($a, $b) {
return $a + $b;
}

Page 20

ユニオン型宣言 (PHP8.0)

function add(int|float $a, int|float $b): int|float {
return $a + $b;
}

Page 21

PHPは強く実行時に保証される型

  • PHPはいわゆる動的言語
  • 構文で型宣言された言語要素(関数パラメータ、戻り値、プロパティ)は、
    実行時にその型の値であることが絶対に保証される
    • null安全 (明示的にnullableとして宣言しない限りnullは受け入れない)
  • declare(strict_types=1) で実行時の振る舞いが少し変わるが、
    型安全性という意味ではあまり変わらないので今回は話しません

Page 22

ユニオン型宣言 (PHP8.0)

intと(cid:152)oat以外は 絶対にありえない

function add(int|float $a, int|float $b): int|float {
return $a + $b;
}

Page 23

PHPDocの型注釈

(アノテーション)

/**

ランタイムにとっては

* @param int|float $a

ただのコメント
(口約束)

* @param int|float $b
* @return int|float
*/

function add($a, $b) {
return $a + $b;
}

Page 24

201X年、PHPは 型の炎に包まれた!!

Page 25

いまやPHPは
静的型付きと言っても
過言ではない
(本当か…?)

Page 26

だが型なしは
滅びていなかった

Page 27

型宣言では配列要素の
型を表現できない

Page 28

PHP組み込み型の限界

  • array型やArrayObjectのような汎用的な型の要素の型がわからない
  • evalやjson_decode()などデシリアライズ結果の型がわからない
    • ……これらの事情によりPHPコードをいくら静的解析しても、
      型が一意に決定されることはない
  • 型宣言されてない既存コード、動的プロパティや動的メソッドが宣言できる
    • 静的解析でも型推論ではない別のアプローチが必要

Page 29

Bookクラスは複数の著者を持つ

arrayにすることで

class Book


複数であることを表現

{

function __construct(
private array $authors

) {

何も書かなくてもプロパティに代入される

// 


Authorクラスの

}

情報が減ってる…

}


Page 30

arrayには無限の可能性

Page 31

PHPDocの帰還

配列の形状はコメントで書く

class Book

{

function __construct(

/** @var list<Authors> */
private array $authors

) {

何も書かなくてもプロパティに代入される

// 

}
}


Page 32

PHPと型の現在地点

  • PHPがランタイムに保証する型と静的解析で検査できる型に分かれる
    • function f(int $n): int のような型宣言は実行時安全
    • function g(array $a): array のような型宣言はarrayの要素が不明
  • PHPDocの型注釈はコメントなので自由に記述できる
    • 標準的な型のほかに静的解析ツールの間で実験的な型も実装されている
    • PHPStanやPsalmの間ではある程度の相互運用が考慮されている

Page 33

PHPStan

Page 34

PHP Static Analysis Tool

Page 35

PHPStanとは

  • 2016年から開発されているPHPの静的解析ツール
    • Ondřej Mirtesさんの個人プロジェクト、2021年からフルタイム開発
  • 開発当初は純粋な静的解析ではなく実行時リフレクションを用いることで
    高速な解析を実現していた
    • 現在は静的解析がデフォルトで、レガシープロジェクトに導入しやすくなった
  • その他のPHP静的解析ツールにはPsalm, Phan, Qodana(PhpStorm)

Page 36

PHPStan拡張

  • PHPStanの機能は組み込みのもの含めextensionとして実装されてる
    • 独自拡張をしたいというときも組み込みの拡張が非常に参考になる
    • PHPでよく使われるパッケージ管理ツールのComposerから追加可能
  • よく使われるのは「Rule拡張」と「Type-Specifying拡張」の二種
    • 今回はクイックマスターなので、この話をします

Page 37

PHPStanの基礎

  • PHPStanは構文木はPHP-Parserのノードを利用している
  • いろいろ困ったらPHPStanの本体コードを見にいくことになる
    • https://github.com/phpstan/phpstan-src
    • (-srcがついてるリポジトリを見ないとコードが読めないので注意)
  • PHP組み込み以外の型システムを用意している
  • いわゆる述語メソッドがboolではなく三値(yes/no/maybe)を返す

Page 38

PHPStanの型システム

  • 型はオブジェクトとして表現されているので実態はコードを読む
    • https://github.com/phpstan/phpstan-src/tree/1.10.x/src/Type
  • PHPの組み込み型、Union/Intersectionなどの複合的な型、
    組み込み型を継承した定数型、これらの型とintersectionで組み合わせて
    複合的な型を表現するAccessory型などがある
    • たとえばリスト(連番の配列) ArrayType&AccessoryArrayListType
    • これだけで一時間くらい話せるネタ

Page 39

Rule拡張でできること

  • 構文木から判断できること全部!
    • 渡されてきたScopeオブジェクトから型がとれる
    • コード上に直接書かれた値や具体的に絞り込まれた型には
      実際の値もとれる

Page 40

実装するルールが

Rule拡張

対象とする構文木の
ノードクラス名

構文木を受け取って
エラーを配列として返す

Page 41

StaticMockRule拡張

Page 42

Type-Specifying拡張

メソッド呼び出しのクラス名

実装する拡張が
対象のメソッド呼び出しかどうか

構文木を受け取って型を返す

Page 43

Type-Specifying拡張でできること

  • 関数呼び出しやメソッド呼び出し、プロパティなどに、
    型宣言やPHPDocで記述されていない型をつけることができる
  • バリデーション関数で実行時に絞り込まれたときに型をつける

Page 44

ReverseRouteReturnTypeExtension

メソッド呼び出しのクラス名

クラスに属する
メソッド全部が対象

Page 45

ReverseRouteReturnTypeExtension

新しい型
string&non-emptyを返す

Page 46

ParamHelperTypeSpecifyingExtension