/
Tpl.php
361 lines (323 loc) · 12.6 KB
/
Tpl.php
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
<?php
/*
* ____ _
* | _ \ ___ ___ _ __ ___ | |_ ___
* | | | |/ _ \/ __| '_ \ / _ \| __/ _ \
* | |_| | __/\__ \ |_) | (_) | || __/
* |____/ \___||___/ .__/ \___/ \__\___|
* |_|
* @author He110 (i@he110.top)
* @namespace despote\kernel
*/
namespace despote\kernel;
use \Despote;
use \despote\base\Service;
class Tpl extends Service
{
/////////////
// 模板配置 //
////////////
// 模板所在模块名
public $module;
// // 模板内部变量
// private $vars = [];
// 模板路径,默认为视图路径
private $tplDir;
// 缓存路径,默认为视图路径下的 cache 文件夹
private $cacheDir;
// 模板引擎左定界符,默认为 <{
private $tplBegin;
// 模板引擎右定界符,默认为 }>
private $tplEnd;
// 模板文件后缀,默认为 tpl
private $suffix;
// 不使用缓存,每次都需要编译,默认为 false
private $noCache;
public function init()
{
// 校验模板引擎配置文件可读性
if (!is_readable(PATH_CONF . 'tpl.php')) {
$this->showError('模板引擎配置文件不存在');
return;
}
// 加载模板引擎配置
$conf = require PATH_CONF . 'tpl.php';
$path = PATH_APP . $this->module . DS . 'tpl' . DS;
// 使用配置文件初始化属性
$this->tplDir = isset($conf['tplDir']) ? $conf['tplDir'] : $path;
$this->cacheDir = isset($conf['cacheDir']) ? $conf['cacheDir'] : $path . 'cache' . DS;
$this->tplBegin = isset($conf['tplBegin']) ? $conf['tplBegin'] : '<{';
$this->tplEnd = isset($conf['tplEnd']) ? $conf['tplEnd'] : '}>';
$this->suffix = isset($conf['suffix']) ? $conf['suffix'] : 'tpl';
$this->noCache = isset($conf['noCache']) ? $conf['noCache'] : false;
}
/**
* 模板引擎对外接口,用于加载模板
* @param String $filename 模板文件名,不需要加后缀
*/
public function display($filename)
{
$tpl = $this->getTpl($filename);
$cache = $this->getCache($filename);
$this->cache($tpl, $cache);
require $cache;
}
// public function assign($key, $value)
// {
// $this->vars[$key] = $value;
// }
/**
* 获取模板绝对路径
* @param String $filename 模板文件名
* @return String 模板文件绝对路径
*/
private function getTpl($filename)
{
return $this->tplDir . DS . $filename . '.' . $this->suffix;
}
/**
* 获取缓存文件绝对路径
* @param String $filename 缓存文件名
* @return String 缓存文件绝对路径
*/
public function getCache($filename)
{
return $this->cacheDir . DS . $filename . '.php';
}
/**
* 是否已经有缓存了
* @param String $tpl 模板文件的绝对路径
* @param String $cache 缓存文件的绝对地址
* @return boolean 缓存存在且有效返回 true,否则返回 false
*/
private function isCache($tpl, $cache)
{
if (!is_readable($tpl)) {
$this->showError('模板文件 ' . $tpl . ' 不可读');
return false;
}
if ($this->noCache or !is_file($cache)) {
return false;
}
if (filemtime($tpl) > filemtime($cache)) {
return false;
}
return true;
}
/**
* 开始进行模板缓存
* @param String $tpl 模板文件的绝对路径
* @param String $cache 缓存文件的绝对路径
*/
private function cache($tpl, $cache)
{
if (!$this->isCache($tpl, $cache)) {
$this->writeCache($cache, $this->compile($tpl));
}
}
/**
* 写入缓存
* @param String $cache 缓存文件路径
* @param String $content 缓存文件内容
*/
private function writeCache($cache, $content)
{
if (!Despote::file()->create($cache) || !is_readable($cache)) {
$this->showError('生成缓存文件失败,请检查 PHP 权限');
}
// 访问校验
$content = "<?php defined('DESPOTE') || die('Forbidden access'); ?>\r\n" . $content;
file_put_contents($cache, $content);
}
/**
* 字符串转义,使之变为普通文本,特殊字符不作为正则表达式
* @param String $regular 字符串
* @return String 转义后的字符串
*/
private function quote($regular)
{
return preg_quote($regular, '/');
}
/**
* 编译模板文件
* @param String $tpl 模板文件的绝对路径
* @return String 编译后的模板文件内容
*/
private function compile($tpl)
{
// 获取模板
if (!is_readable($tpl)) {
$this->showError('模板文件不可读');
return;
}
$content = file_get_contents($tpl);
// 转义定界符,用于拼接正则表达式
$begin = $this->quote($this->tplBegin);
$end = $this->quote($this->tplEnd);
// 匹配文件加载
if (strpos($content, $this->tplBegin . 'inc') !== false) {
// 匹配文件加载语句的正则表达式
$regular = '/' . $begin . 'inc\s+file\s*=\s*["](.+?)["]' . $end . '/i';
if (preg_match_all($regular, $content, $results)) {
$files = array_unique($results[1]);
// 循环处理文件包含
foreach ($files as $file) {
// 待替换的模板
$incTpl = $this->tplBegin . 'inc file="' . $file . '"' . $this->tplEnd;
$tpl = $this->getTpl($file);
if (is_file($tpl)) {
// 假设引入的是另一个模板,则获取对应的缓存文件名
$cache = $this->getCache($file);
} else {
$cache = null;
}
if ($cache) {
// 缓存模板,并加载
$this->cache($tpl, $cache);
$incCache = "<?php require('{$cache}'); ?>";
} else {
// 如果 file 不是模板文件,是单独的文件,加载该文件,否则结束本次循环
if (is_file($file)) {
$incCache = "<?php require('{$file}'); ?>";
} else {
continue;
}
}
// 替换模板
$content = str_ireplace($incTpl, $incCache, $content);
}
}
}
// else 替换
$elseTpl = $this->tplBegin . 'else' . $this->tplEnd;
if (strpos($content, $elseTpl) !== false) {
$elseCache = "<?php }else{ ?>";
$content = str_ireplace($elseTpl, $elseCache, $content);
}
// 结束符替换,结束符是 /if、/for、/foreach、/while
$endTpl = '/' . $begin . '\/(if|for|foreach|while)' . $end . '/i';
if (preg_match_all($endTpl, $content, $results)) {
$results[0] = array_unique($results[0]);
foreach ($results[0] as $endSignTpl) {
$content = str_replace($endSignTpl, '<?php } ?>', $content);
}
}
// if 语句替换
if (strpos($content, $this->tplBegin . 'if') !== false) {
$regular = '/' . $begin . 'if(.*)' . $end . '/isU';
if (preg_match_all($regular, $content, $results)) {
// 循环匹配所有的 if
foreach ($results[1] as $index => $condition) {
// 解析变量
$condCache = $this->parseVars($condition);
$content = str_replace($results[0][$index], '<?php if(' . $condCache . '){ ?>', $content);
}
}
}
// elseif 语句替换
if (strpos($content, $this->tplBegin . 'elseif') !== false) {
$regular = '/' . $begin . 'elseif(.*)' . $end . '/isU';
if (preg_match_all($regular, $content, $results)) {
// 循环匹配所有的 elseif
foreach ($results[1] as $indx => $condition) {
// 解析变量
$condCache = $this->parseVars($condition);
$content = str_replace($results[0][$indx], '<?php }elseif(' . $condCache . '){ ?>', $content);
}
}
}
// foreach 语句替换
if (strpos($content, $this->tplBegin . 'foreach') !== false) {
$regular = '/' . $begin . 'foreach(.*)' . $end . '/isU';
if (preg_match_all($regular, $content, $results)) {
foreach ($results[1] as $key => $condition) {
// 判断是否省略了 as 语句,是的话自动补充为 as $key=>$value
if (strpos($condition, ' as') === false) {
$condition .= ' as $key=>$value';
}
// 解析变量
$condCache = $this->parseVars($condition);
$content = str_replace($results[0][$key], '<?php foreach(' . $condCache . '){ ?>', $content);
}
}
}
// for 语句解析
if (strpos($content, $this->tplBegin . 'for') !== false) {
$regular = '/' . $begin . 'for (.*)' . $end . '/isU';
if (preg_match_all($regular, $content, $results)) {
foreach ($results[1] as $index => $condition) {
$condCache = $this->parseVars($condition);
$content = str_replace($results[0][$index], '<?php for(' . $condCache . '){ ?>', $content);
}
}
}
// while 语句解析
if (strpos($content, $this->tplBegin . 'while') !== false) {
$regular = '/' . $begin . 'while (.*)' . $end . '/isU';
if (preg_match_all($regular, $content, $results)) {
foreach ($results[1] as $index => $condition) {
$condCache = $this->parseVars($condition);
$content = str_replace($results[0][$index], '<?php while(' . $condCache . '){ ?>', $content);
}
}
}
// 解析变量定义
$regular = '/' . $begin . '((\$[\w\.\[\]\$]+)=\s*([\'"].+?[\'"]|.+?))' . $end . '/';
if (preg_match_all($regular, $content, $results)) {
foreach ($results[0] as $index => $varTpl) {
$varCache = '<?php ' . $this->parseVars($results[1][$index]) . '; ?>';
$content = str_replace($varTpl, $varCache, $content);
}
}
// 解析不输出的语句
$regular = '/' . $begin . '\!(.*)' . $end . '/isU';
if (preg_match_all($regular, $content, $results)) {
foreach ($results[1] as $index => $statement) {
$value = $this->parseVars($statement);
$content = str_replace($results[0][$index], '<?php ' . $value . ';?>', $content);
}
}
// 解析输出语句
$regular = '/' . $begin . '(.*)' . $end . '/U';
if (preg_match_all($regular, $content, $results)) {
foreach ($results[1] as $index => $statement) {
$value = $this->parseVars($statement);
$content = str_replace($results[0][$index], '<?= ' . $value . ';?>', $content);
}
}
return $content;
}
/**
* 变量解析
* @param String $content 语句
* @return String 解析完变量的语句
*/
private function parseVars($content)
{
$superVars = [
'$post' => '$_POST',
'$get' => '$_GET',
'$cookie' => '$_COOKIE',
'$session' => '$_SESSION',
'$files' => '$_FILES',
'$server' => '$_SERVER',
'$this' => '$this',
];
if (preg_match_all('/\$(\w+)/', $content, $results)) {
foreach ($results[0] as $var) {
$varCache = array_key_exists($var, $superVars) ? $superVars[$var] : $var;
$content = preg_replace('/' . $this->quote($var) . '/', $varCache, $content, 1);
}
}
return $content;
}
/**
* 输出错误信息,具体是显示还是写入日志看 ErrCatch 设置
* @param String $msg 错误信息
* @throws 模板处理过程出现的错误
*/
private function showError($msg)
{
throw new \Exception($msg, 500);
}
}