以前咱們實現了最簡單的echo
命令的模版替換,就是將{{ $name }}
這樣一段內容替換成<?php echo $name ?>
。php
如今咱們來講下其餘的命令,先來回顧下以前的定義html
{{ }}
表達式的返回值將被自動傳遞給 PHP
的 htmlentities
函數進行處理,以防止 XSS
攻擊。laravel
Hello, {{ $name }}!
Hello, {!! $name !!}!
經過 @if
、@elseif
、@else
和 @endif
指令能夠建立 if
表達式。git
@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
中的使用。github
咱們先在src
下建立view
文件夾,再建立Compiler
類文件。正則表達式
咱們將compiler的方式氛圍兩種,一種是@
開頭的命令(Statements
),一種是輸出(Echos
)。這兩種的正則是不同的。express
首先定義變量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
則仍是跟以前同樣。oop
先來調整下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