CakePHP 1.2アプリケーションを1.3へマイグレーションした話
CakePHP 1.2でできたアプリケーションをを1.3へマイグレーションした話を書きます。
えー、3じゃないのーという話なのですが、大人には事情(お金)がある(ない)のです。
なにかお役に立てる話が書けたらいいなと思ってますが、たぶんほとんど自分のアプリに特化した内容になりそうです。 コーヒー片手に薄目で見てやってください。
参考情報:
1.2から1.3への移行ガイド
http://book.cakephp.org/1.3/ja/The-Manual/Appendices/Migrating-from-CakePHP-1-2-to-1-3.html
さくっとcakeフォルダを入れ替え
まずは既存アプリからcakeフォルダを消して、CakePHP 1.3のcakeフォルダをコピーしました。
index.phpファイルをコピー
CakePHP 1.3のapp/webroot/index.phpファイルを既存アプリへコピーしました。
自前で変更している箇所があるので、それも反映しました。
2か所です。
index.php
if (!defined('ROOT')) {
define('ROOT', dirname(dirname(__FILE__)));
}
if (!defined('APP_DIR')) {
define('APP_DIR', 'app');
}
app/config/core.phpファイルを編集
log
という設定とSession.model
という設定が足りてないので付け足しました。
app/config/core.php
Configure::write('log', true);
Configure::write('Session.model', 'Session');
log4phpを使うには……。
CakePHP付属のCakeLogクラスを自前のものに置き換えて、log4phpを呼ぶようにしていました。
自前のCakeLogクラスは、app/cake_log.php
ファイルに置き、app/config/core.php
ファイルの中からrequire_once
で読み込んでいました。
app/cake_log.php
の中でApp::importでlog4phpを読み込んでいましたが、ここがなぜか動きませんでした。
CakePHP 1.3ではログエンジンを入れ替えられるらしいので、とりあえずコメントアウトして後回しにしました。
ぼちぼちエラーメッセージが出ますが、とりあえずはこれで画面が見えるようになりました。
Smarty Viewをアップグレード
CakePHP 1.3対応版Smarty Viewを https://github.com/tsak/glitch-auto-sell/blob/master/app/views/smarty.php からダウンロードして置き換えます。
Smarty 2.6の場合(?)、assignByRef
が無いのでassign_by_ref
を使うようにします。書き換え箇所は2つあります。
app/views/smarty.php
$this->Smarty->assign_by_ref($camelBackedHelper, ${$camelBackedHelper});
//$this->Smarty->assignByRef($camelBackedHelper, ${$camelBackedHelper});
/* (snip) */
$this->Smarty->assign_by_ref('view', $this);
//$this->Smarty->assignByRef('view', $this);
viewの下にsmartyディレクトリを作っていましたので、$this->subDir = 'smarty'.DS;
としました。
app/views/smarty.php
$this->subDir = 'smarty'.DS;
この辺りはもともとコメントアウトしてあるコードをイキにするだけです。親切ぅ~
Sessionヘルパーを読み込み
Session
コンポーネントとSession
ヘルパーは自動的に読み込まれなくなりました。
Sessionヘルパーは自動で読み込まれなくなったそうで、AppController
の$helpers
に追加しろとのことです。
app/app_controller.php
var $helpers = array('Html', 'Form', 'Javascript', 'Session');
Htmlヘルパー使用箇所の修正
HTMLエスケープするかどうかを指定する専用の引数があったのですが、なくなってしまいました。
HtmlHelper::link()
の$escapeTitle
引数は削除されました。代わりに$options['escape']
を使用してください。
EmailComponentを継承したクラスを変更
EmailComponent
を継承してmb_send_mailを使うクラスを作っていました。
app/controllers/components/mb_email.php
class MbEmailComponent extends EmailComponent {
var $delivery = 'mb_mail';
function __wrap($message) { /* ... */ }
function __mb_mail() { /* ... */ }
function __wordwrap($str, $width = 75, $break = "\n", $cut = false) { /* ... */ }
}
EmailComponent
内にあるメソッドの頭のアンダースコア2つが1つになったので、それぞれメソッド名を変更しました。
__wrap
→_wrap
、__mb_mail
→_mb_mail
たぶんprivate扱いだから、ドキュメントにも書いてない話です。そんなもんオーバーライドしてるのが悪いんです。はい。
Modelの__exists
プロパティがなくなった
これも多分またprivate扱いのやつです。
次のように書き換えました。
$this->exists()
コミット漏れを修正
CakePHP 1.2のころはトランザクションを開始してコミットしなくても、データベースが更新されていました。
こんな感じにトランザクション開始するコードだけがあり、コミットするコードがないという元バグなのですが、1.2では問題なかったのです。
$db =& ConnectionManager::getDataSource($this->useDbConfig);
$db->begin($this);
1.3ではちゃんとコミット$db->commit($this)
しないといけないです。
あるいはsaveメソッドのオプションにatomicを渡すほうがよさそうです。
$myModel->save($myData, array('atomic' => true));
Model::bindModel()
したアソシエーションをModel::saveAll()
で保存できない
CakePHPでモデル間のアソシエーションを設定するとき、モデルクラスに直接プロパティを設定すると、毎回DBから読み込んでくるので困ってしまいます。
そんなわけで、ふつうはModel::bindModel()
で動的にアソシエーションを設定しますよね?よね?ね?
1.3では、Model::saveAll()
の中でModel::resetAssociations()
を呼んでいるため、bindModelしたアソシエーションが消えてしまいます。
従って、アソシエーションしたモデルを保存できないという。。。
仕方がないのでbindModel
するときにリセットしないようにしました。第2引数にfalse
を渡します。
$myModel->bindModel($associatin, false);
Helper::valueの挙動が変わった
これまたアンドキュメンテッドな話です。
1.2までは次のようなコードが動いていました。
$formHelper->value('Model.field.time'); /* Model.fieldの値が返ります */
1.3では、こういう書き方ではModel.fieldの値が返りません。
幸いなことにSmartyプラグインの中で使っていたので、プラグイン側を書き換えて解決しました。
Log4phpを使う
いよいよLog4phpを使えるようにしなくちゃいけなくなりました。
CakeLogの中から使えるようにラッパクラスを作ります。
app/libs/log/Log4phpWrapper.php
<?php
/**
* Small log4php wrapper for CakePHP 1.3
*
* @package app
* @subpackage app.libs.log
* @version $Revision$
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
if (!defined('LOG_FATAL')) {
define('LOG_FATAL', 1);
}
App::import(array('type' => 'Vendor',
'name' => 'log4php',
'file' => 'log4php'.DS.'Logger.php',
));
class Log4phpWrapper {
var $methodMap =
array(
LOG_DEBUG => 'debug',
LOG_INFO => 'info',
LOG_WARNING => 'warn',
LOG_ERR => 'error',
LOG_ERROR => 'error',
LOG_FATAL => 'fatal',
);
/**
* Constructor
*
*/
public function __construct($options = array()) {
Logger::configure(CONFIGS.'log4php.php');
}
/**
* Write log messsage
*
* @param string $type
* @param string $message
*/
public function write($type, $message) {
if (is_int($type) && isset($this->methodMap[$type])) {
$type = $methodMap[$type];
}
if ($type === 'warning') {
$type = 'warn';
}
/* when unknown method provided, fallback to 'info' */
if (array_search($type, $this->methodMap) === false) {
$type = 'info';
}
if (strpos($message, '|') !== false) {
list($name, $message) = explode('|', $message, 2);
}
else {
$backtrace = debug_backtrace(false);
$caller = count($backtrace) > 3
? $backtrace[3] : $backtrace[0];
$name = isset($caller['class'])
? $caller['class'] : $caller['function'];
}
$logger = Logger::getLogger($name);
$logger->{$type}($message);
}
}
作成したクラスを使うようにbootstrap.phpのなかで指示します(app/config/core.phpの中でこれをやろうとして、クラスが読み込まれなくて悩みました)。
app/config/bootstrap.php
CakeLog::config('log4php', array('engine' => 'Log4phpWrapper'));
log4php自体の設定はPHP方式にしています。
app/tmp/logsの下にログが落ちるようにしています。
app/config/log4php.php
return array(
'rootLogger' => array(
'appenders' => array('default'),
),
'appenders' => array(
'default' => array(
'class' => 'LoggerAppenderDailyFile',
'layout' => array(
'class' => 'LoggerLayoutSimple'
),
'params' => array(
'file' => LOGS.'%s.log'
)
)
)
);
known issue
LoggerLayoutPattern
で、呼び出し元を記録するとLog4phpWrapperが記録される。
ほんとは$this->log
を呼び出したところが記録されて欲しい。
これを実現するにはlog4phpのLoggerLoggingEvent
クラスを変更すればよいのだけれど、dirty hackでやだなぁと悩み中。
おしまい
CakePHP 1.2アプリを1.3へマイグレートしたときに詰まった話を書きました。
また何かあれば追記します。