这篇文章被收录与phpwind开发文档之中,教主同学写的一篇phpwind9插件开发速成心法。
什么是插件
插件是增加网站功能,实现差异化运营的重要手段
通常所说的插件,其实都是泛指插件与扩展,两者区别是:
- 插件 具有独立访问地址,功能可以自成体系的功能模块
- 扩展 通过钩子系统注入到系统中以达到接管或改进原有系统的功能模块
有一个比较容易混淆的概念是,phpwind9.0的导航上有一个应用中心 这里所谓的应用,其实都是插件(插件也叫应用)
插件的原理
- 插件 是通过框架分发器的模式匹配功能来实现的,所以开发一个插件本质上跟开发应用是一样的
- 扩展 通过钩子系统注入到系统中的功能模块,依赖系统的功能运行而运行
准备工作
注册成为开发者
- 成为开发者,可以使你开发的插件为所有phpwind站点使用
- 可以让你的插件成为唯一,以防与其他开发者命名冲突
- 当然如果你愿意,这可以为你带来一笔收入
- 开发者注册地址
了解开发助手
- 进入后台->站点设置->全局参数-> DEBUG 模式运行站点,输入520(或1314),开启开发者模式
- 进入后台->云平台->应用管理->开发助手 按照提示输入,建立一个插件
- 在src/extensions/目录下,能找到你刚生成的插件文件夹
- 如果DEBUG模式为1314,还可以在前台页面上看到已埋的钩子标识
开发者版本
- 为了更好地与开发者保持交流,我们实时公布研发版本的代码动态,此上面的更新将全部放入下个版本
- 下载地址 https://github.com/phpwind/nextwind
插件开发规范
由于系统可能同时存在多个插件,尤其是云平台开通之后,插件安装极其便利,为了避免插件之间的冲突,定义如下规范:
插件唯一标识符
-
- 标识符由字符、数字组成,不能包含_
- 标识符需能明确表达插件的含义,否则无法通过云平台验证(不提交到云平台的插件略过)
- 标识符可以到云平台申请
- 比如当前申请到的唯一标识符为sign,以下皆以此为例
数据库命名规范
-
- 表命名 统一添加表前缀,表前缀为pw_app_sign_(pw_前缀是站长安装时选择的,可能各个站不一样)
- 扩展字段 插件不建议在原生数据表上增加字段,但并不限制。所有增加的字段必须带上前缀app_sign_
类命名(文件名)规范
-
- 所有类命名(Controller除外)必须带上前缀 App_Sign_,驼峰形式+下划线_
插件配置规范
- 为防止插件之间配置项命名冲突,我们作如下约定:
- 独立配置域 配置域命名规则为 app_sign
- 公共配置域 对于某些配置到公共配置域的配置项,配置项加前缀 app.sign.
钩子键名(alias)命名规范
-
- alias命名加前缀 app_sign
后台菜单项键名命名规范
-
- 键名命名加前缀 app_sign
用户组权限键名命名规范
-
- 键名命名加前缀 app_sign
开发演示
选取一个题材
我们选取互联网上SNS社区比较流行的一款增加用户粘性的互动游戏“动他一下”作为本次的开发示例。
别忘了,上开发者平台验证一下,是否已经有人开发过了哦
这个插件做些什么
- 应该有一个独立页面,列出我的所有好友,我可以轻松的动“他们”一下
- 访问别人空间的时候,我可以动“他们”一下
- 看别人帖子的时候,我也要动“他们”一下;
- 哦,别忘了,还需要一个后台设置
创建应用
- 进入后台->云平台->应用管理->开发助手
- 按照提示,填写相关信息
- 这里有三个选择需要注意一下
-
- 选择应用额外安装服务 这个有三个菜单可以选择,我们只需要在主导航中添加应用,故勾选nav_main
- 是否需要管理后台界面 选需要,会帮我们自动生成一个菜单项和演示页面
- 是否生成数据服务类 本应用要用到数据库,所以选需要
- 提交,马上创建
- 在应用管理,已安装菜单下,就能发现刚才创建的“动他一下”应用了
- 嗯,logo不对,找到 src/applications/extensions/dongta/res/images 替换一张logo.jpg
- 刷新,搞定!
编写我的后台
- 大部分的插件编写都是先从后台开始的,因为后台功能比较简单,无非就是“设置”和“管理”,就当热身了
- 说到“设置”,就不能不说 PwConfigSet 这个类库了,使用她,轻松搞定,别忘了 插件配置规范 哦
- 附后台源码
-
- ManageController.php 后台管理controller
- manage_run.htm 后台管理模板
编写底层服务
因为我要知道到底是谁动了我一下,所以我需要建一张表来记录这些数据,数据表设计如下,别忘了 数据库命名规范 哦:
CREATE TABLE `pw_app_dongta` ( `id` int(10) unsigned NOT NULL auto_increment, `act` tinyint(1) unsigned NOT NULL default '0', `touid` int(10) unsigned NOT NULL default '0', `created_userid` int(10) unsigned NOT NULL default '0', `creaed_username` varchar(15) NOT NULL default '', `created_time` int(10) unsigned NOT NULL default '0', PRIMARY KEY (`id`), KEY `idx_touid_createdtime` (`touid`,`created_time`) ) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
- 找到自动建立的Dao,Dm,Ds示例文件,发现文件名稍微不一样,没关系,改一下
- 接下来,就是coding,这里可以参考应用开发步骤。附底层服务源码,别忘了 类命名(文件名)规范 哦:
当然,在实际开发过程中,这一步很少能一口气写完的;没关系,需要的时候,回过头来继续完善。
编写我的前台主页面
哈哈,终于到了,最关键的地方了,这里就是各位自由发挥的地方了
- 我的插件功能很简单,但是还是有几个地方不能忘记
-
- 后台设置了关闭,前台不能访问
- 我不允许未登录用户访问
//示例代码 if (!Wekit::C('site', 'app.dongta.ifopen')) { //应用是否关闭 $this->showError('动他一下应用,关闭了'); } if (!$this->loginUser->isExists()) { //应用是否登录 $this->showError('login.not'); }
- 继续coding,附源码
-
- IndexController.php 前台controller
- index_run 首页模板
- index_my 谁了我页面模板
- index_act ajax请求动作列表模板
- 到此,我们的独立插件其实已经完成了,但是为了能更好地融入到系统中,我们还要添加两个钩子。【没错,抢占入口,就是抢占先机!】
添加Simple-Hook
- 找到空间个人头像下面的钩子 s_space_user_info
- 找到后台->云平台->应用管理->动他一下->设计->xml 在 inject-services 项下面添加
<s_space_user_info> <app_dongta> <class>EXT:dongta.service.srv.App_Dongta_Service</class> <loadway>load</loadway> <method>spaceButton</method> <expression>config:site.app.dongta.space.ifopen==1</expression> <description>动他一下空间按钮</description> </app_dongta> </s_space_user_info>
- 完成 App_Dongta_Service 服务,附源码:
添加Model-Hook
- 找到帖子阅读页个人头像下面的钩子 m_PwThreadDisplay.createHtmlAfterUserInfo
- 找到其业务流程类 PwThreadDisplay
- 找到其扩展扩展服务接口基类 PwThreadDisplayDoBase
- 继承基类,并实现相应接口,可选择实现,我们这里需要两个接口配合,分别是 createHtmlAfterUserInfo 和 runJs 接口
- 代码实现,附源码 App_Dongta_ThreadDisplay
- 该钩子的注册方式为
<m_PwThreadDisplay> <app_dongta> <class>EXT:dongta.service.srv.App_Dongta_ThreadDisplay</class> <description>动他一下帖子页按钮</description> </app_dongta> </m_PwThreadDisplay>
- 但是,该注册方式注入的钩子是伴随PwThreadDisplay类的启动而自动启动的,所以一般用该方式注册的,都是偏内在逻辑处理的。显然,我们这里主要是展示内容的,这个我们就要用到我们下一个此类钩子的注册方式
添加Controller-Hook
- 由于Model-Hook的自动启动特性,让重用一个业务流程变得困难
- 自动注入的扩展类没有跟外部输入交互的能力
- 所以为了解决这两个问题,就出现了Controller-Hook
- 通常在实例化业务流程类时,会有如下类似代码
$threadDisplay = new PwThreadDisplay($tid, $this->loginUser); $this->runHook('c_read_run', $threadDisplay);
- 该钩子的注册方式为
<c_read_run> <app_dongta> <class>EXT:dongta.service.srv.App_Dongta_ThreadDisplayDoDongtaInjector</class> <method>run</method> <expression>config:site.app.dongta.read.ifopen==1</expression> <description>动他一下帖子页按钮</description> </app_dongta> </c_read_run>
- runHook就是将App_Dongta_ThreadDisplayDoDongtaInjector类中run方法生成的扩展服务类注入到$threadDisplay,注入后,跟使用 m_PwThreadDisplay方式是一样的
- Controller-Hook中class的要求
-
- 必须继承PwBaseHookInjector,那么在这个类中就可以跟controller一样使用$this->getInput方法接收外部参数了
- 必须返回一个满足业务流程类需求的扩展服务类