CodeIgniter 源码解读之模型

模型

啥是模型?模型是干嘛的?CI中的模型(Model)又是如何工作的?这篇我们就来看看吧~~

首先,我们习惯性的打开 core 目录,瞧瞧是否有个文件名有 model 的字段,哦豁~ 还真有(Model.php),赶紧双击打开看看吧。

class CI_Model {

	/**
	 * Class constructor
	 *
	 * @link	https://github.com/bcit-ci/CodeIgniter/issues/5332
	 * @return	void
	 */
	public function __construct() {}

	/**
	 * __get magic
	 *
	 * Allows models to access CI's loaded classes using the same
	 * syntax as controllers.
	 *
	 * @param	string	$key
	 */
	 # 这是个php的魔术方法,当对象获取一个不存在的类对象时,会自动调用该方法.
	 # 这里的作用是,让模型中可以按 控制器 中的 调用方法的方式,如:$this->load->model()此类
	 # 至于如何工作的,下面会说明
	public function __get($key)
	{
		// Debugging note:
		//	If you're here because you're getting an error message
		//	saying 'Undefined Property: system/core/Model.php', it's
		//	most likely a typo in your model code.
		return get_instance()->$key;
	}

}

鉴于,模型基类太过简单,接下来我们看下:
1、我们在控制器中是如何加载模型及模型的加载原理。
2、模型连接数据库的原理

