MVC中的模板引擎程序实例
2011-03-21
MVC是模型(Model)、视图(View)和控制(Controller)的缩写,PHP中采用MVC模式的目的是实现Web系统的职能分工,通俗的说就是把业务逻辑处理从用户界面视图中分离出来。使Web系统的开发与维护更加方便,从而有效的节省人力物力,受到了越来越多企业的青眯。
模板引擎是MVC模式建立过程的重要方法,开发者可以设计一套赋予含义的标签,通过技术解析处理有效的把数据逻辑处理从界面模板中提取出来,通过解读标签的含义把控制权提交给相应业务逻辑处理程序,从而获取到需要的数据,以模板设计的形式展现出来,使设计人员能把精力更多放在表现形式上。下面是我对模板引擎的认识与设计方法:
说的好听些叫模板引擎,实际就是解读模板数据的过程(个人观点^^)。通过我对建站方面的思考认识,网站在展现形式上无非归纳为单条和多条两种形式,那么我们可以设定两种对应标签(如data、list)来处理这两种情况,关键点在于解决两种标签的多层相互嵌套问题,基本适合实现80%界面形式。
解读模板的方法有多种,常用的包括字符串处理(解决嵌套稍麻烦)、正则表达式。在这里我选用的正则表达式,下面是我的处理方法(本文仅提供思路和参考代码,可能不能直接使用)。
模板文件解析类:
001 | <?php |
002 |
003 | class Template { |
004 | public $html , $vars , $bTag , $eTag ; |
005 | public $bFlag = '{' , $eFlag = '}' , $pfix = 'zmm:' ; |
006 | private $folder , $file ; |
007 |
008 | function __construct( $vars = array ()) { |
009 | ! empty ( $vars ) && $this ->vars = $vars ; |
010 | ! empty ( $GLOBALS [ 'cfg_tag_prefix' ]) && |
011 | $this ->pfix = $GLOBALS [ 'cfg_tag_prefix' ]. ':' ; |
012 | $this ->bTag = $this ->bFlag. $this ->pfix; |
013 | $this ->eTag = $this ->bFlag. '\/' . $this ->pfix; |
014 | empty (Tags:: $vars ) && Tags:: $vars = & $this ->vars; |
015 | } |
016 |
017 | public function LoadTpl( $tpl ) { |
018 | $this ->file = $this ->GetTplPath( $tpl ); |
019 | Tags:: $file = & $this ->file; |
020 | if ( is_file ( $this ->file)) { |
021 | if ( $this ->GetTplHtml()) { |
022 | $this ->SetTplTags(); |
023 | } else { |
024 | exit ( '模板文件加载失败!' ); |
025 | } |
026 | } else { |
027 | exit ( '模板文件[' . $this ->file. ']不存在!' ); |
028 | } |
029 | } |
030 |
031 | private function GetTplPath( $tpl ) { |
032 | $this ->folder = WEBSITE_DIRROOT. |
033 | $GLOBALS [ 'cfg_tpl_root' ]; |
034 | return $this ->folder. '/' . $tpl ; |
035 | } |
036 |
037 | private function GetTplHtml() { |
038 | $html = self::FmtTplHtml( file_get_contents ( $this ->file)); |
039 | if (! empty ( $html )) { |
040 | $callFunc = Tags:: $prefix . 'Syntax' ; |
041 | $this ->html = Tags:: $callFunc ( $html , new Template()); |
042 | } else { |
043 | exit ( '模板文件内容为空!' ); |
044 | } return true; |
045 | } |
046 |
047 | static public function FmtTplHtml( $html ) { |
048 | return preg_replace( '/(\r)|(\n)|(\t)|(\s{2,})/is' , '' , $html ); |
049 | } |
050 |
051 | public function Register( $vars = array ()) { |
052 | if ( is_array ( $vars )) { |
053 | $this ->vars = $vars ; |
054 | Tags:: $vars = & $this ->vars; |
055 | } |
056 | } |
057 |
058 | public function Display( $bool =false, $name = "" , $time =0) { |
059 | if (! empty ( $this ->html)) { |
060 | if ( $bool && ! empty ( $name )) { |
061 | if (! is_int ( $time )) $time = 600; |
062 | $cache = new Cache( $time ); |
063 | $cache ->Set( $name , $this ->html); |
064 | } |
065 | echo $this ->html; flush (); |
066 | } else { |
067 | exit ( '模板文件内容为空!' ); |
068 | } |
069 | } |
070 |
071 | public function SetAssign( $souc , $info ) { |
072 | if (! empty ( $this ->html)) { |
073 | $this ->html = str_ireplace ( $souc , self::FmtTplHtml( $info ), $this ->html); |
074 | } else { |
075 | exit ( '模板文件内容为空!' ); |
076 | } |
077 | } |
078 |
079 | private function SetTplTags() { |
080 | $this ->SetPanelTags(); $this ->SetTrunkTags(); $this ->RegHatchVars(); |
081 | } |
082 |
083 | private function SetPanelTags() { |
084 | $rule = $this ->bTag. '([^' . $this ->eFlag. ']+)\/' . $this ->eFlag; |
085 | preg_match_all( '/' . $rule . '/ism' , $this ->html, $out_matches ); |
086 | $this ->TransTag( $out_matches , 'panel' ); unset( $out_matches ); |
087 | } |
088 |
089 | private function SetTrunkTags() { |
090 | $rule = $this ->bTag. '(\w+)\s*([^' . $this ->eFlag. ']*?)' . $this ->eFlag. |
091 | '((?:(?!' . $this ->bTag. ')[\S\s]*?|(?R))*)' . $this ->eTag. '\\1\s*' . $this ->eFlag; |
092 | preg_match_all( '/' . $rule . '/ism' , $this ->html, $out_matches ); |
093 | $this ->TransTag( $out_matches , 'trunk' ); unset( $out_matches ); |
094 | } |
095 |
096 | private function TransTag( $result , $type ) { |
097 | if (! empty ( $result [0])) { |
098 | switch ( $type ) { |
099 | case 'panel' : { |
100 | for ( $i = 0; $i < count ( $result [0]); $i ++) { |
101 | $strTag = explode ( ' ' , $result [1][ $i ], 2); |
102 | if ( strpos ( $strTag [0], '.' )) { |
103 | $itemArg = explode ( '.' , $result [1][ $i ], 2); |
104 | $callFunc = Tags:: $prefix .ucfirst( $itemArg [0]); |
105 | if (method_exists( 'Tags' , $callFunc )) { |
106 | $html = Tags:: $callFunc ( chop ( $itemArg [1])); |
107 | if ( $html !== false) { |
108 | $this ->html = str_ireplace ( $result [0][ $i ], $html , $this ->html); |
109 | } |
110 | } |
111 | } else { |
112 | $rule = '^([^\s]+)\s*([\S\s]+)$' ; |
113 | preg_match_all( '/' . $rule . '/is' , trim( $result [1][ $i ]), $tmp_matches ); |
114 | $callFunc = Tags:: $prefix .ucfirst( $tmp_matches [1][0]); |
115 | if (method_exists( 'Tags' , $callFunc )) { |
116 | $html = Tags:: $callFunc ( $tmp_matches [2][0]); |
117 | if ( $html !== false) { |
118 | $this ->html = str_ireplace ( $result [0][ $i ], $html , $this ->html); |
119 | } |
120 | } unset( $tmp_matches ); |
121 | } |
122 | } break ; |
123 | } |
124 | case 'trunk' : { |
125 | for ( $i = 0; $i < count ( $result [0]); $i ++) { |
126 | $callFunc = Tags:: $prefix .ucfirst( $result [1][ $i ]); |
127 | if (method_exists( 'Tags' , $callFunc )) { |
128 | $html = Tags:: $callFunc ( $result [2][ $i ], $result [3][ $i ]); |
129 | $this ->html = str_ireplace ( $result [0][ $i ], $html , $this ->html); |
130 | } |
131 | } break ; |
132 | } |
133 | default : break ; |
134 | } |
135 | } else { |
136 | return false; |
137 | } |
138 | } |
139 |
140 | private function RegHatchVars() { |
141 | $this ->SetPanelTags(); |
142 | } |
143 |
144 | function __destruct() {} |
145 | } |
146 | ?> |
标签解析类:(目前暂时提供data、list两种标签的解析,说明思路)
001 | <?php |
002 | class Tags { |
003 | static private $attrs =null; |
004 | static public $file , $vars , $rule , $prefix = 'TAG_' ; |
005 |
006 | static public function TAG_Syntax( $html , $that ) { |
007 | $rule = $that ->bTag. 'if\s+([^' . $that ->eFlag. ']+)\s*' . $that ->eFlag; |
008 | $html = preg_replace( '/' . $rule . '/ism' , '<?php if (\\1) { ?>' , $html ); |
009 | $rule = $that ->bTag. 'elseif\s+([^' . $that ->eFlag. ']+)\s*' . $that ->eFlag; |
010 | $html = preg_replace( '/' . $rule . '/ism' , '<?php } elseif (\\1) { ?>' , $html ); |
011 | $rule = $that ->bTag. 'else\s*' . $that ->eFlag; |
012 | $html = preg_replace( '/' . $rule . '/ism' , '<?php } else { ?>' , $html ); |
013 | $rule = $that ->bTag. 'loop\s+(\S+)\s+(\S+)\s*' . $that ->eFlag; |
014 | $html = preg_replace( '/' . $rule . '/ism' , '<?php foreach (\\1 as \\2) { ?>' , $html ); |
015 | $rule = $that ->bTag. 'loop\s+(\S+)\s+(\S+)\s+(\S+)\s*' . $that ->eFlag; |
016 | $html = preg_replace( '/' . $rule . '/ism' , '<?php foreach (\\1 as \\2 => \\3) { ?>' , $html ); |
017 | $rule = $that ->eTag. '(if|loop)\s*' . $that ->eFlag; |
018 | $html = preg_replace( '/' . $rule . '/ism' , '<?php } ?>' , $html ); |
019 | $rule = $that ->bTag. 'php\s*' . $that ->eFlag. '((?:(?!' . |
020 | $that ->bTag. ')[\S\s]*?|(?R))*)' . $that ->eTag. 'php\s*' . $that ->eFlag; |
021 | $html = preg_replace( '/' . $rule . '/ism' , '<?php \\1 ?>' , $html ); |
022 |
023 | return self::TAG_Execute( $html ); |
024 | } |
025 |
026 | static public function TAG_List( $attr , $html ) { |
027 | if (! empty ( $html )) { |
028 | if (self::TAG_HaveTag( $html )) { |
029 | return self::TAG_DealTag( $attr , $html , true); |
030 | } else { |
031 | return self::TAG_GetData( $attr , $html , true); |
032 | } |
033 | } else { |
034 | exit ( '标签{list}的内容为空!' ); |
035 | } |
036 | } |
037 |
038 | static public function TAG_Data( $attr , $html ) { |
039 | if (! empty ( $html )) { |
040 | if (self::TAG_HaveTag( $html )) { |
041 | return self::TAG_DealTag( $attr , $html , false); |
042 | } else { |
043 | return self::TAG_GetData( $attr , $html , false); |
044 | } |
045 | } else { |
046 | exit ( '标签{data}的内容为空!' ); |
047 | } |
048 | } |
049 |
050 | static public function TAG_Execute( $html ) { |
051 | ob_clean(); ob_start(); |
052 | if (! empty (self:: $vars )) { |
053 | is_array (self:: $vars ) && |
054 | extract(self:: $vars , EXTR_OVERWRITE); |
055 | } |
056 | $file_inc = WEBSITE_DIRINC. '/buffer/' . |
057 | md5(uniqid(rand(), true)). '.php' ; |
058 |
059 | if ( $fp = fopen ( $file_inc , 'xb' )) { |
060 | fwrite( $fp , $html ); |
061 | if (fclose( $fp )) { |
062 | include ( $file_inc ); |
063 | $html = ob_get_contents(); |
064 | } unset( $fp ); |
065 | } else { |
066 | exit ( '模板解析文件生成失败!' ); |
067 | } ob_end_clean(); @unlink( $file_inc ); |
068 |
069 | return $html ; |
070 | } |
071 |
072 | static private function TAG_HaveTag( $html ) { |
073 | $bool_has = false; |
074 |
075 | $tpl_ins = new Template(); |
076 | self:: $rule = $tpl_ins ->bTag. '([^' . $tpl_ins ->eFlag. ']+)\/' . $tpl_ins ->eFlag; |
077 | $bool_has = $bool_has || preg_match( '/' .self:: $rule . '/ism' , $html ); |
078 | self:: $rule = $tpl_ins ->bTag. '(\w+)\s*([^' . $tpl_ins ->eFlag. ']*?)' . $tpl_ins ->eFlag. |
079 | '((?:(?!' . $tpl_ins ->bTag. ')[\S\s]*?|(?R))*)' . $tpl_ins ->eTag. '\\1\s*' . $tpl_ins ->eFlag; |
080 | $bool_has = $bool_has || preg_match( '/' .self:: $rule . '/ism' , $html ); |
081 | unset( $tpl_ins ); |
082 |
083 | return $bool_has ; |
084 | } |
085 |
086 | static private function TAG_DealTag( $attr , $html , $list ) { |
087 | preg_match_all( '/' .self:: $rule . '/ism' , $html , $out_matches ); |
088 | if (! empty ( $out_matches [0])) { |
089 | $child_node = array (); |
090 | for ( $i = 0; $i < count ( $out_matches [0]); $i ++) { |
091 | $child_node [] = $out_matches [3][ $i ]; |
092 | $html = str_ireplace ( $out_matches [3][ $i ], '{-->>child_node_' . $i . '<<--}' , $html ); |
093 | } |
094 | $html = self::TAG_GetData( $attr , $html , $list ); |
095 | for ( $i = 0; $i < count ( $out_matches [0]); $i ++) { |
096 | $html = str_ireplace ( '{-->>child_node_' . $i . '<<--}' , $child_node [ $i ], $html ); |
097 | } |
098 |
099 | preg_match_all( '/' .self:: $rule . '/ism' , $html , $tmp_matches ); |
100 | if (! empty ( $tmp_matches [0])) { |
101 | for ( $i = 0; $i < count ( $tmp_matches [0]); $i ++) { |
102 | $callFunc = self:: $prefix .ucfirst( $tmp_matches [1][ $i ]); |
103 | if (method_exists( 'Tags' , $callFunc )) { |
104 | $temp = self:: $callFunc ( $tmp_matches [2][ $i ], $tmp_matches [3][ $i ]); |
105 | $html = str_ireplace ( $tmp_matches [0][ $i ], $temp , $html ); |
106 | } |
107 | } |
108 | } |
109 | unset( $tmp_matches ); |
110 | } |
111 | unset( $out_matches ); return $html ; |
112 | } |
113 |
114 | static private function TAG_GetData( $attr , $html , $list =false) { |
115 | if (! empty ( $attr )) { |
116 | $attr_ins = new Attbt( $attr ); |
117 | $attr_arr = $attr_ins ->attrs; |
118 | if ( is_array ( $attr_arr )) { |
119 | extract( $attr_arr , EXTR_OVERWRITE); |
120 | $source = table_name( $source , $column ); |
121 |
122 | $rule = '\[field:\s*(\w+)\s*([^\]]*?)\s*\/?]' ; |
123 | preg_match_all( '/' . $rule . '/is' , $html , $out_matches ); |
124 |
125 | $data_str = '' ; |
126 | $data_ins = new DataSql(); |
127 |
128 | $attr_where = $attr_order = '' ; |
129 | if (! empty ( $where )) { |
130 | $where = str_replace ( ',' , ' and ' , $where ); |
131 | $attr_where = ' where ' . $where ; |
132 | } |
133 | if (! empty ( $order )) { |
134 | $attr_order = ' order by ' . $order ; |
135 | } else { |
136 | $fed_name = '' ; |
137 | $fed_ins = $data_ins ->GetFedNeedle( $source ); |
138 | $fed_cnt = $data_ins ->GetFedCount( $fed_ins ); |
139 | for ( $i = 0; $i < $fed_cnt ; $i ++) { |
140 | $fed_flag = $data_ins ->GetFedFlag( $fed_ins , $i ); |
141 | if (preg_match( '/auto_increment/ism' , $fed_flag )) { |
142 | $fed_name = $data_ins ->GetFedName( $fed_ins , $i ); |
143 | break ; |
144 | } |
145 | } |
146 | if (! empty ( $fed_name )) |
147 | $attr_order = ' order by ' . $fed_name . ' desc' ; |
148 | } |
149 |
150 | if ( $list == true) { |
151 | if ( empty ( $source ) && empty ( $sql )) { |
152 | exit ( '标签{list}必须指定source属性!' ); |
153 | } |
154 |
155 | $attr_rows = $attr_page = '' ; |
156 | if ( $rows > 0) { |
157 | $attr_rows = ' limit 0,' . $rows ; |
158 | } |
159 |
160 | if (! empty ( $sql )) { |
161 | $data_sql = $sql ; |
162 | } else { |
163 | $data_sql = 'select * from `' . $source . '`' . |
164 | $attr_where . $attr_order . $attr_rows ; |
165 | } |
166 |
167 | if ( $pages == 'true' && ! empty ( $size )) { |
168 | $data_num = $data_ins ->GetRecNum( $data_sql ); |
169 | $page_cnt = ceil ( $data_num / $size ); |
170 |
171 | global $page ; |
172 | if (!isset( $page ) || $page < 1) $page = 1; |
173 | if ( $page > $page_cnt ) $page = $page_cnt ; |
174 | $data_sql = 'select * from `' . $source . '`' . $attr_where . |
175 | $attr_order . ' limit ' .( $page -1) * $size . ',' . $size ; |
176 |
177 | $GLOBALS [ 'cfg_page_curr' ] = $page ; |
178 | $GLOBALS [ 'cfg_page_prev' ] = $page - 1; |
179 | $GLOBALS [ 'cfg_page_next' ] = $page + 1; |
180 | $GLOBALS [ 'cfg_page_nums' ] = $page_cnt ; |
181 | if (function_exists( 'list_pagelink' )) { |
182 | $GLOBALS [ 'cfg_page_list' ] = list_pagelink( $page , $page_cnt , 2); |
183 | } |
184 | } |
185 |
186 | $data_idx = 0; |
187 | $data_ret = $data_ins ->SqlCmdExec( $data_sql ); |
188 | while ( $row = $data_ins ->GetRecArr( $data_ret )) { |
189 | if ( $skip > 0 && ! empty ( $flag )) { |
190 | $data_idx != 0 && |
191 | $data_idx % $skip == 0 && |
192 | $data_str .= $flag ; |
193 | } |
194 |
195 | $data_tmp = $html ; |
196 | $data_tmp = str_ireplace ( '@idx' , $data_idx , $data_tmp ); |
197 | for ( $i = 0; $i < count ( $out_matches [0]); $i ++) { |
198 | $data_tmp = str_ireplace ( $out_matches [0][ $i ], |
199 | $row [ $out_matches [1][ $i ]], $data_tmp ); |
200 | } |
201 | $data_str .= $data_tmp ; $data_idx ++; |
202 | } |
203 | } else { |
204 | if ( empty ( $source )) { |
205 | exit ( '标签{data}必须指定source属性!' ); |
206 | } |
207 | |
208 | $data_sql = 'select * from `' . $source . |
209 | '`' . $attr_where . $attr_order ; |
210 | $row = $data_ins ->GetOneRec( $data_sql ); |
211 | if ( is_array ( $row )) { |
212 | $data_tmp = $html ; |
213 | for ( $i = 0; $i < count ( $out_matches [0]); $i ++) { |
214 | $data_val = $row [ $out_matches [1][ $i ]]; |
215 | if ( empty ( $out_matches [2][ $i ])) { |
216 | $data_tmp = str_ireplace ( $out_matches [0][ $i ], $data_val , $data_tmp ); |
217 | } else { |
218 | $attr_str = $out_matches [2][ $i ]; |
219 | $attr_ins = new Attbt( $attr_str ); |
220 | $func_txt = $attr_ins ->attrs[ 'function' ]; |
221 | if (! empty ( $func_txt )) { |
222 | $func_tmp = explode ( '(' , $func_txt ); |
223 | if (function_exists( $func_tmp [0])) { |
224 | eval ( '$func_ret =' . str_ireplace ( '@me' , |
225 | '\'' . $data_val . '\'' , $func_txt )); |
226 |
227 | $data_tmp = str_ireplace ( $out_matches [0][ $i ], $func_ret , $data_tmp ); |
228 | } else { |
229 | exit ( '调用了不存在的函数!' ); |
230 | } |
231 | } else { |
232 | exit ( '标签设置属性无效!' ); |
233 | } |
234 | } |
235 | } |
236 | $data_str .= $data_tmp ; |
237 | } |
238 | } |
239 | unset( $data_ins ); |
240 |
241 | return $data_str ; |
242 | } else { |
243 | exit ( '标签设置属性无效!' ); |
244 | } |
245 | } else { |
246 | exit ( '没有设置标签属性!' ); |
247 | } |
248 | } |
249 |
250 | static public function __callStatic( $name , $args ) { |
251 | exit ( '标签{' . $name . '}不存在!' ); |
252 | } |
253 | } |
254 | ?> |