## 完整的插件运行流程
### 插件安装流程
首先 ,我们打开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,表示安装插件后是否立即启用。 效果见下图: ![image](http://ww2.sinaimg.cn/mw1024/50075709jw1e8vcw886rhj21320b2tay.jpg)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调用。