之前我们实现了最简单的echo命令的模版替换,就是将{{ $name }}这样一段内容替换成<?php echo $name ?>

现在我们来说下其他的命令,先来回顾下之前的定义

  • 输出变量值

{{ }} 表达式的返回值将被自动传递给 PHPhtmlentities 函数进行处理,以防止 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',
];

然后按着见原来Controllerrender方法的内容迁移到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);
}

EscapedEchosRawEchos的区别在于,第一个会做html转义。

我们再来看Statements命令的处理,其原理也一样,匹配到相应的命令,如ifforeach,调用相应的方法做替换。

代码如下:

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

构建自己的PHP框架--构建模版引擎(3)的更多相关文章

  1. 构建自己的PHP框架--构建模版引擎(1)

    前段时间太忙,导致好久都没有更新博客了,今天抽出点时间来写一篇. 其实这个系列的博客很久没有更新了,之前想好好规划一下,再继续写,然后就放下了,今天再捡起来继续更新. 今天我们来说一下,如何构建自己的 ...

  2. 构建自己的PHP框架--构建模版引擎(2)

    自从来到新公司就一直很忙,最近这段时间终于稍微闲了一点,赶紧接着写这个系列,感觉再不写就烂尾了. 之前我们说到,拿到{{ $name }}这样一段内容时,我们只需要将它转化成<?php echo ...

  3. 构建自己的PHP框架--构建缓存组件(1)

    作为一个框架,我们还没有相应的缓存组件,下面我们就来构建我们的缓存组件. 先来定义一下接口,在 src 文件夹下创建 cache 文件夹,在cache文件夹下创建 CacheInterface.php ...

  4. 构建自己的PHP框架--构建缓存组件(2)

    上一篇博客中使用文件实现了缓存组件,这一篇我们就使用Redis来实现一下,剩下的如何使用memcache.mysql等去实现缓存我就不一一去做了. 首先我们需要安装一下 redis 和 phpredi ...

  5. 构建自己的PHP框架(Twig模板引擎)

    完整项目地址:https://github.com/Evai/Aier Twig 模板引擎 模版引擎 twig 的模板就是普通的文本文件,也不需要特别的扩展名,.html .htm .twig 都可以 ...

  6. 基于laravel框架构建最小内容管理系统

    校园失物招领平台开发 --基于laravel框架构建最小内容管理系统 摘要 ​ 针对目前大学校园人口密度大.人群活动频繁.师生学习生活等物品容易遗失的基本现状,在分析传统失物招领过程中的工作效率低下. ...

  7. net 和Mono 构建的HTTP服务框架

    Nancy是一个基于.net 和Mono 构建的HTTP服务框架,是一个非常轻量级的web框架. 设计用于处理 DELETE, GET, HEAD, OPTIONS, POST, PUT 和 PATC ...

  8. 基于Dubbo框架构建分布式服务(一)

    Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配 ...

  9. 使用 SailingEase WinForm 框架构建复合式应用程序(插件式应用程序)

    对于一些较小的项目,具备一定经验的开发人员应该能够设计和构建出便于进行维护和扩展的应用程序.但是,随着功能模块数量(以及开发维护这些部件的人员)的不断增加,对项目实施控制的难度开始呈指数级增长. Sa ...

随机推荐

  1. java 压缩文件

    package folder; import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundExcept ...

  2. 【Webpack的使用指南 01】Webpack入门

    使用Webpack有一段时间了,但是感觉之前学的用的都比较零散,所以在这里整理一下Webpack的使用知识,从入门到进阶. 创建项目 首先创建最简单的一个项目 npm init 得到以下项目结构: 安 ...

  3. 人生苦短,我用python(目录)

    基础 简介 数据类型 文件操作 迭代器和生成器 函数 内置函数 面向对象编程 异常处理 socket网络编程 线程 进程/协程/异步I/O 数据库 Mysql(一) Mysql(二) Mysql(三) ...

  4. C++编程理论学习笔记

    1.变量: 变量的值都存储在内存中.内存中每个单元都有一个唯一的编号,就是单元的地址,变量在运行时占据内存单元互不相同的地址,C++的目标代码靠地址来区别不同的变量. 2.表达式: 虽然内存是存储c+ ...

  5. jqgrid 单元格引入时间datepicker控件

    简述原理:引入jquery-ui插件,设置好表格所需的字段变量以及字段属性1.设置colName与colModel colNames: ['过期时间''] colModel:[{       name ...

  6. 微信小程序调接口常见问题解决方法

    第一次调接口时遇见的bug. 注意:接口的域名不能使用 IP 地址或 localhost,且不能带端口号: 微信小程序如何调接口? wx.request({ url: 'http://miniapp/ ...

  7. MySQL常用的查询命令

    MySQL常用的查询命令 author: headsen chen   2017-10-19  10:15:25 个人原创.转载请注明作者,出处,否则依法追究法律责任 1,查询现在的时间:mysql& ...

  8. python作业02

    1.请用代码实现:利用下划线将列表的每一个元素拼接成字符串,li=['alex', 'eric', 'rain'] li = ['alex', 'eric', 'rain'] v = "_& ...

  9. C语言第五次博客作业--函数

    一.PTA实验作业 题目1:使用函数判断完全平方数 1. 本题PTA提交列表 2. 设计思路 3.本题调试过程碰到问题及PTA提交列表情况说明. 部分正确 :将else的情况放入for循环内,导致循环 ...

  10. Http最常见的错误代码

    1XX 表示消息 2XX 表示成功 3XX 表示重定向 4XX 表示请求错误 5XX 表示服务器端错误 我们最常见的就是: 404(页面找不到),这个错误代码是由于我们输入的网址不对造成的,浏览器找不 ...