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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185 |
今天花了点时间看了下CI框架源码缓存的实现,写出来梳理下思路. 1:在CI框架中加载视图文件使用的是 $this ->load->view();方法,所以从load类库着手,在ci的system文件夹中可以看到Loader.php,这个类库是在Controller.php中被加载的。Loader类中有个方法: function
view( $view , $vars
= array (), $return
= FALSE) //加载视图 { return $this ->_ci_load( array ( ‘_ci_view‘
=> $view , ‘_ci_vars‘
=> $this ->_ci_object_to_array( $vars ), ‘_ci_return‘
=> $return )); } 调用了自身的一个私有方法_ci_load(),这个方法其中关键部分在: ob_start(); //开启缓存 // If the PHP installation does not support short tags we‘ll // do a little string replacement, changing the short tags // to standard PHP echo statements. if ((bool) @ ini_get ( ‘short_open_tag‘ ) === FALSE AND config_item( ‘rewrite_short_tags‘ ) == TRUE) { echo
eval ( ‘?>‘ .preg_replace( "/;*\s*\?>/" , "; ?>" , str_replace (‘< php echo
file_get_contents_ci_pathbr /> } else { //将视图包含进来 include ( $_ci_path ); // include() vs include_once() allows for multiple views with the same name } if (ob_get_level() < $this -<_ci_ob_level + 1) { ob_end_flush(); } else { $_ci_CI -<output-<append_output(ob_get_contents()); //获取缓存,调用了output类中的append_output方法将缓存的内容放到了output类的全局变量final_output中,供后面使用。 @ob_end_clean(); } 2:CI框架中设置缓存的方法是 $this -<output-<cache(n) //n是分钟数 打开system/core/Output.php在里面有个cache方法: function
cache( $time ) { $this -<cache_expiration = ( ! is_numeric ( $time )) ? 0 : $time ; //output类中变量cache_expiration赋上缓存时间 return
$this ; } 3:打开system/core/Codeigniter.php这个核心文件。可以看到如下代码: $OUT =& load_class( ‘Output‘ , ‘core‘ ); //实例化output类 // 调用钩子 cache_override hook if ( $EXT ->_call_hook( ‘cache_override‘ ) === FALSE) //如果没有设置这个缓存钩子就使用默认的_display_cache方法 { if ( $OUT ->_display_cache( $CFG , $URI ) == TRUE) //将config,uri类的对象传入 { exit ; //如果调用缓存成功就会直接显示页面中断程序,不会加载实例化下面的类,进行一些请求,这就是缓存的好处; } } 4:找到Output.php类中的私有方法_display_cache( $CFG , $URI ): function
_display_cache(& $CFG , & $URI ) { //是否在配置文件中定义了缓存路径,如果没有是用系统默认的cache文件夹作为缓存目录 $cache_path
= ( $CFG ->item( ‘cache_path‘ ) == ‘‘ ) ? APPPATH. ‘cache/‘
: $CFG ->item( ‘cache_path‘ ); // 构造文件路径。文件名是 URI 的 md5 值 $uri = $CFG ->item( ‘base_url‘ ). $CFG ->item( ‘index_page‘ ). $URI ->uri_string; //这是请求的页面的控制器/方法/参数那一串字符 $filepath
= $cache_path .md5( $uri ); // 判断文件是否存在 if ( ! @ file_exists ( $filepath )) { return
FALSE; //到了这里就中断了,而是按照正常的向服务器请求页面内容,下面的return false同理 } if ( ! $fp = @ fopen ( $filepath , FOPEN_READ)) { return
FALSE; } flock ( $fp , LOCK_SH); //读取文件前给文件加个共享锁 $cache
= ‘‘ ; if ( filesize ( $filepath ) > 0) { $cache
= fread ( $fp , filesize ( $filepath )); } flock ( $fp , LOCK_UN); //释放锁 fclose( $fp ); // 匹配内嵌时间戳 if ( ! preg_match( "/(\d+TS--->)/" , $cache , $match )) { return
FALSE; } // Has the file expired? If so we‘ll delete it. // 文件过期了,就删掉 if (time() >= trim( str_replace ( ‘TS--->‘ , ‘‘ , $match [ ‘1‘ ]))) { if (is_really_writable( $cache_path )) { @unlink( $filepath ); log_message( ‘debug‘ , "Cache file has expired. File deleted" ); return
FALSE } // Display the cache // 显示缓存,到了这里说明有缓存文件并且缓存文件没过期,然后执行_display方法 $this ->_display( str_replace ( $match [ ‘0‘ ], ‘‘ , $cache )); log_message( ‘debug‘ , "Cache file is current. Sending it to browser." ); return
TRUE; } 5:找到Output方法中的_display( $output = ‘‘ )方法,这个 方法有两处调用了,1个是在上述的_display_cache中,将缓存文件中的内容取出赋于 $output 变量然后传入_display( $output = ‘‘ )中,这时候只会执行_display中的: //$CI 对象不存在,我们就知道我们是在处理缓存文件,所以简单的输出和退出 if ( ! isset( $CI )) { echo
$output ; //直接将缓存输出,返回ture中断codeigniter继续执行 log_message( ‘debug‘ , "Final output sent to browser" ); log_message( ‘debug‘ , "Total execution time: " . $elapsed ); return
TRUE; } 第二处调用是,当 if
( $OUT ->_display_cache( $CFG , $URI ) == TRUE)这个判断不成立codeigniter向下执行, 先后实例化了一些系统核心类,以及url中请求的控制器方法等.最后执行一个钩子: // 调用 display_override hook if ( $EXT ->_call_hook( ‘display_override‘ ) === FALSE) { $OUT ->_display(); } 这时候执行这个方法是无缓存的情况下. 这时候 $output 为空所以执行了: // 设置输出数据 if ( $output == ‘‘ ) { $output
=& $this ->final_output; //这就是在Loader中设置的输出缓存. } 接下来如果执行了 $this ->output->cache()方法设置了 $this ->cache_expiration 参数且没有缓存文件时: // 启用 cache 时,$CI 没有 _output 函数时,调用 $this->_write_cache,写缓存文件 if ( $this ->cache_expiration > 0 && isset( $CI ) && ! method_exists( $CI , ‘_output‘ )) { $this ->_write_cache( $output ); } _write_cache( $output )方法如下: function
_write_cache( $output ) { $CI
=& get_instance(); $path
= $CI ->config->item( ‘cache_path‘ ); $cache_path
= ( $path == ‘‘ ) ? APPPATH. ‘cache/‘
: $path ; // $cache_path 是目录并且可写 if ( ! is_dir ( $cache_path ) OR ! is_really_writable( $cache_path )) { log_message( ‘error‘ , "Unable to write cache file: " . $cache_path ); return ; } $uri
= $CI ->config->item( ‘base_url‘ ). $CI ->config->item( ‘index_page‘ ). $CI ->uri->uri_string(); $cache_path
.= md5( $uri ); if ( ! $fp = @ fopen ( $cache_path , FOPEN_WRITE_CREATE_DESTRUCTIVE)) { log_message( ‘error‘ , "Unable to write cache file: " . $cache_path ); return ; } // 加个时间戳,指示过期时间 $expire
= time() + ( $this ->cache_expiration * 60); if ( flock ( $fp , LOCK_EX)) //写入前先加个独占锁 { fwrite( $fp , $expire . ‘TS--->‘ . $output ); flock ( $fp , LOCK_UN); //写完解锁 } else { log_message( ‘error‘ , "Unable to secure a file lock for file at: " . $cache_path ); return ; } fclose( $fp ); @ chmod ( $cache_path , FILE_WRITE_MODE); log_message( ‘debug‘ , "Cache file written: " . $cache_path ); } 写完缓存后会进行一系列处理比如设置header等 最后输出 $output : if (method_exists( $CI , ‘_output‘ )) { $CI ->_output( $output ); } else { echo
$output ; // Send it to the browser! } 总结:CI的缓存是在要输出的页面设置ob_start(),使用ob_get_contents()获取缓存内容,然后通过判断设置中 是否设置缓存.如果设置了则将缓存将页面的url地址进行MD5哈希作为缓存文件名创建之,然后将(当前时间+设置的缓存时间)+一个特殊符号+内容写到 缓存文件中,下次访问时候将访问的url进行MD5查找这个缓存文件,如果没有则再创建.有则取出其中的内容,分离出过期时间和内容,判断时间是否过期, 如果过期则丢弃内容,继续进行请求,如果没过期直接取出内容输出到页面,中断执行。CI将这一套缓存机制用面向对象的方法写到了框架中,使用起来很方便。 CI默认的这种缓存方法是缓存整个页面。但有时候只要缓存页面中不变的元素header和footer比较好,CI中还有钩子的机制,可以自己设置缓存的 方法替换其中的_display_cache()方法。具体的可以看手册 |
原文地址:http://www.cnblogs.com/flying-tx/p/3699536.html