·您现在的位置: 云翼网络 >> 文章中心 >> 网站建设 >> 网站建设开发 >> php网站开发 >> CI框架源码阅读笔记3 全局函数Common.php

CI框架源码阅读笔记3 全局函数Common.php

作者:佚名      php网站开发编辑:admin      更新时间:2022-07-23
CI框架源码阅读笔记3 全局函数Common.php

  从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现、结构和设计。

  Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap引导文件都会最先引入全局函数,以便于之后的处理工作)。

  打开Common.php中,第一行代码就非常诡异:

if ( ! defined('BASEPATH')) exit('No direct script access allowed');

  上一篇(CI框架源码阅读笔记2 一切的入口 index.php)中,我们已经知道,BASEPATH是在入口文件中定义的常量。这里做这个判断的原因是:避免直接访问文件,而必须由index.php入口文件进入。其实不仅是Common.php,System中所有文件,几乎都要引入这个常量的判断,避免直接的脚本访问:

本文件中定义的函数如下(查看方式 PRint_r(get_defined_functions())):

CI中所有全局函数的定义方式都为:

if ( ! function_exists('func_name')){    function func_name(){     //function body    }}

这样做,是为了防止定义重名函数(之后如果我们要定义系统的全局函数,也都将使用这种定义方式)。下面,一个个展开来看:

1.  is_php

这个函数的命名很明显,就是判断当前环境的PHP版本是否是特定的PHP版本(或者高于该版本)

该函数内部有一个static的$_is_php数组变量,用于缓存结果(因为在特定的运行环境中,PHP的版本是已知的且是不变的,所以通过缓存的方式,避免每次调用时都去进行version_compare。这种方式,与一般的分布式缓存(如Redis)的处理思维是一致的,不同的是,这里是使用static数组的方式,而分布式缓存大多使用内存缓存)。

为什么要定义这个函数呢?这是因为,CI框架中有一些配置依赖于PHP的版本和行为(如magic_quotes,PHP 5.3版本之前,该特性用于指定是否开启转义,而PHP5.3之后,该特性已经被废弃)。这就好比是针对不同的浏览器进行CSS Hack一样(这里仅仅是比喻,实际上,PHP并没有太多的兼容性问题)。

具体的实现源码:

function is_php($version = '5.0.0'){    static $_is_php;    $version = (string)$version;    if ( ! isset($_is_php[$version]))    {        $_is_php[$version] = (version_compare(PHP_VERSION, $version) < 0) ? FALSE : TRUE;    }    return $_is_php[$version];}
2.  is_really_writable

这个函数用于判断文件或者目录是否真实可写,一般情况下,通过内置函数is_writable()返回的结果是比较可靠的,但是也有一些例外,比如:

(a). Windows中,如果对文件或者目录设置了只读属性,则is_writable返回结果是true,但是却无法写入。

(b). linux系统中,如果开启了Safe Mode,则也会影响is_writable的结果

因此,本函数的处理是:

  如果是一般的Linux系统且没有开启safe mode,则直接调用is_writable

否则:

  如果是目录,则尝试在目录中创建一个文件来检查目录是否可写

  如果是文件,则尝试以写入模式打开文件,如果无法打开,则返回false

注意,即使是使用fopen检查文件是否可写,也一定记得调用fclose关闭句柄,这是一个好的习惯。

该函数的源码:

function is_really_writable($file){    // If we're on a Unix server with safe_mode off we call is_writable    if (DIRECTORY_SEPARATOR == '/' AND @ini_get("safe_mode") == FALSE)    {        return is_writable($file);    }    // For windows servers and safe_mode "on" installations we'll actually write a file then read it    if (is_dir($file))    {        $file = rtrim($file, '/').'/'.md5(mt_rand(1,100).mt_rand(1,100));        if (($fp = @fopen($file, FOPEN_WRITE_CREATE)) === FALSE)        {            return FALSE;        }        fclose($fp);        @chmod($file, DIR_WRITE_MODE);        @unlink($file);        return TRUE;    }    elseif ( ! is_file($file) OR ($fp = @fopen($file, FOPEN_WRITE_CREATE)) === FALSE)    {        return FALSE;    }    fclose($fp);    return TRUE;}
3.  load_class

这个函数有几个特殊的地方需要重点关注:

(1). 注意这个函数的签名,function &load_class( $class,$directory,$prefix).看到前面那个特殊的&符号没?没错,这个函数返回的是一个class实例的引用. 对该实例的任何改变,都会影响下一次函数调用的结果。

(2). 这个函数也有一个内部的static变量缓存已经加载的类的实例,实现方式类似于单例模式(Singleton)

(3). 函数优先查找APPPATH和BASEPATH中查找类,然后才从$directory中查找类,这意味着,如果directory中存在着同名的类(指除去前缀之后同名),CI加载的实际上是该扩展类。这也意味着,可以对CI的核心进行修改或者扩展。

下面是该函数的源码:

function &load_class($class, $directory = 'libraries', $prefix = 'CI_'){    /* 缓存加载类的实例 */    static $_classes = array();    if (isset($_classes[$class]))    {        return $_classes[$class];    }    $name = FALSE;    /* 先查找系统目录 */    foreach (array(APPPATH, BASEPATH) as $path)    {        if (file_exists($path.$directory.'/'.$class.'.php'))        {            $name = $prefix.$class;            if (class_exists($name) === FALSE)            {                require($path.$directory.'/'.$class.'.php');            }            break;        }    }    /*  查找之后并没有立即实例化,而是接着查找扩展目录 */    if (file_exists(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php'))    {        $name = config_item('subclass_prefix').$class;        if (class_exists($name) === FALSE)        {            require(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php');        }    }    /* 没有找到任何文件 */    if ($name === FALSE)    {        exit('Unable to locate the specified class: '.$class.'.php');    }    /*  将$class计入已加载的类列表  */    is_loaded($class);    /* 取得实例化 */    $_classes[$class] = new $name();    return $_classes[$class];}
4.  is_loaded

这个函数用于追踪所有已加载的class。代码比较简洁,没有太多可讲的地方,这里直接贴出源码:

function &is_loaded($class = ''){    static $_is_loaded = array();    if ($class != '')    {       $_is_loaded[strtolower($class)] = $class;    }    return $_is_loaded;}
5.  get_config

这个函数用于加载主配置文件(即位于config/目录下的config.php文件,如果定义了针对特定ENVIRONMENT的config.php文件,则是该文件)。该函数的签名为:

function &get_config($replace = array())

有几个需要注意的点:

(1). 函数只加载主配置文件,而不会加载其他配置文件(这意味着,如果你添加了其他的配置文件,在框架预备完毕之前,不会读取你的配置文件)。在Config组件实例化之前,所有读取主配置文件的工作都由该函数完成。

(2). 该函数支持动态运行的过程中修改Config.php中的条目(配置信息只可能修改一次,因为该函数也有static变量做缓存,若缓存存在,则直接返回配置)

(3). Return $_config[0] = & $config。是config文件中$config的引用,防止改变Config的配置之后,由于该函数的缓存原因,无法读取最新的配置。

这里还有一点无法理解,作者使用了$_config数组来缓存config,而只使用了$_config[0],那么问题来了,为什么不用单一变量代替,即:$_config = & $config; 如果有知道原因的童鞋,麻烦告知一声。

该函数的实现源码:

function &get_config($replace = array()){    static $_config;    if (isset($_config))    {        return $_config[0];    }    if ( ! defined('ENVIRONMENT') OR ! file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/config.php'))    {        $file_path = APPPATH.'config/config.php';    }    if ( ! file_exists($file_path))    {        exit('The configuration file does not exist.');    }    require($file_path);    if ( ! isset($config) OR ! is_array($config))    {        exit('Your config file does not appear to be formatted correctly.');    }    if (count($replace) > 0)    {        foreach ($replace as $key => $val)        {            if (isset($config[$key]))            {                $config[$key] = $val;            }        }    }    return $_config[0] =& $config;}
6.  config_item

这个函数调用了load_config,并获取相应的设置条目。代码比较简洁。不做过多的解释,同样只贴出源码:

function config_item($item){    static $_config_item = array();    if ( ! isset($_config_item[$item]))    {        $config =& get_config();        if ( ! isset($config[$item]))        {            return FALSE;        }        $_config_item[$item] = $config[$item];    }    return $_config_item[$item];}
7.  show_error

这是CI定义的可以用来展示错误信息的函数,该函数使用了Exceptions组件(之后我们将看到,CI中都是通过Exceptions组件来管理错误的)来处理错误。

例如,我们可以在自己的应用程序控制器中调用该函数展示错误信息:

Show_error(“trigger error info”);

CI框架的错误输出还算是比较美观:

注意该函数不仅仅是显示错误,而且会终止代码的执行(exit)

该函数的源码:

function show_error($message, $status_code = 500, $heading = 'An Error Was Encountered'){    $_error =& load_class('Exceptions', 'core');    echo $_error->show_error($heading, $message, 'error_general', $status_code);    exit;}
8.  show_404

没有太多解释的东西,返回404页面

源码:

function show_404($page = '', $log_error = TRUE){    $_error =& load_class('Exceptions', 'core');    $_error->show_404($page, $log_error);    exit;}
9.  log_message

调用Log组件记录log信息,类似Debug。需要注意的是,如果主配置文件中log_threshold被设置为0,则不会记录任何Log信息,该函数的源码:

function log_message($level = 'error', $message, $php_error = FALSE){    static $_log;    if (config_item('log_threshold') == 0)    {        return;    }    $_log =& load_class('Log');    $_log->write_log($level, $message, $php_error);}
10.  set_status_header

CI框架允许你设置HTTP协议的头信息(具体的HTTP状态码和对应含义可以参考:http://blog.csdn.net/ohmygirl/article/details/6922313)。设置方法为:

$this->output->set_status_header(“401”,“lalalala”);(CI的Output组件暴露了set_status_header()对外接口,该接口即是调用set_status_header函数)

值得注意的是,现在很多服务器内部扩展加入了自定义的状态码,如nginx:

ngx_string(ngx_http_error_495_page),   /* 495, https certificate error */ngx_string(ngx_http_error_496_page),   /* 496, https no certificate */ngx_string(ngx_http_error_497_page),   /* 49