這篇文章被收錄與phpwind開(kāi)發(fā)文檔之中,教主同學(xué)寫(xiě)的一篇phpwind9插件開(kāi)發(fā)速成心法。
什么是插件
插件是增加網(wǎng)站功能,實(shí)現(xiàn)差異化運(yùn)營(yíng)的重要手段
通常所說(shuō)的插件,其實(shí)都是泛指插件與擴(kuò)展,兩者區(qū)別是:
- 插件 具有獨(dú)立訪問(wèn)地址,功能可以自成體系的功能模塊
- 擴(kuò)展 通過(guò)鉤子系統(tǒng)注入到系統(tǒng)中以達(dá)到接管或改進(jìn)原有系統(tǒng)的功能模塊
有一個(gè)比較容易混淆的概念是,phpwind9.0的導(dǎo)航上有一個(gè)應(yīng)用中心 這里所謂的應(yīng)用,其實(shí)都是插件(插件也叫應(yīng)用)
插件的原理
- 插件 是通過(guò)框架分發(fā)器的模式匹配功能來(lái)實(shí)現(xiàn)的,所以開(kāi)發(fā)一個(gè)插件本質(zhì)上跟開(kāi)發(fā)應(yīng)用是一樣的
- 擴(kuò)展 通過(guò)鉤子系統(tǒng)注入到系統(tǒng)中的功能模塊,依賴系統(tǒng)的功能運(yùn)行而運(yùn)行
準(zhǔn)備工作
注冊(cè)成為開(kāi)發(fā)者
- 成為開(kāi)發(fā)者,可以使你開(kāi)發(fā)的插件為所有phpwind站點(diǎn)使用
- 可以讓你的插件成為唯一,以防與其他開(kāi)發(fā)者命名沖突
- 當(dāng)然如果你愿意,這可以為你帶來(lái)一筆收入
- 開(kāi)發(fā)者注冊(cè)地址
了解開(kāi)發(fā)助手
- 進(jìn)入后臺(tái)->站點(diǎn)設(shè)置->全局參數(shù)-> DEBUG 模式運(yùn)行站點(diǎn),輸入520(或1314),開(kāi)啟開(kāi)發(fā)者模式
- 進(jìn)入后臺(tái)->云平臺(tái)->應(yīng)用管理->開(kāi)發(fā)助手 按照提示輸入,建立一個(gè)插件
- 在src/extensions/目錄下,能找到你剛生成的插件文件夾
- 如果DEBUG模式為1314,還可以在前臺(tái)頁(yè)面上看到已埋的鉤子標(biāo)識(shí)
開(kāi)發(fā)者版本
- 為了更好地與開(kāi)發(fā)者保持交流,我們實(shí)時(shí)公布研發(fā)版本的代碼動(dòng)態(tài),此上面的更新將全部放入下個(gè)版本
- 下載地址 https://github.com/phpwind/nextwind
插件開(kāi)發(fā)規(guī)范
由于系統(tǒng)可能同時(shí)存在多個(gè)插件,尤其是云平臺(tái)開(kāi)通之后,插件安裝極其便利,為了避免插件之間的沖突,定義如下規(guī)范:
插件唯一標(biāo)識(shí)符
-
- 標(biāo)識(shí)符由字符、數(shù)字組成,不能包含_
- 標(biāo)識(shí)符需能明確表達(dá)插件的含義,否則無(wú)法通過(guò)云平臺(tái)驗(yàn)證(不提交到云平臺(tái)的插件略過(guò))
- 標(biāo)識(shí)符可以到云平臺(tái)申請(qǐng)
- 比如當(dāng)前申請(qǐng)到的唯一標(biāo)識(shí)符為sign,以下皆以此為例
數(shù)據(jù)庫(kù)命名規(guī)范
-
- 表命名 統(tǒng)一添加表前綴,表前綴為pw_app_sign_(pw_前綴是站長(zhǎng)安裝時(shí)選擇的,可能各個(gè)站不一樣)
- 擴(kuò)展字段 插件不建議在原生數(shù)據(jù)表上增加字段,但并不限制。所有增加的字段必須帶上前綴app_sign_
類(lèi)命名(文件名)規(guī)范
-
- 所有類(lèi)命名(Controller除外)必須帶上前綴 App_Sign_,駝峰形式+下劃線_
插件配置規(guī)范
- 為防止插件之間配置項(xiàng)命名沖突,我們作如下約定:
- 獨(dú)立配置域 配置域命名規(guī)則為 app_sign
- 公共配置域 對(duì)于某些配置到公共配置域的配置項(xiàng),配置項(xiàng)加前綴 app.sign.
鉤子鍵名(alias)命名規(guī)范
-
- alias命名加前綴 app_sign
后臺(tái)菜單項(xiàng)鍵名命名規(guī)范
-
- 鍵名命名加前綴 app_sign
用戶組權(quán)限鍵名命名規(guī)范
-
- 鍵名命名加前綴 app_sign
開(kāi)發(fā)演示
選取一個(gè)題材
我們選取互聯(lián)網(wǎng)上SNS社區(qū)比較流行的一款增加用戶粘性的互動(dòng)游戲“動(dòng)他一下”作為本次的開(kāi)發(fā)示例。
別忘了,上開(kāi)發(fā)者平臺(tái)驗(yàn)證一下,是否已經(jīng)有人開(kāi)發(fā)過(guò)了哦
這個(gè)插件做些什么
- 應(yīng)該有一個(gè)獨(dú)立頁(yè)面,列出我的所有好友,我可以輕松的動(dòng)“他們”一下
- 訪問(wèn)別人空間的時(shí)候,我可以動(dòng)“他們”一下
- 看別人帖子的時(shí)候,我也要?jiǎng)?ldquo;他們”一下;
- 哦,別忘了,還需要一個(gè)后臺(tái)設(shè)置
創(chuàng)建應(yīng)用
- 進(jìn)入后臺(tái)->云平臺(tái)->應(yīng)用管理->開(kāi)發(fā)助手
- 按照提示,填寫(xiě)相關(guān)信息
- 這里有三個(gè)選擇需要注意一下
-
- 選擇應(yīng)用額外安裝服務(wù) 這個(gè)有三個(gè)菜單可以選擇,我們只需要在主導(dǎo)航中添加應(yīng)用,故勾選nav_main
- 是否需要管理后臺(tái)界面 選需要,會(huì)幫我們自動(dòng)生成一個(gè)菜單項(xiàng)和演示頁(yè)面
- 是否生成數(shù)據(jù)服務(wù)類(lèi) 本應(yīng)用要用到數(shù)據(jù)庫(kù),所以選需要
- 提交,馬上創(chuàng)建
- 在應(yīng)用管理,已安裝菜單下,就能發(fā)現(xiàn)剛才創(chuàng)建的“動(dòng)他一下”應(yīng)用了
- 嗯,logo不對(duì),找到 src/applications/extensions/dongta/res/images 替換一張logo.jpg
- 刷新,搞定!
編寫(xiě)我的后臺(tái)
- 大部分的插件編寫(xiě)都是先從后臺(tái)開(kāi)始的,因?yàn)楹笈_(tái)功能比較簡(jiǎn)單,無(wú)非就是“設(shè)置”和“管理”,就當(dāng)熱身了
- 說(shuō)到“設(shè)置”,就不能不說(shuō) PwConfigSet 這個(gè)類(lèi)庫(kù)了,使用她,輕松搞定,別忘了 插件配置規(guī)范 哦
- 附后臺(tái)源碼
-
- ManageController.php 后臺(tái)管理controller
- manage_run.htm 后臺(tái)管理模板
編寫(xiě)底層服務(wù)
因?yàn)槲乙赖降资钦l(shuí)動(dòng)了我一下,所以我需要建一張表來(lái)記錄這些數(shù)據(jù),數(shù)據(jù)表設(shè)計(jì)如下,別忘了 數(shù)據(jù)庫(kù)命名規(guī)范 哦:
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
- 找到自動(dòng)建立的Dao,Dm,Ds示例文件,發(fā)現(xiàn)文件名稍微不一樣,沒(méi)關(guān)系,改一下
- 接下來(lái),就是coding,這里可以參考應(yīng)用開(kāi)發(fā)步驟。附底層服務(wù)源碼,別忘了 類(lèi)命名(文件名)規(guī)范 哦:
當(dāng)然,在實(shí)際開(kāi)發(fā)過(guò)程中,這一步很少能一口氣寫(xiě)完的;沒(méi)關(guān)系,需要的時(shí)候,回過(guò)頭來(lái)繼續(xù)完善。
編寫(xiě)我的前臺(tái)主頁(yè)面
哈哈,終于到了,最關(guān)鍵的地方了,這里就是各位自由發(fā)揮的地方了
- 我的插件功能很簡(jiǎn)單,但是還是有幾個(gè)地方不能忘記
-
- 后臺(tái)設(shè)置了關(guān)閉,前臺(tái)不能訪問(wèn)
- 我不允許未登錄用戶訪問(wèn)
//示例代碼 if (!Wekit::C('site', 'app.dongta.ifopen')) { //應(yīng)用是否關(guān)閉 $this->showError('動(dòng)他一下應(yīng)用,關(guān)閉了'); } if (!$this->loginUser->isExists()) { //應(yīng)用是否登錄 $this->showError('login.not'); }
- 繼續(xù)coding,附源碼
-
- IndexController.php 前臺(tái)controller
- index_run 首頁(yè)模板
- index_my 誰(shuí)了我頁(yè)面模板
- index_act ajax請(qǐng)求動(dòng)作列表模板
- 到此,我們的獨(dú)立插件其實(shí)已經(jīng)完成了,但是為了能更好地融入到系統(tǒng)中,我們還要添加兩個(gè)鉤子!緵](méi)錯(cuò),搶占入口,就是搶占先機(jī)!】
添加Simple-Hook
- 找到空間個(gè)人頭像下面的鉤子 s_space_user_info
- 找到后臺(tái)->云平臺(tái)->應(yīng)用管理->動(dòng)他一下->設(shè)計(jì)->xml 在 inject-services 項(xiàng)下面添加
<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>動(dòng)他一下空間按鈕</description> </app_dongta> </s_space_user_info>
- 完成 App_Dongta_Service 服務(wù),附源碼:
添加Model-Hook
- 找到帖子閱讀頁(yè)個(gè)人頭像下面的鉤子 m_PwThreadDisplay.createHtmlAfterUserInfo
- 找到其業(yè)務(wù)流程類(lèi) PwThreadDisplay
- 找到其擴(kuò)展擴(kuò)展服務(wù)接口基類(lèi) PwThreadDisplayDoBase
- 繼承基類(lèi),并實(shí)現(xiàn)相應(yīng)接口,可選擇實(shí)現(xiàn),我們這里需要兩個(gè)接口配合,分別是 createHtmlAfterUserInfo 和 runJs 接口
- 代碼實(shí)現(xiàn),附源碼 App_Dongta_ThreadDisplay
- 該鉤子的注冊(cè)方式為
<m_PwThreadDisplay> <app_dongta> <class>EXT:dongta.service.srv.App_Dongta_ThreadDisplay</class> <description>動(dòng)他一下帖子頁(yè)按鈕</description> </app_dongta> </m_PwThreadDisplay>
- 但是,該注冊(cè)方式注入的鉤子是伴隨PwThreadDisplay類(lèi)的啟動(dòng)而自動(dòng)啟動(dòng)的,所以一般用該方式注冊(cè)的,都是偏內(nèi)在邏輯處理的。顯然,我們這里主要是展示內(nèi)容的,這個(gè)我們就要用到我們下一個(gè)此類(lèi)鉤子的注冊(cè)方式
添加Controller-Hook
- 由于Model-Hook的自動(dòng)啟動(dòng)特性,讓重用一個(gè)業(yè)務(wù)流程變得困難
- 自動(dòng)注入的擴(kuò)展類(lèi)沒(méi)有跟外部輸入交互的能力
- 所以為了解決這兩個(gè)問(wèn)題,就出現(xiàn)了Controller-Hook
- 通常在實(shí)例化業(yè)務(wù)流程類(lèi)時(shí),會(huì)有如下類(lèi)似代碼
$threadDisplay = new PwThreadDisplay($tid, $this->loginUser); $this->runHook('c_read_run', $threadDisplay);
- 該鉤子的注冊(cè)方式為
<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>動(dòng)他一下帖子頁(yè)按鈕</description> </app_dongta> </c_read_run>
- runHook就是將App_Dongta_ThreadDisplayDoDongtaInjector類(lèi)中run方法生成的擴(kuò)展服務(wù)類(lèi)注入到$threadDisplay,注入后,跟使用 m_PwThreadDisplay方式是一樣的
- Controller-Hook中class的要求
-
- 必須繼承PwBaseHookInjector,那么在這個(gè)類(lèi)中就可以跟controller一樣使用$this->getInput方法接收外部參數(shù)了
- 必須返回一個(gè)滿足業(yè)務(wù)流程類(lèi)需求的擴(kuò)展服務(wù)類(lèi)
- 代碼實(shí)現(xiàn),附源碼 App_Dongta_ThreadDisplayDoDongtaInjector