关于CI中的模型的标准用法是先要load然后才能使用的,基于这种调用模型的做法长久下来就让paperen觉得麻烦,一开头就得要将用到的模型手动全部load过来,当然这种算是比较苛刻的做法也是有它的理由,毕竟这就清楚这部分引用了哪些模型与减少加载多余模型的机会。

// 加载用户模型
$this->load->model('user');
// 获取所有用户数据
$this->user->all( $per_page, $offset );

CI的模型文档 http://ellislab.com/codeigniter/user-guide/general/models.html

在开始后续内容之前还是声明一下

调用即加载

paperen想象中要达到的目的就是

// YY一下要实现的调用写法
$this->model_user_all( $per_page, $offset );

我们不需要再先load,调用的时候就会自动加载,格式必须是model_{模型名称}_{方法}( 参数1, 参数2, ... )

如果你觉得这样不适合自己使用就不用往下看了

附带查询缓存的概念
// YY一下要实现的调用写法
$this->model_cache( 'model_user_all', $per_page, $offset );

之前也有一篇关于查询缓存的文章 http://paperen.com/post/ci-querycache-extend

其实就是避免不同模块执行了相同的SQL语句,模块之间的数据完成是可以公用的,而这个querycache就是桥梁,也算是一种解决办法吧,不过在这次扩展中querycache将成为其中的组件,对于我们来说它完全是透明的

强调一下 对插入与更新、删除的动作不要使用查询缓存 paperen在代码中并没有限制这个也就是意味着你可以使用model_cache来完成update、insert、delete等操作,但是这有什么意义呢。。。

使用例子
// 某个模块某个方法中------------------
// 获取所有用户数据(获取第5条后的5条记录)
$user_data = $this->model_user_all( 5, 5 );
// 获取所有用户数据(获取第5条后的5条记录 带缓存模式)
$user_data = $this->model_cache( 'model_user_all', 5, 5 );

// user模型的all方法------------------
public function all( $per_page = 0, $offset = 0 ) {
 $query = $this->db->from('user');
 if ( $per_page ) $query->limit ( $per_page, $offset );
 return $query->get()->result_array();
}

如何观察缓存模式是否起作用?

开启output的enable_profiler,在不同的模块中使用缓存模式执行同一个模型同一个方法相同的参数

这里加载了match模块main控制器的index方法,而里面也会对user_test模型执行all方法一次,相同的参数,再观察底部的调试输出

可以看到整个过程只执行了一条SQL,就证明查询缓存起作用了

此扩展涉及文件
  • 调整 application/core/MY_Loader.php
  • 新增 application/core/MY_Module.php
  • 新增 application/libraries/Querycache.php
MY_Loader.php

在hex的基础上允许CI_Module可以被继承 第133行加入以下代码

// 如果有继承Module
// @author paperen
$extend_module = APPPATH . 'core/' . config_item( 'subclass_prefix' ) . 'Module' . EXT;
if ( file_exists( $extend_module ) ) require_once( $extend_module );

去掉模型加载失败后就exit的情况 317行的show_error注释掉

// @paperen
// 去掉报错
//show_error('Unable to locate the model you have specified: '.$model);

新增的两个文件放到相应目录就可以了

值得注意的有模块要继承MY_Module,修改config/autoload.php中的libraries加入querycache,一开始就自动加载它 而当然如果你测试模型的话也可以在此加入database 自动加载db类

之后你就可以在自己的模块中体验了,希望对你有用~~

相关代码

http://paperen.com/demo/ci-model-extend/application.zip

还可以在GIST上查看到

=============== 到这里也算是尾声了 如果对代码分析也有兴趣的可以继续往下看 =====================

代码解析

主要是解析一下MY_Module,MY_Loader主要是修改了加载CI_Module前检查一下是否存在MY_Module,有则加载没有什么亮点,而querycache其实也是个打酱油的货……

MY_Module

/**
 * 通过魔法方法__call
 * @param string $name
 * @param array $arguments
 */
function __call( $name, $arguments ) {
 $tmp_arr = explode( '_', strtolower( $name ) );
 if ( isset( $tmp_arr[0] ) && $tmp_arr[0] == 'model' ) return $this->_model( $name, $arguments );
}

