PHP7.3対応版のcakephp-1.3.21
PHP5.6からPHP7.3やPHP7.4に移行すると、処理速度が150%から200%改善するそうです。
ところが、CakePHP1.3はPHP7.xで動きません。
CakePHP1.3にはObjectクラスやStringクラスが定義してあります。ところが、object(PHP 7.2以降)やstring(PHP 7以降)が、PHPの予約語になってしまったからです。
なんと、有志によるPHP7.3対応版のcakephp-1.3.21とsimpletest-1.0.1がgithubで公開されています。
ロードマップ
マルチドメイン、ドメインごとにPHPバージョンを設定できる共用サーバを想定しています。(AWSやGCPでも同じ要領です)
(1)本番環境用のgitリポジトリを作成します。
もしソース管理していなければ、gitリポジトリを作ります。
masterブランチにpushすると、本番環境に自動デプロイするように設定します。
PHP 5.xのdocker環境を作ります。
PHPバージョンの設定が、.htaccessなのか、php.iniなのか、その記述内容を確認し、必要ならgitに追加します。
ローカル | 共用サーバ | DBサーバ |
---|---|---|
(1)masterブランチ docker PHP5.x | 本番環境 www.example.jp PHP5.x | 本番DB mysql123.example.jp production_db |
(2)共用サーバで、ステージング環境用のサブドメインを作ります。
サブドメインのPHPバージョンをPHP7.3に設定して、phpinfoを確認します。
PHPバージョンの設定が、.htaccessなのか、php.iniなのか、その記述内容を確認します。
また、ステージングDBを新規作成します。
本番DBからエクスポートして、ステージングDBへインポートします。
ローカル | 共用サーバ | DBサーバ |
---|---|---|
masterブランチ docker PHP5.x | 本番環境 www.example.jp PHP5.x | 本番DB mysql123.example.jp production_db |
(2)ステージング環境 stg.example.jp PHP7.3 | (2)ステージングDB mysql456.example.jp staging_db |
(3)PHP7.3用のstagingブランチを作成します。
(2)で調べたステージング環境のPHPバージョンの設定ファイル(.htaccessやphp.ini)を必要に応じてgitに追加します。
stagingブランチにpushすると、ステージング環境に自動デプロイするように設定します。
PHP7.3のdocker環境を作ります。
PHP7.3対応版のcakephp-1.3.21のcake/をコピーします。
必要な修正をします。
- ステージング環境で動作確認
- ローカルのstagingブランチで修正
- ローカルのdockerで動作確認
- gitコミット、stagingブランチをpush
を繰り返します。
ローカル | 共用サーバ | DBサーバ |
---|---|---|
masterブランチ docker PHP5.x | 本番環境 www.example.jp PHP5.x | 本番DB mysql123.example.jp production_db |
(3)stagingブランチ docker PHP7.3 | (3)ステージング環境 stg.example.jp PHP7.3 | ステージングDB mysql456.example.jp staging_db |
(4)stagingブランチからmasterブランチへマージします。
githubコンソールで、Pull Requestを新規作成して、stagingブランチからmasterブランチへマージします。
masterブランチの内容が、本番環境に自動デプロイされます。
ローカル | 共用サーバ | DBサーバ |
---|---|---|
(4)masterブランチ docker PHP7.3 | (4)本番環境 www.example.jp PHP7.3 | 本番DB mysql123.example.jp production_db |
stagingブランチ docker PHP7.3 | ステージング環境 stg.example.jp PHP7.3 | ステージングDB mysql456.example.jp staging_db |
準備
デイリーバックアップの設定
もしデイリーバックアップしていないなら、共用サーバの管理画面にログインして、バックアップオプションをポチっとしましょう。多くの共用サーバで、月額1000円〜2000円です。
さらに、共用サーバのバックアップオプションとは別に、独自のデイリーバックアップも設定したほうがいいでしょう。
2012年、ファーストサーバのデータ消失事故がありました。ファーストサーバ側の作業ミスで、バックアップも含めてデータ消失してしまいました。ほとんどのサイトは独自のバックアップをしていなかったので、サイトの復旧は難しかったそうです。
git管理とgithub
もしソース管理していないなら、git管理を始めましょう。ここではgithubで説明しますが、backlogもおすすめです。backlogは日本で広く使われているGit総合サービスです。
自社のgithubアカウントを作り、githubにprivateリポジトリを作ります。ローカルPC内のgitリポジトリとpush/pullして同期します。
もしPHP7.3対応の作業を外注会社に発注する場合、外注会社のgithubアカウント下にリポジトリを作るのはおすすめしません。
githubのリポジトリには、課題管理のIssue、資料やメモを残すwikiがあります。ソースファイルだけでなく、これらも貴重な財産です。
外注会社を変更するときや、外注会社と揉めたりしたとき、リポジトリ譲渡してもらえないと、貴重なIssueやwikiを失ってしまいます。最初から自社のgithubアカウントのリポジトリにしたほうがいいでしょう。
デプロイの自動化
次に、githubからpullして、共用サーバにアップロードする作業を自動化します。
共用サーバへのアップロード方法は、FTPでもSSHでも自動化できます。
デプロイ自動化はシェルスクリプトを書く必要があるので、少しハードルが高いですが、開発者もデザイナーもアップロード作業をする必要がなくなるので、それだけの価値があります。
- FTP操作ミスがなくなります。
- 複数人によるファイルの上書き事故や先祖返りがなくなります。
- FTPアカウントを使いまわす必要がありません。
- 一人ひとりにFTPアカウントを発行する必要がありません。
自動化のクラウドサービスは、GithubAction、Circle CI、Travis CIなど、いくつもの選択肢があります。Jenkinsサーバを用意する方法は柔軟性がありますが、難易度も高くなります。
本番サーバへのFTPは何回やっても慣れなかったよ、そのたびに緊張して心臓ドキドキするよね
PHP4.xのdocker環境
PHP4.xの頃からのユーザ向けに、PHP4.xを提供している共用サーバはあるようです。ですので、PHP4.x + CakePHP1.3のWebアプリがないとは言い切れません。
ただし、PHP4.xのdocker環境を作ろうと思っても、いまやPHP4.xはyumでインストールできません。yumリポジトリがPHP4.xの公開を停止しているためです。
そこで、docker環境をCentOS/6 + xamppで用意するか、(docker環境ではなく)Windows + xamppで用意します。
xamppは、Windows版、macOS版、Linux版とあり、PHP4.xからのPHPバージョンをダウンロードできます。
PHP4.xからいきなりPHP7.3へ移行するのではなく、PHP4.x〜PHP5.2〜PHP5.6〜PHP7.3と少しづつ移行とリリースを繰り返したほうがいいかもしれません。
PHP5.xのdocker環境
現在の本番環境のPHPやMySQLのバージョンを調べて、CentOS/6やCentOS/7をベースに、PHP5.x、MySQL/5.xやmariadbを選び、docker環境を用意します。
xdebugも使えるようにしておきます。
RUN yum install -y http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
RUN yum install -y --enablerepo=remi-php56 php
RUN yum install -y --enablerepo=remi-php56 php-pecl-debug
略
Code language: Dockerfile (dockerfile)
一時的に <?php phpinfo();
を書いた phpinfo.phpを置いて、たしかにPHP5.x になっていることを確認します。
本番データベースからデータをエクスポートして、docker環境のMySQLにインポートします。
もしPHP5.1やPHP5.2など古いバージョンの場合、PHP5.1からいきなりPHP7.3へ移行するのではなく、PHP5.2〜PHP5.6〜PHP7.3と少しづつ移行とリリースを繰り返したほうがいいかもしれません。
ユニットテスト、E2Eテスト
simpletestのユニットテスト、Selenium IDEのE2Eテストがあれば、docker環境でテストして、オールグリーンを確認します。
Selenium IDEは、操作を記録して、自動再生します。今からでも、比較的簡単にE2Eテストを用意できます。
E2Eテスト実行後、PHPエラーログを調べて、Notice、Warning、Errorがないか確認します。
デプロイ自動化に組み入れて、テストを合格したら、デプロイするようにすると、テストし忘れがありません。
PHP7.3のdocker環境
PHP 5.xのDockerfileをコピペして、PHP 7.3のdocker環境を作ります。
--enablerepo=remi-php56
を--enablerepo=remi-php73
に置換します。
RUN yum install -y http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
RUN yum install -y --enablerepo=remi-php73 php
RUN yum install -y --enablerepo=remi-php73 php-pecl-debug
略
Code language: Dockerfile (dockerfile)
一時的に <?php phpinfo();
を書いた phpinfo.phpを置いて、たしかに PHP7.3 になっていることを確認します。
公式のcakephp-1.3.xとの差分を調べる
CakePHP公式のcake/と、myproject/cake/ の修正箇所(差分)を調べます。
その差分を、PHP7.3対応版のcakephp-1.3.21にも適用します。
CakePHP公式のcake/ +「差分」=myproject/cake/
PHP7.3対応版のcake/ +「差分」=PHP7.3対応版のmyproject/cake/
としたいわけです。
cake/ をまったく修正していなければ、それでいいのですが、意外にも cake/ を修正していることが多いものです。
myproject/当初からgit管理している場合は、git log -p cake で調べることができます。
$ git log -p cake/
Code language: Backus–Naur Form (bnf)
git管理していたとしても、CakePHP公式のcake/ と myproject/cake/ の差分を調べたほうがいいでしょう。
まず、CakePHPのバージョンを調べます。myproject/cake/VERSION.txt の最後に記載してあります。
////////////////////////////////////////////////////////////////////////////////////////////////////
// +--------------------------------------------------------------------------------------------+ //
// CakePHP Version
//
略
// @license MIT License (http://www.opensource.org/licenses/mit-license.php)
// +--------------------------------------------------------------------------------------------+ //
////////////////////////////////////////////////////////////////////////////////////////////////////
1.3.21
Code language: plaintext (plaintext)
この例では「1.3.21」です。
次に、githubのCakePHP公式リリースを調べます。
1.3.21なら、https://github.com/cakephp/cakephp/releases/tag/1.3.21
にアクセスします。
Source code(zip)、Source code(tar.gz)どちらかをダウンロードして、解凍します。
解凍した cakephp-1.3.21/cake/ と、myproject/cake/ の差分を調べます。
myproject/をmyproject_copy/へコピーしておき、cakephp-1.3.21/cake/ と myproject_copy/cake/ の差分を調べると、myproject/のブランチに関係なく、差分を表示できるので便利です。
ターミナルでは、diffコマンドに -Rオプションをつけます。しかし、ターミナルで見ても、わかりづらいです。
GUIのdiffツール、DiffMergeがおすすめです。Windows、macOS、Ubuntu版があります。
また、Ununtuには、meldというGUI diffツールが標準インストールされているので、meldを使ってもいいでしょう。
myproject/cake/ をphpcsで自動整形していないといいね
自動整形した分も「差分」になってしまって、何を修正したのかわからないのよ
ベンチマーク
どれくらい速くなるのか、各環境でトップページの表示時間を測定します。測定は、ab(apache bench)コマンドを使います。
次のような表を用意しておき、各環境の準備ができたら、測定して記録していきます。
環境 | PHP | 同時=1 mean (ミリ秒) | 同時=1 median (ミリ秒) | 同時=10 mean (ミリ秒) | 同時=10 median (ミリ秒) |
---|---|---|---|---|---|
本番 www.example.jp | 5.6 | 169 | 97 | ||
本番 docker | 5.6 | ||||
ステージング docker | 7.3 | ||||
ステージング stg.example.jp | 7.3 | ||||
本番 www.example.jp | 7.3 |
各環境について、同時接続数=1、同時接続数=10の2パターンを測定します。
# 同時接続数=1
$ ab -n 100 -c 1 "https://www.example.jp/"
# 同時接続数=10
$ ab -n 100 -c 10 "https://www.example.jp/"
Code language: Bash (bash)
abコマンドの結果の最後のほうに、meanやmedianがあります。
Connection Times (ms) min mean[+/-sd] median max Connect: 28 60 128.2 37 1111 Processing: 35 109 108.6 54 556 Waiting: 35 106 101.2 54 396 Total: 68 169 167.4 97 1200
Totalのmeanとmedianの値を、さきほどの表に記録します。
項目 | 意味 | 計算方法 |
---|---|---|
mean 平均値 | 最大値も含めた 全体としてのパフォーマンス | 全ての接続のレスポンス時間を 合計して、回数で割った値。 最大値の影響を受けやすい。 |
median 中央値 | 普通派のパフォーマンス | 全ての接続のレスポンス時間を 短い順にソートして、その真ん中の値。 個数が99個なら、50番目の値。 |
mode 最頻値 (abにはない) | 多数派のパフォーマンス | 全ての接続のレスポンス時間を 10〜、20〜と範囲に分けて、カウントして 最も多かった範囲 |
共用サーバは、曜日や時間帯でアクセス数が違うから、ベンチマークにも影響しそうだよ?
気になるなら、ベンチマークをとる曜日や時間帯をそろえるといいわね
ローカルPCのdocker環境だって、ウィルススキャンやバックアップ処理が、ベンチマークに影響しそうだよ?
気になるなら、ウィルススキャンやバックアップが走っていないときにベンチマークをとればいいのよ
PHP7.3対応版cakephp-1.3.21へアップグレード
こまめに、gitコミット、動作確認を繰り返します。
cake/とsimpletest/
別のディレクトリに、PHP7.3対応版cakephp-1.3.21とsimpletest-1.0.1をダウンロードします。
https://github.com/littleant/cakephp-1.3.21
https://github.com/littleant/simpletest-1.0.1
既存のmyproject/のcake/、app/vendors/simpletest/ を削除します。
同じ位置へ、PHP7.3対応版のcake/、simpletest/ をコピーします。
PHP7.3対応版では、いくつかのファイル名がリネームされました。myproject/のcake/を削除せずに上書きコピーすると、リネーム前の(公式版の)古いファイルも残ってしまいます。myproject/のcake/を削除してからコピーするほうが確実です。
ここで、gitコミットします。
この時点で、PHP5.6では動きませんので、PHP7.3のdockerを動かします。一時的に <?php phpinfo();
を書いた phpinfo.phpを置いて、たしかに PHP7.3.x になっていることを確認します。
app/config/core.phpで、debugを1にします。ErrorやWarnig、Noticeが表示されるようになります。もし、header sent alreadyが表示されてエラー停止するようなら、debugを0も戻します。
Configure::write('debug', 1);
Code language: PHP (php)
ここからはふつうのデバグだね。xdebugでトレースできると原因を探りやすいよね
cake/の差分
「公式のcakephp-1.3.xとの差分を調べる」で調べたcake/の差分を適用します。
つまり、IDEやテキストエディタで該当ファイルを開いて、メソッド名を検索して、確認しながら、差分を追加・削除します。
gitコミットします。
クラス名の変更
PHP7.3対応版では、次のクラス名がリネームされました。
app/ の *.phpや*.ctpを検索して、置換します。
CakePHP1.3公式 | PHP7.3対応版 |
---|---|
Object | CakeObject |
String | CakeString |
class Foo extends Object だけでなく、
if (get_parent_class($called) == 'Object') {
Code language: PHP (php)
のように参照していることもあります。
メソッド名の変更
PHP7.3対応版では、次のメソッド名がリネームされました。
app/ の *.phpや*.ctp を検索して、置換します。
CakePHP1.3公式 | PHP7.3対応版 |
---|---|
JsHelper->value() | JsHelper->jsonValue() |
JavascriptHelper->value() | JavascriptHelper->jsonValue() |
JsBaseEngineHelper->value() | JsBaseEngineHelper->jsonValue() |
ファイル名の変更
PHP7.3対応版では、cake/libs/error.phpが cake/libs/error_handler.phpにリネームされました。
404エラーや500エラーの表示をカスタマイズしている場合、app/app_error.phpが参照しています。
app/ の *.php や *.ctp を検索して、置換します。
CakePHP1.3公式 | PHP7.3対応版 |
---|---|
cake/libs/error.php | cake/libs/error_handler.php |
App::import('Core', 'Error') | App::import('Core', 'ErrorHandler') |
データベースドライバの変更
PHP7.3対応版では、「mysql」と「mssql」が削除されました。
app/config/database.phpで、mysqlを使っている場合は、mysqli にリネームします。
app/models/datasource/db/*.php で、DboMysql をextendsしている場合は、DboMysqli にリネームします。
CakePHP1.3公式 | PHP7.3対応版 |
---|---|
mssql | 削除 |
mysql | mysqli |
DboMysql | DboMysqli |
App::import('Datasource', 'DboMysql'); | App::import('Datasource', 'DboMysqli'); |
Security.cipherSeed
PHP標準やCakePHP標準のセッションクッキーには、影響しません。
app/config/core.phpのConfigureのSecurity.cipherSeedが、int型になりました。
以前のように、string型29文字のままでは、Cookieが正しくデコードされません。
17桁以内にするか、サンプルコードのように9桁にするといいでしょう。
// 公式 string 29文字
Configure::write('Security.cipherSeed', '76859309657453542496749683645');
// php 7.3対応版 64bitでは、最大 9*10^18
Configure::write('Security.cipherSeed', 768593096);
Code language: PHP (php)
暗号化クッキー
CakePHP1.3の暗号化クッキーに関して重要なことは、PHP5.6からPHP7.3へのアップグレード前後で、Security.cipherSeedに同じ値を設定できたとしても、
CakePHP1.3の暗号化クッキーに関して重要なこと
PHP5.6のサイトの暗号化クッキーを
PHP7.3のサイトは復号できません。
(暗号化をfalseにして、$this->Cookie->writeしたクッキーには影響しません)
CakePHP1.3はクッキーの暗号化/復号化を処理するとき、rand()のシーケンスを利用します。暗号化で使うrand()のシーケンスと、復号時のrand()のシーケンスは同じである必要があります。
ところが、PHP5.xとPHP7.1で、srandの仕様が変わりました。
後方互換性オプション MT_RAND_PHPがあるので、PHP5.6とPHP7.3のrand()のシーケンスは同じであると、期待できそうですが...
ねんのため、PHP5.6とPHP7.3のrand()のシーケンスを確認しました。srandに同じint値を渡し、後方互換性オプション MT_RAND_PHPを渡しました。
残念ながら、PHP5.6とPHP7.3のrand()のシーケンスは違うものでした。
つまり、PHP5.6のサイトで暗号化したクッキーを、PHP7.3のサイトは復号できません。
// PHP5.6
srand(0);
rand(0, 255); // 215
rand(0, 255); // 100
rand(0, 255); // 200
// PHP7.3
srand(0);
rand(0, 255); // 172
rand(0, 255); // 47
rand(0, 255); // 117
// PHP7.3
srand(0, MT_RAND_PHP);
rand(0, 255); // 114
rand(0, 255); // 151
rand(0, 255); // 183
Code language: PHP (php)
クッキーはブラウザ側で消去できてしまうよね
だから、クッキーに重要なデータを保存していないはずだから、引き継げなくても問題ないと思うけど
もし引き継ぎたいとき、方法はあるの?
暗号化クッキーは、ブラウザ上のjavascriptではクッキーの値を読めないから、ブラウザ側では使っていないはず。つまり、サーバー側でしか利用していない、セッションデータ的なものの可能性が高いわね。だとしたら、PHP7.3に移行する前に、セッションを使うように改修すれば、セッションデータは引き継げると思うわ
セッションに改修せずに、暗号化クッキーを使いたい場合は?
クッキーの暗号処理だけ、PHP5.6で処理できればいいんだけど
えー!そんなことできるの?というか、せっかくPHP7.3に移行するのに残念な感じだね
簡単ではないわよ。
PHP5.6を別サイトとして用意して、クッキーの暗号処理のAPIを用意したり、
共用サーバは、PHPバージョン別にphp-cliを呼べることが多いから、クッキーの暗号処理をコマンラインプログラムとして切り出すとかかしら。
MySQL
sql-modeをONLY_FULL_GROUP_BY
無効にする必要があります。CakePHPは、このモードと互換性のないクエリを作成します。
以下、5.1.7 サーバー SQL モード から引用
ONLY_FULL_GROUP_BY
GROUP BY
句で名前が指定されていない非集約カラムを、選択リスト、HAVING
条件、または (MySQL 5.6.5 以降で) ORDER
リストが参照するクエリーを拒否します。
ONLY_FULL_GROUP_BY
が有効な場合、次のクエリーは無効です。1 番目は、選択リスト内の非集約の address
カラムが GROUP BY
句で名前を指定されておらず、2 番目は、HAVING
句の max_age
が GROUP BY
句で名前を指定されていないため、ともに無効になります。
mysql> SELECT name, address, MAX(age) FROM t GROUP BY name;
ERROR 1055 (42000): 't.address' isn't in GROUP BY
Code language: plaintext (plaintext)
mysql> SELECT name, MAX(age) AS max_age FROM t GROUP BY name
-> HAVING max_age < 30;
Empty set (0.00 sec)
ERROR 1463 (42000): Non-grouping field 'max_age' is used in HAVING clause
Code language: plaintext (plaintext)
2 番目の例では、HAVING MAX(age)
を使用するようにクエリーを書き換えることで、集約関数で名前を指定されているカラムが参照されるようになります。(max_age
は集約関数そのものであるため失敗します。)
クエリーに集約関数があって GROUP BY
句がない場合、ONLY_FULL_GROUP_BY
が有効なときに、クエリーは選択リストまたは ORDER BY
リストに非集約カラムを含めることができません。
mysql> SELECT name, MAX(age) FROM t;
ERROR 1140 (42000): Mixing of GROUP columns (MIN(),MAX(),COUNT(),...)
with no GROUP columns is illegal if there is no GROUP BY clause
Code language: plaintext (plaintext)
新機能: Session.cookie_samesite と CookieComponent
PHP7.3+が必要です。
SameSite-cookie-parameterを設定できるようになりました。
app/config/core.phpやapp/config/bootstrap.phpなどで、
Configure::write('Session.cookie_samesite', 'Lax');
Code language: PHP (php)
コントローラで、
$this->Cookie->samesite = "None";
Code language: PHP (php)
phpcc7(PHP 7 Compatibility Checker)
コードのPHP7との互換性をチェックします。
splitなどの非推奨となった関数や、PHP4形式のコンストラクタを検出します。
インストール方法
$ composer global require sstalle/php7cc
Code language: Bash (bash)
チェック方法
app/ 下をチェックするには、
$ ~/.config/composer/vendor/bin/php7cc --except=vendor --except=foo app/
Code language: Bash (bash)
app/vendor/ を除外して、app/ 下をチェックするには、
$ ~/.config/composer/vendor/bin/php7cc --except=vendor app/
Code language: Bash (bash)
その他
各ページにアクセスして、エラー表示がないか確認します。おそらく多数のWarningやErrorがでるので、コツコツと修正します。
Headers already sent
PHP error ini_set(): Headers already sent. You cannot change the session module's ini settings at this time PHP error session_set_save_handler(): Cannot change save handler when headers already sent
ターミナルからsimpletestを実行すると、headers already sent警告が表示されます。次の箇所を@でエラー抑制します。
// cake/libs/cake_session.php:220付近
// 修正前
$this->__initSession();
// 修正後 @でエラー抑制
@$this->__initSession();
Code language: PHP (php)
Declaration x should be compatible with y
Warning Declaration of Foo::yyy() should be compatible with Bar::yyy($options = Array)
子クラスのメソッドの引数定義が、親クラスのメソッドと違うという警告です。コピペし忘れの場合が多いので、親クラスからコピペします。
count(): Parameter must be an array
Warning count(): Parameter must be an array or an object that implements Countable
$this->MyModel->find()の返り値は、false|arrayです。falseのときに、count()に渡すと、この警告がでます。is_array()でチェックしてから、count()に渡すようにします。
// 修正前
$data_arr = $this->MyModel->find('all', $params);
if (count($data_arr) > 0) {
// 修正後
$data_arr = $this->MyModel->find('all', $params);
if (is_array($data_arr) && count($data_arr) > 0) {
Code language: PHP (php)
ソースを見ていると、リファクタ魂に火がつきそうになるよ
コメントにTODOを残しておくだけにして、PHP7.3で動かすことに集中してね