从CakePHP 1.3升级到2.5
摘要:最近把一个CakePHP 1.3的项目升级到了2.x,当然就用最新的版本2.5.3了,结果基本满意。本文记录了升级的过程,包括使用的工具,遇到的问题和相应的解决方法。
这篇文章涉及的内容要求至少有CakePHP中级水平,如果你是刚开始使用CakePHP,建议先不要作这样的尝试,否则遇到问题都不知道怎么解决。
目录
1. 为什么要升级
升级到最新版本有很多好处,可以得到最新的更新和功能,可以使用DebugKit这样的调试工具,可以使用View Block方便地把CSS和JavaScript放在页面中你希望的地方,而不再痛苦地受限于旧的方式,等等。View Block一直是我最希望使用的特性。
2. 项目概况
这个项目的开发大致起始于2012年底,我从2013年中开始参与,到2014年中,所有用户要求的功能基本都完成了。我参与了大概1年左右,只是利用业余时间进行。代码一开始是抄自于一个CakePHP 1.3的项目,而没有使用当时最新的2.x版本,这也使得许多开发工作不能利用框架最新的特性和工具,延长了开发周期。可以说,这个项目从开始就做了错误的选择。先后参与的开发人员前后有6个人之多,多数是临时抓来的,大部分人做一段时间之后就会离开,有些功能甚至没有完成就走人了。而且大家对CakePHP这个框架没有深入的了解,导致这个项目的代码没有一致性,也缺乏统一的计划、架构和规范,没有单元测试。总之,项目的代码质量是不能令人满意的。这些恐怕是大部分业余项目难以避免的问题。
幸好,这个项目不算特别复杂,计有:
- 数据库有36张表
- 19个控制器(Controller)
- 33个模型(Model)
- 63个视图(View)
- 2个插件(Plugin)
- 当然还有其他一些东西
通过这些数据可以看出这个项目的规模,应该说是个中小型网站吧。
3. 使用的工具
升级只使用了2个工具,首先是官方的Upgrade Shell,以及DerEuroMark的Upgrade Plugin。
4. 升级的过程
真正动手升级之前我花了一些时间阅读参考资料中列出的文档,实际的升级过程前后总共花了4天时间,不过这里面相当一部分时间用于升级后的测试、以及寻找解决问题的方法。我毕竟从1年前才开始真正使用PHP和CakePHP来开发实际使用的网站,而且只是利用业余时间,所以时间有限,很多东西也都是边做边摸索学习,主要目标是要完成用户需求所要求的功能,来不及了解CakePHP框架的各个方面。如果你有相应的经验,或者有解决下面所涉及问题的这些知识,这个过程会快许多。
4.1 备份
首先,确保升级之前,应用程序运行良好,做好代码和数据的备份。如果没有备份,请就此打住!!!
我使用Git(TortoiseGit)做源码控制,这让我可以随时回到过去某一个时间点。数据库使用的是MySQL,升级之前也要做好备份。如果升级过程出了问题,进行不下去,备份让你可以恢复到开始升级之前的状态;如果没有备份,那你可就进退维谷了。
4.2 更新CakePHP的核心库到2.x
这个项目在升级前的目录结构,如下图所示,
图中,cake为CakePHP 1.3的核心库,app是应用程序代码。
下载最新的CakePHP,我用的是2.5.3,打开压缩包,如下图所示。
图中的lib目录就是CakePHP的核心库,把这个lib目录拷贝到根目录下,然后删除1.3的cake目录,得到如下图所示的目录结构。
图中的lib\Cake目录就是CakePHP 2.x的核心库。
4.3 准备升级工具Upgrade Shell
Upgrade Shell是CakePHP核心的一部分,位于lib\Cake\Console\目录下,只需确保能够运行CakePHP Console可执行文件就可以了,这可以参考CakePHP Console的文档。如下图所示,
图中的cake是*nix下的可执行文件,cake.bat是Windows下的可执行文件。我采用的做法,是参考了Upgrade Shell的文档,把lib\Cake\Console\目录拷贝到app目录下,这样只是执行的命令行路径有所不同,结果是一样的,如下图所示。
图中的app\Console\就是我采用的Console路径。
4.4 准备升级工具Upgrade Plugin
下载DerEuroMark的Upgrade Plugin,解压之后拷贝到app\Plugin\目录下,如下图所示。
4.5 运行Upgrade Shell
按照Upgrade Shell的文档,可以运行.\Console\cake Upgrade.Upgrade all,这会执行所有的升级任务;当然也可以逐个执行单个任务。因为这是官方的工具,我就所有的任务一起运行了。
我的操作系统是Windows 7,在命令行下进入app\目录,然后运行.\Console\cake upgrade all,结果如下:
D:\...\app>.\Console\cake upgrade all Welcome to CakePHP v2.5.3 Console
---------------------------------------------------------------
App : app
Path: D:\...\app\
---------------------------------------------------------------
Running tests
Running locations
Upgrading locations for plugin contact_us
Upgrading locations for plugin file_upload
Moving View\handler to View\Handler
Moving View\helpers to View\Helpers
Moving View\layouts to View\Layouts
Upgrading locations for plugin popup
Moving View\elements to View\Elements
Moving View\helpers to View\Helpers
Upgrading locations for app directory
Moving config to Config
Moving Config\schema to Config\Schema
Moving View\contacts to View\Contacts
Moving View\elements to View\Elements
Moving View\errors to View\Errors
Moving View\group to View\Group
Moving View\help to View\Help
... ...
... ...
Running components
Done updating D:\...\app\Controller\Component\CropComponent.php
Running exceptions
Done updating D:\...\app\Controller\AppController.php
Done updating D:\...\app\Controller\CommentController.php
Done updating D:\...\app\Controller\ContactsController.php
Done updating D:\...\app\Controller\DataConversionController.php
Done updating D:\...\app\Controller\GroupPostController.php
Done updating D:\...\app\Controller\GroupTalkController.php
Done updating D:\...\app\Controller\HelpController.php
... ...
注:这里看到的\...\是我省略了真实路径。
注:这个结果有1815行,故只是截取了首尾若干行。
小窍门:一些升级任务,比如locations,会改变目录和文件名称,比如把app\config (注意是小写)改名为app\Config (注意是大写),详细情况可参考CakePHP 2.0 Migration Guide中File and Folder naming一节。在Windows下,由于目录名和文件名不区分大小写,所以目录名仅仅大小写的改变,在Windows看来并不认为目录名发生了变化,所以不会引起git对目录进行重命名。这个问题可以通过2次git mv命令(使用TortoiseGit就是rename...)来强制git重命名。运行升级任务locations之后,在Windows Explorer中看已经是app\Config (注意是大写)了,但在git库(即TortoiseGit->repo-browser)中,看到目录还是app\config (注意是小写)。这时,先在Windows Explorer中把app\Config改名回app\config。然后进行2次git重命名(TortoiseGit->Rename...),先把config改名为config2(随便增减一些字符都行),然后再次用git重命名把config2改为Config,最后再git commit,这样git库中就也是app\Config了。
4.6 运行Upgrade Plugin
虽然Upgrade Plugin来自CakePHP的核心开发者DerEuroMark,但仍然不是CakePHP官方发布的工具,故相比Upgrade Shell存在较高的风险。所以我采用的是逐个运行任务的方式,谨慎一些比较好,毕竟这是我第一次升级CakePHP。每执行完一个任务,我就git commit,保存每次任务的变化。这样就可以通过git的历史知道每次任务改变了哪些文件,作了什么变 化。以后如果发生问题,也可以缩小范围,知道是哪个任务做的改变引起了问题,便于积累经验。
在逐个运行升级任务之前,注意Upgrade Plugin文档中推荐的顺序,故此我先执行cake Upgrade.Upgrade locations。
在命令行下进入app\目录,然后运行.\Console\cake Upgrade.Upgrade locations,结果如下:
D:\...\app>.\Console\cake Upgrade.Upgrade locations Welcome to CakePHP v2.5.3 Console
---------------------------------------------------------------
App : app
Path: D:\...\app\
---------------------------------------------------------------
Moving locale to Locale
Moving config to Config
Moving Config\schema to Config\Schema
Moving View\contacts to View\Contacts
Moving View\elements to View\Elements
Moving View\errors to View\Errors
Moving View\group to View\Group
Moving View\help to View\Help
Moving View\helpers to View\Helpers
Moving View\index to View\Index
Moving View\job to View\Job
Moving View\layouts to View\Layouts
Moving View\messages to View\Messages
Moving View\news to View\News
Moving View\os to View\Os
Moving View\pages to View\Pages
Moving View\scaffolds to View\Scaffolds
Moving View\uc to View\Uc
Moving View\users to View\Users
Moving View\video to View\Video
Removing empty folder \controllers
我又继续运行了其它命令,所有命令如下面列表所示。注意,有些升级任务没有引起任何变化,故记录为no change。
[x] .\Console\cake Upgrade.Upgrade locations
[x] .\Console\cake Upgrade.Upgrade webroot
[x] .\Console\cake Upgrade.Upgrade routes
[x] .\Console\cake Upgrade.Upgrade database
[x] .\Console\cake Upgrade.Upgrade basics
[x] .\Console\cake Upgrade.Upgrade helpers: no change
[x] .\Console\cake Upgrade.Upgrade request
[x] .\Console\cake Upgrade.Upgrade configure: no change
[x] .\Console\cake Upgrade.Upgrade constants: no change
[x] .\Console\cake Upgrade.Upgrade controllers
[x] .\Console\cake Upgrade.Upgrade components: no change
[x] .\Console\cake Upgrade.Upgrade exceptions: no change
[x] .\Console\cake Upgrade.Upgrade views
[x] .\Console\cake Upgrade.Upgrade stylesheets
[x] .\Console\cake Upgrade.Upgrade legacy
[x] .\Console\cake Upgrade.Upgrade constructors: no change
[x] .\Console\cake Upgrade.Upgrade paginator: no change
[x] .\Console\cake Upgrade.Upgrade name_attribute
[x] .\Console\cake Upgrade.Upgrade methods
[x] .\Console\cake Upgrade.Upgrade cake13: no change
[x] .\Console\cake Upgrade.Upgrade cake20: no change
[x] .\Console\cake Upgrade.Upgrade cake21
[x] .\Console\cake Upgrade.Upgrade cake23
[x] .\Console\cake Upgrade.Upgrade cake24: no change
[x] .\Console\cake Upgrade.Upgrade cake25
[x] .\Console\cake Upgrade.Upgrade validation: no change
[x] .\Console\cake Upgrade.Upgrade estrict: no change
[x] .\Console\cake Upgrade.Correct stable
[x] .\Console\cake Upgrade.Convert arrays -v
应当注意的是,在运行每个命令之前,最好还是读一下源代码,知道每个升级任务做的是什么改变。比如最后一个命令cake Upgrade.Convert arrays -v,这个升级任务把数组由下面的长格式:
array('admin', 'api')
变成了短格式:
['admin', 'api']
这个任务的源代码位于app\Plugin\Upgrade\Console\Command\ConvertShell.php的arrays()方法中。这是我后悔运行了的一个任务,因为数组的短格式是在PHP 5.4.0中增加的,改变的文件又相当多。做了这个变化后,代码就会要求PHP的版本是5.4.0或者更高,而CakePHP2.x只要求PHP 5.2.8,这样就提高了对运行环境的要求,缩小了代码的适用范围,如果将来遇到迁移服务器的情况,在选择运行环境时,就会少了很多选择,也可能意味着不得不付出更高的服务器空间租用费用。所以,建议这个升级任务,除非有必要,不要执行。
到此,所有升级工具能做的事情都已经做了。似乎升级大业已经完成了,后来才知道,这才进行了不到一半,真是行百里者半九十。做完升级之后,一定要对网站做充分的测试,不要放过每个细小的功能,毕竟升级是很大的变化,难免会有预料不到的问题发生,由开发人员发现问题,总比让用户发现问题要好得多。文章剩下的篇幅,都将讨论升级之后在测试中发现的问题和解决方法。
5. 升级之后遇到的问题及解决方法
5.1 空白屏幕
该试着运行一下应用程序了。当我打开首页的时候,我惊呆了,白屏!坏了!由Firebug知道服务器返回的是错误500,这意味着服务器错误,但仍然不知道服务器上哪里出问题了,当时真有些不知所措,这已经是晚上11点多了,在QQ群里问了一圈,也不得要领。试了各种调试方法,PHP的错误日志,CakePHP的日志,都没有任何记录。这一晚没睡好!
第二天,经过文档的阅读以及很多思考,我想到,升级工具已经帮我做了很多事情,但仍然有些部分是升级工具没有做的,这恐怕要我自己手工做了,毕竟工具不是万能的,还有不少事非得自己动手不可。
我先着手的是下面这些配置文件:
app\Config\bootstrap.php
app\Config\core.php
我逐个配置比较1.3和2.x的两个版本,一些配置取消了,一些配置是新增的。改了个七七八八,但仍然是白屏,不过这项工作并不是没用的,这毕竟还是必不可少的。
我再按照更改目录名称的小窍门,改了不少git库中的目录名大小写,不过我也知道这对于白屏没有帮助,因为git库不会直接影响程序运行结果。不过这仍然是保证git库一致性必须的工作。
最终,我想到了应用程序的入口,index.php。整个目录结构中总共有3个index.php:
index.php
app\index.php
app\webroot\index.php
比较1.3和2.x的版本,其中,根目录下的index.php和app\webroot\index.php有实质性的变化,另外一个app\index.php只是注释的变化。
改完3个index.php后,首页总算是能够显示了,只是还是很不正常,但CakePHP的日志也有了,这样就可以通过日志准确定位错误发生的位置了。
5.2 MissingPluginException
下面要对付的错误在CakePHP的日志error.log中是这样记录的:
2014-07-23 17:44:01 Error: [MissingPluginException] Plugin Popup could not be found.
Exception Attributes: array (
'plugin' => 'Popup',
)
Request URL: /
Stack Trace:
后面还有更详细的Stack Trace。
这个Popup插件,我之前用的是1.5版本,经过这将近1年已经升级到2.0版本了。我进行了升级,这个问题就解决了。
我担心Popup插件2.0发布之后,CakePHP又进行了升级,而Popup插件却没有做相应的升级,所以又运行了下面的升级任务:
D:\...\app>.\Console\cake Upgrade.Upgrade locations -p Popup
没有文件发生变化,说明Popup插件2.0已经符合2.5.3的要求了。
5.3 Unsupported operand types in FormHelper.php
下一个错误:
Fatal Error (1): Unsupported operand types in [...\html\lib\Cake\View\Helper\FormHelper.php, line 2477]
这一行代码位于FormHelper::dateTime()方法。这个错误是由于从1.3到2.5,FormHelper的一些方法去掉了$select参数,这正包括FormHelper::dateTime(),其原型从:
FormHelper::dateTime($fieldName, $dateFormat = 'DMY', $timeFormat = '12', $selected = null, $attributes = array())
变成了:
FormHelper::dateTime($fieldName, $dateFormat = 'DMY', $timeFormat = '12', $attributes = array())
我对应用程序的代码做的相应变化就是,从:
echo $this->Form->dateTime('Voucher.expired', 'YMD', '24', strtotime('+15 day'), array(
'label' => false,
'minYear' => date('Y'),
'maxYear' => date('Y') + 1,
'empty' => false
));
改成:
echo $this->Form->dateTime('Voucher.expired', 'YMD', '24', array(
'selected' => strtotime('+15 day'),
'label' => false,
'minYear' => date('Y'),
'maxYear' => date('Y') + 1,
'empty' => false
));
5.4 MissingComponentException
这个错误如下:
Error: [MissingComponentException] Component class UploadFileComponent could not be found.
原因在于,从2.0开始,所有的组件都必须继承自Component基类,而在1.3中没有这个要求。
5.5 Cannot use object of type ComponentCollection as array
这个错误是:
Error: Fatal Error (1): Cannot use object of type ComponentCollection as array in [...\app\Plugin\FileUpload\Controller\Component\UploadFileComponent.php, line 61]
原因是Component的构造函数也发生了变化,在1.3中,是:
function __construct($options = null) {
}
而在2.x中,这变成了:
function __construct(ComponentCollection $collection, $options = null ) {
}
这是因为,从2.0开始,控制器(Controller)不再直接连接控件(Component),而是通过ComponentCollection(即Controller::$Components)来操纵它的控件,详见2.0 Migration Guide中的Controller一节。
请对所有控件(Component)的构造函数做相应的修改。
5.6 "Indirect modification" for pagination
错误信息为:
Notice (8):
Indirect modification of overloaded property PostsController::$paginate has no effect [APP/Controller/PostsController.php, line 13
这个错误的解释见DerEuroMark博客中“Indirect modification” for pagination一节。较好的做法是采用2.0的语法,不过我匆匆阅读了文档,仍然没有搞明白2.0的语法的含义,就采用了文中提到的简便方法:
class AppController extends Controller {
//... /**
* The paginate options for this controller
*
* @var array
*/
// TODO: upgrade to the new PaginatorComponent syntax
public $paginate = array(); //...
}
这就足够消除这个问题提示,并让应用程序正常运行。而我在注释中留下TODO标签,等以后有时间再来改进。我需要尽可能缩短升级所用的时间,因为有可能项目中别的开发者也正在做其他的改动,我升级所花的时间越多,这期间别人做的改动就可能越多,合并的时候发生矛盾的机会就越多。
5.7 JavascriptHelper could not be found
这个错误的日志为:
Error: [MissingHelperException] Helper class JavascriptHelper could not be found.
Exception Attributes: array (
'class' => 'JavascriptHelper',
'plugin' => false,
)
2.0中,JavascriptHelper已作废,而由JsHelper和HtmlHelper代替,详见2.0 Migration Guide中的XmlHelper, AjaxHelper and JavascriptHelper removed一节。所以,我要做的是,在相应的控制器中用JsHelper代替JavascriptHelper,把:
public $helpers = array(
//...
,'Javascript'
);
改为:
public $helpers = array(
//...
,'Js' => array('Jquery')
);
5.8 Cannot use isset() on the result of a function call
错误信息:
Error: Fatal Error (64): Cannot use isset() on the result of a function call (you can use "null !== func()" instead) in [D:\...\app\Controller\SoldiersController.php, line 175]
引起错误的代码为:
if (isset($this->request->query('email'))) {
}
改为:
if (null !== $this->request->query('email')) {
}
5.9 用户登录总是失败
这是因为在2.0中,AuthComponent验证用户的方式略有变化,用户代码需要显示调用AuthComponent::login()来验证用户登录信息。原来的代码:
// 只是示意
public function login() {
if (!$this->request->is('post')) {
// 显示登录表单
} else {
// 登录成功
}
}
需要变成:
// 只是示意
public function login() {
if (!$this->request->is('post')) {
// 显示登录表单
} else {
if ($this->Auth->login()) { // 现在需要显示调用
return $this->redirect($this->Auth->redirectUrl());
}
}
}
5.10 JavaScript变量由public声明
这是由于在CakePHP 2.0中,废止了对PHP 4的支持,所有的类成员都可以用public、protected和private来声明。Upgrade插件要把视图(View)中的JavaScript变量误当做PHP类成员,而改变了声明,导致了浏览器中JavaScript的错误,这可以从Firebug中看到,如下图所示。
aaarticlea/png;base64,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" alt="" />
错误的代码为:
<?php $this->Html->scriptStart(['inline' => false]); ?>
$(document).ready(function() {
// ...
public $that = $(this);
// ...
});
<?php $this->Html->scriptEnd(); ?>
很容易地改正为:
<?php $this->Html->scriptStart(['inline' => false]); ?>
$(document).ready(function() {
// ...
var $that = $(this);
// ...
});
<?php $this->Html->scriptEnd(); ?>
5.11 error in $this->Js->scriptBlock();
这个发生在视图(View)中的错误乍看起来很奇怪,不论在1.3还是2.0中JsHelper都没有scriptBlock()这个方法啊。其实它的演变过程是这样的,2.0用JsHelper/HtmlHelper替换了JavascriptHelper,所以Upgrade插件就在视图中把$this->Javascript替换成了$this->Js,所以下面的代码:
<?php $this->Javascript->codeBlock(); ?>
// 中间是JavaScript代码
<?php $this->Javascript->blockEnd(); ?>
就变成了:
<?php $this->Js->codeBlock(); ?>
// 中间是JavaScript代码
<?php $this->Js->blockEnd(); ?>
这就是PHP代码中发生错误的原因。改正的方法,就是把上面的代码进一步改为:
<?php $this->Html->scriptStart(); ?>
// 中间是JavaScript代码
<?php $this->Html->scriptEnd(); ?>
这样就好了。
5.12 Undefined index: User
出问题的代码为:
$user = $this->Auth->user();
$this->Soldier->id = $user['User']['id']; // error: Undefined index: User
这是因为在2.0中,AuthComponent::user()的返回值发生了变化,只需做如下变化就可逢凶化吉、遇难成祥:
$user = $this->Auth->user();
$this->Soldier->id = $user['id'];
5.13 如果用户登录时输入的用户名或者密码错误,没有提示
这个问题和前面的5.9 用户登录总是失败类似,都是因为在2.0中AuthComponent对用户登录时的验证发生了变化,需要进一步在修改上面的login()方法,在验证失败时显示错误提示,即为:
// 只是示意
public function login() {
if (!$this->request->is('post')) {
// 显示登录表单
} else {
if ($this->Auth->login()) { // 现在需要显示调用
return $this->redirect($this->Auth->redirectUrl());
} else {
$this->Session->setFlash(
'账号或密码错误!',
'default',
array(),
'auth'
);
}
}
}
5.14 日期输入中,月份的下拉框中文翻译变成了英文
这个涉及i18n & l10n,即国际化与本地化(Internationalization & Localization),这其实有很多内容,但在这里不便展开讨论,更多的信息可见参考资料中的[10][11]。在2.0中,国际化与本地化也发生了一些变化,这里只简单解释一下这个问题中涉及的一些要点:
原来这个项目中1.3版本的代码使用的中文locale代码为cn,我没有去深究1.3中应当是什么。而2.0中这应当按照ISO 639-2规范为zho,这可以在lib\Cake\I18n\L10n.php中看到:
class L10n {
// ... /**
* HTTP_ACCEPT_LANGUAGE catalog
*
* holds all information related to a language
*
* @var array
*/
protected $_l10nCatalog = array(
// ...
'zh' => array('language' => 'Chinese', 'locale' => 'zho', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'),
'zh-cn' => array('language' => 'Chinese (PRC)', 'locale' => 'zh_cn', 'localeFallback' => 'zho', 'charset' => 'GB2312', 'direction' => 'ltr'),
'zh-hk' => array('language' => 'Chinese (*)', 'locale' => 'zh_hk', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'),
'zh-sg' => array('language' => 'Chinese (Singapore)', 'locale' => 'zh_sg', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'),
'zh-tw' => array('language' => 'Chinese (*)', 'locale' => 'zh_tw', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'),
// ...
); // ...
}
FormHelper的日期方法在1.3就支持国际化与本地化,2.0也仍然支持,只是两个版本本地化翻译所在的domain(域)不同,这就导致所在的文件不同。详细解释如下。
在1.3中,我们可以查看cake\libs\view\helpers\form.php,在2140行左右,代码为:
/**
* Generates option lists for common <select /> menus
* @access private
*/
function __generateOptions($name, $options = array()) {
// ...
case 'month':
if ($options['monthNames'] === true) {
$data['01'] = __('January', true);
$data['02'] = __('February', true);
$data['03'] = __('March', true);
$data['04'] = __('April', true);
$data['05'] = __('May', true);
$data['06'] = __('June', true);
$data['07'] = __('July', true);
$data['08'] = __('August', true);
$data['09'] = __('September', true);
$data['10'] = __('October', true);
$data['11'] = __('November', true);
$data['12'] = __('December', true);
} else if (is_array($options['monthNames'])) {
这里没有指定域(domain),所以用的是缺省的域default。而且,在app\config\core.php的最后一行,指定了语言:
Configure::write('Config.language', 'cn');
所以在1.3这个项目中月份的中文翻译位于app\locale\cn\LC_MESSAGES\default.po文件中。注意这个文件的路径是由多项因素组合确定的。
而在2.x中,在lib\Cake\View\Helper\FormHelper.php的2875行左右,我们可以看到:
/**
* Generates option lists for common <select /> menus
*
* @param string $name List type name.
* @param array $options Options list.
* @return array
*/
protected function _generateOptions($name, $options = array()) {
// ...
case 'month':
if ($options['monthNames'] === true) {
$data['01'] = __d('cake', 'January');
$data['02'] = __d('cake', 'February');
$data['03'] = __d('cake', 'March');
$data['04'] = __d('cake', 'April');
$data['05'] = __d('cake', 'May');
$data['06'] = __d('cake', 'June');
$data['07'] = __d('cake', 'July');
$data['08'] = __d('cake', 'August');
$data['09'] = __d('cake', 'September');
$data['10'] = __d('cake', 'October');
$data['11'] = __d('cake', 'November');
$data['12'] = __d('cake', 'December');
} elseif (is_array($options['monthNames'])) {
这里指定了域cake。在2.x的代码中,我按照ISO 639-2规范把语言设置为:
/**
* Set the language for your application
*/
// http://book.cakephp.org/2.0/en/core-libraries/internationalization-and-localization.html#localization-in-cakephp
Configure::write('Config.language', 'zho');
所以,在2.x中月份的中文翻译位于app\Locale\zho\LC_MESSAGES\cake.po。我们只需把1.3中的月份中文翻译搬到这个文件中即可:
# LANGUAGE translation of CakePHP Application
# Copyright YEAR NAME <EMAIL@ADDRESS>
#
msgid ""
msgstr ""
"Project-Id-Version: zhandianr VERSION\n"
"POT-Creation-Date: 2014-07-26 23:04+0800\n"
"PO-Revision-Date: 2014-07-27 21:38+0800\n"
"Last-Translator: Zhu Ming <mingzhu.z@gmail.com>\n"
"Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"Language: zho\n"
"X-Generator: Poedit 1.6.7\n"
"X-Poedit-SourceCharset: UTF-8\n" msgid "January"
msgstr "1月" msgid "February"
msgstr "2月" msgid "March"
msgstr "3月" msgid "April"
msgstr "4月" msgid "May"
msgstr "5月" msgid "June"
msgstr "6月" msgid "July"
msgstr "7月" msgid "August"
msgstr "8月" msgid "September"
msgstr "9月" msgid "October"
msgstr "10月" msgid "November"
msgstr "11月" msgid "December"
msgstr "12月"
问题解决了。
5.15 模型(Model)的find方法返回值变化了
在1.3中,如果Model::find()没有读取到数据,那么返回值为false。而在2.x中,这种情况下返回值为空数组array()。所以,我把代码由原来的:
$soldier = $this->Solder->find('first', array(
'conditions' => array('id' => 267)
));
if ($soldier !== false) {
// 找到了
} else {
// 没找到
}
改为:
$soldier = $this->Solder->find('first', array(
'conditions' => array('id' => 267)
));
if (count($soldier) !== 0) {
// 找到了
} else {
// 没找到
}
即可。
至此,所有我在升级过程中遇到的与升级相关的问题,都讨论完毕了。
欢迎大家批评指正,留言讨论,互相切磋,共同提高。
A. 参考资料
- CakePHP官方网站 http://cakephp.org/
- CakePHP 2.0 Migration Guide http://book.cakephp.org/2.0/en/appendices/2-0-migration-guide.html
- DerEuroMark的博客中关于升级的说明 http://www.dereuromark.de/#upgrade-(deprecated)-code
- Tips Upgrading to CakePHP 2.x https://github.com/dereuromark/upgrade/wiki/Tips-Upgrading-to-CakePHP-2.x
- Included fixes and migration tweaks https://github.com/dereuromark/tools/wiki/Included-fixes-and-migration-tweaks
- Upgrading Notes for CakePHP 3.x https://github.com/dereuromark/upgrade/wiki/Upgrading-Notes-for-CakePHP-3.x
- Upgrade Shell http://book.cakephp.org/2.0/en/console-and-shells/upgrade-shell.html
- Upgrade Plugin https://github.com/dereuromark/cakephp-upgrade
- “Indirect modification” for pagination http://www.dereuromark.de/2013/01/22/cakephp-tips/#%E2%80%9Cindirect-modification%E2%80%9D-for-pagination
- Internationalization & Localization http://book.cakephp.org/2.0/en/core-libraries/internationalization-and-localization.html
- Cakephp 2.x guide for Internationalizing your website using poedit http://www.pabloleanomartinet.com/cakephp-2-x-guide-internationalizing-website-using-poedit/
- CakePHP 2.x tutorial for internationalizing your website using poedit
上篇文章好像转移到这里了:
http://deliciouscakephp.com/cakephp-2-x-tutorial-for-internationalizing-your-website-using-poedit/