ForgeVision Engineer Blog

フォージビジョン エンジニア ブログ

CakePHP 3.2.x → 3.3.x アップデートで、盛大に失敗した話

白米と納豆と梅干しがあれば生きていける、かーさん (id:Kaa_saan) です。
主にPHP案件担当してます。

今回は、痛いお話

CakePHP3.x系のお話です。
去年の話になりますが、CakePHPのアップデートを試みて、 盛大に失敗した ときの覚書です。
今後の戒めとして残しておきます。

アップデートの経緯・・・?

1年前のことなので、詳細な経緯は忘れてしまいました。 :)
・・・が、おそらく単純に、「そろそろ新しくしようぜ~♫」的な軽い気持ちだったような・・・。
すでに稼働しているサービスだったので、とりあえず、CakePHPPHPUnitのアップデートが目標でした。
ちなみに、サービスは本番・検証・開発環境全て、AWSAmazon Linux AMI release 2015.09)で稼働しています。

アップデート検証 CakePHPPHPUnit

検証のシナリオ

アップデートを試す順番は、①~③で考えました。
②が目標ですが、あわよくば③まで行きたいなと。

CakePHPのみアップデート
CakePHPPHPUnit アップデート
③ 全プラグインアップデート

事前準備

  • アップデート 検証用ディレクトリを作成して、developソースを git clone

①~③各手順共通事項

それでは、順に試していきます。

CakePHPのみアップデート(CakePHP:3.2.13 → 3.3.13)

$ composer update "cakephp/cakephp"
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing zendframework/zend-diactoros (1.3.10)
    Downloading: 100%

  - Removing cakephp/cakephp (3.2.13)
  - Installing cakephp/cakephp (3.3.13)
    Downloading: 100%

Writing lock file
Generating autoload files
> Cake\Composer\Installer\PluginInstaller::postAutoloadDump

補足 :composer update [プラグイン名] で、アップデートするプラグインを限定できます。

  • CakePHPが 3.2.13 から 3.3.13 になった
  • 新しく、 zendframework/zend-diactoros (1.3.10) が追加
  • UTもOK

①がうまく行ったので、次は②を試します。

CakePHPPHPUnit アップデート(PHPUnit:5.5.0→ 5.6.4)

$ composer update cakephp/cakephp phpunit/phpunit
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Removing phpunit/phpunit (5.5.0)
  - Installing phpunit/phpunit (5.6.4)
    Loading from cache

Writing lock file
Generating autoload files
> Cake\Composer\Installer\PluginInstaller::postAutoloadDump

補足:CakePHPは①でアップデート済みですので、ログにでてきません。

  • PHPUnitが 5.5.0 から 5.6.4 になった
  • UTも、エラー無しでOK

②も問題なくOK でした。ので、③へ進みます。(この辺でだいぶ調子に乗っている)

③ 全プラグインアップデート ・・・ダメでした

$ composer update 

