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
【PHP】破壊的バージョンアップと戦った話〜決断と説得
Search
Satoshi Kaneyasu
January 19, 2025
Programming
550
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
【PHP】 破壊的バージョンアップと戦った話〜決断と説得
Satoshi Kaneyasu
January 19, 2025
More Decks by Satoshi Kaneyasu
See All by Satoshi Kaneyasu
AWS Transform Customによる Spring Boot 2.xから4.xへのVerUp
satoshi256kbyte
2
97
運用ダッシュボードの設計を誰も教えてくれないのだけどみなさんどうしてるんですか? - チームに監視するという文化を根付かせるための第一歩を踏みたい -
satoshi256kbyte
1
140
AWS CDK ExpressモードとCI/CDの組み合わせ
satoshi256kbyte
1
47
AWS CDK ExpressモードとCI/CDの組み合わせ
satoshi256kbyte
0
31
AWS re:Invent 2025の少し振り返り + DevOps AgentとBacklogを連携させてみた
satoshi256kbyte
3
230
Amazon_Cognito_で構築する_スケーラブルな_Web_アプリケーション__シングルページ_Web_アプリケーションに認証を組み込む
satoshi256kbyte
0
51
人間とAI、どちらが書いたコードもCI/CDでチェックしてみよう
satoshi256kbyte
0
56
今こそ押さえておきたい アマゾンウェブサービス(AWS)の データベースの基礎 おもクラ #6版
satoshi256kbyte
1
300
今こそ押さえておきたい アマゾンウェブサービス(AWS)の データベースの基礎
satoshi256kbyte
1
72
Other Decks in Programming
See All in Programming
プロポーザルを書いてもらう
pvcresin
0
570
関東Kaggler会_NVIDIA_Nemotron_コンペ_振り返り
rick_ds
0
690
全PRの83%がAIレビューだけでマージできるようになった開発組織はその後どうなったか
athug
1
2k
PyConJP2026_wat_Python × Signal Processing: How to Draw Pictures with Sound Using Spectrogram Art
wat
0
140
What's New in Android 2026
veronikapj
0
270
Go を使い始めて 2 ヶ月の学び / My first two months with Go
contour_gara
0
360
2年かけて Deno に DOMMatrix を実装した話 / How I implemented DOMMatrix in Deno over two years
petamoriken
0
220
Claude Code全社展開のためにやったことn選~プラグイン302個・コミッター271人を支えるために~
kenchan
5
1.6k
メールのエイリアス機能を履き違えない
isshinfunada
0
250
いまどきの Codex で開発する visionOS アプリの開発スタイルについて
karad
0
160
実装をデザインガイドラインに追従させるための取り組み / 260731-dip-mosh-design-system
dachi023
0
7.4k
進化を続けるGo toolsの現在地 / The Current State of Ever-Evolving Go Tools
hond0413
0
250
Featured
See All Featured
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
17k
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
63
45k
The agentic SEO stack - context over prompts
schlessera
0
870
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1.3k
How to make the Groovebox
asonas
2
2.3k
Prompt Engineering for Job Search
mfonobong
0
410
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
A better future with KSS
kneath
240
18k
A Soul's Torment
seathinner
6
3.5k
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
300
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.8k
Transcript
【PHP】 破壊的バージョンアップと 戦った話〜決断と説得 PHP勉強会in広島 vol.3 2025.01.24 SATOSHI KANEYASU 1
自己紹介 氏名:兼安 聡 所属:株式会社サーバーワークス アプリケーションサービス部 在住:広島(フルリモート) 担当:DevOps、技術支援、PM、SM 2024 Japan AWS
Top Engineers (Database) 2024 Japan AWS All Certifications Engineers Certified ScrumMaster PMP X:@satoshi256kbyte 2
このお話の設定 ➢PHP5→PHP7.3、同時にCakePHP2→CakePHP4への移行を行った話をします。 ➢この話に出てくるプロジェクトは最終的に無事終わってるので安心してください。 ➢インフラ周りの話は今回は対象外とします。 ➢システムはBtoBのWEBシステムです。 ➢サポート切れに伴うバージョンバップです。 ➢故に、期限はマストです。 ➢既存コードに自動テストはありません。 ➢チームにオリジナルの開発者はいません。 ➢CakePHPは2から3の間に破壊的バージョンアップがなされています。
3
CakePHPの破壊的バージョンアップとは 主にモデルの扱いが大幅に変わり、 同名メソッドでも入出力フォーマットが変わっていることを指します。 // CakePHP 2.x $this->loadModel('Post'); $post =
$this->Post->find('first', [ 'conditions' => ['Post.id' => 1] ]); echo $post['Post']['title']; [ 'Post' => [ 'id' => 1, 'title' => 'CakePHP 2 example' ] ] use Cake\ORM\TableRegistry; $postsTable = TableRegistry::getTableLocator()- >get('Posts'); $post = $postsTable->find('all') ->where(['id' => 1]) ->first(); echo $post->title; App\Model\Entity\Post Object ( [id] => 1 [title] => 'CakePHP 3 example' ) CakePHP2 CakePHP3以降 出力形式 出力形式 4
CakePHPの破壊的バージョンアップとは 主にモデルの扱いが大幅に変わり、 同名メソッドでも入出力フォーマットが変わっていることを指します。 // CakePHP 2.x $this->loadModel('Post'); $post =
$this->Post->find('first', [ 'conditions' => ['Post.id' => 1] ]); echo $post['Post']['title']; [ 'Post' => [ 'id' => 1, 'title' => 'CakePHP 2 example' ] ] use Cake\ORM\TableRegistry; $postsTable = TableRegistry::getTableLocator()- >get('Posts'); $post = $postsTable->find('all') ->where(['id' => 1]) ->first(); echo $post->title; App\Model\Entity\Post Object ( [id] => 1 [title] => 'CakePHP 3 example' ) CakePHP2 CakePHP3以降 出力形式 出力形式 5 昔ながらの配列を中心とした実装は、 中に何が入っているか不明瞭で、バグの温床となりやすい。 (いわゆる配列地獄) これの是正がフォーマットが変えられた理由(だと思う)
いきなり頓挫したところから話を始めます ➢PHP5→PHP7.3、同時にCakePHP2→CakePHP4への移行プロジェクト ➢厳密には、プロジェクト内でCakePHP2→3→4というステップを踏ませている ➢これを、各開発者が既存ソースを解析して新Verで作り直すという方法で開始。 ➢途中まで進めてこのままでは頓挫する!となったところから話は始まります。 ➢なお、新Verで作り直すという方針においては、一定のルール作成とトレーニング期間を設けており、 各開発者に特段の落ち度はありませんでした。 ➢純粋に難易度が高すぎ・作業量が多すぎで無理だった次第です。 6
4つの決断と説得 ➢プロジェクトを立て直し、最後までやり切るまでに4つの決断と説得を行っています。 変換関数を作り、既存コードを活かす 安易にフレームワークを乗り換えない 静的解析とフォーマッターをフル活用する ソースコードのメトリクスを算出する 7
決断と説得その1 8
変換関数を作り、既存コードを活かす 決断 • 入出力フォーマットが変わっている全ての 箇所について、変換関数をかまして極力 既存コードを流用できる方向にする • モダンなコーディングスタイルの優先度を 下げる 上層部への説得
• 方針転換により作業が一旦停滞するが、 コードが流用可能になるのでリカバリでき る • 既存コードが流用可能となれば、バグが あるとしたら変換関数がおかしいか、変換 関数がうまく適用できてないかになる。 • となれば、動かして通れば大体OKといえ る • テストに人海戦術が効かせやすく、プロ ジェクト後半のリスクが下がる 9
変換関数適用の流れ $this->loadModel('Post'); $post = $this->Post->find('first', ['conditions' => ['Post.id' => 1]])
$this->loadModel('Post'); $post = $this->Post->findOld('first', ['conditions' => ['Post.id' => 1]]) 入出力フォオーマットが変わっているメソッドを、一旦一括でリネームする リネームしたfindOldは、中で新旧メソッドの入出力フォーマットの変換を行う findOldは、@depcreatedをつけて将来的には廃止する意思を示しておく。 実際にはモデルだけでなく、バリデーションなど各所に変換関数を仕込む。 CakePHP2 10
変換関数適用の流れ /** * @deprecated 本メソッドは新規コードには使用しないでください */ public function findOld($type, $params
= []) { $where = $params['where'] ?? []; $query = null; select ($type) { case 'first': // ここで新しいfind()を呼ぶ $query = $this->find('all’)->where($where)->first(); break; // 以下、他の$type } if ($query === null) { return []; } // 新しいfind()は遅延実行のクエリを返すので、ここで実行しつつ配列に変換 $results = $query->toArray(); // CakePHP 2の配列構造に変換 return 変換メソッド($results); } 11
変換関数適用の流れ /** * @deprecated 本メソッドは新規コードには使用しないでください */ public function findOld($type, $params
= []) { $where = $params['where'] ?? []; $query = null; select ($type) { case 'first': // ここで新しいfind()を呼ぶ $query = $this->find('all’)->where($where)->first(); break; // 以下、他の$type } if ($query === null) { return []; } // 新しいfind()は遅延実行のクエリを返すので、ここで実行しつつ配列に変換 $results = $query->toArray(); // CakePHP 2の配列構造に変換 return 変換メソッド($results); } 12 パラメータ指定ではなく、メソッドチェーンになってるので 変換を入れる
変換関数適用の流れ /** * @deprecated 本メソッドは新規コードには使用しないでください */ public function findOld($type, $params
= []) { $where = $params['where'] ?? []; $query = null; select ($type) { case 'first': // ここで新しいfind()を呼ぶ $query = $this->find('all’)->where($where)->first(); break; // 以下、他の$type } if ($query === null) { return []; } // 新しいfind()は遅延実行のクエリを返すので、ここで実行しつつ配列に変換 $results = $query->toArray(); // CakePHP 2の配列構造に変換 return 変換メソッド($results); } 13 最近のFW・ライブラリは、 クエリの遅延実行が多いのでフォローを入れないと 昔の感覚では使えないことに留意
変換関数適用の流れ /** * @deprecated 本メソッドは新規コードには使用しないでください */ public function findOld($type, $params
= []) { $where = $params['where'] ?? []; $query = null; select ($type) { case 'first': // ここで新しいfind()を呼ぶ $query = $this->find('all’)->where($where)->first(); break; // 以下、他の$type } if ($query === null) { return []; } // 新しいfind()は遅延実行のクエリを返すので、ここで実行しつつ配列に変換 $results = $query->toArray(); // CakePHP 2の配列構造に変換 return 変換メソッド($results); } 14 単にtoArrayするだけではCakePHP2と同じデータ 構造にならない(ネストの深さが合わない)ので、 調整する関数を挟む
決断と説得その2 15
破壊的バージョンアップに振り回されるなら、 いっそLaravelに移行した方が早いのでは? 16
安易にフレームワークを乗り換えない 決断 • ビジネスが維持できることを最優先に考え る • Laravelには行かない、CakePHPのまま とする チームへの説得 •
サポート切れによるバージョンアップは基 本マイナスをゼロに戻す作業 • Laravelにしても開発者の満足感以外 のメリットはない • これ以上のリスクを背負うべきではない • CakePHPは悪いフレームワークではない • Googleトレンドなどを見てもCakePHP の需要は一定以上はキープされている • 後にCakePHP5が出るのでこの判断は正しかった 17
決断と説得その3 18
静的解析とフォーマッターをフル活用する 決断 • 静的解析はPHPStanを使用し、Level Maxとする • フォーマッターは、{}を[]に変換するなど に使用 • CakePHP2からの移行では、使わなくな
るインスタンス変数が大量に発生する • これらの移行の成否を目視で確認しきる のは無理なので、静的解析を活用する チームへの説得 • 機械でできるチェックはやらせる • 機械によるフォーマットはバグを生まないと すると言い切る • PHPStanはMax以外は選んだ理由に 妥当性が見つからないだろう • Levelを下げるよりも例外を指定した方 が良い • 下準備が必要なのは受け入れる、後で 苦労するよりずっと良い 19
CakePHP2からの移行では、インスタンス変数が大量に不要になる class Post extends AppModel { public $validate = [
'title' => [ 'notEmpty' => [ 'rule' => 'notEmpty', 'message' => 'タイトルは必須です。' ], 'maxLength' => [ 'rule' => ['maxLength', 255], 'message' => 'タイトルは255文字以内で入力してください。' ] ], 'content' => [ 'notEmpty' => [ 'rule' => 'notEmpty', 'message' => 'コンテンツは必須です。' ] ] ]; } CakePHP2 例えばバリデーション定義 20
CakePHP2からの移行では、インスタンス変数が大量に不要になる namespace App\Model\Table; use Cake\ORM\Table; use Cake\Validation\Validator; class PostsTable extends
Table { public $validate = [略 public function validationDefault(Validator $validator): Validator { $validator ->notEmptyString('title', 'タイトルは必須です。') ->maxLength('title', 255, 'タイトルは255文字以内で入力してください。') ->notEmptyString('content', 'コンテンツは必須です。'); return $validator; } CakePHP3以降 ➢CakePHP3以降ではバリデーション定義はメソッドで定義する ➢静的解析で未使用のインスタンス変数をチェックすることで、バリデーション定義の移行漏れ を拾う 21 これが残っていても何も意味はない。 故に静的解析で未使用変数を拾うことで移行漏れ を見つけられる。
下準備が必要なのは受け入れる ➢静的解析はメソッドコメントなどが揃ってないと十分に力が発揮されない ➢これについては受け入れて数日間粛々とコメントを追記していく 22
PHPStanはCakePHPに沿った静的解析ができる ➢PHPStanはプラグインを入れることでCakePHPに沿った静的解析ができる ➢https://github.com/CakeDC/cakephp-phpstan ➢MVCに違うものが混じっているとこのプラグインが活かしきれない ➢本PJの時は帳票出力を丸ごと移動させ、↑のプラグインを参考に帳票出力用の静的解析 ロジックを作成して解析させた ➢同じことをする必要はないと思う ➢Modelにいろんなものが混じってるのだけは脱却し、 ディレクトリで単位で対象にする・しないとかの選択ができるように持っていくのが大事 23
CakePHPのMVCに本来ないものを移動させる ➢MVCの概念が出始めた頃に作られたシステムは、モデルに他のものが混じってることがある ➢混じってると静的解析がうまく動かないので移動させる . ├── Controller ├── Model │ ├──
モデル │ └── モデル(実質帳票出力処理) └── View . ├── Controller ├── Document │ └── 実質帳票出力処理 ├── Model │ └── モデル └── View 24
決断と説得その4 25
ソースコードのメトリクスを算出する 決断 • 移行前後のステップ数を計測 • 増減した理由を整理しておく • PHP Mess Detector
(PHPMD)を用 いて、移行前後の複雑度などを計測 • 同じく変化の理由を整理しておく 関係者への説得 • バージョンアップはQCDに対する目が厳し いので、品質に対する説得材料として使 用する • ステップ数・複雑度の低下は、ディレクトリ 構造や重複コードの整理によるものと説 明 • ステップカウントはテストケース・バグ数と 照らし合わせ、IPAのゾーンモデルを元に 品質の証明材料として使用 26
ゾーンモデル 27 ➢引用:https://www.ipa.go.jp/archive/files/000072870.pdf
まとめ 28
まとめ 29 ➢破壊的バージョンアップと戦うには、技術だけでなく、施策の決断・順番、そして説得が必要 ➢もしみなさんが破壊的バージョンアップと戦うことがあれば、これらを参考にして欲しい。 変換関数を作り、既存コードを活かす 安易にフレームワークを乗り換えない 静的解析とフォーマッターをフル活用する ソースコードのメトリクスを算出する
ご清聴ありがとうございました。 30
参考リンク 31 ➢[Zenn]CakePHP2からCakePHP4への移行のポイント ➢[Qiita]破壊的変更のあるバージョンアップ作業の流れ