Skip to content

PHPの「型」はどこまで戦えるか

公開日:

に愛媛県松山市の愛媛大学 城北キャンパスで開催された『PHPカンファレンス愛媛2026』でレギュラートーク(20分)として発表しました。

Download PDFPDF

スライドテキスト

Page 1

PHPの「型」は

どこまで戦えるか

Can PHP Types Still Compete?

Pushing the Limits of Static Analysis.

pixiv Inc.

USAMI Kenta

2026-10-03 #phpcon_ehime PHPカンファレンス愛媛 @愛媛大学 城北キャンパス

Page 2

みなさまはPHPの無限の可能性が好きですか?

Page 3

問題です

Page 4

PHPの型についての質問で正しいものは?(全4問)

Page 5

1. PHPは型なし言語である

Page 6

2. PHPには型宣言がある

Page 7

3. PHPに書く型は実行時に保証されない

Page 8

4. PHPは静的型付きである

Page 9

正しいものは? (複数回答可能)

1. PHPは型なし言語である2. PHPには型宣言がある3. PHPに書く型は実行時に保証されない4. PHPは静的型付きである

Page 10

お前誰よ

  • うさみけんた (@tadsan) / Zonu.EXE / にゃんだーすわん
  • ピクシブ株式会社 Platform Div > WebTechnology Team PHPer
  • 2012年末から現職、APIとかCIとかいろいろなところを見つめてきました
  • 最近チームが再編されてインフラっぽい仕事もしてます
  • Emacs PHP Modeを開発しています (2017年-)
  • プログラミング言語にちょっとこだわりのある素人 (spcamp2010)

Page 11

さて

Page 12

型がついている#とは

Page 13

型推論規則に従ってすべての項に矛盾なく型が割り当てられた状態

…ですが何か?

Page 14

よくわからん

Page 15

覚えておいてほしい用語

Page 16

型付け

(Typing)

Page 17

型付き

(Typed)

Page 18

型チェッカー

$a = 3;$b = 2;$c = pow($a, $b);

Page 19

型チェッカー

int

$a = 3;

$b = 2;

$c = pow($a, $b);

Page 20

型チェッカー

int

int

$a = 3;

$b = 2;

$c = pow($a, $b);

Page 21

型チェッカー

int

int

$a = 3;

$b = 2;

$c = pow($a, $b);

引数ok

Page 22

型チェッカー

int

int

$a = 3;

$b = 2;

$c = pow($a, $b);

引数ok

引数ok

Page 23

型チェッカー

int

int

$a = 3;

$b = 2;

$c = pow($a, $b);

引数ok

引数ok

int

Page 24

型付け(typing) vs 型付き(typed)

  • プログラムを検査して、項(≒ 式, expression)のとりうる状態を

分類するのが型付け(typing)。型を明示しなくても項の種類を特定する具体的な手続きが型推論(type inference)。

  • 実行時に値の型チェックを行なうことを、

便宜的に動的型付け(dynamic typing)と呼ぶこともある

  • 静的な型付けによってとりうる状態が定まったプログラムのことを

「型付き(typed)」と呼ぶ

Page 25

1. PHPは型なし言語である2. PHPには型宣言がある3. PHPに書く型は実行時に保証されない4. PHPは静的型付きである

Page 26

健全性

(Soundness)

Page 27

静的解析が通れば実行時に(型に関する)エラーは起こらないという性質

Page 28

いわゆる「型安全」(type safety)

  • 健全性(Soundness)と同じ意味
  • 濫用されがちな用語だが、概ね型に関する記述に矛盾がないことを保証でき

るようになっていることを表わしている

  • 「健全性がある」 実行したときに、型に関するエラーが起こらないといえる
  • 「健全性がない」 実行してみたときに、エラーが起こりかねない
  • C言語全般、Javaの「ぬるぽ」、TypeScriptのany/asや配列など

Page 29

言語が静的か動的かとは[独自研究]

  • ある言語の仕様または標準的な処理系によって構造が事前に検証されている

ことが前提になっている言語が静的(型付き)

  • 事前検証が前提ではなく、実行時(run-time)に構造が決定されるのが動的
  • 一般的にはコンパイラ vs インタプリタに一致するが、必ずしもそうではない
  • 静的言語にも多かれ少なかれ動的な機能が存在し、グラデーションは大きい
  • 動的言語でもPHP, Python, Ruby, JavaScriptで動的要素が全然違う

