CI框架源码阅读笔记6 扩展钩子 Hook.php,cihook.php
CI框架源码阅读笔记6 扩展钩子 Hook.php,cihook.php
CI框架允许你在不修改系统核心代码的基础上添加或者更改系统的核心功能(如重写缓存、输出等)。例如,在系统开启hook的条件下(config.php中$config['enable_hooks'] = TRUE;
),通过添加特定的钩子,可以让系统在特定的时刻触发特定的脚本:
<span>$hook</span>['post_system'] = <span>array</span><span>( </span>'class' => 'frameLog', 'function' => 'postLog', 'filename' => 'post_system.php', 'filepath' => 'hooks',<span> );</span>
上述钩子定义了一个post_system的钩子,用于在最终的页面渲染之后的脚本处理(参数的含义可以参考后面或者手册,这里暂时不做更多解释)。
那么问题来了:
我们一步步来看。
1. 钩子是什么
百度百科上对于钩子的定义是:
<p><span>钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。</span></p>
从上述定义我们可以看出几点:
2. CI中预定义钩子
CI中提供了7个可用的预设挂钩点,分别是:
pre_system: 指在系统加载前期的钩子
pre_controller:调用控制器之前的钩子,路由与安全性检查已经完毕
post_controller_constructor:控制器实例化之后,任何方法调用之前
post_controller:控制器完全运行之后
display_override:重写display
cache_override :重写缓存
post_system:最终的页面发送到客户端之后
3. CI中钩子的实现
CI中钩子的核心功能是由Hook组件完成的,先看该组件的类图:
其中:
enabled: 钩子功能是否开启的标志。
hooks :保存系统中启用的钩子列表
in_progress:之后我们会看到,这个标志位用于防止钩子之间的互相调用而导致的死循环。
_construct是Hook组件的构造函数,这其中调用了_initialize来完成初始化的工作
_call_hook: 调用_run_hook来call指定的钩子程序。之前CodeIgniter.php中我们已经看到,_call_hook是实际提供给外部调用的接口。
_run_hook: 实际执行钩子程序的函数
在开始之前,我们先贴出预定义钩子的结构。这个结构可能会贯穿在源代码的始终,因而我们有必要知道该结构的参数含义。
<span>$hook</span>['xx'] = <span>array</span><span>( </span>'class' => 'xx', <span>//</span><span>钩子调用的类名,可以为空</span> 'function' => 'xx',<span>//</span><span>钩子调用的函数名</span> 'filename' => 'xx',<span>//</span><span>该钩子的文件名</span> 'filepath' => 'xx',<span>//</span><span>钩子的目录</span> 'params' => 'xx'<span>//</span><span>传递给钩子的参数</span> );
1. 钩子组件初始化
_initialize函数用于钩子组件的初始化,该函数主要完成的工作有:
(1) 检查配置文件中hook功能是否被启用,这需要加载Config(配置管理组件):
$CFG =& load_class('Config', 'core'); if ($CFG->item('enable_hooks') == FALSE) { return; }
(2) 加载定义的hook列表
同样,你可以设定不同的ENVIRONMENT启用不同的hook,如果有的话,优先加载ENVRIONMENT下的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'); }
(3) Hook的检查。如果未设置任何hook,或者设置的hook格式错误,则不作任何处理,直接退出:
if ( ! isset($hook) OR ! is_array($hook)) { return; }
经过initialize之后,Hook::hooks中存储了已经定义的hook列表:
$this->hooks =& $hook;
2. Call指定的钩子
_call_hook是主程序中直接调用的接口。该接口主要的工作有:
(1). 检查钩子是否被启用,以及call的钩子是否被预定义(如果未启用或者call的钩子不存在,则直接返回):
if ( ! $this->enabled OR ! isset($this->hooks[$which])) { return FALSE; }
(2). 检查同一个挂钩点是否启用了多个钩子,如果有,则依次执行之:
if (isset($this->hooks[$which][0]) AND is_array($this->hooks[$which][0])) { foreach ($this->hooks[$which] as $val) { $this->_run_hook($val); } }
(3). 否则,只有一个钩子,执行它
else { $this->_run_hook($this->hooks[$which]); }
_run_hook是实际执行hook的函数。
3. run执行特定的钩子程序
_run_hook函数是hook的实际执行者,该函数接收一个预定义的hook数组作为参数,实现如下:
(1). 如果传递的参数压根就不是数组(自然也就不是有效的hook),那么直接返回:
if ( ! is_array($data)) { return FALSE; }
(2). 检查hook执行状态。
in_progress用于标志当前hook的执行状态。这个参数的主要作用,是防止hook之间的相互调用而导致的死循环。
if ($this->in_progress == TRUE) { return; }
(3). Hook的合法性检查。
为了方便讲述,我们再次提出一个预定义的hook需要的参数:
<span>$hook</span>['xx'] = <span>array</span><span>( </span>'class' => 'xx', <span>//</span><span>钩子调用的类名,可以为空</span> 'function' => 'xx',<span>//</span><span>钩子调用的函数名</span> 'filename' => 'xx',<span>//</span><span>该钩子的文件名</span> 'filepath' => 'xx',<span>//</span><span>钩子的目录</span> 'params' => 'xx'<span>//</span><span>传递给钩子的参数</span> );
其中class和params是可选参数,其他3个参数为必选参数,如果不提供,则由于无法准确定位到hook程序,只能直接返回:
if ( ! isset($data['filepath']) OR ! isset($data['filename'])) { return FALSE; } $filepath = APPPATH.$data['filepath'].'/'.$data['filename']; if ( ! file_exists($filepath)) { return FALSE; }
(4). 到这里,已经基本确认钩子程序的位置了,这里有两种情况:
a. 预定义的hook中class参数为空,表明使用的是过程式的调用方式,则直接执行hook文件中的function xxx
b. class参数不为空,提供的是面向对象的方式,则实际的钩子程序是$class->$function .同样,如果既没有设置class,也没有设置function参数,则无法执行hook,直接返回:
$class = FALSE; $function = FALSE; $params = ''; /* 获取 hook class */ if (isset($data['class']) AND $data['class'] != '') { $class = $data['class']; } /* 获取 hook function */ if (isset($data['function'])) { $function = $data['function']; } /* 获取传递的 hook 参数 */ if (isset($data['params'])) { $params = $data['params']; } /* 如果class和function都不存在,则无法定位hook程序,直接返回 */ if ($class === FALSE AND $function === FALSE) { return FALSE; }
(5). 设置执行标志in_progress,并执行上述两种情况下的hook:
/* 面向对象的设置方式 */ if ($class !== FALSE) { if ( ! class_exists($class)) { require($filepath); } $HOOK = new $class; $HOOK->$function($params); } /* 过程式的执行方式 */ else { if ( ! function_exists($function)) { require($filepath); } $function($params); }
最后,别忘了在hook执行完之后,设置标识位in_progress为false,并返回执行成功的标志:
$this->in_progress = FALSE; return TRUE;
Hook组件的完整源码:
_initialize(); log_message('debug', "Hooks Class Initialized"); } /** * Initialize 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; } 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; } /** * Call Hook * * Calls a particular hook * * @access private * @param string the hook name * @return mixed */ function _call_hook($which = '') { if ( ! $this->enabled OR ! isset($this->hooks[$which])) { return FALSE; } if (isset($this->hooks[$which][0]) AND is_array($this->hooks[$which][0])) { foreach ($this->hooks[$which] as $val) { $this->_run_hook($val); } } else { $this->_run_hook($this->hooks[$which]); } return TRUE; } /** * Run Hook * * Runs a particular hook * * @access private * @param array the hook details * @return bool */ function _run_hook($data) { if ( ! is_array($data)) { return FALSE; } // If the script being called happens to have the same hook call within it a loop can happen if ($this->in_progress == TRUE) { return; } if ( ! isset($data['filepath']) OR ! isset($data['filename'])) { return FALSE; } $filepath = APPPATH.$data['filepath'].'/'.$data['filename']; if ( ! file_exists($filepath)) { return FALSE; } $class = FALSE; $function = FALSE; $params = ''; if (isset($data['class']) AND $data['class'] != '') { $class = $data['class']; } if (isset($data['function'])) { $function = $data['function']; } if (isset($data['params'])) { $params = $data['params']; } if ($class === FALSE AND $function === FALSE) { return FALSE; } $this->in_progress = TRUE; // Call the requested class and/or function if ($class !== FALSE) { if ( ! class_exists($class)) { require($filepath); } $HOOK = new $class; $HOOK->$function($params); } else { if ( ! function_exists($function)) { require($filepath); } $function($params); } $this->in_progress = FALSE; return TRUE; } }
参考文献
1. http://codeigniter.org.cn/user_guide/general/hooks.html 手册
2. http://itopic.org/codeigniter-hook.html
3. http://codeigniter.org.cn/forums/thread-4947-1-1.html 钩子实现的Layout
这个……好像CI没啥方法,倒是可以通过写模板的时候include进去header.php和footer.php,倒是还有听说smarty模板引擎中有模板继承这个概念,可以让你的内容页继承某个页面,那个页面上写着header.php和footer.php,貌似CI是可以使用smarty模板引擎的,不过我没有那样用过,还有,CI有hook(钩子)这个东西,老实说我没用过,不知道它能不能实现。
是记录有多少人进去你的网站还是说点击的某个连接多少次?

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









Java フレームワークの商用サポートのコスト/パフォーマンスを評価するには、次の手順が必要です。 必要な保証レベルとサービス レベル アグリーメント (SLA) 保証を決定します。研究サポートチームの経験と専門知識。アップグレード、トラブルシューティング、パフォーマンスの最適化などの追加サービスを検討してください。ビジネス サポートのコストと、リスクの軽減と効率の向上を比較検討します。

PHP フレームワークの学習曲線は、言語熟練度、フレームワークの複雑さ、ドキュメントの品質、コミュニティのサポートによって異なります。 PHP フレームワークの学習曲線は、Python フレームワークと比較すると高く、Ruby フレームワークと比較すると低くなります。 Java フレームワークと比較すると、PHP フレームワークの学習曲線は中程度ですが、開始までの時間は短くなります。

軽量の PHP フレームワークは、サイズが小さくリソース消費が少ないため、アプリケーションのパフォーマンスが向上します。その特徴には、小型、高速起動、低メモリ使用量、改善された応答速度とスループット、および削減されたリソース消費が含まれます。 実際のケース: SlimFramework は、わずか 500 KB、高い応答性と高スループットの REST API を作成します。

ベンチマークによると、小規模で高性能なアプリケーションの場合、Quarkus (高速起動、低メモリ) または Micronaut (TechEmpower に優れた) が理想的な選択肢です。 SpringBoot は大規模なフルスタック アプリケーションに適していますが、起動時間とメモリ使用量が若干遅くなります。

明確で包括的なドキュメントを作成することは、Golang フレームワークにとって非常に重要です。ベスト プラクティスには、Google の Go コーディング スタイル ガイドなど、確立されたドキュメント スタイルに従うことが含まれます。見出し、小見出し、リストなどの明確な組織構造を使用し、ナビゲーションを提供します。スタート ガイド、API リファレンス、概念など、包括的で正確な情報を提供します。コード例を使用して、概念と使用法を説明します。ドキュメントを常に最新の状態に保ち、変更を追跡し、新機能を文書化します。 GitHub の問題やフォーラムなどのサポートとコミュニティ リソースを提供します。 API ドキュメントなどの実践的なサンプルを作成します。

アプリケーションのシナリオに基づいて最適な Go フレームワークを選択します。アプリケーションの種類、言語機能、パフォーマンス要件、エコシステムを考慮します。一般的な Go フレームワーク: Jin (Web アプリケーション)、Echo (Web サービス)、Fiber (高スループット)、gorm (ORM)、fasthttp (速度)。実際のケース: REST API (Fiber) の構築とデータベース (gorm) との対話。フレームワークを選択します。主要なパフォーマンスには fasthttp、柔軟な Web アプリケーションには Jin/Echo、データベース インタラクションには gorm を選択してください。

Go フレームワーク開発における一般的な課題とその解決策は次のとおりです。 エラー処理: 管理にはエラー パッケージを使用し、エラーを一元的に処理するにはミドルウェアを使用します。認証と認可: サードパーティのライブラリを統合し、資格情報を確認するためのカスタム ミドルウェアを作成します。同時処理: ゴルーチン、ミューテックス、チャネルを使用してリソース アクセスを制御します。単体テスト: 分離のために getest パッケージ、モック、スタブを使用し、十分性を確保するためにコード カバレッジ ツールを使用します。デプロイメントとモニタリング: Docker コンテナを使用してデプロイメントをパッケージ化し、データのバックアップをセットアップし、ログ記録およびモニタリング ツールでパフォーマンスとエラーを追跡します。

Go フレームワークの学習には、フレームワークへの過度の依存と柔軟性の制限という 5 つの誤解があります。フレームワークの規則に従わない場合、コードの保守が困難になります。古いライブラリを使用すると、セキュリティと互換性の問題が発生する可能性があります。パッケージを過度に使用すると、コード構造が難読化されます。エラー処理を無視すると、予期しない動作やクラッシュが発生します。
