💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
我以自己的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下的结构是这样子的: ![](https://box.kancloud.cn/2015-12-19_5674d7727cf1b.png) 添加自动完成时,一个模块一个模块的加。然后依次是模块对应的子目录`__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文件 ![](https://box.kancloud.cn/2015-12-19_5674d77296a1f.png) 有对应的注释。 PS:有的类的构造方法里的配置,需要写在对应的命名空间里,如 ![](https://box.kancloud.cn/2015-12-19_5674d773443eb.png) 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 方法: ![](https://box.kancloud.cn/2015-12-19_5674d7745495e.png) 因此我们只需要有视图的控制器继承 \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的插件目录结构和文件名: ![](https://box.kancloud.cn/2015-12-19_5674d7751592f.png) 5.0 的: ![](https://box.kancloud.cn/2015-12-19_5674d77523cc2.png) 就是插件目录小写了,后缀变了。目录名也变了,然后类名不变。 以前一个控制器是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。 尝鲜就得会折腾。