補足:プラグイン指定なしの composer update は、全プラグインを最新バージョンで更新します。

  • UTで大量のエラー(´;ω;`)
  • プラグインアップデートは断念する

エラーの原因 ・・・使ってるプラグインが無くなっていた

  • MissingComponentException: Component class AuthUserComponent could not be found. が大量に出た(Controllerテストで主にでてる)
  • AuthUserComponent(dereuromark/cakephp-tools*1)は 後から入れたプラグイン
  • cakephp-toolsの 検証時、既存ソースはv1.1.3、最新バージョンは2.x
  • 2.x系になって、AuthUserComponent は cakephp-toolsから 抹殺された → TinyAuthプラグイン*2として 独り立ち!→現行ソースの書き換え必須!*3

検証の結果

  • ③の全プラグインアップデートは、変更の入ったプラグインも存在していて、コードの書き換えが必須なので断念
  • アップデートの対象は、当初のCakePHPPHPUnitに絞る(他のプラグインは、現行バージョンのままで行く)
  • ②の内容の、 composer.jsoncomposer.lock をバージョン管理コミット

検証環境で、アプリ動作確認をする

検証の結果、CakePHPPHPUnitは問題が出なかったので、次は検証環境でアップデートしてみて、実際にアプリの動作確認を行います。
ここで、一連の機能が問題なく使えれば、本番へ反映ということになります。
が、、、、、、事はすんなり行きませんでした。

検証環境でのアプリ動作確認中に、不具合発生

JSON出力のDateデータに不具合?

アプリ動作確認しているチームから、非同期処理部分で機能が動かなくなった、というような報告が上がってきました・・・。
調査してみたら、以下のような不具合が確認できました。

  • AjaxJSONデータを利用して処理している機能で、一部データの不整合が発生していた
  • 具体的に言うと、JSONで取得している Date(日付)の戻り値に、3.2系と3.3系で差分が発生していた
    • 3.2:2017-02-22T18:17:15+09:00
    • 3.3:2017-02-22T18:17:15
    • +09:00 が 付いてこない
  • DBから取得したデータをdumpしてみると、ちゃんと +09:00 が付いている状態なのは確認できた
  • どうやら、JSONに加工された時になにかが起こっているのでは?と当たりをつける
#DBデータのダンプ
[
        'update_date' => object(Cake\I18n\Time) {

            'time' => '2017-02-22T18:17:15+09:00',
            'timezone' => 'Asia/Tokyo',
            'fixedNowTime' => false

        },
],

Cakeのコアソースを調べる

3.3.13のdateのフォーマット部分のソースは以下になります。

protected static $_jsonEncodeFormat = "yyyy-MM-dd'T'HH:mm:ssxxx"; *4

3.2.13では以下の通り、フォーマットが違いました。

protected static $_jsonEncodeFormat = "yyyy-MM-dd'T'HH:mm:ssZ"; *5

どうやら、3.x系でフォーマットが変更されたようです。

それでも、なぜ、検証環境でこのフォーマットが効かないのか原因がさっぱりわかりませんでした。
最後の手段で、CakePHP(JP)のslackチャンネル*6で相談したところ、 「サーバのICU*7のバージョンが古いのでは?」という助言をいただくことが出来ました。
(Cakeチャンネルの皆様、あのときは大変お世話になりました。m(_ _)m 感謝)
Cakeチャンネルの @chinpei215 さんにいろいろ確認手順を教えていただいて、以下のことがわかりました。

調査結果

  • ICUが古くて、 xxx フォーマットに対応していない可能性
  • ICUバージョン確認 php -i | grep ICU
$ php -i | grep ICU
ICU version => 50.1.2
ICU Data version => 50.1
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  • 正常に動く人の環境は バージョン 57.1
  • CentOS 6.5の バージョン 50.1.2 で確認してもらったら、同じ現象になった
  • おまけ:CakePHPソースコード上での、動作確認コード

    echo (new \IntlDateFormatter('ja_JP', null, null, null, null, "yyyy-MM-dd'T'HH:mm:ssxxx"))->format(new \DateTime());

対応案

  • ① src/Controller/AppController.php::initialize() に 以下を追加
    • Time::setJsonEncodeFormat('YYYY-MM-dd HH:mm:ssZ');
    • なんか、付け焼き刃みたいな対応で嫌だ
  • ICUのバージョンを上げる
    • 本当はこっちが正解なんだと思う
    • ICUの詳細については後述

今回の反省点

結局、CakePHPのアップデートは見送りに

今回は、PHPの拡張モジュールである intl *8のバージョンを上げないとならないという、問題にぶち当たってしまいました。
CakePHPの動作環境は、公式のページ*9にも記載されていて、PDO 始め主要なものはちゃんとチェックしていたつもりでいました。
が、まさか intl に落とし穴があるとは・・・。
簡単にPHP拡張モジュールのアップデートも行えないので、今回のアップデートは、見送ることになりました。

もうちょっとちゃんとUTすればよかった

非同期でデータを取得してくる機能のUTは、戻り値の項目数を全てテストはしていませんでした。
任意の何項目かをピックアップしてテストしていて、肝心の日付(Date)部分は、テストしていませんでした。
今回の事で、日付項目もテストに追加したいと思います。

あと、コアのCakePHP自体のUTも、開発環境で流してみたいところです。

以上、めちゃ長くなりましたが、次回のCakePHPでの案件の課題にしたいと思います。

おまけ PHPICUとintl

国際化用拡張モジュール(Intl)

問題発覚時の環境メモ

  • PHPは、PHP 7.0.15
  • intl のバージョンは以下
    • version 1.1.0
    • ICU version 50.1.2
    • ICU Data version 50.1
      • phpinfo(); でも確認できる

ICUおまけ