打开主菜单

Blog/2019/08/20/CodeIgnitor 3.0.x 之 Load 加载机制

< Blog‎ | 2019

CI_Loader 初始化

CI 框架中的 library 和很多内置的类都是通过 CI_Loader 这个类加载的,也就是 Controller 中常常出现的 $this->load 属性。那么它本身是如何被加载的呢?

这一切又要从最初的入口点 index.php 说起了。首先,外界的请求定位到了 index.php,所有的请求的处理都是从这里开始的。大略看一下 index.php 的内容,其实也很简单,就是利用当前文件的位置,定义了一些环境和路径常量,比如 ENVIRONMENT, BASEPATH, APPPATH, VIEWPATH,基于这些常量,又定义了一些全局变量:$system_path, $application_folder, $view_folder,最后加载了CI框架的核心类:core/CodeIgnitor.php,完成了它的使命。

在 CodeIgnitor.php 中执行了框架的初始化操作,最终调用 call_user_func_array(array(&$CI, $method), $params);,将处理控制权转到继承自 CI_Controller 的具体的控制器类之中;而 CI_Loader 就是在框架初始化的过程中加载并作为 CI_Controller 的属性而存在的。

在 CodeIgnitor.php 的开头,就加载了 Core/Common.php,这个文件中定义了几个重要的全局辅助函数,它们是加载类的前提。其中最重要的是 load_class():

function &load_class($class, $directory = 'libraries', $param = NULL)
	{
		static $_classes = array();

		// Does the class exist? If so, we're done...
		if (isset($_classes[$class]))
		{
			return $_classes[$class];
        }
        
        // ...

        foreach (array(APPPATH, BASEPATH) as $path)
		{
			if (file_exists($path.$directory.'/'.$class.'.php'))
			{
				$name = 'CI_'.$class;

				if (class_exists($name, FALSE) === FALSE)
				{
					require_once($path.$directory.'/'.$class.'.php');
				}

				break;
			}
		}
        // ...

        $_classes[$class] = isset($param)
			? new $name($param)
			: new $name();
		return $_classes[$class];
	}
}

这个方法维护了一个全局的静态数组 $_classes,如果待加载的类不存在,则实例化之并添加到静态数组中;如果之前已经存在,则直接返回该实例。

随后,直接使用 load_class 加载了这些CI核心类 Benchmark, Hooks, Config, Utf8, URI, Router, Output, Security, Input, Lang。

最后,调用 call_user_func_array(array(&$CI, $method), $params);时,代码执行转到 CI_Controller::__construct() 之中:

class CI_Controller {

	private static $instance;

	public function __construct()
	{
		self::$instance =& $this;

		foreach (is_loaded() as $var => $class)
		{
			$this->$var =& load_class($class);
		}

		$this->load =& load_class('Loader', 'core');
		$this->load->initialize();
		log_message('info', 'Controller Class Initialized');
    }
    
    public static function &get_instance()
	{
		return self::$instance;
	}
}

从中可以看到,对于所有已加载的核心类,会再执行一次 load_class() 并将其返回的类实例作为 Controller 的属性。所以在控制器的action方法中,下面直接调用都是可以的:

$this->input; // CI_Input
$this->output; // CI_Output
$this->uri; // CI_URI
$this->router; // CI_Router
$this->lang; // CI_Lang

第二步,便是用 load_class() 加载了 core/Loader.php 并将其赋值为 Controller 的 load 变量。所以,在控制器的Action方法中,$this->load 就是一个 CI_Loader 的对象。

然后,$this->load->initialize(); 主要是读取 config/autoload.php 中定义的类,然后加载之。所以说,如果想要在框架启动时加载某些类,就可以将这些类名加到 autoload.php 中的相应的配置中去。

使用 CI_Loader 加载类

先看一下 CI_Loader 的结构:

library()
driver() -> library()
model()
database()
helpers()
language()
config()
view() -> _ci_load()

CI_Loader 提供了几种不同的加载方法,用来加载 CI 目录中对应目录下的类。

library() 用于加载 system/libraries/ 或 application/libraries/ 目录下的系统类库或用户创建的类库

driver() 用于加载 system/libraries/ 或 application/libraries/ 目录下的基于驱动的类库;

model() 用于加载 application/models/ 目录下的模型类;

database 用于加载由 application/config/database.php 配置文件指定的数据库连接;

helpers 用于加载 application/helpers 或 system/helpers/ 下的辅助函数;

language() 用于加载 system/language/ 或 application/language/ 下的语言配置文件;

