Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
夢の無限スパゲッティ製造機 -実装篇- #phpstudy
Search
hideki kinjyo
PRO
March 25, 2026
Programming
310
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
夢の無限スパゲッティ製造機 -実装篇- #phpstudy
PHP勉強会@東京 第185回の発表資料です。
前段の話:
https://speakerdeck.com/o0h/phperkaigi-2026
hideki kinjyo
PRO
March 25, 2026
More Decks by hideki kinjyo
See All by hideki kinjyo
phperだってパンチカードでコーディングしたい
o0h
PRO
0
530
Symfonyが 簡単にAI agentになる
o0h
PRO
1
150
yield再入門 #phpcon
o0h
PRO
0
1.9k
PHPで使える日時の表現と、その知り方 #frontend_phpcon_do
o0h
PRO
0
520
Composerを使ったサプライチェーン攻撃の様子を眺めてみる #phpstudy
o0h
PRO
3
420
ソースコード→AST→オペコード、の旅を覗いてみる
o0h
PRO
1
400
PCOVから学ぶコードカバレッジ #phpcon_odawara
o0h
PRO
0
680
夢の無限スパゲッティ製造機 #phperkaigi
o0h
PRO
0
1.1k
PHPer Book Revue 「雑に作る」 #phperkaigi
o0h
PRO
0
480
Other Decks in Programming
See All in Programming
HTML-in-Canvasで作る未来のWeb UI表現
clockmaker
5
6.1k
学びを記事・スライド・Xへ高速展開する― AI-assisted Learning Loopで回すコンテンツ生成術
ochtum
0
190
Zero-Tap Sign-In対応、結局何をすればいいの?
mitohato14
0
1.3k
FactoryBotをやめればRSpecは速くなるのか?
yuta_onishi_97
0
1.3k
Kotlinize Is Not Just Converting to Kotlin
chigichan24
1
210
當我們開始不讀 Code - AI Agent 時代,Code Review 與 Clean Code 還剩下什麼?
jame2408
1
350
AIで開発が速くなった先で、開発チームは何を目指すか
shihochan
1
2k
Lambda durable functionsでHuman in the Loopを楽に…楽に……
har1101
2
250
Spec-driven Development with AI
simas
PRO
0
2.2k
TanStack Vue Query と Pinia Colada から学ぶ非同期状態の型設計
aoshi
0
600
プロジェクトコードをキレイキレイしよう!暗黙的な参照を読み解く静的解析
ostk0069
0
200
ActionController meets Protocol Buffers
sorah
1
770
Featured
See All Featured
Thoughts on Productivity
jonyablonski
76
5.4k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
20k
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.8k
The Invisible Side of Design
smashingmag
301
52k
Facilitating Awesome Meetings
lara
57
7.2k
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
310
Lessons Learnt from Crawling 1000+ Websites
charlesmeaden
PRO
1
1.7k
The Pragmatic Product Professional
lauravandoore
37
7.5k
Google's AI Overviews - The New Search
badams
0
1.6k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
550
Making the Leap to Tech Lead
cromwellryan
135
10k
Transcript
夢の無限スパゲッティ製造機 -実装篇- 第185回 PHP勉強会@東京 Hideki Kinjyo GitHub: o0h / X:
@o0h_ [発表用] v1.0.0
夢の無限スパゲッティ製造機 -実装篇- #phpstudy.185 Hideki Kinjyo GitHub:o0h / X:@o0h_
自己紹介 • 金城秀樹 / きんじょうひでき • GitHub: @o0h / 𝕏
: @o0h_ • 好きなスパゲッティはカルボナーラ • アイコンは美味しい鮭親子丼のChef ver.です • 何故ならスパゲッティを作っていますからね • 最近はPodcastをやっています • ハッシュタグ: #readlinefm 3
3月22日に発表したやつ 4
今日のお話 PHPerKaigiでは、生成されるコードの話がメインだったので その実装についての話をします 1. そもそも何これ 2. 全体の処理の流れ 3. 前半戦: 元PHPコードから「中間表現」まで
4. 後半戦: 「中間表現」から PHPコードへ 5
1. そもそも何これ 2. 全体の処理の流れ 3. 前半戦: 元PHPコードから「中間表現」まで 4. 後半戦: 「中間表現」から
PHPコードへ
The Spaghetti Dream #とは • PHPで書かれたコードを「gotoベースのコード」に変換するプログラム • 制御構文の排除 • クラスやそれに類する機能の排除
• もともとは、「構造化プログラミング」の説明を読んで 「何を言っているんだ、当たり前じゃない…?」と思ったのが始まり • それが無い世界を味わってみたくなった • PHPerKaigi 2025 『PHPによる"非"構造化プログラミング入門』 • 「gotoを使わなくても制御フローを実現できる」なら、 「今のコードをgotoでも作れる」のではないか 7
DEMO the-spaghetti-dream.nichiyou.be
1. PHPで書かれたソースコードをインプットとして 2. いったんオペコードに変換し 3. 再びPHPの表現に戻す! <?php $tmp0 = …
sub: ————— goto hoge; end: 何をしているのか 9 ソースコード オペコード <?php function hoge($x) { ————— } 0 INIT_FCALL 'hoge' 1 SEND_VAL 1 2 SEND_VAL 10 3 DO_ICALL $0 4 ECHO $0 5 RETURN 1 オペコード スパゲッティ
オペコードってこんなもの • 通常のPHPのコードを、ZendVM(仮想マシン)が使える形に変換したもの • 制御フローは、「CPU(機械)向けに近く、単純化」された形に • ifやforが無くなり、「ジャンプ命令」に書き換えらえる 10
オペコードってこんなもの • 基本的に上から実行される • 1行 = 1ステップ 11
オペコードってこんなもの • 各行は、インデックス・命令・引数の3つのパーツで構成される • 引数の数は命令によって異なる 12 命令(オペコード) 引数(オペランド) インデックス(番号)
オペコードってこんなもの • 「CV0にtrueを代入する」 13 boolのtrue ローカル変数 (CV0 = $iikanji) ASSIGN:
引数1に引数2を代入する
オペコードってこんなもの • 「CV0をチェックして、値が `0` だったら、3行目にジャンプする」 14 JMPZ: 引数1がゼロだったら、 引数2の番号にジャンプする ローカル変数
(CV0 = $iikanji) 番号3
こんな感じのやつをアレコレする話です • これを変換していきましょう!! • 変換できると嬉しいですよね! 15 <?php $tmp0 = …
sub: ————— goto hoge; end: ソースコード オペコード <?php function hoge($x) { ————— } 0 INIT_FCALL 'hoge' 1 SEND_VAL 1 2 SEND_VAL 10 3 DO_ICALL $0 4 ECHO $0 5 RETURN 1 オペコード スパゲッティ
1. そもそも何これ 2. 全体の処理の流れ 3. 前半戦: 元PHPコードから「中間表現」まで 4. 後半戦: 「中間表現」から
PHPコードへ
全体フロー 17 コマンド起動/src受け取り オペコード変換 オペコードに無い情報の補完 (依存ファイル読み込み) 🍝化 冗長なコードのクリーンアップ
None
1. そもそも何これ 2. 全体の処理の流れ 3. 前半戦: 元PHPコードから「中間表現」まで 4. 後半戦: 「中間表現」から
PHPコードへ
やっていること 1. インプットとなるソースコードを受け取る 2. オペコードの出力の実行 3. オペコードのフレームごとに `OpCodeCollection`を作成 • `\O0h\SpaghettiDream\Extractor\IR\OpcodeCollection`
4. 各ステップを`OpCode` クラスのオブジェクトに変換 • `\O0h\SpaghettiDream\Extractor\IR\Opcode` 20
オペコードの出力 • オペコードの出力には色々な方法がある • PHPをそのまま使う: php -d opcache.opt_debug_level=0x10000 • 拡張を入れる:
VLD • コマンドを使う: phpdbgコマンド • 今回はphpdbgコマンドを使っている • スクリプトの実行をせずにオペコードだけ取り出したかったので 21
オペコードの出力 • 素朴にexec() 22 $command = sprintf( '%s -n -p\\*
%s 2>&1', escapeshellcmd($this->phpdbgPath), escapeshellarg($phpFilePath), ); $output = []; $returnCode = 0; exec($command, $output, $returnCode);
オペコードのフレームごとに `OpCodeCollection`を作成 • オペコードに変換された時に、スコープごとにフレームが形成される 23 <?php function add($a, $b) {
return $a + $b; } function sub($a, $b) { return $a - $b; } var_dump( sub(100,add(10, 20)) ); add: ; (lines=7, args=2, vars=2, tmps=2) ; /private/tmp/piyo.php:3-6 L0003 0000 CV0($a) = RECV 1 L0003 0001 CV1($b) = RECV 2 ɾ ɾ sub: ; (lines=7, args=2, vars=2, t ; /private/tmp/piyo.php:8-11 L0008 0000 CV0($a) = RECV 1 L0008 0001 CV1($b) = RECV 2 ɾ ɾ $_main: ; (lines=13, args=0, ; /private/tmp/piyo. L0013 0000 EXT_STMT L0013 0001 INIT_FCALL 1 1 ɾ ɾ
Extractor\IR\OpcodeCollection • 1フレームに対して1インスタンス 24 sub: ; (lines=7, args=2, vars=2, tmps=2)
; /private/tmp/piyo.php:8-11 L0008 0000 CV0($a) = RECV 1 L0008 0001 CV1($b) = RECV 2 ɾ ɾ add: ; (lines=7, args=2, vars=2, tmps=2) ; /private/tmp/piyo.php:3-6 L0003 0000 CV0($a) = RECV 1 L0003 0001 CV1($b) = RECV 2 ɾ ɾ $_main: ; (lines=13, args=0, vars=0, tmps=4) ; /private/tmp/piyo.php:1-17 L0013 0000 EXT_STMT L0013 0001 INIT_FCALL 1 112 string("var_dump") ɾ ɾ OpcodeCollection OpcodeCollection OpcodeCollection
オペコードのパース • 1行ごとに地道に正規表現 でパースしていく • とはいえ、普通のPHPより はよっぽどシンプル 25
Extractor\IR\Opcode • オペコードの1行に対して1インスタンス • ここでは素直にパースして、難しいことは後で考える 26 new Opcode( line: 3,
offset: 0, opcode: 'ASSIGN', operands: [ new Operand(TYPE_CV, 0, 'a'), new Operand(TYPE_CONST, 1), ], ) L0003 0000 ASSIGN CV0($a) int(1)
Extractor\IR\Opcode • オペコードの1行に対して1インスタンス • ここでは素直にパースして、難しいことは後で考える 27 new Opcode( line: 5,
offset: 3, opcode: 'JMPZ', operands: [ new Operand(TYPE_TMP, 2), new Operand(TYPE_JUMP, 7), ], ) L0005 0003 JMPZ T2 ->7
Extractor\IR\Operand • オペランド1つに対して1インスタンス • ここも素直にパースして、難しいことは後で考える 28 new Operand(TYPE_TMP, 2) L0005
0003 JMPZ T2 ->7 new Operand(TYPE_JUMP, 7)
1. そもそも何これ 2. 全体の処理の流れ 3. 前半戦: 元PHPコードから「中間表現」まで 4. 後半戦: 「中間表現」から
PHPコードへ
• OpcodeCollectionをイテレーションして(-> OpCodeを取り出す)、 OpcodeTranslatorを介してPHPコードに変換していく 後半戦でやること 30 foreach ($opcodes as $opcode)
{ $label = $this->labelManager ->formatLabelDefinition($opcode->offset, $scopePrefix); try { $code = $this->translator->translate($opcode, $context); } catch (GeneratorException $e) { throw new GeneratorException(
• オペコードごとに対応する変換器を判定して、変換を実行 Generator\OpcodeTranslator 31 public function translate(Opcode $opcode, Context $context):
?string { foreach ($this->translators as $translator) { if ($translator->supports($opcode)) { return $translator->translate($opcode, $context); } } throw new GeneratorException( "Unsupported opcode: {$opcode->opcode}"); }
• Translatorはたくさんある • オペコード:Translator は、1:1関係ではない 色々なTranslator 32
例: ComparisonTranslator 33 <?php $a = time(); $b = time();
var_dump($a < $b);
例: ComparisonTranslator 34 class ComparisonTranslator implements TranslatorInterface { public function
supports(Opcode $opcode): bool { return isset(self::OPCODE_OPERATORS[$opcode->opcode]); }
例: ComparisonTranslator 35 class ComparisonTranslator implements TranslatorInterface { private const
OPCODE_OPERATORS = [ 'IS_EQUAL' => '==', 'IS_NOT_EQUAL' => '!=', 'IS_IDENTICAL' => '===', 'IS_NOT_IDENTICAL' => '!==', 'IS_SMALLER' => '<', 'IS_SMALLER_OR_EQUAL' => '<=', 'SPACESHIP' => '<=>', 'CASE_STRICT' => '===', ];
例: ComparisonTranslator 36 class ComparisonTranslator implements TranslatorInterface { public function
translate(Opcode $opcode, Context $context): ?string { $operator = self::OPCODE_OPERATORS[$opcode->opcode]; $operands = $opcode->operands; $left = $context->getValue($operands[0]); $right = $context->getValue($operands[1]); $resultVar = $context->extractResultVar($opcode); return "{$resultVar} = {$left} {$operator} {$right};"; } }
before / after 37 <?php $a = time(); $b =
time(); var_dump($a < $b); <?php // ===== Main code $_tmp2 = time(); $a = $_tmp2; $_tmp4 = time(); $b = $_tmp4; $_tmp6 = $a < $b; var_dump($_tmp6); goto __end; __end:
オペコードから取れないデータの補完
通常のオペコードには現れないものがあるんですよね • 値が決定されている配列とか • 関数の引数とか • クラス定数とか • etc 39
定数配列 • 次のコードのオペコードを生成すると、 • 通常は、このように配列の中身が現れない • 夢の無限スパゲッティ製造機だと、こうなる $config1 = ['name'
=> 'test', 'value' => 42]; L0003 0000 ASSIGN CV0($config) array(...) 3: ASSIGN CV0($config1) "[\'name\' => \'test\',\'value\' => 42]" 40
valueに 定数を使った配列
🍝
値がない配列を見つけたら
元のコードの行番号を取って
token_get_allで解析
"=", [397, " ", 2], "[", [269, "'name'", 2], [397,
" ", 2], [391, "=>", 2], [397, " ", 2], [269, "'test'", 2], ",", [397, " ", 2], [269, "'value'", 2], [397, " ", 2], [391, "=>", 2], [397, " ", 2], token_get_allの結果 (説明用に整形しています)
"=", [397, " ", 2], "[", [269, "'name'", 2], [397,
" ", 2], [391, "=>", 2], [397, " ", 2], [269, "'test'", 2], ",", [397, " ", 2], [269, "'value'", 2], [397, " ", 2], [391, "=>", 2], [397, " ", 2], 0:トークンの識別ID 1: ソース 2: 行番号
"=", [397, " ", 2], "[", [269, "'name'", 2], [397,
" ", 2], [391, "=>", 2], [397, " ", 2], [269, "'test'", 2], ",", [397, " ", 2], [269, "'value'", 2], [397, " ", 2], [391, "=>", 2], [397, " ", 2], array(…)があった 「2行目」のトークンを探して
"=", [397, " ", 2], "[", [269, "'name'", 2], [397,
" ", 2], [391, "=>", 2], [397, " ", 2], [269, "'test'", 2], ",", [397, " ", 2], [269, "'value'", 2], [397, " ", 2], [391, "=>", 2], [397, " ", 2], array の開始位置を見つけたら
"=", [397, " ", 2], "[", [269, "'name'", 2], [397,
" ", 2], [391, "=>", 2], [397, " ", 2], [269, "'test'", 2], ",", [397, " ", 2], [269, "'value'", 2], [397, " ", 2], [391, "=>", 2], [397, " ", 2], そこから「配列の中身」を抜き出す = "[" と "]" の間のトークン
"=", [397, " ", 2], "[", [269, "'name'", 2], [397,
" ", 2], [391, "=>", 2], [397, " ", 2], [269, "'test'", 2], ",", [397, " ", 2], [269, "'value'", 2], [397, " ", 2], [391, "=>", 2], [397, " ", 2], ['name' => 'test', 'value' => 42]
ちなみに: valueに変数を使った配列の場合 52
参考資料 • PHP7から定数配列がOPcacheに乗るので巨大配列が使い放題という話 - hnwの日記 https://hnw.hatenablog.com/entry/2020/08/12/212433 53
そんな感じでできあがり(まだ始まったばかりだ)
よかったら遊んでみてくださいね • https://the-spaghetti-dream.nichiyou.be/ • さくらのAppRunさんを使っています!! • 簡単!最高!! 55
おまけ • なんやかんやで、 Slimを使ったアプリケーションの スパゲッティ化に成功しました • →のコードが変換元(32行) => 53,957行 •
github.com/o0h/the-spaghetti-dream-gallery を見てみてください • 僕は怖くて中身を見ていません • ただし変換前後のコードに対して 同じ内容のE2Eテストはパス済み(thx runn!) 56
おしまい! お付き合いいただき ありがとうございました!!
おしまい! お付き合いいただき ありがとうございました!!