首頁  >  文章  >  後端開發  >  PHP模板解析类

PHP模板解析类

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


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn