我以自己的oneblog體驗了一下tp5 beta 版的使用,寫下最小修改的升級方式。
# 目的
告訴大家如何去移植舊項目到新版tp5 beta中去。以及可能遇到的問題,和老楊是怎么解決的。
# 前提
做正確的移植之前,我們得熟讀一下本手冊,這樣才能少走彎路。
# 幾個巨大的變化
## 命名規范
一開始說是PSR0 后來大家協作時說規范是PSR2, 有人建議Sublime 裝一個phpfmt 插件,可是我裝了提示php版本低于5.6 用不了。不折騰了。
建議大家看看之前的命名規范章節。 主要是命名空間,目錄和文件名小寫,分割用_,但是類名要大寫首字母駝峰式。開始老楊也不理解,為什么類名不也小寫。后來一想,類名一小寫。就難和文件和目錄區分開了。
## 配置不合并
以往我們記得有個Common模塊, 里面有公共函數、公共配置,然后其他模塊的會先加載這個配置和函數然后去跟當前模塊的合并。現在不行了。現在的公共配置不放在Common里,放在application APP_PATH根目錄下。什么config.php、database.php、common.php啦。后來問老大那現在的Common 干嘛用,他說放公共的類,比如公共的model、behavior
## Traits
新框架用了php5.4開始添加的Traits功能,老楊看了下,就是用于多繼承的,使得代碼更精簡,比方 原來我們有高級模型、視圖模型,然后,我們一個類既有視圖模型、又有高級模型的功能,要實現這樣的怎么辦,定義一個第三方模型,復制這兩個模型里的代碼。。 現在有了Traits,我們可以 通過use 關鍵字,將Traits 實現的功能,直接在模型里 復用一下。框架新增了T函數是為了兼容php5.4版本的,5.5的可以直接引用。
因此,老大在架構上把一些常用的自動完成、高級模型給分離開到traits目錄里去了,這帶來了一些不便。后面我會講如何去使用,達到以前的效果。
## 調試
以前的調試工具條被舍棄了,換成了專門調試api的不影響頁面輸出的SocketLog工具。當然也不說不能用,只是有一些不便:依賴網絡、空值不輸出。
## 耦合低了,很多東西獨立開來了,比如視圖 以前我們控制器里可以直接display、error、succes。現在必須依賴視圖類,因為老大認為面向api的框架,很少需要視圖。當然如果能用視圖的話,原先的視圖功能還是完整的,只不過使用上不方便。
# 移植的步驟
## 入口+框架
首先去<https://github.com/top-think/think> git 工具下載一份 beta版 tp5。 放入自己的環境里去。
然后修改入口文件,改為以下的:
~~~
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// 應用入口文件
// 定義項目路徑
define('APP_PATH', './application/');
define('ONETHINK_ADDON_PATH', './addons/');
// 開啟調試模式
define('APP_DEBUG', true);
define('APP_HOOK', true);
define('SLOG_ON', true);
// define('BIND_MODULE','home');
// define('BIND_CONTROLLER','index');
// 加載框架引導文件
require './thinkphp/start.php';
~~~
## 自動生成
默認的示列應用APP_PATH下帶了一個build.php 自動生成配置文件,我們可以修改一下,用于生成自己以前項目的目錄結構,不過有點繁瑣。
比方老楊以前的tp3.2的application下的結構是這樣子的:

