近来因为布置了每个人了解一个框架,paperen依旧选择CI(从这方面也可以看出我很专一…)作为进一步研究,所以paperen又再次看了它的核心代码,而看到hooks的实现时不禁有感而发,感叹之前自己试着在CI的基础上设计一个hook的做法实在太SB。

paperen并不想放什么概念的跟大家分享,而是从自己博客开始。

你看到博客的右边栏,在进入某篇文章详细时是会多出这个附带图片的栏目,就是将该文章中所有的附带图片在此用缩略图形式展现,方便浏览者点击查看。这个地方就是应用了钩子,或许说到这里你还是很模糊,但下面就会进行更多解析。

如果是你要实现这个功能,你会怎样实现?怎样设计?这个文章图片栏目是动态的,也就是说不是每个页面都会呈现只是在某文章详细页面才会出现。

所以我们自然而然地脑中会想到用if来控制


if(是文章详细页面) {
呈现文章图片缩略图栏目
}


没错,那就试着按这个思路来调整一下代码看看如何

这是边栏视图文件的代码

<!-- sidebar -->
<div class="span3 sidebar">

    <div class="extra">
 <a href="<?php echo rss_url(); ?>" class="icon-paperen icon-rss" rel="rss" title="RSS源"></a>
 <div class="c"></div>
    </div>

 <?php $this->hooks->_call_hook('post_images'); ?>

 <?php $this->load->module('calendar/common/locus', array()); ?>
 <?php $this->load->module('post/common/hot', array()); ?>
 <?php $this->load->module('comment/common/recent', array()); ?>
 <?php $this->load->module('link/common/all', array()); ?>
</div>
<!-- sidebar -->

暂且抛开$this->hools->_call_hook('post_images'),关于这个文章图片缩略图栏目其实也对应一个模块,在modules/post/controllers/common.php的images方法

 /**
  * 獲取指定ID文章的附帶圖片列表
  * @param int $post_id 文章ID
  */
 public function images( $post_id )
 {
  $data = array( );
  $post_id = intval( $post_id );

  // 根據文章ID獲取文章圖片數據
  $post_images = $this->querycache->get( 'post', 'get_images', $post_id );

  // 總數
  $data['total'] = count( $post_images );
  $data['post_images'] = $this->_format_by_col( $post_images, 3 );

  $this->load->view( 'images', $data );
 }

所以要调用这个栏目只要在视图中

$this->load->module('post/common/images', array(文章ID));

加上上面我们的条件判断于是代码写成

<?php if( isset( $post_id ) && $post_id ) { ?>
<?php $this->hooks->_call_hook('post_images'); ?>
<?php } ?>

那更改后的边栏视图的代码就是


<!-- sidebar -->
<div class="span3 sidebar">

 <div class="extra">
 <a href="<?php echo rss_url(); ?>" class="icon-paperen icon-rss" rel="rss" title="RSS源"></a>
 <div class="c"></div>
 </div>

 <?php if( isset( $post_id ) && $post_id ) { ?>
 <?php $this->load->module('post/common/images', array( $post_id ) ); ?>
 <?php } ?>

 <?php $this->load->module('calendar/common/locus', array()); ?>
 <?php $this->load->module('post/common/hot', array()); ?>
 <?php $this->load->module('comment/common/recent', array()); ?>
 <?php $this->load->module('link/common/all', array()); ?>
</div>
<!-- sidebar -->


locus、hot、recent、all,其实就是那个日历、热门文章、最近评论、所有链接,可以看到我都把它们做成了一个模块

那再看看文章详细页面的视图

<?php $this->load->module( 'header/common', array( 'archive', $post['title'] ) ); ?>
<div class="span9 main">

 <div class="post">
  <div class="row-fluid">
   <?php $this->load->module('post/common/meta', array($post, 'span2', TRUE)); ?>
   <div class="span10 post-entry">
    <h2><?php echo $post['title']; ?></h2>
    <div class="post-content"><?php echo $post['content']; ?></div>
   </div>
   <div class="c"></div>
   <?php $this->load->module( 'comment/common/index', array( $post['id'] ) );?>
  </div>
 </div>

</div>
<script>
$('.post-content .post-image').slimbox();
</script>
<?php $this->load->module( 'sidebar/common', array( ) ); ?>
<?php $this->load->module( 'footer/common', array( ) ); ?>

任何一个页面都需要加载header、sidebar、footer,而中间的部分自由发挥。所以你看到如果要实现上面那个动态加载文章图片栏目的话,在调用sidebar模块时就需要传入$post_id,而sidebar的common的index方法也要有一个可选填参数$post_id

<?php $this->load->module( 'sidebar/common', array( $postid ) ); ?>

sidebar的common的index也要改为

