Upgrade to Pro — share decks privately, control downloads, hide ads and more …

【PHP】 破壊的バージョンアップと戦った話〜決断と説得

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

【PHP】 破壊的バージョンアップと戦った話〜決断と説得

Avatar for Satoshi Kaneyasu

Satoshi Kaneyasu

January 19, 2025
Tweet

More Decks by Satoshi Kaneyasu

Other Decks in Programming

Transcript

  1. 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
  2. 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 昔ながらの配列を中心とした実装は、 中に何が入っているか不明瞭で、バグの温床となりやすい。 (いわゆる配列地獄) これの是正がフォーマットが変えられた理由(だと思う)
  3. 変換関数を作り、既存コードを活かす 決断 • 入出力フォーマットが変わっている全ての 箇所について、変換関数をかまして極力 既存コードを流用できる方向にする • モダンなコーディングスタイルの優先度を 下げる 上層部への説得

    • 方針転換により作業が一旦停滞するが、 コードが流用可能になるのでリカバリでき る • 既存コードが流用可能となれば、バグが あるとしたら変換関数がおかしいか、変換 関数がうまく適用できてないかになる。 • となれば、動かして通れば大体OKといえ る • テストに人海戦術が効かせやすく、プロ ジェクト後半のリスクが下がる 9
  4. 変換関数適用の流れ $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
  5. 変換関数適用の流れ /** * @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
  6. 変換関数適用の流れ /** * @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 パラメータ指定ではなく、メソッドチェーンになってるので 変換を入れる
  7. 変換関数適用の流れ /** * @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・ライブラリは、 クエリの遅延実行が多いのでフォローを入れないと 昔の感覚では使えないことに留意
  8. 変換関数適用の流れ /** * @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と同じデータ 構造にならない(ネストの深さが合わない)ので、 調整する関数を挟む
  9. 安易にフレームワークを乗り換えない 決断 • ビジネスが維持できることを最優先に考え る • Laravelには行かない、CakePHPのまま とする チームへの説得 •

    サポート切れによるバージョンアップは基 本マイナスをゼロに戻す作業 • Laravelにしても開発者の満足感以外 のメリットはない • これ以上のリスクを背負うべきではない • CakePHPは悪いフレームワークではない • Googleトレンドなどを見てもCakePHP の需要は一定以上はキープされている • 後にCakePHP5が出るのでこの判断は正しかった 17
  10. 静的解析とフォーマッターをフル活用する 決断 • 静的解析はPHPStanを使用し、Level Maxとする • フォーマッターは、{}を[]に変換するなど に使用 • CakePHP2からの移行では、使わなくな

    るインスタンス変数が大量に発生する • これらの移行の成否を目視で確認しきる のは無理なので、静的解析を活用する チームへの説得 • 機械でできるチェックはやらせる • 機械によるフォーマットはバグを生まないと すると言い切る • PHPStanはMax以外は選んだ理由に 妥当性が見つからないだろう • Levelを下げるよりも例外を指定した方 が良い • 下準備が必要なのは受け入れる、後で 苦労するよりずっと良い 19
  11. 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
  12. 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 これが残っていても何も意味はない。 故に静的解析で未使用変数を拾うことで移行漏れ を見つけられる。
  13. CakePHPのMVCに本来ないものを移動させる ➢MVCの概念が出始めた頃に作られたシステムは、モデルに他のものが混じってることがある ➢混じってると静的解析がうまく動かないので移動させる . ├── Controller ├── Model │ ├──

    モデル │ └── モデル(実質帳票出力処理) └── View . ├── Controller ├── Document │ └── 実質帳票出力処理 ├── Model │ └── モデル └── View 24
  14. ソースコードのメトリクスを算出する 決断 • 移行前後のステップ数を計測 • 増減した理由を整理しておく • PHP Mess Detector

    (PHPMD)を用 いて、移行前後の複雑度などを計測 • 同じく変化の理由を整理しておく 関係者への説得 • バージョンアップはQCDに対する目が厳し いので、品質に対する説得材料として使 用する • ステップ数・複雑度の低下は、ディレクトリ 構造や重複コードの整理によるものと説 明 • ステップカウントはテストケース・バグ数と 照らし合わせ、IPAのゾーンモデルを元に 品質の証明材料として使用 26