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