Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
20年もののレガシープロダクトに 0からPHPStanを入れるまで / phpcon2024
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
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.2k
PHP8.1で、リソースがオブジェクトに!? マイナーリリースの変更が レガシープロダクトに与えた影響
hirobe1999
0
2k
フレームワークが存在しない時代からのレガシープロダクトを、 Laravelに”載せる”実装戦略
hirobe1999
0
1.8k
フレームワークが存在しない時代からのレガシープロダクトを、 Laravelに”載せる”実装戦略
hirobe1999
0
2.3k
新卒PHPer奮闘記 ~配属されたのは3歳違いのプロダクト!?~ / phperkaigi-2022-lt
hirobe1999
0
1.9k
Other Decks in Programming
See All in Programming
Go1.27で導入されるジェネリクスメソッドでできること
mackee
0
240
吝嗇家のためのAI活用 / AI development for miser - ChatGPT + Issue Driven Development
tooppoo
0
170
才能?センス?知らん、 続けたもん勝ちだ。-- 結婚・出産・癌を越えてなお、私がプロダクトを創り続ける理由
16bitidol
2
730
スマートグラスで並列バイブコーディング
hyshu
0
280
そのテスト、説明できますか?~LWテスト戦略FW~のご紹介
nakahara
0
190
Generative UI & AI-Assistants for Your Angular Solutions
manfredsteyer
PRO
0
120
Signal Forms: Details & Live Coding @enterJS 2026 in Mannheim
manfredsteyer
PRO
0
220
エンジニアと一緒にテストコードの設計と実装を改善した話
mototakatsu
0
250
Mujeres en SEO Summit 2026 - Greatest Disaster Hits en Web Performance
guaca
0
230
キャリア迷子上等 ─ "ない道"は自分で作ればいい
16bitidol
3
2.8k
act1-costs.pdf
sumedhbala
0
160
エンジニアにデザインハーネスを 〜デザインプロセスを規定するためのハーネス〜 / Design harness from an engineer's perspective
rkaga
2
710
Featured
See All Featured
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
150
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.1k
Between Models and Reality
mayunak
4
360
Code Reviewing Like a Champion
maltzj
528
40k
Why Our Code Smells
bkeepers
PRO
340
58k
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.1k
Large-scale JavaScript Application Architecture
addyosmani
515
110k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
56k
Visualization
eitanlees
152
17k
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
450
Designing for humans not robots
tammielis
254
26k
How Software Deployment tools have changed in the past 20 years
geshan
0
34k
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