作者:mckee

这篇文章主要介绍了PHP模板解析类,涉及php针对模板文件的解析与字符串处理的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下

  1. <?php
  2. class template {
  3. private $vars = array();
  4. private $conf = '';
  5. private $tpl_name = 'index';
  6. //如果模板不存在 会查找当前 controller默认index模板
  7. private $tpl_suffix = '.html';//如果CONFIG没配置默认后缀 则显示
  8. private $tpl_compile_suffix= '.tpl.php';//编译模板路径
  9. private $template_tag_left = '<{';//模板左标签
  10. private $template_tag_right = '}>';//模板右标签
  11. private $template_c = '';//编译目录
  12. private $template_path = '';//模板完整路径
  13. private $template_name = '';//模板名称 index.html
  14. //定义每个模板的标签的元素
  15. private $tag_foreach = array('from', 'item', 'key');
  16. private $tag_include = array('file');//目前只支持读取模板默认路径
  17. public function __construct($conf) {
  18. $this->conf = &$conf;
  19. $this->template_c = $this->conf['template_config']['template_c'];//编译目录
  20. $this->_tpl_suffix = $this->tpl_suffix();
  21. }
  22. private function str_replace($search, $replace, $content) {
  23. if(empty($search) || empty($replace) || empty($content)) return false;
  24. return str_replace($search, $replace, $content);
  25. }
  26. /**
  27. * preg_match_all
  28. * @param $pattern 正则
  29. * @param $content 内容
  30. * @return array
  31. */
  32. private function preg_match_all($pattern, $content) {
  33. if(empty($pattern) || empty($content)) core::show_error('查找模板标签失败!');
  34. preg_match_all("/".$this->template_tag_left.$pattern.$this->template_tag_right."/is", $content, $match);
  35. return $match;
  36. }
  37. /**
  38. * 模板文件后缀
  39. */
  40. public function tpl_suffix() {
  41. $tpl_suffix = empty($this->conf['template_config']['template_suffix']) ?
  42. $this->tpl_suffix :
  43. $this->conf['template_config']['template_suffix'] ;
  44. return $tpl_suffix;
  45. }
  46. /**
  47. * 此处不解释了
  48. * @return
  49. */
  50. public function assign($key, $value) {
  51. $this->vars[$key] = $value;
  52. }
  53. /**
  54. * 渲染页面
  55. * @param
  56. * 使用方法 1
  57. * $this->view->display('error', 'comm/');
  58. * 默认是指向TPL模版的跟目录,所以comm/就是 tpl/comm/error.html
  59. * 使用方法 2
  60. * $this->view->display('errorfile');
  61. * 默认指向控制器固定的文件夹
  62. * 例如你的域名是 http://heartphp/admin/index, 那么正确路径就是tpl/admin/index/errorfile.html
  63. * @return
  64. */
  65. public function display($filename = '', $view_path = '') {
  66. $tpl_path_arr = $this->get_tpl($filename, $view_path);//获取TPL完整路径 并且向指针传送路径以及名称
  67. if(!$tpl_path_arr) core::show_error($filename.$this->_tpl_suffix.'模板不存在');
  68. //编译开始
  69. $this->view_path_param = $view_path;//用户传递过来的模版跟目录
  70. $this->compile();
  71. }
  72. /**
  73. * 编译控制器
  74. * @param
  75. * @return
  76. */
  77. private function compile() {
  78. $filepath = $this->template_path.$this->template_name;
  79. $compile_dirpath = $this->check_temp_compile();
  80. $vars_template_c_name = str_replace($this->_tpl_suffix, '', $this->template_name);
  81. $include_file = $this->template_replace($this->read_file($filepath), $compile_dirpath, $vars_template_c_name);//解析
  82. if($include_file) {
  83. $this->read_config() && $config = $this->read_config();
  84. extract($this->vars, EXTR_SKIP);
  85. [url=home.php?mod=space&uid=48608]@include[/url] $include_file;
  86. }
  87. }
  88. /**
  89. * 读取当前项目配置文件
  90. */
  91. protected function read_config() {
  92. if(file_exists(SYSTEM_PATH.'conf/config.php')) {
  93. @include SYSTEM_PATH.'conf/config.php';
  94. return $config;
  95. }
  96. return false;
  97. }
  98. /**
  99. * 解析模板语法
  100. * @param $str 内容
  101. * @param $compile_dirpath 模版编译目录
  102. * @param $vars_template_c_name 模版编译文件名
  103. * @return 编译过的PHP模板文件名
  104. */
  105. private function template_replace($str, $compile_dirpath, $vars_template_c_name) {
  106. if(empty($str)) core::show_error('模板内容为空!');
  107. //处理编译头部
  108. $compile_path = $compile_dirpath.$vars_template_c_name.$this->tpl_compile_suffix;//编译文件
  109. if(is_file($compile_path)) {
  110. //$header_content = $this->get_compile_header($compile_path);
  111. //$compile_date = $this->get_compile_header_comment($header_content);
  112. $tpl_filemtime = filemtime($this->template_path.$this->template_name);
  113. $compile_filemtime = filemtime($compile_path);
  114. //echo $tpl_filemtime.'=='.date('Y-m-d H:i:s', $tpl_filemtime).'<br/>';
  115. //echo $compile_filemtime.'=='.date('Y-m-d H:i:s', $compile_filemtime);
  116. //如果文件过期编译 当模板标签有include并且有修改时 也重新编译
  117. //<{include file="public/left.html"}> 当修改include里的文件,非DEBUG模式时 如果不更改主文件
    目前是不重新编译include里的文件,我在考虑是否也要更改,没想好,暂时这样,所以在开发阶段一定要开启DEBUG=1模式
    要不然修改include文件无效 有点罗嗦,不知道表述清楚没
  118. if($tpl_filemtime > $compile_filemtime || DEBUG) {
  119. $ret_file = $this->compile_file($vars_template_c_name, $str, $compile_dirpath);
  120. } else {
  121. $ret_file = $compile_path;
  122. }
  123. } else {//编译文件不存在 创建他
  124. $ret_file = $this->compile_file($vars_template_c_name, $str, $compile_dirpath);
  125. }
  126. return $ret_file;
  127. }
  128. /**
  129. * 模板文件主体
  130. * @param string $str 内容
  131. * @return html
  132. */
  133. private function body_content($str) {
  134. //解析
  135. $str = $this->parse($str);
  136. $header_comment = "Create On##".time()."|Compiled from##".$this->template_path.$this->template_name;
  137. $content = "<? if(!defined('IS_HEARTPHP')) exit('Access Denied');/*{$header_comment}*/?>\r\n$str";
  138. return $content;
  139. }
  140. /**
  141. * 开始解析相关模板标签
  142. * @param $content 模板内容
  143. */
  144. private function parse($content) {
  145. //foreach
  146. $content = $this->parse_foreach($content);
  147. //include
  148. $content = $this->parse_include($content);
  149. //if
  150. $content = $this->parse_if($content);
  151. //elseif
  152. $content = $this->parse_elseif($content);
  153. //模板标签公用部分
  154. $content = $this->parse_comm($content);
  155. //转为PHP代码
  156. $content = $this->parse_php($content);
  157. return $content;
  158. }
  159. /**
  160. * echo 如果默认直接<{$config['domain']}> 转成 <?php echo $config['domain']?>
  161. */
  162. private function parse_echo($content) {
  163. }
  164. /**
  165. * 转换为PHP
  166. * @param $content html 模板内容
  167. * @return html 替换好的HTML
  168. */
  169. private function parse_php($content){
  170. if(empty($content)) return false;
  171. $content = preg_replace("/".$this->template_tag_left."(.+?)".$this->template_tag_right."/is", "<?php $1 ?>", $content);
  172. return $content;
  173. }
  174. /**
  175. * if判断语句
  176. * <{if empty($zhang)}>
  177. * zhang
  178. * <{elseif empty($liang)}>
  179. * liang
  180. * <{else}>
  181. * zhangliang
  182. * <{/if}>
  183. */
  184. private function parse_if($content) {
  185. if(empty($content)) return false;
  186. //preg_match_all("/".$this->template_tag_left."if\s+(.*?)".$this->template_tag_right."/is", $content, $match);
  187. $match = $this->preg_match_all("if\s+(.*?)", $content);
  188. if(!isset($match[1]) || !is_array($match[1])) return $content;
  189. foreach($match[1] as $k => $v) {
  190. //$s = preg_split("/\s+/is", $v);
  191. //$s = array_filter($s);
  192. $content = str_replace($match[0][$k], "<?php if({$v}) { ?>", $content);
  193. }
  194. return $content;
  195. }
  196. private function parse_elseif($content) {
  197. if(empty($content)) return false;
  198. //preg_match_all("/".$this->template_tag_left."elseif\s+(.*?)".$this->template_tag_right."/is", $content, $match);
  199. $match = $this->preg_match_all("elseif\s+(.*?)", $content);
  200. if(!isset($match[1]) || !is_array($match[1])) return $content;
  201. foreach($match[1] as $k => $v) {
  202. //$s = preg_split("/\s+/is", $v);
  203. //$s = array_filter($s);
  204. $content = str_replace($match[0][$k], "<?php } elseif ({$v}) { ?>", $content);
  205. }
  206. return $content;
  207. }
  208. /**
  209. * 解析 include include标签不是实时更新的 当主体文件更新的时候 才更新标签内容,所以想include生效 请修改一下主体文件
  210. * 记录一下 有时间开发一个当DEBUG模式的时候 每次执行删除模版编译文件
  211. * 使用方法 <{include file="www.phpddt.com"}>
  212. * @param $content 模板内容
  213. * @return html
  214. */
  215. private function parse_include($content) {
  216. if(empty($content)) return false;
  217. //preg_match_all("/".$this->template_tag_left."include\s+(.*?)".$this->template_tag_right."/is", $content, $match);
  218. $match = $this->preg_match_all("include\s+(.*?)", $content);
  219. if(!isset($match[1]) || !is_array($match[1])) return $content;
  220. foreach($match[1] as $match_key => $match_value) {
  221. $a = preg_split("/\s+/is", $match_value);
  222. $new_tag = array();
  223. //分析元素
  224. foreach($a as $t) {
  225. $b = explode('=', $t);
  226. if(in_array($b[0], $this->tag_include)) {
  227. if(!empty($b[1])) {
  228. $new_tag[$b[0]] = str_replace("\"", "", $b[1]);
  229. } else {
  230. core::show_error('模板路径不存在!');
  231. }
  232. }
  233. }
  234. extract($new_tag);
  235. //查询模板文件
  236. foreach($this->conf['view_path'] as $v){
  237. $conf_view_tpl = $v.$file;//include 模板文件
  238. if(is_file($conf_view_tpl)) {
  239. $c = $this->read_file($conf_view_tpl);
  240. $inc_file = str_replace($this->_tpl_suffix, '', basename($file));
  241. $this->view_path_param = dirname($file).'/';
  242. $compile_dirpath = $this->check_temp_compile();
  243. $include_file = $this->template_replace($c, $compile_dirpath, $inc_file);//解析
  244. break;
  245. } else {
  246. core::show_error('模板文件不存在,请仔细检查 文件:'. $conf_view_tpl);
  247. }
  248. }
  249. $content = str_replace($match[0][$match_key], '<?php include("'.$include_file.'")?>', $content);
  250. }
  251. return $content;
  252. }
  253. /**
  254. * 解析 foreach
  255. * 使用方法 <{foreach from=$lists item=value key=kk}>
  256. * @param $content 模板内容
  257. * @return html 解析后的内容
  258. */
  259. private function parse_foreach($content) {
  260. if(empty($content)) return false;
  261. //preg_match_all("/".$this->template_tag_left."foreach\s+(.*?)".$this->template_tag_right."/is", $content, $match);
  262. $match = $this->preg_match_all("foreach\s+(.*?)", $content);
  263. if(!isset($match[1]) || !is_array($match[1])) return $content;
  264. foreach($match[1] as $match_key => $value) {
  265. $split = preg_split("/\s+/is", $value);
  266. $split = array_filter($split);
  267. $new_tag = array();
  268. foreach($split as $v) {
  269. $a = explode("=", $v);
  270. if(in_array($a[0], $this->tag_foreach)) {//此处过滤标签 不存在过滤
  271. $new_tag[$a[0]] = $a[1];
  272. }
  273. }
  274. $key = '';
  275. extract($new_tag);
  276. $key = ($key) ? '$'.$key.' =>' : '' ;
  277. $s = '<?php foreach('.$from.' as '.$key.' $'.$item.') { ?>';
  278. $content = $this->str_replace($match[0][$match_key], $s, $content);
  279. }
  280. return $content;
  281. }
  282. /**
  283. * 匹配结束 字符串
  284. */
  285. private function parse_comm($content) {
  286. $search = array(
  287. "/".$this->template_tag_left."\/foreach".$this->template_tag_right."/is",
  288. "/".$this->template_tag_left."\/if".$this->template_tag_right."/is",
  289. "/".$this->template_tag_left."else".$this->template_tag_right."/is",
  290. );
  291. $replace = array(
  292. "<?php } ?>",
  293. "<?php } ?>",
  294. "<?php } else { ?>"
  295. );
  296. $content = preg_replace($search, $replace, $content);
  297. return $content;
  298. }
  299. /**
  300. * 检查编译目录 如果没有创建 则递归创建目录
  301. * @param string $path 文件完整路径
  302. * @return 模板内容
  303. */
  304. private function check_temp_compile() {
  305. //$paht = $this->template_c.
  306. $tpl_path = ($this->view_path_param) ? $this->view_path_param : $this->get_tpl_path() ;
  307. $all_tpl_apth = $this->template_c.$tpl_path;
  308. if(!is_dir($all_tpl_apth)) {
  309. $this->create_dir($tpl_path);
  310. }
  311. return $all_tpl_apth;
  312. }
  313. /**
  314. * 读文件
  315. * @param string $path 文件完整路径
  316. * @return 模板内容
  317. */
  318. private function read_file($path) {
  319. //$this->check_file_limits($path, 'r');
  320. if(($r = @fopen($path, 'r')) === false) {
  321. core::show_error('模版文件没有读取或执行权限,请检查!');
  322. }
  323. $content = fread($r, filesize($path));
  324. fclose($r);
  325. return $content;
  326. }
  327. /**
  328. * 写文件
  329. * @param string $filename 文件名
  330. * @param string $content 模板内容
  331. * @return 文件名
  332. */
  333. private function compile_file($filename, $content, $dir) {
  334. if(empty($filename)) core::show_error("{$filename} Creation failed");
  335. $content = $this->body_content($content);//对文件内容操作
  336. //echo '开始编译了=====';
  337. $f = $dir.$filename.$this->tpl_compile_suffix;
  338. //$this->check_file_limits($f, 'w');
  339. if(($fp = @fopen($f, 'wb')) === false) {
  340. core::show_error($f.'<br/>编译文件失败,请检查文件权限.');
  341. }
  342. //开启flock
  343. flock($fp, LOCK_EX + LOCK_NB);
  344. fwrite($fp, $content, strlen($content));
  345. flock($fp, LOCK_UN + LOCK_NB);
  346. fclose($fp);
  347. return $f;
  348. }
  349. /**
  350. * 这个检查文件权限函数 暂时废弃了
  351. * @param [$path] [路径]
  352. * @param [status] [w=write, r=read]
  353. */
  354. public function check_file_limits($path , $status = 'rw') {
  355. clearstatcache();
  356. if(!is_writable($path) && $status == 'w') {
  357. core::show_error("{$path}<br/>没有写入权限,请检查.");
  358. } elseif(!is_readable($path) && $status == 'r') {
  359. core::show_error("{$path}<br/>没有读取权限,请检查.");
  360. } elseif($status == 'rw') {//check wirte and read
  361. if(!is_writable($path) || !is_readable($path)) {
  362. core::show_error("{$path}<br/>没有写入或读取权限,请检查");
  363. }
  364. }
  365. }
  366. /**
  367. * 读取编译后模板的第一行 并分析成数组
  368. * @param string $filepath 文件路径
  369. * @param number $line 行数
  370. * @return 返回指定行数的字符串
  371. */
  372. /*
  373. private function get_compile_header($filepath, $line = 0) {
  374. if(($file_arr = @file($filepath)) === false) {
  375. core::show_error($filepath.'<br/>读取文件失败,请检查文件权限!');
  376. }
  377. return $file_arr[0];
  378. }
  379. */
  380. /**
  381. * 分析头部注释的日期
  382. * @param string $cotnent 编译文件头部第一行
  383. * @return 返回上一次日期
  384. */
  385. /*
  386. private function get_compile_header_comment($content) {
  387. preg_match("/\/\*(.*?)\*\//", $content, $match);
  388. if(!isset($match[1]) || empty($match[1])) core::show_error('编译错误!');
  389. $arr = explode('|', $match[1]);
  390. $arr_date = explode('##', $arr[0]);
  391. return $arr_date[1];
  392. }
  393. */
  394. /**
  395. * 获取模板完整路径 并返回已存在文件
  396. * @param string $filename 文件名
  397. * @param string $view_path 模板路径
  398. * @return
  399. */
  400. private function get_tpl($filename, $view_path) {
  401. empty($filename) && $filename = $this->tpl_name;
  402. //遍历模板路径
  403. foreach($this->conf['view_path'] as $path) {
  404. if($view_path) {//直接从tpl跟目录找文件
  405. $tpl_path = $path.$view_path;
  406. $view_file_path = $tpl_path.$filename.$this->_tpl_suffix;
  407. } else {//根据目录,控制器,方法开始找文件
  408. $view_file_path = ($tpl_path = $this->get_tpl_path($path)) ? $tpl_path.$filename.$this->_tpl_suffix : exit(0);
  409. }
  410. if(is_file($view_file_path)) {
  411. //向指针传送模板路径和模板名称
  412. $this->template_path = $tpl_path;//
  413. $this->template_name = $filename.$this->_tpl_suffix;
  414. return true;
  415. } else {
  416. core::show_error($filename.$this->_tpl_suffix.'模板不存在');
  417. }
  418. }
  419. }
  420. /**
  421. * 获取模板路径
  422. * @param string $path 主目录
  423. * @return URL D和M的拼接路径
  424. */
  425. private function get_tpl_path($path = '') {
  426. core::get_directory_name() && $path_arr[0] = core::get_directory_name();
  427. core::get_controller_name() && $path_arr[1] = core::get_controller_name();
  428. (is_array($path_arr)) ? $newpath = implode('/', $path_arr) : core::show_error('获取模板路径失败!') ;
  429. return $path.$newpath.'/';
  430. }
  431. /**
  432. * 创建目录
  433. * @param string $path 目录
  434. * @return
  435. */
  436. private function create_dir($path, $mode = 0777){
  437. if(is_dir($path)) return false;
  438. $dir_arr = explode('/', $path);
  439. $dir_arr = array_filter($dir_arr);
  440. $allpath = '';
  441. $newdir = $this->template_c;
  442. foreach($dir_arr as $dir) {
  443. $allpath = $newdir.'/'.$dir;
  444. if(!is_dir($allpath)) {
  445. $newdir = $allpath;
  446. if(!@mkdir($allpath, $mode)) {
  447. core::show_error( $allpath.'<br/>创建目录失败,请检查是否有可都写权限!');
  448. }
  449. chmod($allpath, $mode);
  450. } else {
  451. $newdir = $allpath;
  452. }
  453. }
  454. return true;
  455. }
  456. public function __destruct(){
  457. $this->vars = null;
  458. $this->view_path_param = null;
  459. }
  460. }