config() 用于加载 application/config/ 目录下的配置文件。

view() 用于加载 application/views/ 目录下的视图模板文件。

加载类库 —— library()

示例1:CI 提供了一个日历类 CI_Calendar,可以像这样加载并使用它:

$this->load->library('calendar');
echo $this->calendar->generate(); // 显示一个日历

/* 输出
November 2019
Su	Mo	Tu	We	Th	Fr	Sa
 	 	 	 	 	1	2
3	4	5	6	7	8	9
10	11	12	13	14	15	16
17	18	19	20	21	22	23
24	25	26	27	28	29	30
*/

示例2:CI 提供了一个表单字段验证类 CI_Form_validation,加载并使用它:

$this->load->library('form_validation');
$this->form_validation->set_rules('username', 'Username', 'required');
$this->form_validation->set_rules('password', 'Password', 'required');
if ($this->form_validation->run() == FALSE)
{
	// 输出错误信息
}

从这两个例子可以看出来,加载系统类库,只需要指定一个小写的{类名},使用时也是直接 $this->{类名} 即可。

加载驱动器类库 —— driver()

驱动器类加载与普通类库加载过程其实一样,区别就在于驱动器类存在多个具体驱动的实现类,同样一个功能,以不同的驱动实现不同场景的使用。

驱动器类的类文件结构如下:

system/libraries
    Cache/
        drivers/
            Cache_apc.php - CI_Cache_apc
            Cache_file.php - CI_Cache_file
            Cache_memcached.php - CI_Cache_memcached
            Cache_redis.php - CI_Cache_redis
    Cache.php - CI_Cache extends CI_Driver_Library

必须要以这样的类/文件命名结构,才能被 driver() 方法所支持。使用时如下:

$this->load->driver('cache');
$this->cache->redis->get($key);

第一行加载 CI_Cache 类,第二行动态加载了 CI_Cache_redis 类。能够实现这样的动态加载的原因就是 CI_Driver_Library 在魔术方法中动态加载具体的驱动器:

public function __get($child)
	{
		// Try to load the driver
		return $this->load_driver($child);
	}

加载模型类 —— model()

这个比较简单,直接去 application/models/ 目录下寻找对应的文件加载之。

$this->load->model('model_name');


如果模型类在子目录中,则带上目录名即可:

$this->load->model('blog/queries');


使用模型

php

$this->load->model('model_name');

$this->model_name->method();


加载数据库连接 database()

database() 读取 application/config/database.php 文件中的数据库配置,加载数据库连接。

配置文件内容:

$db['default'] = array(
    'dsn'   => '',
    'hostname' => 'localhost',
    'username' => 'root',
    'password' => '',
    'database' => 'database_name',
    'dbdriver' => 'mysqli',
    'dbprefix' => '',
    'pconnect' => TRUE,
    'db_debug' => TRUE,
    'cache_on' => FALSE,
    'cachedir' => '',
    'char_set' => 'utf8',
    'dbcollat' => 'utf8_general_ci',
    'swap_pre' => '',
    'encrypt' => FALSE,
    'compress' => FALSE,
    'stricton' => FALSE,
    'failover' => array()
);

使用:

$this->load->database();
$query = $this->db->query('SELECT name, title, email FROM my_table');

foreach ($query->result_array() as $row)
{
    echo $row['title'];
    echo $row['name'];
    echo $row['email'];
}

database() 不指定参数时,则使用 default 配置项,其实现如下:

public function database($params = '', $return = FALSE, $query_builder = NULL)
{
	$CI =& get_instance();

	require_once(BASEPATH.'database/DB.php');

	if ($return === TRUE)
	{
		return DB($params, $query_builder);
	}
	
	// Load the DB class
	$CI->db =& DB($params, $query_builder);
	return $this;
}


function &DB($params = '', $query_builder_override = NULL)
{
	require_once(BASEPATH.'database/DB_driver.php');
	// ...
	// Load the DB driver
	$driver_file = BASEPATH.'database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php';
	
	require_once($driver_file);
	
	// Instantiate the DB adapter
	$driver = 'CI_DB_'.$params['dbdriver'].'_driver';
	$DB = new $driver($params);
	
	// ...
	return $DB;
}

database() 第二个参数如果为TRUE则返回 db,否则设置为CI控制器的属性。这里的 db 是 DB() 返回的对象,实际上是 DB() 根据配置,加载对应的数据库驱动类,所返回的数据库驱动对象。

评论


您未被允许发表评论。