94hwan-PHP框架基本原理

Source:94hwan 与众不同  Author:网络部
2011-09-20 14:11

一、初始化过程
1、94hwan-PHP框架的入口点都是加载根目录的 init.php 来初始化的,因此在这里主要介绍一下初始化时到底做了一些什么。

  1. // 严格开发模式 
  2. error_reporting( E_ALL ); 
  3.  
  4. //开启register_globals会有诸多不安全可能性,因此强制要求关闭register_globals 
  5. if ( ini_get('register_globals') ) 
  6. { 
  7.     exit('php.ini register_globals must is Off! '); 
  8. } 
  9.  
  10. //核心库目录 
  11. define('CORE', dirname(__FILE__)); 
  12.  
  13. //系统配置 
  14. require CORE.'/../config/inc_config.php'; 
  15.  
  16. //外部请求程序处理(路由) 
  17. require CORE.'/req.php'; 
  18. req::init(); 
  19.  
  20. //设置时区 
  21. date_default_timezone_set( $GLOBALS['config']['timezone_set'] ); 
  22.  
  23. //加载核心类库 
  24. require CORE.'/util.php'; 
  25. require CORE.'/db.php'; 
  26. require CORE.'/tpl.php'; 
  27. require CORE.'/log.php'; 
  28. require CORE.'/cache.php'; 
  29.  
  30. //debug设置 
  31. $_debug_safe_ip = false; 
  32. require PATH_LIBRARY.'/debug/lib_debug.php'; 
  33. if( in_array( util::get_client_ip(), $GLOBALS['config']['safe_client_ip']) || OPEN_DEBUG === true ) 
  34. { 
  35.     $_debug_safe_ip = true; 
  36.     ini_set('display_errors', 'On'); 
  37. } 
  38. else 
  39. { 
  40.     ini_set('display_errors', 'Off'); 
  41. } 
  42. set_exception_handler('handler_debug_exception'); 
  43. set_error_handler('handler_debug_error', E_ALL); 
  44. register_shutdown_function('handler_php_shutdown'); 
  45.  
  46. //session接口(使用session前需自行调用session_start) 
  47. require CORE.'/session.php'; 
  48.  
  49. /** 
  50.  * 程序结束后执行的动作 
  51.  */ 
  52. function handler_php_shutdown() 
  53. { 
  54.     show_debug_error(); 
  55.     log::save(); 
  56.     if( defined('CLS_CACHE') ) { 
  57.         cache::free(); 
  58.     } 
  59.     if( defined('CLS_CACHE_NATIVE') ) { 
  60.         cls_cache_native::close(); 
  61.     } 
  62. } 
  63.  
  64. /** 
  65.  * 致命错误处理接口 
  66.  * 系统发生致命错误后的提示 
  67.  * (致命错误是指发生错误后要直接中断程序的错误,如数据库连接失败、找不到控制器等) 
  68.  */ 
  69. function handler_fatal_error( $errtype, $msg ) 
  70. { 
  71.     global $_debug_safe_ip; 
  72.     $log_str = $errtype.':'.$msg; 
  73.     if ( OPEN_DEBUG === true || $_debug_safe_ip ) 
  74.     { 
  75.         throw new Exception( $log_str ); 
  76.     } 
  77.     else 
  78.     {         
  79.         log::add('fatal_error', $msg); 
  80.         header ( "location:/404.html" ); 
  81.         exit(); 
  82.     } 
  83. } 
  84.  
  85. /** 
  86.  * 路由控制 
  87.  * 
  88.  * @param $ctl  控制器 
  89.  * @parem $ac   动作 
  90.  * @return void 
  91.  */ 
  92. function run_controller() 
  93. { 
  94.     try 
  95.     { 
  96.         $ac = preg_replace("/[^0-9a-z_]/i", '', req::item('ac', 'index') ); 
  97.         $ac = emptyempty ( $ac ) ? $ac = 'index' : $ac; 
  98.              
  99.         $ctl = 'ctl_'.preg_replace("/[^0-9a-z_]/i", '', req::item('ct', 'index') ); 
  100.         $path_file = PATH_CONTROL . '/' . $ctl . '.php'; 
  101.  
  102.         if( file_exists( $path_file ) ) 
  103.         { 
  104.             require $path_file; 
  105.         } 
  106.         else 
  107.         { 
  108.             throw new Exception ( "Contrl {$ctl}--{$path_file} is not exists!" ); 
  109.         } 
  110.         if( method_exists ( $ctl, $ac ) === true ) 
  111.         { 
  112.             $instance = new $ctl ( ); 
  113.             $instance->$ac (); 
  114.         } 
  115.         else 
  116.         { 
  117.             throw new Exception ( "Method {$ctl}::{$ac}() is not exists!" ); 
  118.         } 
  119.     } 
  120.     catch ( Exception $e ) 
  121.     { 
  122.         handler_fatal_error( 'init.php run_controller()', $e->getMessage().' url:'.util::get_cururl() ); 
  123.     } 
  124. } 
  125.  
  126. /** 
  127.  * 自动加载类库处理 
  128.  * 加载优先级 /core/library => 应用目录/model => 根目录/model 
  129.  * (如果不在这些位置, 则需自行手工加载,对于小型项目,也可以把model全放到library以减少类文件查找时间) 
  130.  * @return void 
  131.  */ 
  132. function __autoload( $classname ) 
  133. { 
  134.     $classname = preg_replace("/[^0-9a-z_]/i", '', $classname); 
  135.     if( class_exists ( $classname ) ) { 
  136.         return true; 
  137.     } 
  138.     $classfile = $classname.'.php'; 
  139.     try 
  140.     { 
  141.         if ( file_exists ( PATH_LIBRARY.'/'.$classfile ) ) 
  142.         { 
  143.             require PATH_LIBRARY.'/'.$classfile; 
  144.         } 
  145.         else if( file_exists ( PATH_MODEL.'/'.$classfile ) )  
  146.         { 
  147.             require PATH_MODEL.'/'.$classfile; 
  148.         } 
  149.         else if( file_exists ( require PATH_ROOT.'/model/'.$classfile ) )  
  150.         { 
  151.             require PATH_ROOT.'/model/'.$classfile; 
  152.         } 
  153.         else 
  154.         { 
  155.             return false; 
  156.             throw new Exception ( 'Error: Cannot find the '.$classname ); 
  157.         } 
  158.     } 
  159.     catch ( Exception $e ) 
  160.     { 
  161.         handler_fatal_error( 'init.php __autoload()', $e->getMessage().'|'.$classname.' url:'.util::get_cururl() ); 
  162.     } 
  163. } 
  164.  
  165. /** 
  166.  * req::item 别名函数 
  167.  */ 
  168. function request($key, $df='') 
  169. { 
  170.     return req::item($key, $df); 
  171. } 