class Sidebar_Common_Module extends CI_Module
{

 /**
  * 加载边栏
  */
 public function index( $postid = '' )
 {
  $data = array(
   'postid' => $postid,
  );
  $this->load->view( 'index', $data );
 }

}

这倒是能实现功能,但是仔细一想如果我还要在文章详细页面的边栏附加上作者的简要信息,那岂不是又要多加一个参数?我将需要在sidebar的common的index方法中再加一个可选填参数$author_id,再在sidebar的视图文件index.php加上if进行动态选择作者简介栏目的出现,如果还要加一些额外栏目,这样的话我看就不合理了。

暂且先不谈引入钩子这个,从这个方面考虑的话,我们应该怎样使用比较舒服的方法解决上面的问题

要知道$post_id这个变量是在post的common的single中出现的,调用sidebar的视图,sidebar/common/index的视图里面再调用post/common/images,那么我可以使用全局变量来实现这个目的

在post的single方法里面,声明一个全局变量

global $_globals;
$_globals['postid'] = $post_data['id'];

那么sidebar的视图

<?php
 global $_globals;
 if( isset( $_globals['postid'] ) && $_globals['postid'] ) { 
?>
<?php $this->load->module('post/common/images', array($post_id)); ?>
<?php } ?>
<?php $this->load->module('calendar/common/locus', array()); ?>
<?php $this->load->module('post/common/hot', array()); ?>
<?php $this->load->module('comment/common/recent', array()); ?>
<?php $this->load->module('link/common/all', array()); ?>

当然这已经避开了增加参数来实现这个功能,但是却增加了视图的代码量与简洁。

我想将代码写得更加简洁,更加美观,我想在视图中像调用一个函数或者调用一个类方法就能实现动态显示栏目

假设有这么一个机制,我们可以注册一些方法,当特定的时候我们也可以调用它,比如在边栏我就写成

<?php $this->hook->trigger('post_images'); ?>
<?php $this->load->module('calendar/common/locus', array()); ?>
<?php $this->load->module('post/common/hot', array()); ?>
<?php $this->load->module('comment/common/recent', array()); ?>
<?php $this->load->module('link/common/all', array()); ?>

$this->hook->trigger('post_images');这个就是激活post_images这个东西来呈现文章的附加图片(如果存在这个post_image钩子的话),那么我们只需要在post的single方法中对这个方法进行注册就可以了

/**
 * 加载指定ID或URL标题的文章
 * @param string $postid_or_urltitle 指定ID或URL标题
 */
public function single( $postid_or_urltitle )
{
 //
 $post_data = $this->_get_by_postid_or_urltitle( $postid_or_urltitle );

 // 没有找到
 if ( empty( $post_data ) ) show_404();
 $this->_post_data = $post_data;

 // 更新阅读数
 $this->_update_post_clicknum( $post_data['id'] );

 // 加工
 $this->_prepare( FALSE );

 $post_data = $this->_post_data;

 // 註冊邊欄文章圖片挂入點
 $this->hook->register( 'post_images', 'module_post/common/images', $post_data['id'] );

 global $_globals;
 $_globals['postid'] = $post_data['id'];

 $data = array(
  'post' => $post_data,
 );

 $this->load->view( 'single', $data );
}

$this->hook->register,这里就会注册post_images这个钩子,并传入参数,若后续有调用这个钩子则会触发钩子的动作,这样是不是更简洁与方便了?而这个是自己写的一个hook,当我了解到CI_Hook也能实现同样的功能时,已经感到自己又做了一些多余的东西……不过当留个纪念吧,以下是自己的Hooks代码

<?php

/**
* 作為博客的鉤子,用於在其他模塊或者控制器中為其他模塊增加內容或元素
* 依賴于CI模型
* @author paperen
*/
class Hook
{

    /**
     * 挂入点
     * @var array
     */
    private $_hook_points = array( );

    /**
     * CI實例
     * @var object
     */
    private $_CI;

    function __construct()
    {
        $this->_CI = & get_instance();
    }

    /**
     * 註冊挂入点
     * @param string $hp 挂入点
     * @param string $name 模塊+控制器(module_開頭) 其他 函數調用
     * @param mixed $args 參數
     */
    public function register( $hp, $name, $args = array( ) )
    {
        $this->_hook_points[$hp][] = array(
            'name' => $name,
            'args' => $args,
        );
    }

    /**
     * 執行hook
     * @param string $hp 挂入点
     */
    public function trigger( $hp )
    {
        if ( !isset( $this->_hook_points[$hp] ) || empty( $this->_hook_points[$hp] ) ) return FALSE;
        foreach ( $this->_hook_points[$hp] as $single )
        {
            $name = $single['name'];
            $args = $single['args'];
            if ( strpos( $name, 'module_' ) !== FALSE )
            {
                // 模塊調用
                $this->_CI->load->module( str_replace( 'module_', '', $name ), array( $args ) );
            }
            else
            {
                // 函數調用
                call_user_func( $name, $args );
            }
        }
    }

}