添加自動完成時,一個模塊一個模塊的加。然后依次是模塊對應的子目錄`__dir__`、`__file__`、controller、model、view 之類的子目錄。比如老楊的admin模塊:
~~~
'admin' => [
'__file__' => [],
'__dir__' => ['controller', 'view'],
'controller' => ['Addons', 'Article', 'Cate', 'Common', 'Config', 'System', 'Theme', 'User'],
'view' => ['addons/index', 'article/add', 'cate/add', 'config/index', 'public/base', 'system/index', 'tags/index', 'theme/index', 'user/index'],
],
~~~
因為模塊里的視圖模板文件太多了,一個個手寫很累,所以老楊只寫了一個控制器里的一個模板,生成好后,手動復制過來整個目錄的模板即可(因為模板一般都小寫的,不用改文件名)。
自動生成控制器和模型的目的是節省時間,因為變化太大了:沒有.class.php后綴,首字母要小寫,對應命名空間也變了。能偷懶為什么不用呢。其實如果針對移植升級,可以寫一個小腳本,掃描舊目錄去生成對應新版的。
PS:sublime 對于rename這件事有個bug,就是如果有的目錄或者文件更改大小寫,然后這個目錄和文件就打不開了。即使用菜單的刷新功能更也沒用。只能手動關閉整個項目,再重新打開st。老楊發現重命名時先改小寫的,同時多加一個s后 先重命名一次,然后在去掉s 百分百不會觸發bug。
PS:自動生成每次訪問都會生成,如果你后來想刪除某些文件了。記得把自動生成的也改一下,要是node 可以用gulp之類的watch 一下。php 還是人工吧。
完整的oneblog 生成:
~~~
return [
// 生成運行時目錄
'runtime' => [
'__dir__' => ['cache', 'log', 'temp'],
],
'admin' => [
'__file__' => [],
'__dir__' => ['controller', 'view'],
'controller' => ['Addons', 'Article', 'Cate', 'Common', 'Config', 'System', 'Theme', 'User'],
'view' => ['addons/index', 'article/add', 'cate/add', 'config/index', 'public/base', 'system/index', 'tags/index', 'theme/index', 'user/index'],
],
'common'=>[
'__file__' => [],
'__dir__' => ['behavior', 'controller', 'model', 'api'],
'controller' => ['Addon'],
'model' => ['Addons', 'Article', 'Cate', 'Config', 'File', 'Hooks', 'Picture', 'Tree', 'Url'],
],
'home'=>[
'__file__' => ['config.php', 'common.php'],
'__dir__' => ['widget', 'controller', 'view'],
'controller' => ['Addons', 'Api', 'Error', 'Index'],
'view' => ['index/index', 'widget/archive'],
'widget' =>['Common']
],
// 。。。 其他更多的模塊定義
];
~~~
## 更新數據庫配置、slog和其他配置
首先數據庫的配置獨立出來再APP_PATH下的database.php。記得新版 咱數組用[] 來寫,多精簡。
然后就是老幾項了。hostname、username、password 之類的。
slog 的配置:
~~~
'slog' => [
'enable' => true, //是否記錄日志的開關
'host' => '111.202.76.133',
//是否顯示利于優化的參數,如果允許時間,消耗內存等
'optimize' => true,
'show_included_files' => true,
'error_handler' => true,
//日志強制記錄到配置的client_id
'force_client_id' => 'XXX',
//限制允許讀取日志的client_id
'allow_client_ids' => ['XXX'],
],
~~~
新版的配置文件中鍵名都是小寫。老的用來寫舊版的和自定義配置吧。
注意的是入口要定義 SLOG_ON 常量為true才起作用,老楊也是追了源碼才知道的。
其他配置項,可以參考框架的convention.php文件

有對應的注釋。
PS:有的類的構造方法里的配置,需要寫在對應的命名空間里,如

template的配置,并不是不可變,而是要
~~~
'template'=>[
'compile_type'=>'sae'
]
~~~
這樣去改變,老楊也是測試sae試出來的。
## 移植模塊目錄里的函數、配置、tags
公共函數移出來,對應模塊的函數復制到對應模塊的common.php中,配置也是對應的config.php tags.php也是,只不過現在沒有多余的Common和Config目錄了,都是單文件。
## 將視圖復制過來,修復命名上的錯誤
將對應的模塊的view目錄復制過來。基本上就能用了,但是注意一件事,include 和 extend 的name 對應的路徑,只支持控制器/模板名了,且區分大小寫。因此Public/Common 和public::common都是不對的。必須public/common
等搞定視圖輸出,時測試一些用法。老楊測試出include 屬性變量沒解析對和switch的 case 多層嵌套有問題,當然已經修復了,你們可以測測看有什么不兼容的。去提issue。
## 局部訪問 測試bug 差異化
控制器的一些方法、框架函數之類的。
常見單字母函數,框架還是留了可以兼容。如D('Article') 會找當前模塊的,找不到去找common/model下的article.php就是Article類。
# 遇到的問題
## 公共模塊的配置不合并了?
參見上面的介紹,位置變了。
## 視圖里一些方法不見了
為了能更好的測試,及系統流程的變化,現在tp的控制器必須有返回值。(特殊方法除外)。
返回給Response類(去thinkphp\libary\think下找),
所以官方的例子是 new \think\View 自己fetch 自己return。
后來群友提出來以前方便的succes和error 自動判斷ajax 不見。我覺得每次自己實例化視圖類很麻煩,老大就用Traits支持了 controller類里 的視圖替換字符串變了fetch、show、assign 方法:

