CakePHP – 1.3のPHP7移行と延命 その参

完結編。前回の続きだ。

今回はCakePHP1.3 + PHP7ならではのバグを修正していき、PHP7移行を完了させたい。

初歩的なバグを潰す

今回はCakePHP1.3で通常行う設定などを一切やってないわけで、書き込みエラーとか出てきている。
そこら辺をまず設定しよう。

tmp以下を書き込み可にする

これで2つほどエラーが消える。

セキュリティ設定

core.phpを開いて、Security.saltSecurity.cipherSeedを設定する必要がある。

自動生成するコマンドを使いたいので、そのためのパッケージをインストールしておく。

これでmkpasswdコマンドが使えるようになる。
Security.saltは40文字なので、以下のコマンドで自動生成する。

mkpasswd -l 40 -s 0

英数字だけの40文字が生成される。

Security.cipherSeedは29文字なので、以下のコマンドで自動生成する。

mkpasswd -l 29 -s 0 -c 0 -C 0 -d 29

数字だけの29文字が生成される。

オプション 内容
-l 全体の長さ
-s 記号の数
-c 小文字の数
-C 大文字の数
-d 数字の数

上記で生成されたハッシュ値を、app/config/core.phpを編集して貼り付ける。

これでエラーが2個ほど消える。

データベースを作る

取り急ぎ適当なデータベースを用意することにする。

これでMySQLのrootユーザとしてログインする。

テーブルを使いたい場合は以下のSQLでusersテーブルを作ると良い。
動かすだけなら別に作らなくてOK。

上記を画面に貼り付ける。

これでMySQLの操作は終わり。あとはCakePHP側だ。

以下のように書き換える。

注意点は、mysqlではなくmysqliだ。

これでエラーが1個消える。

セッション方式を変更する

初期設定ではセッションはphpのデフォルトを使うようになっているが、これをphpからcakeに変更する。

# vi app/config/core.php

これでエラーが1個消える。

cake_core_default_ja 対策

画面にはこう表示されている。

該当ファイルのパーミッションを707などにすればすぐに解決する。

しかしこのファイルはなんだろうか。調べてみると、多言語化で使うファイルのようだ。

削除するか、パーミッションを変えてしまえば問題ないだろう。
俺はpersistant内にあるキャッシュはすべて削除した。どうせまた同じものが作られるんで、それでパーミッションが正しくなるし。

これでエラーは残す所あと1個になった。

Declaration of SessionHelper::write() should be compatible 対策

これは継承したクラスで同じメソッド名なら引数も同じでなくてはならない、という現象だ。案外やっかいだ。

いま出ているのはWarning (2): Declaration of SessionHelper::write() should be compatible with CakeSession::write($name, $value) [CORE/cake/libs/view/helpers/session.php, line 201]だ。

これはコアのヘルパーであるSessionHelperクラスのwriteメソッドが該当する。

このように引数をちゃんと指定すればOK。

しかし、この問題はヘルパーだけではなく、モデルなどで大量にエラーが出ることになる。

今は上記の解決だけで十分だが、すでにCakePHPで組まれているWebサービスでは、こんな1箇所だけ直せば全部うごく!なんて都合の良い事は起きない

いろいろなブログを見る限り、上記の様に一部だけ書き直して”動いた!”とか一喜一憂してるものが多いが、それはたまたまだろう、という解釈。

なので、上記の様に引数を与えて解決!とはならない。なのでここでは変更せずにしておく

ではどうすればいいのだろうか。もっと根本的な部分で解決できないものだろうか。

エラーを調べる

さて、このDeclaration ~というエラーななんだろうか。調べまくった。

原因は、PHP7で廃止になったE_STRICTだ。

PHP7では、今までE_STRICTだったエラーはE_WARNING、E_DEPRECATED、E_NOTICEに振り分けられた。

実は今まで(PHP5.6まで)殆どのPHP使いがこのE_STRICTを無効化する設定でPHPを使っていた。エラー自体を先延ばしにしてしまっていたんだ。

その為、メソッドのオーバーロードのエラーが起きてもエラーがパスされ、画面には全くエラーが表示されず、正しく動いているかのような挙動をしていた。

PHP7ではこの問題が正しく露呈するようになったので、結果的にオーバーロードできないエラーがちゃんとエラーとして認識されるようになったわけだ。

厳密に言えばPHPはメソッドのオーバーロードはできないわけだから、無かったかのように隠しちゃダメだろ!!ということだ。

なので、クラスAのmethod($a, $b)を継承したクラスAAのmethod()メソッドの引数は、クラスAのmethod()と同じく、引数に$aと$bを取らないといけない。

もちろん順序が違ってしまっている場合、呼び出す方と呼ばれる関数の両方を変更しないといけない。

CakePHPなんていうフルスタックのフレームワークで、メソッドを全部調べて修正するのは途方もない。

エラーをぶっちぎる

さて、いろいろ考えた結果、以下の2つの解決法があると思った。

  • PHP5.6までの方法をPHP7向けにできないか
  • 問題が起きているメソッドをすべて書き換える