// end of Hook

在CI_Hooks基础上实现这个功能,因为本身CI就已经有hook的概念,只不过它指定的钩子只有以下这些pre_system,pre_controller,post_controller_constructor,post_controller,display_override,cache_override,post_system 具体请翻看手册 http://codeigniter.org.cn/user_guide/general/hooks.html,钩子都要在config/hooks.php中定义好。

CI_Hooks是不支持动态注册钩子的,但要实现这个就必须允许动态注册,所以我稍微扩展了CI_Hooks

<?php

/**
* 继承CI_Hooks,在其基础上加入了允许动态注册钩子的方法
* @author paperen
*/
class MY_Hooks extends CI_Hooks
{

    /**
     * override the Hooks Preferences
     *
     * @access    private
     * @return    void
     */
    function _initialize()
    {
        $CFG =& load_class('Config', 'core');

        // If hooks are not enabled in the config file
        // there is nothing else to do

        if ($CFG->item('enable_hooks') == FALSE)
        {
            return;
        }

        // Grab the "hooks" definition file.
        // If there are no hooks, we're done.

        if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'))
        {
            include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php');
        }
        elseif (is_file(APPPATH.'config/hooks.php'))
        {
            include(APPPATH.'config/hooks.php');
        }


        if ( ! isset($hook) OR ! is_array($hook))
        {
            $this->hooks =& $hook;
        }

        $this->enabled = TRUE;
    }

    function _register( $hook_name, $args = array() )
    {
        $this->hooks[$hook_name][] = $args;
    }

}

// end of Hook

其实改动很少,多了一个_register的方法(使用下划线开头只是为了迎合CI_Hooks的方法命名…我也不知道CI为什么要加下划线,可能CI_Hooks并不是设计用来手动调用的吧),同时值得注意是重写了_initialize方法其实重写这个方法是为了实现动态注册钩子,你可以看回CI_Hooks的代码(system/core/Hooks.php)

if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'))
{
 include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php');
}
elseif (is_file(APPPATH.'config/hooks.php'))
{
 include(APPPATH.'config/hooks.php');
}


if ( ! isset($hook) OR ! is_array($hook))
{
 return;
}

$this->hooks =& $hook;
$this->enabled = TRUE;
判断是否存在config/hooks.php并加载(这里也会根据ENVIRCONMENT加载不同的hooks.php配置,不管这么多,反正就是加载了hooks.php钩子配置文件),如果我不在hooks.php里面写任何钩子配置信息的话,那么之后的(isset($hook)) OR !is_array($hook)肯定过不去,所以要实现动态注册钩子这个地方必须要改一下


if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'))
{
 include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php');
}
elseif (is_file(APPPATH.'config/hooks.php'))
{
 include(APPPATH.'config/hooks.php');
}


if ( ! isset($hook) OR ! is_array($hook))
{
 $this->hooks =& $hook;
}

$this->enabled = TRUE;

将$this->hooks =& $hook;放到if里面就好了,因为有$hook时才需要初始化$this->hooks

那么post的single方法改为

/**
 * 加载指定ID或URL标题的文章
 * @param string $postid_or_urltitle 指定ID或URL标题
 */
public function single( $postid_or_urltitle )
{
 //
 $post_data = $this->_get_by_postid_or_urltitle( $postid_or_urltitle );

 // 没有找到
 if ( empty( $post_data ) ) show_404();
 $this->_post_data = $post_data;

 // 更新阅读数
 $this->_update_post_clicknum( $post_data['id'] );

 // 加工
 $this->_prepare( FALSE );

 $post_data = $this->_post_data;

 // 註冊邊欄文章圖片挂入點
 $this->hooks->_register( 'post_images', array(
  'class' => 'Post_Common_Module',
  'function' => 'images',
  'filename' => 'common.php',
  'filepath' => 'modules/post/controllers',
  'params' => $post_data['id'],
 ) );

 $data = array(
  'post' => $post_data,
 );

 $this->load->view( 'single', $data );
}
在此动态注册一个叫post_images的钩子,第二个参数就如CI手册的钩子参数那样就可以了。

sidebar的视图变为

<?php $this->hooks->_call_hook('post_images'); ?>

<?php $this->load->module('calendar/common/locus', array()); ?>
<?php $this->load->module('post/common/hot', array()); ?>
<?php $this->load->module('comment/common/recent', array()); ?>
<?php $this->load->module('link/common/all', array()); ?>

使用_call_hook调用这个钩子就好了,到此关于钩子的研究到此结束