模型
啥是模型?模型是干嘛的?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)是如何工作的?再会~~