说明一:新版框架大的不同之处是,在引导文件里引入了重新封装过几个重要核心类,这些东西加起来,其实就是组成了迷你的一个框架,但是功能少却基本完全兼顾,包括下面几个方面:
(1) 路由功能req.php:即是全局环境检查与请求参数初始化;
(2) 基本数据库类db.php:因为数据库类是常用的的类,因此名称跳出规范的标准,直接使用db作为名称,更便于记忆;
(3) 视图类tpl.php:视图类是MVC框架里不可缺少的东西,本框架通过对smarty进行简化封装后,得到的 tpl 类,即是相当于别的框架的视图对应的功能;
(4) 缓存类cache.php:支持本地单文件hash缓存和memcache缓存,取代原来memcache + 打散文件的模式(这种模式缺点是时间长之后文件碎片极多,造成维护困难);
(5) 日志类log.php:目前仅进行了简单的规范;
(6) 调试程序debug/lib_debug.php:引导文件中定义了debug的方案并且定义了register_shutdown_function接口。
(7) session(sesion.php):重写了session接口,确保在各种场合下都可以使用session。
(8) 引导功能:定义了如何引导到控制器和加载类库路径(un_controller、__autoload)。

说明二:通过分析上面入口源码,必须重点清楚几个事情:
(1)、传入系统的请求参数都是已经转义的,如果需要用到不转义的情况,需要还原后使用;
(2)、对于_GET、_POST、_FILES ,不要再使用原来的操作方法,而是使用 req类去操作(具体可以参考类文档);
(3)、/core/library 目录存放的是系统基础类和公共类文件,不要存放 mod_*.php(如果是很小的项目则没关系),公共接口类要存放这个位置的用 pub_*.php 命名,普通模型类应该存放在 应用目录/model 文件夹,并命名为 mod_*.php (私有逻辑类编程规范里要求用静态成员方法)。

2、分析入口文件 index.php 这里分别把 /index.php 和 admin/index.php 列出来,对比这它们的不同
/index.php

  1. <?php 
  2. header('Content-Type: text/html; charset=utf-8'); 
  3. $page_start_time = microtime(true); 
  4.  
  5. require './init.php'; 
  6.  
  7. $config_pool_name = $config_appname  =  $config_cp_url = ''; 
  8.  
  9. execute_ctl(req::$forms['ct'], req::$forms['ac']); 
  10.  
  11. ?> 

