插件开发指南

最后更新于:2022-04-01 05:42:20

### **完整的插件运行流程** **插件安装流程** 首先 ,我们打开Editor插件的定义类 ~~~ <?php // +---------------------------------------------------------------------- // | OneThink [ WE CAN DO IT JUST THINK IT ] // +---------------------------------------------------------------------- // | Copyright (c) 2013 http://www.onethink.cn All rights reserved. // +---------------------------------------------------------------------- // | Author: yangweijie <yangweijiester@gmail.com> <code-tech.diandian.com> // +---------------------------------------------------------------------- namespace Addons\Editor; use Common\Controller\Addon; /** * 编辑器插件 * @author yangweijie <yangweijiester@gmail.com> */ class EditorAddon extends Addon{ public $info = array( 'name'=>'Editor', 'title'=>'前台编辑器', 'description'=>'用于增强整站长文本的输入和显示', 'status'=>1, 'author'=>'thinkphp', 'version'=>'0.1' ); public function install(){ return true; } public function uninstall(){ return true; } /** * 编辑器挂载的文章内容钩子 * @param array('name'=>'表单name','value'=>'表单对应的值') */ public function documentEditFormContent($data){ $this->assign('addons_data', $data); $this->assign('addons_config', $this->getConfig()); $this->display('content'); } /** * 讨论提交的钩子使用编辑器插件扩展 * @param array('name'=>'表单name','value'=>'表单对应的值') */ public function topicComment ($data){ $this->assign('addons_data', $data); $this->assign('addons_config', $this->getConfig()); $this->display('content'); } } ~~~ 整个插件就是一个特殊的继承了 Addon抽象类的子类。必须实现 install和uninstall方法。 然后必须有一个自己的info属性,作为插件自己的信息。name、titile、description、status、author、version这6个是必须的。到时候后台列表里在未安装时会读取插件信息,显示出来。status为1或者0,表示安装插件后是否立即启用。 install和uninstall方法用于后台插件安装和卸载时候调用。返回true或者false用于告诉后台我安装卸载的准备工作是否做好了。比如我安装时候创建了某些表,创建成功可以安装,不成功提示错误。卸载前应该将安装时做的操作恢复到安装前状态。 其次,插件被安装后才能配置插件,卸载后会同时去除钩子处挂载的插件名,安装会添加钩子对应的插件名。 后台AddonsController.class.php ~~~ /** * 安装插件 */ public function install(){ $addon_name = trim(I('addon_name')); $class = get_addon_class($addon_name); if(!class_exists($class)) $this->error('插件不存在'); $addons = new $class; $info = $addons->info; if(!$info || !$addons->checkInfo())//检测信息的正确性 $this->error('插件信息缺失'); session('addons_install_error',null); $install_flag = $addons->install(); if(!$install_flag){ $this->error('执行插件预安装操作失败'.session('addons_install_error')); } $addonsModel = D('Addons'); $data = $addonsModel->create($info); if(is_array($addons->admin_list) && $addons->admin_list !== array()){ $data['has_adminlist'] = 1; }else{ $data['has_adminlist'] = 0; } if(!$data) $this->error($addonsModel->getError()); if($addonsModel->add($data)){ $config = array('config'=>json_encode($addons->getConfig())); $addonsModel->where("name='{$addon_name}'")->save($config); $hooks_update = D('Hooks')->updateHooks($addon_name); if($hooks_update){ S('hooks', null); $this->success('安装成功'); }else{ $addonsModel->where("name='{$addon_name}'")->delete(); $this->error('更新钩子处插件失败,请卸载后尝试重新安装'); } }else{ $this->error('写入插件数据失败'); } } ~~~ 实例化插件类->插件info是否正确->执行install方法,预安装操作->添加插件数据到数据库Addons表和Hooks表 每个步骤出错都会提示,install 方法中错误用 session('addons_install_error', 'error')传递。 **插件卸载流程** 卸载流程和安装相反 后台AddonsController.class.php ~~~ /** * 卸载插件 */ public function uninstall(){ $addonsModel = M('Addons'); $id = trim(I('id')); $db_addons = $addonsModel->find($id); $class = get_addon_class($db_addons['name']); $this->assign('jumpUrl',U('index')); if(!$db_addons || !class_exists($class)) $this->error('插件不存在'); session('addons_uninstall_error',null); $addons = new $class; $uninstall_flag = $addons->uninstall(); if(!$uninstall_flag) $this->error('执行插件预卸载操作失败'.session('addons_uninstall_error')); $hooks_update = D('Hooks')->removeHooks($db_addons['name']); if($hooks_update === false){ $this->error('卸载插件所挂载的钩子数据失败'); } S('hooks', null); $delete = $addonsModel->where("name='{$db_addons['name']}'")->delete(); if($delete === false){ $this->error('卸载插件失败'); }else{ $this->success('卸载成功'); } } ~~~ 实例化插件类->执行预卸载->卸载插件所挂载过的钩子处信息->删除插件文件。 每个步骤出错都会提示,install 方法中错误用 session('addons_uninstall_error', 'error')传递。 **插件运行流程** hook函数调用Hook类的listen静态方法触发钩子->获取钩子挂载的开启的插件->执行对应插件实现的钩子同名方法(这个在init_hooks函数会初始化) 代码上就是如下的过程: >hook('documentEditFormContent'); 然后hook函数遍历 Hook类里的$tag属性,知道有哪些插件可被调用,接下来去读取配置和状态,启用就去执行钩子 ~~~ /** * 处理插件钩子 * @param string $hook 钩子名称 * @param mixed $params 传入参数 * @return void */ function hook($hook,$params=array()){ \Think\Hook::listen($hook,$params); } 而Hook::listen方法里面 /** * 监听标签的插件 * @param string $tag 标签名称 * @param mixed $params 传入参数 * @return void */ static public function listen($tag, &$params=NULL) { if(isset(self::$tags[$tag])) { if(APP_DEBUG) { G($tag.'Start'); trace('[ '.$tag.' ] --START--','','INFO'); } foreach (self::$tags[$tag] as $name) { APP_DEBUG && G($name.'_start'); $result = self::exec($name, $tag,$params); if(APP_DEBUG){ G($name.'_end'); trace('Run '.$name.' [ RunTime:'.G($name.'_start',$name.'_end',6).'s ]','','INFO'); } if(false === $result) { // 如果返回false 则中断插件执行 return ; } } if(APP_DEBUG) { // 记录行为的执行日志 trace('[ '.$tag.' ] --END-- [ RunTime:'.G($tag.'Start',$tag.'End',6).'s ]','','INFO'); } } return; } /** * 执行某个插件 * @param string $name 插件名称 * @param Mixed $params 传入的参数 * @return void */ static public function exec($name, $tag,&$params=NULL) { if(false === strpos($name,'\\')) { // 插件(多个入口) $class = "Addons\\{$name}\\{$name}Addon"; }else{ // 行为扩展(只有一个run入口方法) $class = $name.'Behavior'; $tag = 'run'; } $addon = new $class(); return $addon->$tag($params); } #实例化一个插件类,我们有get_addon_classs()函数,传入插件名即可或得插件类名,然后直接new 就行了,区分大小写 $addons_class = new get_addon_classs($addon_name); ~~~ $addon->$tag() 这个方法就是去执行钩子处挂载的和钩子同名的插件方法。 这个方法里可以display渲染模板,默认插件的模板就在插件目录下,比如Editor插件类里用的$this->display('content');。 如果你想有目录层可以传入目录/模板这样的参数,这样是不支持主题的,如果要支持主题,也可以传入具体的模板路径,使用T函数如T('Addons://Attachment@Article/edit')这样,到时候想切换主题了,T函数定位模板之前C('DEFAULT_THEME','default')这样就行了。 当然这个方法支持传参,只允许一个,为了能实现引用。所以多个参数,请封装成数组,传入hook函数的第二个参数。 执行完毕,这个钩子的某个插件前台功能方法就运行完了。 插件被禁用,钩子处的插件不会被执行该同名方法,并且插件后台列表里不会出现该插件的列表。 插件不光前台能用 ,后台有钩子,并且插件里实现了该钩子,也可以用。为了前后台编辑器插件可以配置不同的编辑器,我们复制了一份Editor插件,改名为EditroForAdmin了。 还有后台首页,其实是用AdminIndex 钩子挂载了几个插件。 后台中,插件默认会在 插件列表里出现。默认没有安装过的会显示在前面。 基本的数据字段都是读取的插件类里的info属性数组。插件未安装的时候是不可以设置的。 假如插件需要url访问,就必须插件里有Controller目录和tp结构一样。 只不过生成这个方法的用addons_url('插件名://控制器名/操作方法'),生成访问url。并且访问权限由插件去做。具体写法,参照附件Attachment插件 里的控制器写法。和模板里url调用。
';