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
20年もののレガシープロダクトに 0からPHPStanを入れるまで / phpcon2024
Search
hirobe
December 20, 2024
Programming
1.8k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
20年もののレガシープロダクトに 0からPHPStanを入れるまで / phpcon2024
hirobe
December 20, 2024
More Decks by hirobe
See All by hirobe
PHPでOfficeファイルを取り扱う! PHP Officeライブラリを プロダクトに組み込んだ話
hirobe1999
0
3.4k
PHP8.1で、リソースがオブジェクトに!? マイナーリリースの変更が レガシープロダクトに与えた影響
hirobe1999
0
2.1k
フレームワークが存在しない時代からのレガシープロダクトを、 Laravelに”載せる”実装戦略
hirobe1999
0
1.9k
フレームワークが存在しない時代からのレガシープロダクトを、 Laravelに”載せる”実装戦略
hirobe1999
0
2.4k
新卒PHPer奮闘記 ~配属されたのは3歳違いのプロダクト!?~ / phperkaigi-2022-lt
hirobe1999
0
2k
Other Decks in Programming
See All in Programming
APNsからLive Activityを開始する話
yumnumm
0
150
スマートフォンでモールス信号を送受信する 〜スマートフォンのLEDとカメラで作る光通信の設計と実装〜
atsuki_seo
0
230
大喜利で理解するLLM as a Judge / Understanding LLM-as-a-Judge through Ogiri
rockname
0
190
GemmaをJevのように使ってみる / Use Gemma like Jev
kishida
5
710
動作中のプログラムの中身をリアルタイムに覗く / Realtime Debugger for CSharp with Roslyn
prota
1
1.8k
コードレビューのボトルネックを"する側"と"される側"の両面から解消する
yub0n
2
1.5k
AWS CDKのカスタムリソースでContinuum(旧Security Agent)を実装した話 | Implementing Continuum (formerly Security Agent) via AWS CDK Custom Resource
akihisaikeda
2
140
速く作れる。その次は、速く確かめられる開発へ 〜AIネイティブ開発を支える、Shift Down〜 / Can build fast. Next, moving to development where we can verify fast.
rkaga
8
5.5k
Apple Intelligence を用いた個人情報誤送信防止、及びユーザーリクエスト体験の改善について
yukiny
0
310
AI が書く Go コードの品質を劇的に向上させる Linter: “declscope”
mpyw
0
480
[2026-09-26]空論ジェネリックプロセス~テスト資産とAIで紡ぐ、再現可能なパフォーマンスチューニングの話~
tosite
0
260
R8 の設定見直しでアプリサイズ削減
andpad
0
190
Featured
See All Featured
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.7k
Design in an AI World
tapps
1
350
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
68
58k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
530
The SEO Collaboration Effect
kristinabergwall1
1
590
The Power of CSS Pseudo Elements
geoffreycrofte
82
6.6k
Writing Fast Ruby
sferik
630
63k
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
510
Everyday Curiosity
cassininazir
0
350
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.2k
Java REST API Framework Comparison - PWX 2021
mraible
34
9.7k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
610
Transcript
© RAKUS Co., Ltd. 20年もののレガシープロダクトに 0からPHPStanを⼊れるまで #phpcon 2024/12/22 廣部 知⽣(@tomoki2135)
2 ⾃⼰紹介 21卒で株式会社ラクスに⼊社 PHPでMail Dealerの開発を⾏っています レガシープロダクトの改善について考える⽇々 趣味はスト6で戦いの螺旋を登り続けています (Act5はMR1700でフィニッシュ)
3 について メール共有管理システム 15年連続シェアNo.1(2009〜2023)※ 歴史は更に⻑く、2001年4⽉に販売開始 Laravelは2011年リリースなので、10歳年上 ※出典:ITR「ITR Market View:メール∕Webマーケティング市場2024」
メール処理市場:ベンダー別売上⾦額推移およびシェア2009-2023年度(予測値)
© RAKUS Co., Ltd. 4 Q.なぜこんなレガシープロダクトに PHPStanを?
© RAKUS Co., Ltd. 5 A.コード品質担保のため
6 MailDealerが抱えていた課題 • コード品質担保において、機械的な対処をなにもしていない ◦ 実装者や、コードレビュアーが⼈⼒で頑張るしかない ◦ 特にオフショア担当分のコードレビューは 指摘も多くなりレビュアーの負担が⼤きい •
新しい技術的負債を作りやすい環境になってしまっている ◦ PhpStormのInspectionはあるが、既存コードが 警告だらけで、新しく警告が出ても気付けない ◦ PHPのerror_reportingも致命的なエラー表⽰のみ
© RAKUS Co., Ltd. 7 このままの環境で開発を続けても コード品質は良くならない
© RAKUS Co., Ltd. PHPStan導⼊で 少しでも環境を改善したい! 8
9 PHPStan • PHP⽤の静的解析ツール • コードを解析し、未定義変数やデッドコード、型の不⼀致 などを⾒つけてくれる • baselineという機能がレガシーコードと相性がいい ◦
既存のエラーを無視して、新しいコードにのみ エラー報告を⾏ってくれる。 • 11/11にPHPStan2.0がリリースされた(今回は1系の話です)
10 なぜPHPStanなのか? • PHPの静的解析ツールのデファクトスタンダード (だと思ってる) • MailDealer以外の弊社PHPプロダクトでは、 PHPStanを導⼊している ◦ プロダクト間のノウハウ共有がしやすい
© RAKUS Co., Ltd. いざ初回実⾏! 11
© RAKUS Co., Ltd. するもうまく解析できていない…… 12
13 原因 • Getting Startedの⽅法に従って、CLIで実⾏した ◦ vendor/bin/phpstan analyse {ディレクトリ} •
MailDealerはフレームワークを導⼊しておらず、 オリジナリティあふれるディレクトリ構造をしている ◦ ⼀般的な指定⽅法では、ライブラリ等も まとめて解析されてしまう • includeしているファイルの拡張⼦を.inc にしているため 解析対象にならなかった(初期値は .php のみ)
© RAKUS Co., Ltd. まずは設定から 14
PHPStanの設定 neonというyamlに似た形式で設定を記述できる 解析レベル、解析対象のファイル、解析対象外ファイル、 無視するエラー等、様々な設定ができる 詳細:https://phpstan.org/config-reference 今回は最低限の設定だけ紹介します 15
PHPStanの設定 解析レベル • レベルが⾼いほど厳しくチェックされる • 今回は最低限のレベル0で設定 ◦ レベル0ですら⼤量のエラーがでることが予測される ◦ まずはレベル0で導⼊して様⼦⾒がしたい
◦ 実装担当のオフショアチームとの 丁寧なコミュニケーションが必要 16
PHPStanの設定 excludePaths • 解析対象外のディレクトリを指定できる fileExtensions • 解析対象の拡張⼦を指定できる。デフォルトは.phpのみ • 前述した通り、MailDealerは.incファイルがあるので指定 17
ここで学んだこと • 適切な設定をしてからPHPStanを実⾏すること ◦ 解析レベル ◦ 解析対象外のディレクトリ ◦ 解析対象のファイル拡張⼦ は要注意!
18
© RAKUS Co., Ltd. いざ再実⾏! 19
© RAKUS Co., Ltd. レベル0で271エラー 20
© RAKUS Co., Ltd. 思ったより少ないな……🤔 21
© RAKUS Co., Ltd. ちょっとレベル上げてみるか…… 22
© RAKUS Co., Ltd. レベル9:29,842 errors! 正直、こんなもんか😊と思いました 23
© RAKUS Co., Ltd. レベル4:32,495 errors! ふ、増えてる…… 😱 24
© RAKUS Co., Ltd. そんなことありえるのか……? 正しく解析できてないのでは……? 25
エラー分析 Reached internal errors count limit of 50, exiting… Internal
error: Internal error: Class "Hoge" not found while analysing file このようなエラーが⼤量に出ていた Hoge Classが読み込まれていない……? 26
PHPStanの特性 PHPStanは、composerのautoloadを⾃動で解析してくれる composerをインストールしていて、autoloadを採⽤していれば ほぼ設定せずに利⽤できる が……requireは読み込んでくれない! 27
MailDealerのアーキテクチャ maildealer ├web │├index.php │├top.php │└side.php └common ├global.inc └func.inc Apacheがweb配下をhtdocsとして読み込む
それぞれのファイルが、func.incのような 共通したファイルをrequireで読み込みに⾏く 28
© RAKUS Co., Ltd. autoloadをほぼ使っていない! \(^o^)∕ ⼀応新しく作った箇所はautoload対応 している箇所もあります…… 29
PHPStanの特性 autoloadは⾃動で読み込んでくれるが、 requireは解析してくれない Reached internal errors count limit of 50,
exiting... Internal error: Internal error: Class "Hoge" not found while analysing file このエラーが出ている Hoge もrequireで読み込んでいるファイル 30
requireを読み込むには bootstrapFilesという設定値が存在する bootstrapFilesに事前に読み込みたいファイルを指定すると、 解析前に読み込んでくれる ⾃作のautoloadもここに指定する 今回は、func.incのような汎⽤ファイルを読み込ませる 31
ここで学んだこと • PHPStanは、composerのautoloadを解析してくれる • ⾃前のautoloaderを利⽤している、 そもそもautoloadを使っていない場合は、 bootstrapFilesを利⽤して事前に読み込ませること 32
© RAKUS Co., Ltd. いざ再実⾏! 33
© RAKUS Co., Ltd. 解析すら実⾏されず、PHPエラー 34
bootstrapFilesの注意点 bootstrapFilesに指定したファイルは ”PHPランタイムによって実⾏される” 実⾏して問題ない形式でないとPHPエラーが発⽣してしまう 逆に⾔うと、PHPランタイムを実⾏してくれるので、 ある程度⾃由にbootstrapFilesを記述できる 35
© RAKUS Co., Ltd. まてよ…… 36
© RAKUS Co., Ltd. なんで普段動いているはずのPHPファイルで PHPエラーがでるんですか? 37
出ていたエラーの例① Uncaught Error: Failed opening required '〇〇〇.inc' • bootstrapFilesに指定したファイルは ”PHPランタイムによって実⾏される”
• 解析を実⾏した環境でも、 実際にrequireできるPathが存在しないといけない • MailDealerは、個⼈の仮想環境にデプロイして動作確認をする 解析を実⾏した開発環境ではrequire先が存在しなかった 38
出ていたエラーの例① Uncaught Error: Failed opening required '〇〇〇.inc' • 解決案1 ◦
requireのパスを開発環境、デプロイ環境両⽅でも 実⾏できる形式に書き換える • 解決案2 ◦ PHPStanをDocker上で実⾏する Docker上でシンボリックリンクを張り、パスを解決する 39
出ていたエラーの例① Uncaught Error: Failed opening required '〇〇〇.inc' • 解決案1 ◦
requireのパスを開発環境、デプロイ環境両⽅でも 実⾏できる形式に書き換える • 解決案2 ◦ PHPStanをDocker上で実⾏する Docker上でシンボリックリンクを張り、パスを解決する 採用 40
出ていたエラーの例① Uncaught Error: Failed opening required '〇〇〇.inc' • 解決案1 ◦
requireのパスを開発環境、デプロイ環境両⽅でも 実⾏できる形式に書き換える • 解決案2 ◦ PHPStanをDocker上で実⾏する Docker上でシンボリックリンクを張り、パスを解決する したんですが…… 41
出ていたエラーの例① Uncaught Error: Failed opening required '〇〇〇.inc' • PHPStanは、シンボリックリンクを利⽤するときに 解析がバグるときがある
https://github.com/phpstan/phpstan/issues/7241 • シンボリックを採⽤すべきではないと判断 42
出ていたエラーの例① Uncaught Error: Failed opening required '〇〇〇.inc' • 解決案1 ◦
requireのパスを開発環境、デプロイ環境両⽅でも 実⾏できる形式に書き換える • 解決案2 ◦ PHPStanをDocker上で実⾏する Docker上でシンボリックリンクを張り、パスを解決する 採用 43
出ていたエラーの例① Uncaught Error: Failed opening required '〇〇〇.inc' • bootstrapFilesは、”PHPランタイムによって実⾏される” •
幸いにも、requireパスは定数で指定してあった ◦ require LIBPATH . ‘hoge.inc’ • bootstrapFiles内で、定数を上書きして 開発環境のパスでrequireを⾏うようにした ◦ この作業中、定数を使っていないrequireを発⾒しました…… 44
出ていたエラーの例② Call to a member function get() on null •
謎 • 当該オブジェクト作成処理が冗⻑で、 PHPStanの解析上なんらかの問題があった? 45
$object = createObject(); function createObject() { global $object; if (is_null($object))
{ $_object = new SampleObject(); } else { return $object; } return $_object; } 46
出ていたエラーの例② Call to a member function get() on null •
謎 • 当該オブジェクト作成処理がかなり冗⻑で、 PHPStanの解析上なんらかの問題があった? • リファクタリングしたらエラーが出なくなった 47
$object = createObject(); function createObject() { global $object; if (is_null($object))
{ $_object = new SampleObject(); } else { return $object; } return $_object; } $object = createObject(); function createObject() { global $object; if (is_null($object)) { return new SampleObject(); } return $object; } 48
出ていたエラーの例② Call to a member function get() on null •
謎 • 当該オブジェクト作成処理がかなり冗⻑で、 PHPStanの解析上なんらかの問題があった? • リファクタリングしたらエラーが出なくなった • もしわかる⽅いれば…… 49
ここで学んだこと • bootstrapFilesは、PHPランタイムで実⾏される ◦ bootstrapFilesでは、PHPコードを記述できる • bootstrapFilesは、PHPランタイムで実⾏できる ようにしなければならない 50
おまけ(bootstrapFilesの設定イメージ) 51 $global_hoge = "hoge"; $global_fuga = "fuga"; define("REQUIRE_PATH", "/usr/local/...");
require "REQUIRE_PATH" . "/sample.inc"; require "REQUIRE_PATH" . "/DB.php"; $db = new Db(); if (!$global_piyo) { require "REQUIRE_PATH" . "/piyo.inc";
© RAKUS Co., Ltd. 三度⽬の正直! (とはいいつつ、エラー解消のため何度もトライしてる) 52
© RAKUS Co., Ltd. 無事に解析できた! 53
© RAKUS Co., Ltd. レベル0で 759 errors 54
© RAKUS Co., Ltd. レベル1で 68,374 errors 55
エラー数 • レベル0で759 errors ◦ エラー内容をみても、妥当そう • レベル1で68,374 errors ◦
レベル1から、条件によっては未定義になる変数 をチェックしてくれる ◦ 悲しいかな、これも正しいエラーの可能性が⾼い ◦ これ以上レベルを上げても、爆発的には増えなかった 56
© RAKUS Co., Ltd. さすがに全部対応するのは現実的ではない 57
baseline 既存のエラーを無視し、新しいエラーのみ教えてくれる --generatebaseline というオプションをつけて解析すると baselineファイルを作成してくれる ファイル名の指定なしだと phpstan-baseline.neon が⽣成される ファイル名を指定して .php
ファイルにしたほうが、 解析パフォーマンスが上がるらしい 58
© RAKUS Co., Ltd. baselineを作成して再実⾏! エラーは出ない想定 59
© RAKUS Co., Ltd. がっ、ダメ……! 出る……!エラーが……!まだ……! 60
baselineでも無視できないエラーが存在する Unignorable could not be added to the baseline: Cannot
use [] for reading. 以下のようなコードがあったとき、PHPエラーにはならないが、 PHPStanでは無視できないエラーとして扱われる $arr[] .= ""; Issueでも修正しないと⾔われてしまっているので、 コード修正しかない 61
© RAKUS Co., Ltd. 頑張って修正していきます 62
PHPStanを導⼊してみて 出ているエラーには納得感がある 新規で未定義変数が新たに増えないだけでも品質が上がりそう PHP9では未定義変数が致命的なエラーになる予定なので PHPStanを活⽤すれば合わせて修正が進みそう CI導⼊も進めていく 63
まとめ レガシープロダクトでも、適切に設定することで PHPStanで静的解析が実⾏できる baselineを導⼊し、まずは新規コードから品質を担保する コード品質を諦めない!まずは⼀歩踏み出してみる 次回、運⽤編へ続く…… 64