实现这个魔法方法其实是为了在模块调用一些莫名奇妙的方法时能进行特殊处理,要明白$this->model_user_all(2)执行时其实就来到这个魔法方法,因为model_user_all并没有声明,也就是$name是model_user_all,$arguments为array(2),关于__call这些魔法方法不太了解的可以自行查查或者自己写一段一跑就明白了

可以看到这里对$name统一小写并使用下划线分割,之后检查数组第一个元素是否model,是则执行_model方法后续操作

_model这个方法先分开三部分来看

第一部份
$tmp_arr = explode( '_', strtolower( $name ) );
array_shift( $tmp_arr );

// 没有声明使用哪个模型
if ( empty( $tmp_arr ) ) exit( "\$this->{$name} ,model doesn't define" );

// 没有声明使用哪个方法
if ( count( $tmp_arr ) == 1 ) exit( "\$this->{$name} ,method doesn't define" );

// 正常模式
// 穷举所有可能性
$possible_couple = array( );
$length = count( $tmp_arr );
for ( $i = 0; $i < $length - 1; $i++ ) {
 $possible_couple[] = array(
  'model' => implode( '_', array_slice( $tmp_arr, 0, $i + 1 ) ),
  'method' => implode( '_', array_slice( $tmp_arr, $i + 1 ) ),
 );
}

将$name做了同样的操作(其实这里可以在__call中将$tmp_arr直接传过来得了,真是多此一举……现在才发现)

array_shift($tmp_arr)将该数组第一个元素弹出,也就是没有model了,后续连续两个检测,若果弹出model后是空则是没定义调用的model(额……这里的exit应该换成show_error();),如果数组元素不超过两个证明没有定义调用哪个方法,记住model_user_all这个字符串,也就是说中间那个是模型名,后面那个是方法名,缺一不可

再看后续的穷举,为什么要这样?因为这种规则对于模型是存在下划线的情况会崩溃,比如一个模型叫user_test,那么按照这种写法就是$this->model_user_test_all(),我们无法知道是user模型的test_all方法还是user_test模型的all方法,既然这样干脆就穷举好了,把所有组合都列出来,看哪个行德通,这里用到array_slice来分割数组

对于$this->model_user_test_all()这种运行完这步后,$possible_couple变量应该是

array(
array('model'=>'user','method'=>'test_all'),
array('model'=>'user_test','method'=>'all'),
)
第二部份
foreach ( $possible_couple as $single ) {
 $model_name = $single['model'];
 $method_name = $single['method'];
 $this->load->model( $model_name );
 if ( !isset( $this->$model_name  ) ) continue;
 if ( !in_array( $method_name, get_class_methods( $this->$model_name ) ) ) continue;
 return call_user_func_array( array( $this->$model_name, $method_name ), $arguments );
}
return NULL;

每种组合都进行一下加载,若成功而且判断该模型存在该方法就执行并返回结果

第三部份
// 缓存模式
if ( $tmp_arr[0] == 'cache' ) {
 $model_method = array_shift( $arguments );
 $hash = md5( $model_method . '_' . serialize( $arguments ) );
 $data = $this->querycache->get( $hash );
 if ( empty( $data ) ) {
  $data = call_user_func_array( array( $this, $model_method ), $arguments );
  $this->querycache->save( $hash, $data );
 }
 return $data;
}

如果去掉model后那个数组第一个元素是cache那么就是缓存模式的调用,记得缓存模式是怎调用吧?$this->model_cache( 'model_user_all', 5, 0 );也就是说$arguments现在是array('model_user_all',5,0)将该数组第一个元素弹出就是模型与方法的组合字符串。剩下的是参数,将参数序列化拼上模型方法的字符串再MD5作为缓存索引,问querycache是否存在这个索引,有则返回,没有则调用自身$model_method,注意看call_user_func_array( array( $this, $model_method ), $arguments );其实对于这个例子就是$this->model_user_all( 5,0 );不懂的可以查查call_user_func_array,执行后将结果放回缓存中

我想到这里已经没什么可以说的了