其实在 Yii 中,一个插件的模块可以通过 Widget 或者 Module 来实现。后来考虑到希望做成 Wordpress 那样,可以安装,卸载,配置,甚至灵活实现权限和菜单的控制,所以抽空研究了下 Wordrpress 的插件实现机制,并集成到 Yii 中。不过最终和同事讨论以后还是决定改造 Module 来实现的插件机制,这里记录一下 Wordrpress 的插件思想集成到 Yii 中的过程。

类似 Wordrpress 的钩子机制插件原理主要就是:注册钩子,放置钩子,运行钩子,不多说,直接上代码。

1.新建 app/protected/components/UPlugin.php

<?php

/**
 * 插件机制 Component
 */
class UPlugin extends CApplicationComponent {

        public $pluginDir = '';
        private $_listeners = array();

        /**
         * 初始化
         */
        public function init() {
                parent::init();
                $plugins = $this->getActivePlugs();

                if ($plugins && is_array($plugins)) {
                        foreach ($plugins as $plugin) {
                                $path = $this->pluginDir . $plugin['directory'] . '/' . ucfirst($plugin['directory']) . '.php';
                                if (file_exists($path)) {
                                        require_once ($path);
                                        $class = ucfirst($plugin['directory']);
                                        if (class_exists($class)) {
                                                new $class($this);
                                        }
                                }
                        }
                }
        }

        /**
         *  注册hook
         * @param string $hook
         * @param object $reference
         * @param string $method
         */
        public function register($hook, &$reference, $method) {
                $key = get_class($reference) . '->' . $method;
                $this->_listeners[$hook][$key] = array(&$reference, $method);
        }

        /**
         * 执行 hook
         * @param string $hook
         */
        public function trigger($hook) {
                if ($this->checkHookExist($hook)) {
                        foreach ($this->_listeners[$hook] as $listener) {
                                $class = $listener[0];
                                $method = $listener[1];
                                if (method_exists($class, $method)) {
                                        $args = array_slice(func_get_args(), 1);
                                        call_user_func_array(array($class, $method), $args);
                                }
                        }
                }
        }

        /**
         * 检查hook是否存在
         * @param string $hook
         * @return boolean
         */
        public function checkHookExist($hook) {
                if (isset($this->_listeners[$hook]) && is_array($this->_listeners[$hook]) && count($this->_listeners[$hook])) {
                        return TRUE;
                }
                return false;
        }

        /**
         * 获取激活的插件
         * @return array
         */
        public function getActivePlugs() {
                $arr = array(
                    'helloworld' => array(
                        'name' => 'helloworld',
                        'directory' => 'hello'
                    ),
                );
                return $arr;
        }

        /**
         * 插件模板渲染
         * @param string $pluginName
         * @param string $templateName
         * @param array $data
         */
        public function render($pluginName, $templateName, $data = array()) {
                $template = new Template;
                $template->init($pluginName, $templateName, $data, $this->pluginDir);
                $template->outPut();
        }

}

/**
 * 简单的模板输出类
 * @author Ryan
 */
final class Template {

        public $pluginName = null;
        public $pluginDir = null;
        public $templateName = null;
        public $data = array();
        public $outPut = null;

        public function init($pluginName = '', $templateName = '', $data = array(), $pluginDir = '') {
                $this->pluginName = $pluginName;
                $this->templateName = $templateName;
                $this->pluginDir = $pluginDir;
                $this->data = $data;
                $this->fetch();
        }

        /**
         * 加载模板文件
         * @access public
         * @param string $file
         */
        public function fetch() {
                $viewFile = $this->pluginDir . $this->pluginName . '/views/' . $this->templateName . '.php';
                if (file_exists($viewFile)) {
                        extract($this->data);
                        ob_start();
                        include $viewFile;
                        $content = ob_get_contents();
                        ob_end_clean();
                        $this->outPut = $content;
                } else {
                        trigger_error('加载 ' . $viewFile . ' 模板不存在');
                }
        }

        /**
         * 输出模板
         * @access public
         * @return string
         */
        public function outPut() {
                echo $this->outPut;
        }

}

getActivePlugs 这里获取激活的插件我为了演示写死了,其实这里的业务逻辑应该是从配置文件或者数据库中读取已经激活的插件的相关的信息。

final class Template 这里我只是为了实现一个简单模板输出类,主要用在插件开发中实现 MVC 的类似的感觉,为了避免将插件的模板作为字符串写到 action 里面输出,等下就知道这个爽了。

2.在配置文件中配置这个 components,在 app/protected/config/main.php 中添加:

 'components' => array(
                'UPlugin' => array(
                    'class' => 'application.components.UPlugin',
                    'pluginDir' => realpath(dirname(__FILE__)) . '/../plugins/',
                ),
),

pluginDir 就是插件所放的目录了。

3.新建一个插件 app/protected/plugins/hello,Hello 插件具体目录插件的目录结构如下:

plugins/
└── hello
    ├── Hello.php
    └── views
        └── index.php

这里的 views 下面就是模板文件了,Hello.php 就是插件的业务逻辑。有点象 MVC 没有 M 层

Hello.php 代码如下:

<?php

class Hello
{

    private $plugin = NULL;

    public function __construct($obj)
    {
        $this->plugin = $obj;
        $this->plugin->register('test', $this, 'plugin'); //将plugin方法注册到test钩子上
    }

    public function plugin()
    {
        echo 'this is the plugin';
    }

}

这样我们用 Yii 的 Blog Demo 来做实验,在 Blog 的首页我们来触发这个 test 的钩子上的 plugin 方法:

<?php if (!empty($_GET['tag'])): ?>
        <h1>Posts Tagged with <i><?php echo CHtml::encode($_GET['tag']); ?></i></h1>
<?php endif; ?>

        <p style="color: #009933"><?php Yii::app()->UPlugin->trigger('test'); ?></p>

<?php
$this->widget('zii.widgets.CListView', array(
    'dataProvider' => $dataProvider,
    'itemView' => '_view',
    'template' => "{items}\n{pager}",
));
?>

关键代码就是 UPlugin->trigger(‘test’); ?>了,这样刷新一下页面,我们就会发现下面的状况:

QQ图片2013092620582

这样看起来插件机制已经起效了,但是我们可以不可以在插件中来实现插件机制呢,其实也是可以的,再来演示一下,随便来用上我们上面提到的 Tepmlete 的模板输出功能。

修改 Hello.php 如下 :

<?php

class Hello {

        private $plugin = NULL;

        public function __construct($obj) {
                $this->plugin = $obj;
                $this->plugin->register('test', $this, 'show');
        $this->plugin->register('test2', $this, 'show2');
        }

        public function show() {
                $data = array('foo' => "this is foo");
                $this->plugin->render('hello', 'index', $data);
        }

        public function show2() {
                echo "this is the show2";
        }
}

编写模板文件:Hello/views/ index.php

<div style="color:red;"><?php echo $foo; ?></div>
<p style="color: #009933"><?php Yii::app()->UPlugin->trigger('test2'); ?></p>

UPlugin->trigger(‘test2’); ?>是另外一个钩子,触发 show2 方法的钩子,这样我们就可以在一个插件中也可以定义钩子,很灵活。

我们再来看下效果:

QQ图片2013092620574

转载请注明: 转载自Yuansir-web 菜鸟 | LAMP 学习笔记

本文链接地址: Yii 中实现插件机制

知识共享许可协议 本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可