之前我们实现了最简单的`echo`命令的模版替换,就是将`{{ $name }}`这样一段内容替换成``。 现在我们来说下其他的命令,先来回顾下之前的定义 + 输出变量值 `{{ }}` 表达式的返回值将被自动传递给 `PHP` 的 `htmlentities` 函数进行处理,以防止 `XSS` 攻击。 ```php Hello, {{ $name }}! ``` + 输出未转义的变量值 ```php Hello, {!! $name !!}! ``` + If 表达式 通过 `@if`、`@elseif`、`@else` 和 `@endif` 指令可以创建 `if` 表达式。 ```php @if (count($records) === 1) I have one record! @elseif (count($records) > 1) I have multiple records! @else I don't have any records! @endif ``` + 循环 ```php @for ($i = 0; $i < 10; $i++) The current value is {{ $i }} @endfor @foreach ($users as $user)
This is user {{ $user->id }}
@endforeach @while (true)I'm looping forever.
@endwhile ``` + 引入其他视图 ```php @include('view.name', ['some' => 'data']) ``` 要匹配这些定义,我们要写出相应的正则表达式,关于`@`开头的命令直接拿了`laravel`中的使用。 我们先在`src`下创建`view`文件夹,再创建`Compiler`类文件。 我们将compiler的方式氛围两种,一种是`@`开头的命令(`Statements`),一种是输出(`Echos`)。这两种的正则是不一样的。 首先定义变量`compliers`,定义如下: ```php protected $compilers = [ 'Statements', 'Echos', ]; ``` 然后按着见原来`Controller`中`render`方法的内容迁移到`Complier`类中,按这两种依次匹配,代码如下: ```php 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('/{{(.*)}}/', '', $content); } ``` 其中的`Statements`完全没有处理,`Echos`则还是跟之前一样。 先来调整下`Echos`中的处理,添加变量记录`{{ }}`和`{!! !!}`的名称 ```php protected $echoCompilers = [ 'RawEchos', 'EscapedEchos' ]; ``` 处理的时候可以添加一下存在的判断,默认值是`null`,内容可以调整如下: ```php protected function compileEchos($content) { foreach ($this->echoCompilers as $type) { $content = $this->{"compile{$type}"}($content); } return $content; } protected function compileEscapedEchos($content) { return preg_replace('/{{(.*)}}/', '', $content); } protected function compileRawEchos($content) { return preg_replace('/{!!(.*)!!}/', '', $content); } ``` `EscapedEchos`和`RawEchos`的区别在于,第一个会做`html`转义。 我们再来看`Statements`命令的处理,其原理也一样,匹配到相应的命令,如`if`、`foreach`,调用相应的方法做替换。 代码如下: ```php 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 ""; } protected function compileElseif($expression) { return ""; } protected function compileElse($expression) { return ""; } protected function compileEndif($expression) { return ''; } protected function compileFor($expression) { return ""; } protected function compileEndfor($expression) { return ''; } protected function compileForeach($expression) { return ""; } protected function compileEndforeach($expression) { return ''; } protected function compileWhile($expression) { return ""; } protected function compileEndwhile($expression) { return ''; } protected function compileContinue($expression) { return ''; } protected function compileBreak($expression) { return ''; } ``` 其中的`include`实现比较麻烦,就没有做,留给大家思考啦。 然后,我们再考虑一下,不可能每次都去操作文件重新生成,我应该要判断文件改变,如果没改变直接使用缓存就可以了。 调整代码如下: ```php 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