构建自己的PHP框架--构建模版引擎(3)
之前我们实现了最简单的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
构建自己的PHP框架--构建模版引擎(3)的更多相关文章
- 构建自己的PHP框架--构建模版引擎(1)
前段时间太忙,导致好久都没有更新博客了,今天抽出点时间来写一篇. 其实这个系列的博客很久没有更新了,之前想好好规划一下,再继续写,然后就放下了,今天再捡起来继续更新. 今天我们来说一下,如何构建自己的 ...
- 构建自己的PHP框架--构建模版引擎(2)
自从来到新公司就一直很忙,最近这段时间终于稍微闲了一点,赶紧接着写这个系列,感觉再不写就烂尾了. 之前我们说到,拿到{{ $name }}这样一段内容时,我们只需要将它转化成<?php echo ...
- 构建自己的PHP框架--构建缓存组件(1)
作为一个框架,我们还没有相应的缓存组件,下面我们就来构建我们的缓存组件. 先来定义一下接口,在 src 文件夹下创建 cache 文件夹,在cache文件夹下创建 CacheInterface.php ...
- 构建自己的PHP框架--构建缓存组件(2)
上一篇博客中使用文件实现了缓存组件,这一篇我们就使用Redis来实现一下,剩下的如何使用memcache.mysql等去实现缓存我就不一一去做了. 首先我们需要安装一下 redis 和 phpredi ...
- 构建自己的PHP框架(Twig模板引擎)
完整项目地址:https://github.com/Evai/Aier Twig 模板引擎 模版引擎 twig 的模板就是普通的文本文件,也不需要特别的扩展名,.html .htm .twig 都可以 ...
- 基于laravel框架构建最小内容管理系统
校园失物招领平台开发 --基于laravel框架构建最小内容管理系统 摘要 针对目前大学校园人口密度大.人群活动频繁.师生学习生活等物品容易遗失的基本现状,在分析传统失物招领过程中的工作效率低下. ...
- net 和Mono 构建的HTTP服务框架
Nancy是一个基于.net 和Mono 构建的HTTP服务框架,是一个非常轻量级的web框架. 设计用于处理 DELETE, GET, HEAD, OPTIONS, POST, PUT 和 PATC ...
- 基于Dubbo框架构建分布式服务(一)
Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配 ...
- 使用 SailingEase WinForm 框架构建复合式应用程序(插件式应用程序)
对于一些较小的项目,具备一定经验的开发人员应该能够设计和构建出便于进行维护和扩展的应用程序.但是,随着功能模块数量(以及开发维护这些部件的人员)的不断增加,对项目实施控制的难度开始呈指数级增长. Sa ...
随机推荐
- js实现对树深度优先遍历与广度优先遍历
深度优先与广度优先的定义 首先我们先要知道什么是深度优先什么是广度优先. 深度优先遍历是指从某个顶点出发,首先访问这个顶点,然后找出刚访问这个结点的第一个未被访问的邻结点,然后再以此邻结点为顶点,继续 ...
- angular路由模块(二)
上一章写的是如何创建一个简单的路由,这一样我们来看看如何创建一个路由模块.angular的思想就是(模块,组件,子组件.....). 我们在src/app目录下创建一个跟路由模块app-routing ...
- jsp常见jstl语法(二)
<c:choose>标签与Javascript switch语句的功能一样,用于在众多选项中做出选择. 语法格式 <c:choose> <c:when test=&quo ...
- C# Redis实战(六)
六.查询数据 在C# Redis实战(五)中介绍了如何删除Redis中数据,本篇将继续介绍Redis中查询的写法. 1.使用Linq匹配关键字查询 using (var redisClient = R ...
- Hive数据仓库笔记(三)
Joins: Inner joins: hive> SELECT * FROM sales; Joe 2 Hank 4 Ali 0 Eve 3 Hank 2 hive> SELECT * ...
- EasyUI 二次加载toolbar异常问题解决
问题:easyUI初次渲染正常,之后会出问题,toolbar重复加载 解决办法:每次加载之前先从body中删除已经渲染的工具栏-->重新加载-->手动渲染工具栏 代码: function ...
- 机器学习之类别不平衡问题 (2) —— ROC和PR曲线
机器学习之类别不平衡问题 (1) -- 各种评估指标 机器学习之类别不平衡问题 (2) -- ROC和PR曲线 完整代码 ROC曲线和PR(Precision - Recall)曲线皆为类别不平衡问题 ...
- 问题排查:The requested URL /test/index.jsp was not found on this server
问题描述 添加一个新模块,部署在服务器上.服务器上还部署有其他模块且访问正常,新模块和其他模块共用同一个域名.服务部署之后,请求如下: http://my.domain.com/test/index. ...
- Mysql使用规范文档 20180223版
强制:不允许在跳板机上/生产服务器上手工连接,查询或更改线上数据 强制:所有上线脚本必须先在测试环境执行,验证通过以后方可在生产环境执行. 强制:上线脚本的编码格式统一为UTF-8 强制:访问数据库需 ...
- Maven-11: 从命令行调用插件
mvn -h显示mvn命令帮助: usage: mvn [options] [<goal(s)>] [<phase(s)>] Options: -am,--also-make ...