Page 30

TypeScriptにとっての型

  • 型宣言をもとに型チェックを行なって、分析情報を報告
  • 実行時には型情報は何も残らない
  • JavaScriptにコンパイル ≒ 型定義の部分をひっぺがして実行
  • Node.js 25.2ではType Strippingで直接実行できるようになった
  • 実際にはTypeScript enum など別のコード生成をする機能もある
  • 型がついたコードは実行時にはエラーが出ないでほしい(願望)

Page 31

ファクトチェック

Page 32

1. PHPは型なし言語である2. PHPには型宣言がある3. PHPに書く型は実行時に保証されない4. PHPは静的型付きである

Page 33

型理論から見たPHPインタプリタ

  • 型が合わない(Ill-typed)コードであっても、エラーとして弾かれず、

正当なプログラム(構文木)として受理される → 実行時エラー

  • 関数は型宣言に拘らず、任意の値を返そうとすることができる
  • プロパティは型宣言に拘らず、任意の値を代入しようとすることができる
  • ローカル変数に型宣言はなく、任意の型の値を代入可能
  • オペコードコンパイラは実行前にプログラムの健全性を「証明」しない

Page 34

実行時のインタプリタの振る舞い

  • PHP処理系(ZendVM)内では、zval構造体に型タグを保持している
  • 常に is_int() のような関数で型を識別できる
  • 型宣言と反した値が渡されると、キャストまたは実行時エラーが起こる
  • いわゆる「強い型付け」であり、実行時に型がとり違えられることはない
  • RTTI(実行時型情報)は型理論における(静的な)型ではない
  • 言語セマンティクスとしては型なしだが、JITは静的型情報を活用する

Page 35

ここまでのまとめ

Page 36

PHPインタプリタは型なしである

Page 37

1. PHPは型なし言語である2. PHPには型宣言がある3. PHPに書く型は実行時に保証されない4. PHPは静的型付きである

Page 38

PHPと型宣言

  • 引数などのデータ型を表わす手段としてコメントにPHPDocを書く文化
  • PHP 5.xで関数/メソッドのパラメータに「型ヒント」が書けるようになった
  • array, callable, クラス名, インターフェイス名だけを書けた
  • PHP 7.0で正式に「型宣言」としてスカラー型宣言と戻り値型が記述可能に
  • PHP 8.0以降でユニオン型宣言などが機能追加

Page 39

型宣言がない関数(PHP5)

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

Page 40

スカラー型宣言(PHP7)

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

Page 41

スカラー型宣言(PHP7)

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

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

Page 42

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

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

Page 43

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

ひとつの解決策ではあるが… 不必要にfloatを強制するのか

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

Page 44

PHPDocの型注釈(アノテーション)

/*** @param int|float $a* @param int|float $b* @return int|float*/function add($a, $b) {return $a + $b;}

Page 45

PHPDocの型注釈(アノテーション)

あえて型宣言を省略する

/*** @param int|float $a* @param int|float $b* @return int|float*/function add($a, $b) {return $a + $b;}

Page 46

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

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

Page 47

