之前我們實現了最簡單的 命令的模版替換,就是將 這樣一段內容替換成``。 現在我們來說下其他的命令,先來回顧下之前的定義 + 輸出變數值 表達式的返回值將被自動傳遞給 的 函數進行處理,以防止 攻擊。 + 輸出未轉義的變數值 + If 表達式 通過 、`@elseif @else @endif if ...
之前我們實現了最簡單的echo
命令的模版替換,就是將{{ $name }}
這樣一段內容替換成<?php echo $name ?>
。
現在我們來說下其他的命令,先來回顧下之前的定義
- 輸出變數值
{{ }}
表達式的返回值將被自動傳遞給 PHP
的 htmlentities
函數進行處理,以防止 XSS
攻擊。
Hello, {{ $name }}!
- 輸出未轉義的變數值
Hello, {!! $name !!}!
- If 表達式
通過 @if
、@elseif
、@else
和 @endif
指令可以創建 if
表達式。
@if (count($records) === 1)
I have one record!
@elseif (count($records) > 1)
I have multiple records!
@else
I don't have any records!
@endif
- 迴圈
@for ($i = 0; $i < 10; $i++)
The current value is {{ $i }}
@endfor
@foreach ($users as $user)
<p>This is user {{ $user->id }}</p>
@endforeach
@while (true)
<p>I'm looping forever.</p>
@endwhile
- 引入其他視圖
@include('view.name', ['some' => 'data'])
要匹配這些定義,我們要寫出相應的正則表達式,關於@
開頭的命令直接拿了laravel
中的使用。
我們先在src
下創建view
文件夾,再創建Compiler
類文件。
我們將compiler的方式氛圍兩種,一種是@
開頭的命令(Statements
),一種是輸出(Echos
)。這兩種的正則是不一樣的。
首先定義變數compliers
,定義如下:
protected $compilers = [
'Statements',
'Echos',
];
然後按著見原來Controller
中render
方法的內容遷移到Complier
類中,按這兩種依次匹配,代碼如下:
public function compile($path = null)
{
$fileContent = file_get_contents($path);
$result = '';
foreach (token_get_all($fileContent) as $token) {
if (is_array($token)) {
list($id, $content) = $token;
if ($id == T_INLINE_HTML) {
foreach ($this->compilers as $type) {
$content = $this->{"compile{$type}"}($content);
}
}
$result .= $content;
} else {
$result .= $token;
}
}
$generatedFile = '../runtime/cache/' . md5($path);
file_put_contents($generatedFile, $result);
require_once $generatedFile;
}
protected function compileStatements($content)
{
return $content;
}
protected function compileEchos($content)
{
return preg_replace('/{{(.*)}}/', '<?php echo $1 ?>', $content);
}
其中的Statements
完全沒有處理,Echos
則還是跟之前一樣。
先來調整下Echos
中的處理,添加變數記錄{{ }}
和{!! !!}
的名稱
protected $echoCompilers = [
'RawEchos',
'EscapedEchos'
];
處理的時候可以添加一下存在的判斷,預設值是null
,內容可以調整如下:
protected function compileEchos($content)
{
foreach ($this->echoCompilers as $type) {
$content = $this->{"compile{$type}"}($content);
}
return $content;
}
protected function compileEscapedEchos($content)
{
return preg_replace('/{{(.*)}}/', '<?php echo htmlentities(isset($1) ? $1 : null) ?>', $content);
}
protected function compileRawEchos($content)
{
return preg_replace('/{!!(.*)!!}/', '<?php echo isset($1) ? $1 : null ?>', $content);
}
EscapedEchos
和RawEchos
的區別在於,第一個會做html
轉義。
我們再來看Statements
命令的處理,其原理也一樣,匹配到相應的命令,如if
、foreach
,調用相應的方法做替換。
代碼如下:
protected function compileStatements($content)
{
return preg_replace_callback(
'/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', function ($match) {
return $this->compileStatement($match);
}, $content
);
}
protected function compileStatement($match)
{
if (strpos($match[1], '@') !== false) {
$match[0] = isset($match[3]) ? $match[1].$match[3] : $match[1];
} elseif (method_exists($this, $method = 'compile'.ucfirst($match[1]))) {
$match[0] = $this->$method(isset($match[3]) ? $match[3] : null);
}
return isset($match[3]) ? $match[0] : $match[0].$match[2];
}
protected function compileIf($expression)
{
return "<?php if{$expression}: ?>";
}
protected function compileElseif($expression)
{
return "<?php elseif{$expression}: ?>";
}
protected function compileElse($expression)
{
return "<?php else{$expression}: ?>";
}
protected function compileEndif($expression)
{
return '<?php endif; ?>';
}
protected function compileFor($expression)
{
return "<?php for{$expression}: ?>";
}
protected function compileEndfor($expression)
{
return '<?php endfor; ?>';
}
protected function compileForeach($expression)
{
return "<?php foreach{$expression}: ?>";
}
protected function compileEndforeach($expression)
{
return '<?php endforeach; ?>';
}
protected function compileWhile($expression)
{
return "<?php while{$expression}: ?>";
}
protected function compileEndwhile($expression)
{
return '<?php endwhile; ?>';
}
protected function compileContinue($expression)
{
return '<?php continue; ?>';
}
protected function compileBreak($expression)
{
return '<?php break; ?>';
}
其中的include
實現比較麻煩,就沒有做,留給大家思考啦。
然後,我們再考慮一下,不可能每次都去操作文件重新生成,我應該要判斷文件改變,如果沒改變直接使用緩存就可以了。
調整代碼如下:
public function isExpired($path)
{
$compiled = $this->getCompiledPath($path);
if (!file_exists($compiled)) {
return true;
}
return filemtime($path) >= filemtime($compiled);
}
protected function getCompiledPath($path)
{
return '../runtime/cache/' . md5($path);
}
public function compile($file = null, $params = [])
{
$path = '../views/' . $file . '.sf';
extract($params);
if (!$this->isExpired($path)) {
$compiled = $this->getCompiledPath($path);
require_once $compiled;
return;
}
$fileContent = file_get_contents($path);
$result = '';
foreach (token_get_all($fileContent) as $token) {
if (is_array($token)) {
list($id, $content) = $token;
if ($id == T_INLINE_HTML) {
foreach ($this->compilers as $type) {
$content = $this->{"compile{$type}"}($content);
}
}
$result .= $content;
} else {
$result .= $token;
}
}
$compiled = $this->getCompiledPath($path);
file_put_contents($compiled, $result);
require_once $compiled;
}
這個系列的博客到這裡就暫時告一段落了~
項目內容和博客內容也都會放到Github上,歡迎大家提建議。
code:https://github.com/CraryPrimitiveMan/simple-framework/tree/1.2
blog project:https://github.com/CraryPrimitiveMan/create-your-own-php-framework