在实际项目中,我经常这样引用模型,下面我写了个Test控制器及Test_model模型,代码如下:

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Test extends CI_Controller {

	/**
	 * Test Page for this controller.
	 * /
	
	public function __construct() {

		parent::__construct();
		# 加载模型
		$this->load->model('Test_model');
	}

	public function index()
	{
		echo $this->Test_model->list();
	}
}
<?php


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

/**
* 测试模型
*/
class Test_model extends CI_Model
{
	
	function __construct()
	{
		parent::__construct();
		# 加载数据库
		$this->load->database();
	}
	
	# 返回列表
	public function list() {

		return 'list';
	}
}

我们先看控制器Test类中的构造方法有这么一行:$this->load->model(‘Test_model’),上一篇中的控制器中已经提到了 $this 这个超级对象(忘记的可以回到上一篇再看下),这里,我们可以看到它调用了load(Loader类实例)对象的model方法,代码走起~~

	/**
	 * Model Loader
	 *
	 * Loads and instantiates models.
	 *
	 * @param	mixed	$model		Model name
	 * @param	string	$name		An optional object name to assign to
	 * @param	bool	$db_conn	An optional database connection configuration to initialize
	 * @return	object
	 */
	public function model($model, $name = '', $db_conn = FALSE)
	{
		# 如果为空则返回
		if (empty($model))
		{
			return $this;
		}
		elseif (is_array($model))
		{
			# $model 可接受数组,所以这里循环数组调用了 model 方法
			foreach ($model as $key => $value)
			{
				# 由于我们在控制器中加载模型时,可以给模型弄个别名
				# 所以这里加了一个判断,如果是普通数组的话则直接加载
				# 若是索引数组的话,他会将$key作为模型名,$value作为别名加载
				is_int($key) ? $this->model($value, '', $db_conn) : $this->model($key, $value, $db_conn);
			}

			return $this;
		}

		$path = '';

		// Is the model in a sub-folder? If so, parse out the filename and path.
		# 如果模型名中带有 / 则说明他是在 models目录的子目录中
		# 得到最后一个 / 所在的位置
		if (($last_slash = strrpos($model, '/')) !== FALSE)
		{
			// The path is in front of the last slash
			# 分割出目录 如:blog/v1/
			$path = substr($model, 0, ++$last_slash);
			
			// And the model name behind it
			# 分割出模型名
			$model = substr($model, $last_slash);
		}
		
		# 将 $model 赋值给 $name
		if (empty($name))
		{
			$name = $model;
		}
		
		# 若这个模型已经被加载则跳出函数
		if (in_array($name, $this->_ci_models, TRUE))
		{
			return $this;
		}
		# 调用 CodeIgniter.php 中的 get_instance() 方法,获取 超级对象
		$CI =& get_instance();
		# 判断当前需要加载的模型名是否存在于 超级 对象中
		# 若存在,则抛出异常:说明该名称已被框架核心类名占用
		if (isset($CI->$name))
		{
			throw new RuntimeException('The model name you are loading is the name of a resource that is already being used: '.$name);
		}
		
		# 是否自动连接数据库
		if ($db_conn !== FALSE && ! class_exists('CI_DB', FALSE))
		{
			if ($db_conn === TRUE)
			{
				$db_conn = '';
			}
			
			# 连接数据库
			$this->database($db_conn, FALSE, TRUE);
		}

		// Note: All of the code under this condition used to be just:
		//
		//       load_class('Model', 'core');
		//
		//       However, load_class() instantiates classes
		//       to cache them for later use and that prevents
		//       MY_Model from being an abstract class and is
		//       sub-optimal otherwise anyway.
		# 加载模型基类及自定义的前缀模型(MY_开头的)
		if ( ! class_exists('CI_Model', FALSE))
		{
			$app_path = APPPATH.'core'.DIRECTORY_SEPARATOR;
			if (file_exists($app_path.'Model.php'))
			{
				require_once($app_path.'Model.php');
				if ( ! class_exists('CI_Model', FALSE))
				{
					throw new RuntimeException($app_path."Model.php exists, but doesn't declare class CI_Model");
				}

				log_message('info', 'CI_Model class loaded');
			}
			elseif ( ! class_exists('CI_Model', FALSE))
			{
				require_once(BASEPATH.'core'.DIRECTORY_SEPARATOR.'Model.php');
			}
			# 尝试拼装自定义的模型类文件名,如(MY_Model),并尝试加载
			$class = config_item('subclass_prefix').'Model';
			if (file_exists($app_path.$class.'.php'))
			{
				require_once($app_path.$class.'.php');
				if ( ! class_exists($class, FALSE))
				{
					throw new RuntimeException($app_path.$class.".php exists, but doesn't declare class ".$class);
				}

				log_message('info', config_item('subclass_prefix').'Model class loaded');
			}
		}
		
		# 模型类名首字母大写
		$model = ucfirst($model);
		if ( ! class_exists($model, FALSE))
		{
			foreach ($this->_ci_model_paths as $mod_path)
			{
				if ( ! file_exists($mod_path.'models/'.$path.$model.'.php'))
				{
					continue;
				}

				require_once($mod_path.'models/'.$path.$model.'.php');
				if ( ! class_exists($model, FALSE))
				{
					throw new RuntimeException($mod_path."models/".$path.$model.".php exists, but doesn't declare class ".$model);
				}

				break;
			}

			if ( ! class_exists($model, FALSE))
			{
				throw new RuntimeException('Unable to locate the model you have specified: '.$model);
			}
		}
		elseif ( ! is_subclass_of($model, 'CI_Model'))
		{
			throw new RuntimeException("Class ".$model." already exists and doesn't extend CI_Model");
		}
		
		# 将模型名加入到数组中,方便调用
		$this->_ci_models[] = $name;
		# 实例化模型类
		$model = new $model();
		# 赋值,如加载Test_model 后,则可以使用 $this->Test_model->method() 方式
		$CI->$name = $model;
		log_message('info', 'Model "'.get_class($model).'" initialized');
		return $this;
	}

连接数据库代码如下:

	/**
	 * Database Loader
	 *
	 * @param	mixed	$params		Database configuration options
	 * @param	bool	$return 	Whether to return the database object
	 * @param	bool	$query_builder	Whether to enable Query Builder
	 *					(overrides the configuration setting)
	 *
	 * @return	object|bool	Database object if $return is set to TRUE,
	 *					FALSE on failure, CI_Loader instance in any other case
	 */
	public function database($params = '', $return = FALSE, $query_builder = NULL)
	{
		// Grab the super object
		# 同样获取 超级 对象
		$CI =& get_instance();

		// Do we even need to load the database class?
		# 一系列的条件判断
		if ($return === FALSE && $query_builder === NULL && isset($CI->db) && is_object($CI->db) && ! empty($CI->db->conn_id))
		{
			return FALSE;
		}
		# 引入 DB 文件
		require_once(BASEPATH.'database/DB.php');
		
		# 如果$return为TRUE,表示需要返回数据库(DB)对象
		if ($return === TRUE)
		{
			# 这里调用DB 方法进行初始化数据库连接对象
			return DB($params, $query_builder);
		}

		// Initialize the db variable. Needed to prevent
		// reference errors with some configurations
		$CI->db = '';

		// Load the DB class
		# 获取数据库连接对象,并赋值给$CI->db
		$CI->db =& DB($params, $query_builder);
		return $this;
	}

如果我们在控制器中加载模型时,使用 FALSE 标识后,我们可以在自己的模型文件的构造方法中,使用 $this->load-database()获取数据库实例:

<?php


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

/**
* 测试模型
*/
class Test_model extends CI_Model
{
	
	function __construct()
	{
		parent::__construct();
		# 这里同样使用了 load 类的 database 方法
		$this->load->database();
	}

	public function list() {

		return 'list';
	}
}

好啦!这期的模型就讲完了,是不是很简单,哦,还有一点,CI加载数据库是,会根据 application\config\database.php配置去加载你配置的驱动类,根据不同的驱动是实现不同的数据库连接,这里你可以去看一下 system\database 目录的内容。
现在,我们总结一下,模型类在加载的时候,会做三件事:
1、加载框架的核心模型基类
2、实例化加载的模型,并将它赋值给 超级 对象,这样,我们就能通过 $this 去方便调用它
3、如果配置的 TRUE 标识,这会自动加载数据库连接,我一般都是手动加载的。

ok,我们下一期会带大家学习 CI 的 视图(View)是如何工作的?再会~~

发布了8 篇原创文章 · 获赞 0 · 访问量 166

猜你喜欢

转载自blog.csdn.net/weixin_43950095/article/details/104589943
今日推荐