PHPの型宣言の特徴

  • コードごとに declare(strict_types=1); が宣言できる
  • 引数とデータ型の種類を厳密にチェックしてTypeErrorを出す
  • デフォルト(無宣言)では、 add("1", "2”) を add(1, 2) としてキャスト
  • add(“a”, “b”) のような数値ではない文字列はTypeErrorになり安心
  • add((int) $a, (int) $b); のようなコードとは安全性のレベルが違う

Page 48

ここまでのまとめ

Page 49

PHPに型宣言はある

Page 50

型宣言された型は口約束ではなく実効性がある

Page 51

型理論としては型なしだががんばって動作確認するなら実用上ほぼ型付きと認められなくもなくなってきた

Page 52

1. PHPは型なし言語である2. PHPには型宣言がある3. PHPに書く型は実行時に保証されない4. PHPは静的型付きである

Page 53

PHPDocに型を書く文化もある

Page 54

PHPDocの型注釈(アノテーション)

/*** @param int|float $a* @param int|float $b* @return int|float*/function add($a, $b) {return $a + $b;}

Page 55

PHPDocの型注釈(アノテーション)

あえて型宣言を省略する

/*** @param int|float $a* @param int|float $b* @return int|float*/function add($a, $b) {return $a + $b;}

Page 56

いろんなところから湧き出す

型の特定できないデータ

Page 57

arrayは無限の可能性を秘める

(悪い意味で)

Page 58

扱う対象が複数になったのに

型に反映されない

Page 59

型宣言にarrayとしか書かないと

  • メソッドに何を渡せばいいのかわからない
  • どんな形式の配列を渡しても動くという関数は決して多くない
  • 標準関数の count() のようなユーティリティが多め
  • プロパティにも何が入ってるのかわからない
  • 従来のLaravelは @var array まみれだった
  • Laravel 9.xのコードベースでは配列の内部構造に型がついている

Page 60

配列はひとつ!じゃない!!

  • ユーザIDがならんだリスト [123, 456]
  • ユーザIDと名前の対応表 [123 => '野比のび太', 234 => '源静香']
  • ユーザを表す構造体 ['id' => 123, 'name' => '野比のび太']
  • ユーザを表す構造体がならんだリスト

[['id' => 123, 'name' => '野比のび太'],['id' => 234, 'name' => '源静香'],]

Page 61

配列はひとつ!じゃない!!

list<int>

  • ユーザIDがならんだリスト [123, 456]
  • ユーザIDと名前の対応表 [123 => '野比のび太', 234 => '源静香']
  • ユーザを表す構造体 ['id' => 123, 'name' => '野比のび太']
  • ユーザを表す構造体がならんだリスト

[['id' => 123, 'name' => '野比のび太'],['id' => 234, 'name' => '源静香'],]

Page 62

配列はひとつ!じゃない!!

list<int>

array<int,string>

  • ユーザIDがならんだリスト [123, 456]
  • ユーザIDと名前の対応表 [123 => '野比のび太', 234 => '源静香']
  • ユーザを表す構造体 ['id' => 123, 'name' => '野比のび太']
  • ユーザを表す構造体がならんだリスト

[['id' => 123, 'name' => '野比のび太'],['id' => 234, 'name' => '源静香'],]

Page 63

配列はひとつ!じゃない!!

list<int>

array<int,string>

array{id:int, name:string}

  • ユーザIDがならんだリスト [123, 456]
  • ユーザIDと名前の対応表 [123 => '野比のび太', 234 => '源静香']
  • ユーザを表す構造体 ['id' => 123, 'name' => '野比のび太']
  • ユーザを表す構造体がならんだリスト

[['id' => 123, 'name' => '野比のび太'],['id' => 234, 'name' => '源静香'],]

Page 64

配列はひとつ!じゃない!!

list<int>

array<int,string>

array{id:int, name:string}

  • ユーザIDがならんだリスト [123, 456]
  • ユーザIDと名前の対応表 [123 => '野比のび太', 234 => '源静香']
  • ユーザを表す構造体 ['id' => 123, 'name' => '野比のび太']
  • ユーザを表す構造体がならんだリスト

[['id' => 123, 'name' => '野比のび太'],['id' => 234, 'name' => '源静香'],]

list<array{id:int, name:string}>

Page 65

1. PHPは型なし言語である2. PHPには型宣言がある3. PHPに書く型は実行時に保証されない4. PHPは静的型付きである

Page 66

PHPの組み込みの型機能ではどんなに型推論しても静的に型が定まらない

Page 67

無限の可能性を秘めたarrayを放置するとコード全体のエントロピーが増大しプロジェクトは熱的死を迎える

Page 68

漸進的型付け

Page 69

56

Page 70

型チェックをしたとしても実用的な推論結果にならない(こともある)

Page 71

PHPStan

$a = ‘foo’;$b = rand() === 1 ? ‘foo’ : ‘bar’;$c = $a + $b;

Page 72

'foo'PHPStan

$a = ‘foo’;

$b = rand() === 1 ? ‘foo’ : ‘bar’;

$c = $a + $b;

Page 73

PHPStan

'foo''foo'

$a = ‘foo’;

$b = rand() === 1 ? ‘foo’ : ‘bar’;

$c = $a + $b;

Page 74

PHPStan

'foo''foo''bar'

$a = ‘foo’;

$b = rand() === 1 ? ‘foo’ : ‘bar’;

$c = $a + $b;

Page 75

PHPStan

'foo''foo''bar'

$a = ‘foo’;

$b = rand() === 1 ? ‘foo’ : ‘bar’;

$c = $a + $b;

Page 76

PHPStan

'foo''foo''bar'

$a = ‘foo’;

$b = rand() === 1 ? ‘foo’ : ‘bar’;

$c = $a + $b;

'foo'|'bar'

Page 77

PHPStan

'foo''foo'

'bar'

$a = ‘foo’;

$b = rand() === 1 ? ‘foo’ : ‘bar’;

$c = $a + $b;

'foo'|'bar'

'foofoo'|'foobar'

Page 78

PHPStan

$a = 1;$b = 2;$c = $a + $b;

Page 79

PHPStan

int(1)

$a = 1;

$b = 2;

$c = $a + $b;

Page 80

PHPStan

int(1)

int(2)

$a = 1;

$b = 2;

$c = $a + $b;

Page 81

PHPStan

int(1)

int(2)

int(3)

$a = 1;

$b = 2;

$c = $a + $b;

Page 82

型チェッカー

$json = $_POST['json'];$a = json_decode($json);var_dump($a[‘name']);

Page 83

mixed型チェッカー

$json = $_POST['json'];

$a = json_decode($json);

var_dump($a[‘name']);

Page 84

mixed

string欲しいんだけど…型チェッカー

$json = $_POST['json'];

$a = json_decode($json);

var_dump($a[‘name']);

Page 85

mixed

string欲しいんだけど…型チェッカー

$json = $_POST['json'];

$a = json_decode($json);

var_dump($a[‘name']);

?array|int|float|string|bool

Page 86

mixed

string欲しいんだけど…型チェッカー

$json = $_POST['json'];

$a = json_decode($json);

var_dump($a[‘name']);

?array|int|float|string|boolこのキーあるの…?

Page 87

つまり

Page 88

つまり

Page 89

そのため動的言語ではテストの重要性が高かった

Page 90

型は「ありうる」状態を記述する言語

Page 91

適切に推論できればテストを書くまでもなく変なコードが炙りだせる

Page 92

Page 93

Page 94

Page 95

パラメータを厳しくするより戻り値を適切に

Page 96

戻り値が適切に記述されていれば呼び出し側で型をつける必要はない

Page 97

PHPStanは普通にコードを書いて警告が増加しないレベルが適正

Page 98

複雑な型を書きまくるのが型付けではない

Page 99

PHPコードの@returnが足りていない

Page 100

💡型を書きまくるSKILLを書こう

Page 101

特にarray-shapeを書きまくる必要が生じる

Page 102

そこからレベルを上げるには拡張で解決したい

Page 103

たぶんあんまりよくない

Page 104

型推論ロジックを記述できる

Page 105

理屈上は任意の項に型をつけられる

Page 106

お使いのAIさんと相談しながらサクっと作れる時代になった

Page 107

やるなら今

Page 108

LLMにとってプログラミング言語と人間の言語は同列

Page 109

流暢なコードは効率よく処理しやすい

Page 110

エントロピーを下げて人とAIに優しい予測可能なコードを育てよう

Page 111

まとめ

Page 112

正しいものは? (複数回答可能)

1. PHPは型なし言語である2. PHPには型宣言がある3. PHPに書く型は実行時に保証されない4. PHPは静的型付きである

Page 113

正しいものは? (複数回答可能)

✅ PHPは型なし言語である

✅ PHPには型宣言がある

✅ PHPに書く型は実行時に保証されない

✅ PHPは静的型付きである

Page 114

考え方によってはすべて正しい

Page 115

PHPが静的型付きと言える条件

  • 静的解析やユニットテストなどのCIチェックした上でデプロイされている
  • PHPStan, Psalm, Phan, Magoなど… Lv.maxかどうかは問わない
  • ユニットテストで十分なカバレッジのテストが網羅されている
  • ツールの種類×プロジェクトが静的に信頼に足る状況にできるかが大きい
  • 静的型付きの言語を使っていても本番で健全性がないコードにできる
  • PHPは動的言語の中では実行時バリデーションのお蔭で問題が隠れない

Page 116

AI時代にPHPを使うメンタルモデルについては香川で話した

Page 117

Page 118

今度新潟ではちょっと違う形の話をします

Page 119