PHP模板解析类实例的更多相关文章

  1. CI 模板解析器类

    模板解析器类可以解析你的视图文件中的伪变量.它可以解析简单的变量或者以变量作为标签的结构.如果你以前没有用过模板引擎,那么伪变量如下所示: <html><head><ti ...

  2. 自己用的框架写了一个PHP模版解析类

    <?php if(!defined('IS_HEARTPHP')) exit('Access Denied'); /** * template.class.php 模板解析类 * * @copy ...

  3. PHP模板解析入门

    学习:李炎恢PHP视频第二季 模板引擎的特点: 1.鼓励分离:让更个系统的可读性和维护性得到提高. 2.促进分工:使得程序员和美工去专心处理自己的设计. 3.比PHP更容易解析:编译文件和缓存文件加载 ...

  4. excel模板解析前后设计变化,以及我对此的看法和感受

    前言:近期也在做Excel模板的解析工作,目前来说,应该是第三版了.我最开始做的,就是垒鸡窝,虽然考虑了1.0提出的关于excel解析的一些建议和问题(单个模板),但是真的毫无设计可言.就几个工具类, ...

  5. [Reprint] C++函数模板与类模板实例解析

    这篇文章主要介绍了C++函数模板与类模板,需要的朋友可以参考下   本文针对C++函数模板与类模板进行了较为详尽的实例解析,有助于帮助读者加深对C++函数模板与类模板的理解.具体内容如下: 泛型编程( ...

  6. C++解析(26):函数模板与类模板

    0.目录 1.函数模板 1.1 函数模板与泛型编程 1.2 多参数函数模板 1.3 函数重载遇上函数模板 2.类模板 2.1 类模板 2.2 多参数类模板与特化 2.3 特化的深度分析 3.小结 1. ...

  7. excel模板解析—桥接模式:分离解析模板和业务校验

    在做excel模板解析的时候,其实会有两个部分,第一,将模板读取出来,校验一些必录项等. 但除了这些,在数据真正被业务线使用的时候,还会有一些其他的校验,比如说:根据业务,年龄是不能超过多少岁的,包括 ...

  8. CVPR2020论文解析:实例分割算法

    CVPR2020论文解析:实例分割算法 BlendMask: Top-Down Meets Bottom-Up for Instance Segmentation 论文链接:https://arxiv ...

  9. OpenFaaS实战之七:java11模板解析

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

随机推荐

  1. Android Studio的使用(十四)--如何查看资源或者函数在哪些类中被引用

    1.我们都知道在Eclipse中可以通过快捷键Ctrl+Shift+G开快速搜索方法.类.资源都在那个类中被使用了. 2.在Android Studio中则使用快捷键Ctrl+G.

  2. AFNetWorking 判断当前版本是否是最新版本

    NSString *url = [[NSString alloc] initWithFormat:@"http://itunes.apple.com/lookup?id=%@",@ ...

  3. STM32的外部中断配置及使用

    STM32的外部中断配置及使用 配置1:GPIO: 配置外部中断为输入模式: 配置2:EXTI: 配置外部中断线和触发模式: 配置3:NVIC: 配置外部中断源和中断优先级: 需要注意的是:RCC_A ...

  4. flowers

    问题大全 Do you like flowers?(Why?) What flowers do you like?(why?) What is your favorite flower? Are fl ...

  5. 登录数据库后,use db很慢的问题

    mysql> use dbl Reading table information for completion of table and column names You can turn of ...

  6. springmvc 关于controller的字符编码

    在使用springMVC框架构建web应用,客户端常会请求字符串.整型.json等格式的数据,通常使用@ResponseBody注解使 controller回应相应的数据而不是去渲染某个页面.如果请求 ...

  7. CRT堆

    实验环境:win764位旗舰版.VS2010旗舰版 每个进程都有一个默认堆,在进程初始化的时候会创建这个默认堆,可以通过GetProcessHeap()获取默认堆的句柄.使用CRT时,也会有一个CRT ...

  8. HDU2066一个人的旅行/最短路问题

    一个人的旅行 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Subm ...

  9. C语言开发工具

    1.编写程序的工具: indent命令将自动调整C代码的缩进风格,个人比较喜欢indent -kr 2.编译C语言程序: 1.gcc编译器: gcc是一个ANSI C兼容编译器,C++编译器也可以编译 ...

  10. ExtJS4 的dom

    Ext使用了三个核心的工具类对我们掌握的DOM进行了完美的封装. ┣ Ext.Element(几乎对DOM的一切进行了封彻底装) ┣ Ext.DomHelper(一个强大的操控UI界面的工具类) ┣ ...