admin/index.php

  1. <?php 
  2. header('Content-Type: text/html; charset=utf-8'); 
  3. $page_start_time = microtime(true); 
  4.  
  5. require '../init.php'; 
  6.  
  7. $config_pool_name = 'administrator';       //应用池参数(与权限管理有关) 
  8.  
  9. $config_appname   = 'admin';       //应用名称(与模板文件夹有关,不一定与应用池名称一致) 
  10.  
  11. $config_cp_url = '?ct=index&ac=login';     //用于未登录用户跳转到的url 
  12. cls_template::assign('URL', URL); 
  13.  
  14. //前置的权限控制器 
  15. $config_access_ctl = cls_access::factory( $config_pool_name, $config_cp_url ); 
  16. $config_access_ctl->test_purview( req::$forms['ct'], req::$forms['ac'], '1' ); 
  17.  
  18. execute_ctl( req::$forms['ct'], req::$forms['ac'] ); 
  19. ?> 

通过对比,不难看出,admin 目录的入口文件与根目录相比,主要多了如下代码:
$config_pool_name = 'administrator';       //应用池参数(与权限管理有关)
$config_appname   = 'admin';       //应用名称(与模板文件夹有关,不一定与应用池名称一致)
$config_cp_url = '?ct=index&ac=login';     //用于未登录用户跳转到的url
//前置的权限控制器
$config_access_ctl = cls_access::factory( $config_pool_name, $config_cp_url );
$config_access_ctl->test_purview( req::$forms['ct'], req::$forms['ac'], '1' );

上面的代码实际上就是权限控制类的代码,权限控制类通过监听 ct  和  ac 实现对权限的控制的,权限类里对角色分两种情况处理,一种是组权限,在没指定用户具体权限的情况下,通过组权限配置来识别用户权限,在指定了用户具体权限的情况下,以指定的独立权限为准。

此外,细心的人可能会发现 $forms['ct'], $forms['ac'] 居然没预先判断是否存在,实际上其实在 req 类中,对ct和ac为空的情况会默认赋值为 index,当然如果是其它变量,用:
req::item(formName, 默认值)   或直接使用 request(formName, 默认值)
这样获取即可。

二、程序运行过程
通过分析源码,不难知道,程序终要调用的文件和执行的类及方法,主要在 run_controller() 这函数里处理。
它的源码如下:

  1. /** 
  2.  * 路由控制 
  3.  * 
  4.  * @param $ctl  控制器 
  5.  * @parem $ac   动作 
  6.  * @return void 
  7.  */ 
  8. function run_controller() 
  9. { 
  10.     try 
  11.     { 
  12.         $ac = preg_replace("/[^0-9a-z_]/i", '', req::item('ac', 'index') ); 
  13.         $ac = emptyempty ( $ac ) ? $ac = 'index' : $ac; 
  14.              
  15.         $ctl = 'ctl_'.preg_replace("/[^0-9a-z_]/i", '', req::item('ct', 'index') ); 
  16.         $path_file = PATH_CONTROL . '/' . $ctl . '.php'; 
  17.  
  18.         if( file_exists( $path_file ) ) 
  19.         { 
  20.             require $path_file; 
  21.         } 
  22.         else 
  23.         { 
  24.             throw new Exception ( "Contrl {$ctl}--{$path_file} is not exists!" ); 
  25.         } 
  26.         if( method_exists ( $ctl, $ac ) === true ) 
  27.         { 
  28.             $instance = new $ctl ( ); 
  29.             $instance->$ac (); 
  30.         } 
  31.         else 
  32.         { 
  33.             throw new Exception ( "Method {$ctl}::{$ac}() is not exists!" ); 
  34.         } 
  35.     } 
  36.     catch ( Exception $e ) 
  37.     { 
  38.         handler_fatal_error( 'init.php run_controller()', $e->getMessage().' url:'.util::get_cururl() ); 
  39.     } 
  40. } 


这个函数是通过控制器名称来确定要调用的 control  类文件,并且,初始化这个类,并调用里面的 action(ac) 方法。

PATH_CONTROL == './control'

在上面源码有,有一处语法:empty ( $action ) && $action = 'index';
这种语法是不规范的,倒不是说不合乎PHP标准,而是不具可读性,对于没用过这种方式的人,读起来会很费劲,过于个性化,这是历史留下来的问题,这里我不作修改,让后来人作为反面参考教材。
 

通过分析源码,相信大家都能明白在94hwan框架下是如何进行开发的了,其实主要是两个方面:
1、URL里的ct 和 ac 表示指向 control/ct_ctname.php 里的 action(ac) 方法,开发人员的工作就是写这个ct类和ac方法,但需要注意的是控制器只负责的用户请求进行控制,逻辑代码应该放在 model 目录的相应类, 或 library 里的框架基础类或项目接口类, 除了lurd类之外,尽量不要在 ac 类直接操作数据库,过于复杂的代码都要封装为逻辑类。
2、框架要求对 GET、POST、FILES 请求都用 req 类,因此,开发前必须先完全弄明白这个类的使用方法。

 

...