CakePHPを3.7に上げてみたのでその時の作業のポイントをまとめる

1人AdventのDay-10です。

adventar.org

日本時間の9日、CakePHPの3.7のstableがリリースされました。

bakery.cakephp.org

リリースノートや移行ガイド、 GitHubの3.7ラベルがついているIssue/PRを眺めながら、主観&自分に関係しそうな箇所をまとめたのが昨日のアドカレ記事でした。

cake.nichiyoubi.land

今日は月曜日〜ということで、会社に出社したのですが、モノは試しということで社内ツールのCakeアップグレードに手を出してみました*1

結構スムーズに行ったのかな?と思うので、その際の手順などをまとめてみたいと思います。

前提・ゴール

まず、移行対象とするアプリケーションの状況や今回の作業のゴールをまとめます。

アプリケーションの内容・立ち位置

  • 社内向けの管理ツールです
  • もともとCakePHPは最新版(の1個手前!)である3.6.13までアップデート済み
    • 3.6.xならウィークリーで更新PRが作成されるようにしてあり、原則として最新版に追従させる
    • 3.5/3.6で追加されていたdeprecatedも全て推奨される方式に移行済み
  • サーバーサイドはAPIのみを提供 。なので、View/Form周りはほぼ使っておらず
    • このレイヤーを利用しているアプリケーションにおいては、大まかな作業の進め方は変わらないものの、手間は確実に増えると思います
  • モデルの単体テスト、コントローラの結合テストは原則として必須にしているので、割と機械的なチェックを信用した移行ができる

APIアプリケーションという限定的な機能に留めていること、テストや小まめにバージョンアップしているなど「気軽にアップデートしても大きな作業になりにくい」という状況は整えられていると思います。

今回のバージョンアップ作業のゴール

  • (当然ながら)デグレはさせない
    • 単体テストを完走させる
    • コード解析(PHPStan)も完全に通るようにする
    • 作業後、主要機能を中心に手動でリグレッションテストをかける
  • 新機能の利用などは都度
    • 「普通に動く」を一旦ゴールに

事前準備

変更内容の把握・見立て

何よりも、まず最初にチェックすべきはやはり移行ガイドです

3.7 Migration Guide - 3.7

deprecatedをみる

3.6に引き続き、今回のバージョンの主な意図としては「4に連れて行かない機能に廃止予告をする」ことにあると思います。
その内容は、(またしても)setter/getterの分離が目立ちます。

後述しますが、ここで「変更されるものすべて」を厳密に見る必要はないと思います。特に、メソッドの分離系は「いつものですね!」といって流してしまっても良いかもしれません。
どちらかといえば、私の場合、「自分のアプリケーションで関連しそうなクラスは、どのくらい対象になっているかな?」という観点でチェックをしました。これが多ければ作業は重くなると予想できますし、そうでないならサクッと行けそうです。
「今やっちゃうか後で腰を据えてやるか」を占うために、大体の判断を「どんなクラスがあるか」で見たわけです。

behavior changeをみる

(少なくとも今回の変更においては)「まっとうに使っていてもヤバそう」という内容の破壊的な変更はないように感じました。
しかしながら、アプリケーションに埋め込まれた地雷が発火する可能性もあり、やはり1番ケアするべきはこの項目だと思います。

意図が読み取れない・影響範囲のイメージがパッと出てこない・・というものに関しては、対象PRを探し出して読んでいってしまったほうが、安心して移行作業に取り組めるかもしれません

new featuresをみる

この節は、「とりあえず動くように移行する」という観点で言えば、無関係といえば無関係です。ただ、折角なのでこの機会に読んでみてしまったら?くらいの気持ちで目を通しています。
内容によっては、behavior change/deprecatedになった「背景」や、あるいは「どの方向に進もうとしているのか」という設計者の思想が感じ取りやすくなる場合もあるでしょう。

作業の実施

ここから実際の作業に入ります。

ごく個別的なケースになりますが、(API専門という)小さなアプリケーションですら「踏んだ穴」となれば、一般的なアプリケーションにおいても同様な作業が発生する可能性も低くないと思います。
そういう意味で、「実際に対応した内容」も併せて紹介していきたいと思います

静的解析

IDE・PHPStan(Phan)を利用します。
今回はPhpStormだけで賄えた部分だけ対応し、PHPStanは別箇所での反応がありませんでした。なのでPhpStromの活用に話を絞ります。

  • 検出する
    1. Code -> Run inspection by name 「Deprecated」
    2. 検査対象を指定し、OK
      1. なお、このときに利用するために予め「Custom scope」を定義しておくと便利。私は、AppApp\Test を定義してあります

移行作業に静的解析での結果を利用する目的は、クラス・メソッド単位で宣言されたdeprecatedへの対応です。