後者のほうが本来良さそうだけど、CakePHP1.3のWebサービスをPHP7のサーバに引っ越すなんて時点で精神的に辛いのに、それに輪をかけるようにメソッドにメスをいれるというのは、精神衛生上さらによろしくないわけで、そもそもそんなことした時点で動作保証も担保できないし、最悪の結果を招く可能性が極めて巨大化する。

つまり、現実的ではない。

というわけで、前者で考えてみる。

前者だとPHP5.6時代にE_STRICTだったエラーをすべてパスさせるという、いわば未来への責任転嫁なわけだ。

で、将来PHP8やPHP9が出たときに、またしてもこのCakePHP1.3のプロジェクトを最新のサーバへ移行するなんて事はおそらく起きないと思う。

おそらくクライアント側も、流石にCakePHP1.3時代につくったWebサービスを10年後まで元気に稼働させられるとか思ってないはずだし、そもそも他のフレームワークに切り替えたほうがサービス自体(または本人達も人として)健康体でいられるのは理解できると思う。それに可能性としては、そんな未来にはこのWebサービス自体が消滅してるということも否定できないわけで。

あんまり想像しまくってると枚挙に暇がないのでこの辺で。

そう考えると、CakePHP1.3 + PHP7 という、上半身スーツだけど下半身ジャージみたいなモード学園的スタイルを、一つのコーディネートとして受け入れてしまっても良さそうだ。

この件に関してだけ、ログにエラー出ても、画面にエラー出なけりゃいいじゃん!!! で行く。

止む無し地蔵!

※地蔵に意味はない

というわけで、早速やってみよう。

set_error_handler を使ってみた

E_STRICTがなくなったので、E_WARNING、E_DEPRECATED、E_NOTICEなども今まで通りパスさせてしまえば良いが、そうするとエラーが起きたときに全く気づかなくなるし、そもそもそういう思想自体がよくない。なのでphp.iniはいじらない。

ではどうすればいいのかと考える。

エラーの種類ではなく、エラーメッセージで判断できないだろうか、となる。

つまり今回のオーバーロードで起きているエラー、Declarationから始まるエラーメッセージなので、そのときだけ、E_WARNINGとして扱ってしまえば良いのではないかと。

これなら主にCakePHP1.3で起きているオーバーロードの問題を、メソッドを大量に修正せずに回避できる。つまり未来に託すんだ(まぁ将来このWebサービスを受け取るやつはいないと思うけど)。

エラーをハンドリングする関数はずばりset_error_handlerだ。コールバック関数を指定し、その返却値がTRUEかFALSEによって、エラー内容を変更してくれる。

set_error_handler(コールバック関数, エラー)

つまり、エラーメッセージの先頭がDeclarationで始まっているかどうかをコールバック関数で処理し、もしTRUEなら別のエラー、例えばE_WARNINGにしてしまう、という流れになる。

さて、コールバック関数には引数を取れないものか。調べてみたら、以下のような引数を指定できる。

  • $errno
    • 最初のパラメータ errno は、発生させる エラーのレベルを整数で格納します。
  • $errstr
    • 2 番目のパラメータ errstr は、 エラーメッセージを文字列で格納します。
  • $errfile
    • 3 番目のパラメータ errfile はオプションで、 エラーが発生したファイルの名前を文字列で格納します。
  • $errline
    • 4 番目のパラメータ errline はオプションで、 エラーが発生した行番号を整数で格納します。
  • $errcontext
    • 5 番目のパラメータ errcontext はオプションで、 エラーが発生した場所のアクティブシンボルテーブルを指す配列です。 つまり、エラーが発生したスコープ内でのすべての変数の内容を格納した 配列が errcontext だということです。 ユーザエラーハンドラは、決してエラーコンテキストを書き換えては いけません。

なるほど、$errstrだけで良さそうだ。だけど2番目だけという指定はPHPだとできないので、先頭から2個を指定することにする。つまり、$errnoと$errstrの2個だ。

別の言語みたいに、errstr:$errstrみたいにラベルで指定できると助かる。

$errstrにエラーメッセージの文字列が入るので、Declarationから始まっているかどうかを調べれば良いわけだ。

preg_match()とかすぐに使いたがる人が多いけど、正規表現は処理が果てしなく遅いので、strpos()とか使う。

これなら’Declaration’から始まるエラーのときはコールバック関数がTRUEを返すようになる。
そしてただのE_WARNINGとして処理してくれることになる。

これを、比較的早い段階で処理されるbootstrap.phpに書いておけばいいのではないか。

app/config/bootstrap.php

さて、画面を見てみよう。

俺達の熱く長い戦いが今!
幕を閉じた!

※他のエラーは作ったやつに文句言え

念のため

すでに完成したWebサービスなら使わないと思うが、もしbakeを使うなんてことがある場合、上記set_error_handler関数の処理を、cake/console/cake.phpの一番頭に書いておくと、bakeが正しく動くようになる。

※bakeするならstring.phpcake_string.phpにするのも忘れずに。

唐突に話は変わるが

俺のガレ。

うつくしい。