因此我們只需要有視圖的控制器繼承 \think\controller類就行了和之前一樣的assign、show、及拋棄display 用 fetch 替代。return 模板字符串。
## 配置精簡了
大家看convention.php 文件就知道配置少了很多了。因為很多行為也沒了。
## 分頁類沒有了
為了內核的精簡,老大沒有加,老楊手動移植了一個放到org和ot里 ot 自定義了樣式。其他的類你看著放吧!
## $this->redirect 沒了
去response類看看 可以`\think\response::redirect`
## 模型默認很多快捷操作沒有了?
這個很頭痛,比方說以前M('article')->count()、 M('Article')->getField 和getFieldBy 都不能用了。
現在只能 D后用,然后在對應的模型里,使用 traits。
如
~~~
<?php
namespace common\model;
T('model/adv');
T('model/auto');
class Article extends \think\Model{
use \traits\model\adv;
use \traits\model\auto;
public function _initialize(){
/* 自動驗證規則 */
$this->validate = array(
array('name', '/^[a-zA-Z]\w{0,39}$/', '文檔標識不合法', self::VALUE_VALIDATE, 'regex', self::MODEL_BOTH),
array('name', '', '標識已經存在', self::VALUE_VALIDATE,'unique'),
array('title', 'require', '標題不能為空', self::MUST_VALIDATE, 'regex', self::MODEL_BOTH),
array('title', '1,80', '標題長度不能超過80個字符', self::MUST_VALIDATE, 'length', self::MODEL_BOTH),
array('description', '1,140', '簡介長度不能超過140個字符', self::VALUE_VALIDATE, 'length', self::MODEL_BOTH),
array('cate_id', 'require', '分類不能為空', self::MUST_VALIDATE , 'regex', self::MODEL_INSERT),
array('cate_id', 'require', '分類不能為空', self::EXISTS_VALIDATE , 'regex', self::MODEL_UPDATE),
);
/* 自動完成規則 */
$this->auto = array(
array('uid', 'is_login', self::MODEL_INSERT, 'function'),
array('title', 'htmlspecialchars', self::MODEL_BOTH, 'function'),
array('content', 'base_encode', self::MODEL_BOTH, 'callback'),
array('description', 'htmlspecialchars', self::MODEL_BOTH, 'function'),
array('link_id', 'getLink', self::MODEL_BOTH, 'callback'),
array('view', 0, self::MODEL_INSERT, 'string'),
array('comment', 0, self::MODEL_INSERT, 'string'),
array('create_time', 'getCreateTime', self::MODEL_BOTH,'callback'),
array('update_time', NOW_TIME, self::MODEL_BOTH, 'string'),
array('status', '1', self::MODEL_INSERT, 'string'),
);
}
~~~
有自動完成和自動驗證的用 traits/auto。 有after 后置操作,和快捷操作的 用 traits/adv。 發現自己的查詢不支持了,先去 traits/model 里找找。
因此有時為了一個簡單的復雜查詢得建一個簡單模型:
如user
~~~
<?php
namespace common\model;
T('model/adv');
class User extends \Think\Model{
use \traits\model\adv;
}
~~~
注意的是新版的 auto 和 valiate 不帶_ 了,我的做法是 _initialize 方法里 屬性賦值或者直接= 以前的數組。
## 模板路徑替換不能用了?
因為新版 現在沒有任何內置行為。
開始我是移植舊的ContentReplace行為。`parse_str` 這個配置。
配置里將以前的TPML_PARSE_STRING 替換成這個鍵名即可。
值的注意的是 以前3.2版 dispatch 里定義的很多常量沒了, `__ROOT__` 之類的,我挑了3個常用的 `__ROOT__`、`__APP__`、`__SELF__`
~~~
if (!IS_CLI) {
// 當前文件名
if (!defined('_PHP_FILE_')) {
if (IS_CGI) {
//CGI/FASTCGI模式下
$_temp = explode('.php', $_SERVER['PHP_SELF']);
define('_PHP_FILE_', rtrim(str_replace($_SERVER['HTTP_HOST'], '', $_temp[0] . '.php'), '/'));
} else {
define('_PHP_FILE_', rtrim($_SERVER['SCRIPT_NAME'], '/'));
}
}
if (!defined('__ROOT__')) {
$_root = rtrim(dirname(_PHP_FILE_), '/');
define('__ROOT__', (($_root == '/' || $_root == '\\') ? '' : $_root));
}
define('PHP_FILE', _PHP_FILE_);
}
if(!defined('__APP__'))
define('__APP__', strip_tags(PHP_FILE));
// URL常量
if(!defined('__SELF__'))
define('__SELF__', strip_tags($_SERVER[C('URL_REQUEST_URI')]));
~~~
將舊版的移植到 config.php return 上面。本來是想放common.php公共函數里的,結果不起作用。
## 插件不能用了?
由于oneblog 移植了ot的插件機制,所以也得研究一下了。
插件依賴鉤子,所以入口要定義HOOK_ON 常量。
然后頭疼的就是命名空間了。
3.2的插件目錄結構和文件名:

5.0 的:

就是插件目錄小寫了,后綴變了。目錄名也變了,然后類名不變。
以前一個控制器是BookMark/BookMark 現在變成了 book_mark/BookMark!!!
然后咱找個地方定義插件目錄常量 入口:
`define('ONETHINK_ADDON_PATH', './addons/');`
添加命名空間:
Think\Loader::addNamespace('addons', ONETHINK_ADDON_PATH);
公共函數里添加命名空間印射目錄。
然后修改common/behavior/init_hook.php 初始化鉤子行為
~~~
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2013 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace common\behavior;
use think\Hook;
use think\Loader;
defined('THINK_PATH') or exit();
// 初始化鉤子信息
class InitHook{
// 行為擴展的執行入口必須是run
public function run(&$content) {
if (isset($_GET['m']) && $_GET['m'] === 'Install')
return;
// $data = null;
$data = S('hooks');
if (!$data) {
$hooks = D('Hooks')->getField('name,addons');
foreach ($hooks as $hook => $value) {
if ($value) {
$map['status'] = 1;
$names = explode(',', $value);
$map['name'] = array('IN', $names);
$data = D('Addons')->where($map)->getField('id,name');
if ($data) {
$addons = array_intersect($names, $data);
foreach ($addons as $key => $value) {
$dir = Loader::parseName(lcfirst($value));
$path = "./addons/{$dir}/".Loader::parseName(lcfirst($value)).".php";
$value = "addons\\{$dir}\\{$value}";
$path = realpath($path);
$addons[$key] = $value;
Loader::addMap($value, $path);
}
Hook::add($hook, $addons);
}
}
}
S('hooks', Hook::get());
} else {
Hook::import($data, false);
}
}
}
~~~
里面Loader::addMap 可是我實踐出來的,開始以為定義了命名空間能自已找到類,結果得添加alias。
然后就是公共tags里添加行為:
~~~
<?php
return [
'app_init' => ['common\\behavior\\InitHook'],
];
~~~
這里寫的都是命名空間規則,而不是路徑。
common/controller/addon.php 插件定義抽象類修改,改getName 獲取路徑等方法,改著改著就成了這樣:
~~~
<?php
namespace common\controller;
/**
* 插件類
* @author yangweijie <yangweijiester@gmail.com>
*/
abstract class Addon extends \think\controller{
/**
* 視圖實例對象
* @var view
* @access protected
*/
protected $view = null;
/**
* $info = array(
* 'name'=>'Editor',
* 'title'=>'編輯器',
* 'description'=>'用于增強整站長文本的輸入和顯示',
* 'status'=>1,
* 'author'=>'thinkphp',
* 'version'=>'0.1'
* )
*/
public $info = array();
public $addon_path = '';
public $config_file = '';
public $custom_config = '';
public $admin_list = array();
public $custom_adminlist = '';
public $access_url = array();
public function __construct() {
$this->addon_path = ONETHINK_ADDON_PATH . lcfirst($this->getName()) . '/';
$parse_str = C('parse_str');
$parse_str['__ADDONROOT__'] = __ROOT__ . '/addons/' . $this->getName();
C('parse_str', $parse_str);
if (is_file($this->addon_path . 'config.php')) {
$this->config_file = $this->addon_path . 'config.php';
}
}
/**
* 模板主題設置
* @access protected
* @param string $theme 模版主題
* @return Action
*/
final protected function theme($theme) {
$this->view->theme($theme);
return $this;
}
final protected function addon_path(){
return ONETHINK_ADDON_PATH . lcfirst($this->getName()) . '/';
}
//顯示方法
final protected function display($template = '') {
if (!is_file($template)) {
$template = $this->addon_path() . $template . '.html';
if (!is_file($template)) {
throw new \Exception("模板不存在:$template");
}
}
$html = $this->fetch($template);
return $html;
}
/**
* 獲取插件目錄或名稱
* @param $type 0 - dir 1- name
*/
final public function getName($type = 0) {
$class = get_class($this);
$class = str_replace("addons\\", '', $class);
$class_arr = explode('\\', $class);
return $class_arr[$type];
}
final public function checkInfo() {
$info_check_keys = array('name', 'title', 'description', 'status', 'author', 'version');
foreach ($info_check_keys as $value) {
if (!array_key_exists($value, $this->info))
return FALSE;
}
return TRUE;
}
/**
* 獲取插件的配置數組
*/
final public function getConfig($name = '') {
static $_config = array();
if (empty($name)) {
$name = $this->getName(1);
}
if (isset($_config[$name])) {
return $_config[$name];
}
$config = array();
$map['name'] = $name;
$map['status'] = 1;
$config = D('Addons')->where($map)->getField('config');
if ($config) {
$config = json_decode($config, true);
return $config;
} else if(!empty($this->config_file)){
$temp_arr = include $this->config_file;
foreach ($temp_arr as $key => $value) {
if ($value['type'] == 'group') {
foreach ($value['options'] as $gkey => $gvalue) {
foreach ($gvalue['options'] as $ikey => $ivalue) {
$config[$ikey] = $ivalue['value'];
}
}
} else {
$config[$key] = $temp_arr[$key]['value'];
}
}
$_config[$name] = $config;
return $config;
}else{
return [];
}
}
//必須實現安裝
abstract public function install();
//必須卸載插件方法
abstract public function uninstall();
}
~~~
后來就是改后臺相關的類。主要改了顯示的地方,config方法和controller的沖突了換成conf。后面框架會去掉這個方法。
home 和admin 插件控制器執行方法:
~~~
<?php
namespace home\controller;
class Addons extends \think\controller{
protected $addons = null;
public function execute($_addons = null, $_controller = null, $_action = null){
$dir = \think\Loader::parseName($_addons, 0);
$_controller = \think\Loader::parseName($_controller,1);
$parse_str = C('parse_str');
$parse_str['__ADDONROOT__'] = __ROOT__ . "/addons/{$dir}";
C('parse_str', $parse_str);
if(!empty($_addons) && !empty($_controller) && !empty($_action)){
$addons = A("addons\\$dir/$dir")->$_action();
return $addons;
} else {
return $this->error('沒有指定插件名稱,控制器或操作!');
}
}
public function display($tpl = ''){
if (!is_file($tpl)) {
$tpl = $this->addon_path() . $tpl . '.html';
if (!is_file($tpl)) {
throw new \Exception("模板不存在:$tpl");
}
}
$html = $this->fetch($tpl);
return $html;
}
public function success($msg = '', $data = '', $url = '', $wait = 3){
\think\Response::success($msg, $data, $url, $wait);
}
public function error($msg = '', $data = '', $url = '', $wait = 3){
\think\Response::error($msg, $data, $url, $wait);
}
}
~~~
PS:后臺插件快速創建的還么改,大家可以自己參考類修改一下,因為這只是老楊自己的移植,并不是官方tp5 以后的插件架構。
值得注意的是插件模板的顯示輸出不要exit 也不要return 回給Hook::listen 里,因為, 一個鉤子有多個插件,插件執行不一定都是返回模板。
所以,如果你要顯示什么 請 @print($this->display('single')) 這樣去寫。
## SAE上如何使用
think/model/sae 里的配置,要加入convetion.php里的部分配置。
如果你只是但應用,數據庫就在當前應用,不需要建議database.php去配置數據庫,如果是跨應用的,就要將來源引用的配置 常量 dump出來寫死在項目公共數據庫配置里。
然后是初始化一些服務 用于緩存的 memcache服務,用于文件上傳的storage、注意sae的特性,不可寫。
配置重定向要改 config.yaml
其他基本上沒遇到問題。
# 嘗試一下吧~
更多的以最新框架源碼為準,只要沒發布,每天更新倉庫后自己手動覆蓋已經保存svn或git的本地庫,看看變化,自己的hack是否要去除了。
下載地址<http://git.oschina.net/yangweijie/oneblog/tree/5.0beta/> 自己克隆了 切換分支后看readme。
嘗鮮就得會折騰。