これはPhpStormで洗い出して潰します

IntegrationTestCase

コントローラー及びシェルのテストに主に利用していた、(Console)IntegrationTestCaseがdeprecatedに仲間入りです*2

  1. 継承元をすべて \Cake\TestSuite\TestCase へ変更する
  2. Cake\TestSuite\IntegrationTestTrait をuseする

Runtimeでのdeprecated対応

実行時に、引数やコンテキストによって deprecationWarning() を発火する部分への対応です。

  1. 検出する
    1. これは完全にテストに頼りました。なので、カバーできていない部分は拾えていない・・・逆を言えば「諦めてもいい」のかな、とも思っています

ちらっと蛇足。
ありがたいことに「廃止」ではなく「deprecated」です。つまり「なくなる」訳でもなければ「まぁ別に普通に動きはする」というものです。
どのように最新版対応を進めるか?というやり方次第ですが、大雑把に言えば「もし、見落としがあって対応がとれていなくても、死にはしない」「だから、動かしてみて、warningを感知したらその都度で事後対応を行っていく」というのも1つの上手いやり方のようにも思います。

Plugin::load()

3.5から入った新しいPluginの仕組みを使うように促しています。

  1. (bootstrap.phpから) Plugin::load()を消す
  2. Application.php 上で、 $this->addPlugin()によりプラグインを読みこむ

Email::setConfigTransport()

これもcreate-projectした時からの付き合いだと思います。
私の場合、Email周りは利用していなかったので設定ごと削除しました。
・・・どこかで参照が走ってエラーとなる可能性もありますので、その時に対応すればいいかな、くらいに捉えています。

Plugin::loaded()

今回、これが1番悩んでいるのですが・・・ bakeプラグインが依存している、twig-viewのbootstrap.php内での利用です。
masterにおいては修正が加わっているのですが、まだリリースされていません。

github.com

影響がでるのが「debugが有効」なケースでのみに限定されそうなので、今のところはスルーしています・・

Dispatcher is deprecated.

こちらは厳密には以前のバージョンで入ったDeprecatedですが、今回のIntegrationTestCaseの対応と絡んで引っかかるようになりました。

IntegrationTestがコントローラーアクションをエミュレートする際のcalliingクラスには2つあります。 MiddlewareDispatcherLegacyRequestDispatcherです。
このいずれを使うか?は、IntegrationTestCase/IntegrationTestTraitの setUp() メソッドにて設定が行われています*3

しかしながら、静的解析の時点では「継承元を変えてトレイトを食わせる」という作業を行ったのみであるため、テストクラス自体がsetUp() メソッドを持っている場合は IntegrationTestTrait::setUp() を呼び出すようになっていませんでした。
そのため、正しくフラグがセットされず、LegacyRequestDispatcher が起動される・・という流れになってしまっていたわけです。その先で、deprecatedされている Dispatcherクラスが使役されます。

以下のようにしました。

<?php
//in some controlelr test case 

use IntegrationTestTrait { setUp as integrationSetUp; }

public function setUp()
{
    $this->integrationSetUp();
}

内部的に parent::setUp() も呼ばれているので、既存の実装が破壊されるようなことはないと思います。

Declaring fixtures in underscored format in TestCase::$fixtures is deprecated.

これが1番手間がかかりました!w

利用するFixtureの宣言を、CamelCaseで行うように変更されています。
そのため、これまで $fixtures = ['app.article_categories']; のようにsnake_caseで宣言していた箇所が警告されるようになりました。
・・・fixtureを使うテストクラス全部。。。

PJ全体検索で $fixures = などとして地道に変更を行っていったのですが、Inflectorユーティリティなどと絡めて自動書き換えもできたのかな・・・?という気もしています。
このあたりは、テストのボリュームとの兼ね合いで。

まとめ

バージョン移行系の作業は、手間もストレスもかかりますが、やはりそれ相応のメリットもあるのかなと考えています。
セキュリティリスク含め、バグを含む可能性も時にはありますが、少なくとも定期的な更新(最新化である必要は必ずしもない)はやるべきだ、と思います。

その時の肝が、

  • 大幅アップデートにならないようにこまめに更新を行っておく
  • 安心感を担保するに足る十分量・幅の自動テストを用意しておく

の2つだな、と改めて感じました。

引き続きCakePHP4を待ちながら、また今回入った新しい機能をワクワクしながら使っていきたいなー!と思っています

*1:まだレビュー待ちのステータス

*2:なお、今回のケースに置いてはシェルコマンドの実装がないので、ControllerTestCaseのみの対処で完了しました

*3:正確には、ディスパッチャの選択に関わるフラグのセットをここで行う https://github.com/cakephp/cakephp/pull/12072/files#diff-30ad814a76cb9a0911ed10f377ace431L181