初始化仓库

This commit is contained in:
wangxiaowei
2025-04-22 14:09:52 +08:00
commit 8b100110bb
5155 changed files with 664201 additions and 0 deletions

View File

@ -0,0 +1 @@
.idea

201
vendor/topthink/think-template/LICENSE vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,70 @@
# ThinkTemplate
基于XML和标签库的编译型模板引擎
## 主要特性
- 支持XML标签库和普通标签的混合定义
- 支持直接使用PHP代码书写
- 支持文件包含;
- 支持多级标签嵌套;
- 支持布局模板功能;
- 一次编译多次运行,编译和运行效率非常高;
- 模板文件和布局模板更新,自动更新模板缓存;
- 系统变量无需赋值直接输出;
- 支持多维数组的快速输出;
- 支持模板变量的默认值;
- 支持页面代码去除Html空白
- 支持变量组合调节器和格式化功能;
- 允许定义模板禁用函数和禁用PHP语法
- 通过标签库方式扩展;
## 安装
~~~php
composer require topthink/think-template
~~~
## 用法示例
~~~php
<?php
namespace think;
require __DIR__.'/vendor/autoload.php';
// 设置模板引擎参数
$config = [
'view_path' => './template/',
'cache_path' => './runtime/',
'view_suffix' => 'html',
];
$template = new Template($config);
// 模板变量赋值
$template->assign(['name' => 'think']);
// 读取模板文件渲染输出
$template->fetch('index');
// 完整模板文件渲染
$template->fetch('./template/test.php');
// 渲染内容输出
$template->display($content);
~~~
支持静态调用
~~~
use think\facade\Template;
Template::config([
'view_path' => './template/',
'cache_path' => './runtime/',
'view_suffix' => 'html',
]);
Template::assign(['name' => 'think']);
Template::fetch('index',['name' => 'think']);
Template::display($content,['name' => 'think']);
~~~
详细用法参考[开发手册](https://www.kancloud.cn/manual/think-template/content)

View File

@ -0,0 +1,20 @@
{
"name": "topthink/think-template",
"description": "the php template engine",
"license": "Apache-2.0",
"authors": [
{
"name": "liu21st",
"email": "liu21st@gmail.com"
}
],
"require": {
"php": ">=8.0.0",
"psr/simple-cache": ">=1.0"
},
"autoload": {
"psr-4": {
"think\\": "src"
}
}
}

View File

@ -0,0 +1,12 @@
<phpunit
bootstrap="tests/bootstrap.php"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
>
<testsuites>
<testsuite name="unit">
<directory phpVersion="8.0.0" phpVersionOperator=">=">tests/think</directory>
</testsuite>
</testsuites>
</phpunit>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,83 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\facade;
if (class_exists('think\Facade')) {
class Facade extends \think\Facade
{}
} else {
class Facade
{
/**
* 始终创建新的对象实例
* @var bool
*/
protected static $alwaysNewInstance;
protected static $instance;
/**
* 获取当前Facade对应类名
* @access protected
* @return string
*/
protected static function getFacadeClass()
{}
/**
* 创建Facade实例
* @static
* @access protected
* @return object
*/
protected static function createFacade()
{
$class = static::getFacadeClass() ?: 'think\Template';
if (static::$alwaysNewInstance) {
return new $class();
}
if (!self::$instance) {
self::$instance = new $class();
}
return self::$instance;
}
// 调用实际类的方法
public static function __callStatic($method, $params)
{
return call_user_func_array([static::createFacade(), $method], $params);
}
}
}
/**
* @see \think\Template
* @mixin \think\Template
*/
class Template extends Facade
{
protected static $alwaysNewInstance = true;
/**
* 获取当前Facade对应类名或者已经绑定的容器对象标识
* @access protected
* @return string
*/
protected static function getFacadeClass()
{
return 'think\Template';
}
}

View File

@ -0,0 +1,341 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\template;
use Exception;
use think\Template;
/**
* ThinkPHP标签库TagLib解析基类
* @category Think
* @package Think
* @subpackage Template
* @author liu21st <liu21st@gmail.com>
*/
class TagLib
{
/**
* 标签库定义XML文件
* @var string
* @access protected
*/
protected $xml = '';
protected $tags = []; // 标签定义
/**
* 标签库名称
* @var string
* @access protected
*/
protected $tagLib = '';
/**
* 标签库标签列表
* @var array
* @access protected
*/
protected $tagList = [];
/**
* 标签库分析数组
* @var array
* @access protected
*/
protected $parse = [];
/**
* 标签库是否有效
* @var bool
* @access protected
*/
protected $valid = false;
protected $comparison = [' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < '];
/**
* 架构函数
* @access public
* @param Template $tpl 模板引擎对象
*/
public function __construct(protected Template $tpl)
{
}
/**
* 按签标库替换页面中的标签
* @access public
* @param string $content 模板内容
* @param string $lib 标签库名
* @return void
*/
public function parseTag(string &$content, string $lib = ''): void
{
$tags = [];
$lib = $lib ? strtolower($lib) . ':' : '';
foreach ($this->tags as $name => $val) {
$close = !isset($val['close']) || $val['close'] ? 1 : 0;
$tags[$close][$lib . $name] = $name;
if (isset($val['alias'])) {
// 别名设置
$array = (array) $val['alias'];
foreach (explode(',', $array[0]) as $v) {
$tags[$close][$lib . $v] = $name;
}
}
}
// 闭合标签
if (!empty($tags[1])) {
$nodes = [];
$regex = $this->getRegex(array_keys($tags[1]), 1);
if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
$right = [];
foreach ($matches as $match) {
if ('' == $match[1][0]) {
$name = strtolower($match[2][0]);
// 如果有没闭合的标签头则取出最后一个
if (!empty($right[$name])) {
// $match[0][1]为标签结束符在模板中的位置
$nodes[$match[0][1]] = [
'name' => $name,
'begin' => array_pop($right[$name]), // 标签开始符
'end' => $match[0], // 标签结束符
];
}
} else {
// 标签头压入栈
$right[strtolower($match[1][0])][] = $match[0];
}
}
unset($right, $matches);
// 按标签在模板中的位置从后向前排序
krsort($nodes);
}
$break = '<!--###break###--!>';
if ($nodes) {
$beginArray = [];
// 标签替换 从后向前
foreach ($nodes as $pos => $node) {
// 对应的标签名
$name = $tags[1][$node['name']];
$alias = $lib . $name != $node['name'] ? ($lib ? strstr($node['name'], $lib) : $node['name']) : '';
// 解析标签属性
$attrs = $this->parseAttr($node['begin'][0], $name, $alias);
$method = 'tag' . $name;
// 读取标签库中对应的标签内容 replace[0]用来替换标签头replace[1]用来替换标签尾
$replace = explode($break, $this->$method($attrs, $break));
if (count($replace) > 1) {
while ($beginArray) {
$begin = end($beginArray);
// 判断当前标签尾的位置是否在栈中最后一个标签头的后面,是则为子标签
if ($node['end'][1] > $begin['pos']) {
break;
} else {
// 不为子标签时,取出栈中最后一个标签头
$begin = array_pop($beginArray);
// 替换标签头部
$content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']);
}
}
// 替换标签尾部
$content = substr_replace($content, $replace[1], $node['end'][1], strlen($node['end'][0]));
// 把标签头压入栈
$beginArray[] = ['pos' => $node['begin'][1], 'len' => strlen($node['begin'][0]), 'str' => $replace[0]];
}
}
while ($beginArray) {
$begin = array_pop($beginArray);
// 替换标签头部
$content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']);
}
}
}
// 自闭合标签
if (!empty($tags[0])) {
$regex = $this->getRegex(array_keys($tags[0]), 0);
$content = preg_replace_callback($regex, function ($matches) use (&$tags, &$lib) {
// 对应的标签名
$name = $tags[0][strtolower($matches[1])];
$alias = $lib . $name != $matches[1] ? ($lib ? strstr($matches[1], $lib) : $matches[1]) : '';
// 解析标签属性
$attrs = $this->parseAttr($matches[0], $name, $alias);
$method = 'tag' . $name;
return $this->$method($attrs, '');
}, $content);
}
}
/**
* 按标签生成正则
* @access public
* @param array|string $tags 标签名
* @param boolean $close 是否为闭合标签
* @return string
*/
public function getRegex($tags, bool $close): string
{
$begin = $this->tpl->getConfig('taglib_begin');
$end = $this->tpl->getConfig('taglib_end');
$single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false;
$tagName = is_array($tags) ? implode('|', $tags) : $tags;
if ($single) {
if ($close) {
// 如果是闭合标签
$regex = $begin . '(?:(' . $tagName . ')\b(?>[^' . $end . ']*)|\/(' . $tagName . '))' . $end;
} else {
$regex = $begin . '(' . $tagName . ')\b(?>[^' . $end . ']*)' . $end;
}
} else {
if ($close) {
// 如果是闭合标签
$regex = $begin . '(?:(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)|\/(' . $tagName . '))' . $end;
} else {
$regex = $begin . '(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)' . $end;
}
}
return '/' . $regex . '/is';
}
/**
* 分析标签属性 正则方式
* @access public
* @param string $str 标签属性字符串
* @param string $name 标签名
* @param string $alias 别名
* @return array
*/
public function parseAttr(string $str, string $name, string $alias = ''): array
{
$regex = '/\s+(?>(?P<name>[\w-]+)\s*)=(?>\s*)([\"\'])(?P<value>(?:(?!\\2).)*)\\2/is';
$result = [];
if (preg_match_all($regex, $str, $matches)) {
foreach ($matches['name'] as $key => $val) {
$result[$val] = $matches['value'][$key];
}
if (!isset($this->tags[$name])) {
// 检测是否存在别名定义
foreach ($this->tags as $key => $val) {
if (isset($val['alias'])) {
$array = (array) $val['alias'];
if (in_array($name, explode(',', $array[0]))) {
$tag = $val;
$type = !empty($array[1]) ? $array[1] : 'type';
$result[$type] = $name;
break;
}
}
}
} else {
$tag = $this->tags[$name];
// 设置了标签别名
if (!empty($alias) && isset($tag['alias'])) {
$type = !empty($tag['alias'][1]) ? $tag['alias'][1] : 'type';
$result[$type] = $alias;
}
}
if (!empty($tag['must'])) {
$must = explode(',', $tag['must']);
foreach ($must as $name) {
if (!isset($result[$name])) {
throw new Exception('tag attr must:' . $name);
}
}
}
} else {
// 允许直接使用表达式的标签
if (!empty($this->tags[$name]['expression'])) {
static $_taglibs;
if (!isset($_taglibs[$name])) {
$_taglibs[$name][0] = strlen($this->tpl->getConfig('taglib_begin_origin') . $name);
$_taglibs[$name][1] = strlen($this->tpl->getConfig('taglib_end_origin'));
}
$result['expression'] = substr($str, $_taglibs[$name][0], -$_taglibs[$name][1]);
// 清除自闭合标签尾部/
$result['expression'] = rtrim($result['expression'], '/');
$result['expression'] = trim($result['expression']);
} elseif (empty($this->tags[$name]) || !empty($this->tags[$name]['attr'])) {
throw new Exception('tag error:' . $name);
}
}
return $result;
}
/**
* 解析条件表达式
* @access public
* @param string $condition 表达式标签内容
* @return string
*/
public function parseCondition(string $condition): string
{
if (strpos($condition, ':')) {
$condition = ' ' . substr(strstr($condition, ':'), 1);
}
$condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition);
$this->tpl->parseVar($condition);
return $condition;
}
/**
* 自动识别构建变量
* @access public
* @param string $name 变量描述
* @return string
*/
public function autoBuildVar(string &$name): string
{
$flag = substr($name, 0, 1);
if (':' == $flag) {
// 以:开头为函数调用,解析前去掉:
$name = substr($name, 1);
} elseif ('$' != $flag && preg_match('/[a-zA-Z_]/', $flag)) {
// XXX: 这句的写法可能还需要改进
// 常量不需要解析
if (defined($name)) {
return $name;
}
// 不以$开头并且也不是常量,自动补上$前缀
$name = '$' . $name;
}
$this->tpl->parseVar($name);
$this->tpl->parseVarFunction($name, false);
return $name;
}
/**
* 获取标签列表
* @access public
* @return array
*/
public function getTags(): array
{
return $this->tags;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace think\template\contract;
/**
* 模板编译存储器驱动接口
*/
interface DriverInterface
{
/**
* 写入编译缓存
* @access public
* @param string $cacheFile 缓存的文件名
* @param string $content 缓存的内容
* @return void
*/
public function write(string $cacheFile, string $content): void;
/**
* 读取编译编译
* @access public
* @param string $cacheFile 缓存的文件名
* @param array $vars 变量数组
* @return void
*/
public function read(string $cacheFile, array $vars = []): void;
/**
* 检查编译缓存是否有效
* @access public
* @param string $cacheFile 缓存的文件名
* @param int $cacheTime 缓存时间
* @return bool
*/
public function check(string $cacheFile, int $cacheTime): bool;
}

View File

@ -0,0 +1,84 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\template\driver;
use Exception;
use think\template\contract\DriverInterface;
class File implements DriverInterface
{
protected $cacheFile;
/**
* 写入编译缓存
* @access public
* @param string $cacheFile 缓存的文件名
* @param string $content 缓存的内容
* @return void
*/
public function write(string $cacheFile, string $content): void
{
// 检测模板目录
$dir = dirname($cacheFile);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
// 生成模板缓存文件
if (false === file_put_contents($cacheFile, $content)) {
throw new Exception('cache write error:' . $cacheFile, 11602);
}
}
/**
* 读取编译编译
* @access public
* @param string $cacheFile 缓存的文件名
* @param array $vars 变量数组
* @return void
*/
public function read(string $cacheFile, array $vars = []): void
{
$this->cacheFile = $cacheFile;
if (!empty($vars) && is_array($vars)) {
// 模板阵列变量分解成为独立变量
extract($vars, EXTR_OVERWRITE);
}
//载入模版缓存文件
include $this->cacheFile;
}
/**
* 检查编译缓存是否有效
* @access public
* @param string $cacheFile 缓存的文件名
* @param int $cacheTime 缓存时间
* @return bool
*/
public function check(string $cacheFile, int $cacheTime): bool
{
// 缓存文件不存在, 直接返回false
if (!file_exists($cacheFile)) {
return false;
}
if (0 != $cacheTime && time() > filemtime($cacheFile) + $cacheTime) {
// 缓存是否在有效期
return false;
}
return true;
}
}

View File

@ -0,0 +1,33 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\template\exception;
class TemplateNotFoundException extends \RuntimeException
{
protected $template;
public function __construct(string $message, string $template = '')
{
$this->message = $message;
$this->template = $template;
}
/**
* 获取模板文件
* @access public
* @return string
*/
public function getTemplate(): string
{
return $this->template;
}
}

View File

@ -0,0 +1,715 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\template\taglib;
use think\template\TagLib;
/**
* CX标签库解析类
* @category Think
* @package Think
* @subpackage Driver.Taglib
* @author liu21st <liu21st@gmail.com>
*/
class Cx extends Taglib
{
// 标签定义
protected $tags = [
// 标签定义: attr 属性列表 close 是否闭合0 或者1 默认1 alias 标签别名 level 嵌套层次
'php' => ['attr' => ''],
'volist' => ['attr' => 'name,id,offset,length,key,mod', 'alias' => 'iterate'],
'foreach' => ['attr' => 'name,id,item,key,offset,length,mod', 'expression' => true],
'if' => ['attr' => 'condition', 'expression' => true],
'elseif' => ['attr' => 'condition', 'close' => 0, 'expression' => true],
'else' => ['attr' => '', 'close' => 0],
'switch' => ['attr' => 'name', 'expression' => true],
'case' => ['attr' => 'value,break', 'expression' => true],
'default' => ['attr' => '', 'close' => 0],
'compare' => ['attr' => 'name,value,type', 'alias' => ['eq,equal,notequal,neq,gt,lt,egt,elt,heq,nheq', 'type']],
'range' => ['attr' => 'name,value,type', 'alias' => ['in,notin,between,notbetween', 'type']],
'empty' => ['attr' => 'name'],
'notempty' => ['attr' => 'name'],
'present' => ['attr' => 'name'],
'notpresent' => ['attr' => 'name'],
'defined' => ['attr' => 'name'],
'notdefined' => ['attr' => 'name'],
'load' => ['attr' => 'file,href,type,value,basepath', 'close' => 0, 'alias' => ['import,css,js', 'type']],
'assign' => ['attr' => 'name,value', 'close' => 0],
'define' => ['attr' => 'name,value', 'close' => 0],
'for' => ['attr' => 'start,end,name,comparison,step'],
'url' => ['attr' => 'link,vars,suffix,domain', 'close' => 0, 'expression' => true],
'function' => ['attr' => 'name,vars,use,call'],
];
/**
* php标签解析
* 格式:
* {php}echo $name{/php}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagPhp(array $tag, string $content): string
{
$parseStr = '<?php ' . $content . ' ?>';
return $parseStr;
}
/**
* volist标签解析 循环输出数据集
* 格式:
* {volist name="userList" id="user" empty=""}
* {user.username}
* {user.email}
* {/volist}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagVolist(array $tag, string $content): string
{
$name = $tag['name'];
$id = $tag['id'];
$empty = isset($tag['empty']) ? $tag['empty'] : '';
$key = !empty($tag['key']) ? $tag['key'] : 'i';
$mod = isset($tag['mod']) ? $tag['mod'] : '2';
$offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0;
$length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null';
// 允许使用函数设定数据集 <volist name=":fun('arg')" id="vo">{$vo.name}</volist>
$parseStr = '<?php ';
$flag = substr($name, 0, 1);
if (':' == $flag) {
$name = $this->autoBuildVar($name);
$parseStr .= '$_result=' . $name . ';';
$name = '$_result';
} else {
$name = $this->autoBuildVar($name);
}
$parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): $' . $key . ' = 0;';
// 设置了输出数组长度
if (0 != $offset || 'null' != $length) {
$parseStr .= '$__LIST__ = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); ';
} else {
$parseStr .= ' $__LIST__ = ' . $name . ';';
}
$parseStr .= 'if( count($__LIST__)==0 ) : echo "' . $empty . '" ;';
$parseStr .= 'else: ';
$parseStr .= 'foreach($__LIST__ as $key=>$' . $id . '): ';
$parseStr .= '$mod = ($' . $key . ' % ' . $mod . ' );';
$parseStr .= '++$' . $key . ';?>';
$parseStr .= $content;
$parseStr .= '<?php endforeach; endif; else: echo "' . $empty . '" ;endif; ?>';
return $parseStr;
}
/**
* foreach标签解析 循环输出数据集
* 格式:
* {foreach name="userList" id="user" key="key" index="i" mod="2" offset="3" length="5" empty=""}
* {user.username}
* {/foreach}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagForeach(array $tag, string $content): string
{
// 直接使用表达式
if (!empty($tag['expression'])) {
$expression = ltrim(rtrim($tag['expression'], ')'), '(');
$expression = $this->autoBuildVar($expression);
$parseStr = '<?php foreach(' . $expression . '): ?>';
$parseStr .= $content;
$parseStr .= '<?php endforeach; ?>';
return $parseStr;
}
$name = $tag['name'];
$key = !empty($tag['key']) ? $tag['key'] : 'key';
$item = !empty($tag['id']) ? $tag['id'] : $tag['item'];
$empty = isset($tag['empty']) ? $tag['empty'] : '';
$offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0;
$length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null';
$parseStr = '<?php ';
// 支持用函数传数组
if (':' == substr($name, 0, 1)) {
$var = '$_' . uniqid();
$name = $this->autoBuildVar($name);
$parseStr .= $var . '=' . $name . '; ';
$name = $var;
} else {
$name = $this->autoBuildVar($name);
}
$parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): ';
// 设置了输出数组长度
if (0 != $offset || 'null' != $length) {
if (!isset($var)) {
$var = '$_' . uniqid();
}
$parseStr .= $var . ' = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); ';
} else {
$var = &$name;
}
$parseStr .= 'if( count(' . $var . ')==0 ) : echo "' . $empty . '" ;';
$parseStr .= 'else: ';
// 设置了索引项
if (isset($tag['index'])) {
$index = $tag['index'];
$parseStr .= '$' . $index . '=0; ';
}
$parseStr .= 'foreach(' . $var . ' as $' . $key . '=>$' . $item . '): ';
// 设置了索引项
if (isset($tag['index'])) {
$index = $tag['index'];
if (isset($tag['mod'])) {
$mod = (int) $tag['mod'];
$parseStr .= '$mod = ($' . $index . ' % ' . $mod . '); ';
}
$parseStr .= '++$' . $index . '; ';
}
$parseStr .= '?>';
// 循环体中的内容
$parseStr .= $content;
$parseStr .= '<?php endforeach; endif; else: echo "' . $empty . '" ;endif; ?>';
return $parseStr;
}
/**
* if标签解析
* 格式:
* {if condition=" $a eq 1"}
* {elseif condition="$a eq 2" /}
* {else /}
* {/if}
* 表达式支持 eq neq gt egt lt elt == > >= < <= or and || &&
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagIf(array $tag, string $content): string
{
$condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition'];
$condition = $this->parseCondition($condition);
$parseStr = '<?php if(' . $condition . '): ?>' . $content . '<?php endif; ?>';
return $parseStr;
}
/**
* elseif标签解析
* 格式见if标签
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagElseif(array $tag, string $content): string
{
$condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition'];
$condition = $this->parseCondition($condition);
$parseStr = '<?php elseif(' . $condition . '): ?>';
return $parseStr;
}
/**
* else标签解析
* 格式见if标签
* @access public
* @param array $tag 标签属性
* @return string
*/
public function tagElse(array $tag): string
{
$parseStr = '<?php else: ?>';
return $parseStr;
}
/**
* switch标签解析
* 格式:
* {switch name="a.name"}
* {case value="1" break="false"}1{/case}
* {case value="2" }2{/case}
* {default /}other
* {/switch}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagSwitch(array $tag, string $content): string
{
$name = !empty($tag['expression']) ? $tag['expression'] : $tag['name'];
$name = $this->autoBuildVar($name);
$parseStr = '<?php switch(' . $name . '): ?>' . $content . '<?php endswitch; ?>';
return $parseStr;
}
/**
* case标签解析 需要配合switch才有效
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagCase(array $tag, string $content): string
{
$value = isset($tag['expression']) ? $tag['expression'] : $tag['value'];
$flag = substr($value, 0, 1);
if ('$' == $flag || ':' == $flag) {
$value = $this->autoBuildVar($value);
$value = 'case ' . $value . ':';
} elseif (strpos($value, '|')) {
$values = explode('|', $value);
$value = '';
foreach ($values as $val) {
$value .= 'case "' . addslashes($val) . '":';
}
} else {
$value = 'case "' . $value . '":';
}
$parseStr = '<?php ' . $value . ' ?>' . $content;
$isBreak = isset($tag['break']) ? $tag['break'] : '';
if ('' == $isBreak || $isBreak) {
$parseStr .= '<?php break; ?>';
}
return $parseStr;
}
/**
* default标签解析 需要配合switch才有效
* 使用: {default /}ddfdf
* @access public
* @param array $tag 标签属性
* @return string
*/
public function tagDefault(array $tag): string
{
$parseStr = '<?php default: ?>';
return $parseStr;
}
/**
* compare标签解析
* 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq
* 格式: {compare name="" type="eq" value="" }content{/compare}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagCompare(array $tag, string $content): string
{
$name = $tag['name'];
$value = $tag['value'];
$type = isset($tag['type']) ? $tag['type'] : 'eq'; // 比较类型
$name = $this->autoBuildVar($name);
$flag = substr($value, 0, 1);
if ('$' == $flag || ':' == $flag) {
$value = $this->autoBuildVar($value);
} else {
$value = '\'' . $value . '\'';
}
switch ($type) {
case 'equal':
$type = 'eq';
break;
case 'notequal':
$type = 'neq';
break;
}
$type = $this->parseCondition(' ' . $type . ' ');
$parseStr = '<?php if(' . $name . ' ' . $type . ' ' . $value . '): ?>' . $content . '<?php endif; ?>';
return $parseStr;
}
/**
* range标签解析
* 如果某个变量存在于某个范围 则输出内容 type= in 表示在范围内 否则表示在范围外
* 格式: {range name="var|function" value="val" type='in|notin' }content{/range}
* example: {range name="a" value="1,2,3" type='in' }content{/range}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagRange(array $tag, string $content): string
{
$name = $tag['name'];
$value = $tag['value'];
$type = isset($tag['type']) ? $tag['type'] : 'in'; // 比较类型
$name = $this->autoBuildVar($name);
$flag = substr($value, 0, 1);
if ('$' == $flag || ':' == $flag) {
$value = $this->autoBuildVar($value);
$str = 'is_array(' . $value . ')?' . $value . ':explode(\',\',' . $value . ')';
} else {
$value = '"' . $value . '"';
$str = 'explode(\',\',' . $value . ')';
}
if ('between' == $type) {
$parseStr = '<?php $_RANGE_VAR_=' . $str . ';if(' . $name . '>= $_RANGE_VAR_[0] && ' . $name . '<= $_RANGE_VAR_[1]):?>' . $content . '<?php endif; ?>';
} elseif ('notbetween' == $type) {
$parseStr = '<?php $_RANGE_VAR_=' . $str . ';if(' . $name . '<$_RANGE_VAR_[0] || ' . $name . '>$_RANGE_VAR_[1]):?>' . $content . '<?php endif; ?>';
} else {
$fun = ('in' == $type) ? 'in_array' : '!in_array';
$parseStr = '<?php if(' . $fun . '((' . $name . '), ' . $str . ')): ?>' . $content . '<?php endif; ?>';
}
return $parseStr;
}
/**
* present标签解析
* 如果某个变量已经设置 则输出内容
* 格式: {present name="" }content{/present}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagPresent(array $tag, string $content): string
{
$name = $tag['name'];
$name = $this->autoBuildVar($name);
$parseStr = '<?php if(isset(' . $name . ')): ?>' . $content . '<?php endif; ?>';
return $parseStr;
}
/**
* notpresent标签解析
* 如果某个变量没有设置,则输出内容
* 格式: {notpresent name="" }content{/notpresent}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagNotpresent(array $tag, string $content): string
{
$name = $tag['name'];
$name = $this->autoBuildVar($name);
$parseStr = '<?php if(!isset(' . $name . ')): ?>' . $content . '<?php endif; ?>';
return $parseStr;
}
/**
* empty标签解析
* 如果某个变量为empty 则输出内容
* 格式: {empty name="" }content{/empty}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagEmpty(array $tag, string $content): string
{
$name = $tag['name'];
$name = $this->autoBuildVar($name);
$parseStr = '<?php if(empty(' . $name . ') || ((' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator ) && ' . $name . '->isEmpty())): ?>' . $content . '<?php endif; ?>';
return $parseStr;
}
/**
* notempty标签解析
* 如果某个变量不为empty 则输出内容
* 格式: {notempty name="" }content{/notempty}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagNotempty(array $tag, string $content): string
{
$name = $tag['name'];
$name = $this->autoBuildVar($name);
$parseStr = '<?php if(!(empty(' . $name . ') || ((' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator ) && ' . $name . '->isEmpty()))): ?>' . $content . '<?php endif; ?>';
return $parseStr;
}
/**
* 判断是否已经定义了该常量
* {defined name='TXT'}已定义{/defined}
* @access public
* @param array $tag
* @param string $content
* @return string
*/
public function tagDefined(array $tag, string $content): string
{
$name = $tag['name'];
$parseStr = '<?php if(defined("' . $name . '")): ?>' . $content . '<?php endif; ?>';
return $parseStr;
}
/**
* 判断是否没有定义了该常量
* {notdefined name='TXT'}已定义{/notdefined}
* @access public
* @param array $tag
* @param string $content
* @return string
*/
public function tagNotdefined(array $tag, string $content): string
{
$name = $tag['name'];
$parseStr = '<?php if(!defined("' . $name . '")): ?>' . $content . '<?php endif; ?>';
return $parseStr;
}
/**
* load 标签解析 {load file="/static/js/base.js" /}
* 格式:{load file="/static/css/base.css" /}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagLoad(array $tag, string $content): string
{
$file = isset($tag['file']) ? $tag['file'] : $tag['href'];
$type = isset($tag['type']) ? strtolower($tag['type']) : '';
$parseStr = '';
$endStr = '';
// 判断是否存在加载条件 允许使用函数判断(默认为isset)
if (isset($tag['value'])) {
$name = $tag['value'];
$name = $this->autoBuildVar($name);
$name = 'isset(' . $name . ')';
$parseStr .= '<?php if(' . $name . '): ?>';
$endStr = '<?php endif; ?>';
}
// 文件方式导入
$array = explode(',', $file);
foreach ($array as $val) {
$type = strtolower(substr(strrchr($val, '.'), 1));
switch ($type) {
case 'js':
$parseStr .= '<script type="text/javascript" src="' . $val . '"></script>';
break;
case 'css':
$parseStr .= '<link rel="stylesheet" type="text/css" href="' . $val . '" />';
break;
case 'php':
$parseStr .= '<?php include "' . $val . '"; ?>';
break;
}
}
return $parseStr . $endStr;
}
/**
* assign标签解析
* 在模板中给某个变量赋值 支持变量赋值
* 格式: {assign name="" value="" /}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagAssign(array $tag, string $content): string
{
$name = $this->autoBuildVar($tag['name']);
$flag = substr($tag['value'], 0, 1);
if ('$' == $flag || ':' == $flag) {
$value = $this->autoBuildVar($tag['value']);
} else {
$value = '\'' . $tag['value'] . '\'';
}
$parseStr = '<?php ' . $name . ' = ' . $value . '; ?>';
return $parseStr;
}
/**
* define标签解析
* 在模板中定义常量 支持变量赋值
* 格式: {define name="" value="" /}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagDefine(array $tag, string $content): string
{
$name = '\'' . $tag['name'] . '\'';
$flag = substr($tag['value'], 0, 1);
if ('$' == $flag || ':' == $flag) {
$value = $this->autoBuildVar($tag['value']);
} else {
$value = '\'' . $tag['value'] . '\'';
}
$parseStr = '<?php define(' . $name . ', ' . $value . '); ?>';
return $parseStr;
}
/**
* for标签解析
* 格式:
* {for start="" end="" comparison="" step="" name=""}
* content
* {/for}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagFor(array $tag, string $content): string
{
//设置默认值
$start = 0;
$end = 0;
$step = 1;
$comparison = 'lt';
$name = 'i';
$rand = rand(); //添加随机数,防止嵌套变量冲突
//获取属性
foreach ($tag as $key => $value) {
$value = trim($value);
$flag = substr($value, 0, 1);
if ('$' == $flag || ':' == $flag) {
$value = $this->autoBuildVar($value);
}
switch ($key) {
case 'start':
$start = $value;
break;
case 'end':
$end = $value;
break;
case 'step':
$step = $value;
break;
case 'comparison':
$comparison = $value;
break;
case 'name':
$name = $value;
break;
}
}
$parseStr = '<?php $__FOR_START_' . $rand . '__=' . $start . ';$__FOR_END_' . $rand . '__=' . $end . ';';
$parseStr .= 'for($' . $name . '=$__FOR_START_' . $rand . '__;' . $this->parseCondition('$' . $name . ' ' . $comparison . ' $__FOR_END_' . $rand . '__') . ';$' . $name . '+=' . $step . '){ ?>';
$parseStr .= $content;
$parseStr .= '<?php } ?>';
return $parseStr;
}
/**
* url函数的tag标签
* 格式:{url link="模块/控制器/方法" vars="参数" suffix="true或者false 是否带有后缀" domain="true或者false 是否携带域名" /}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagUrl(array $tag, string $content): string
{
$url = isset($tag['link']) ? $tag['link'] : '';
$vars = isset($tag['vars']) ? $tag['vars'] : '';
$suffix = isset($tag['suffix']) ? $tag['suffix'] : 'true';
$domain = isset($tag['domain']) ? $tag['domain'] : 'false';
return '<?php echo url("' . $url . '","' . $vars . '",' . $suffix . ',' . $domain . ');?>';
}
/**
* function标签解析 匿名函数,可实现递归
* 使用:
* {function name="func" vars="$data" call="$list" use="&$a,&$b"}
* {if is_array($data)}
* {foreach $data as $val}
* {~func($val) /}
* {/foreach}
* {else /}
* {$data}
* {/if}
* {/function}
* @access public
* @param array $tag 标签属性
* @param string $content 标签内容
* @return string
*/
public function tagFunction(array $tag, string $content): string
{
$name = !empty($tag['name']) ? $tag['name'] : 'func';
$vars = !empty($tag['vars']) ? $tag['vars'] : '';
$call = !empty($tag['call']) ? $tag['call'] : '';
$use = ['&$' . $name];
if (!empty($tag['use'])) {
foreach (explode(',', $tag['use']) as $val) {
$use[] = '&' . ltrim(trim($val), '&');
}
}
$parseStr = '<?php $' . $name . '=function(' . $vars . ') use(' . implode(',', $use) . ') {';
$parseStr .= ' ?>' . $content . '<?php }; ';
$parseStr .= $call ? '$' . $name . '(' . $call . '); ?>' : '?>';
return $parseStr;
}
}

View File

@ -0,0 +1,3 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/tag/Demo.php';

View File

@ -0,0 +1,46 @@
<?php
namespace tag;
use think\template\TagLib;
class Demo extends TagLib{
/**
* 定义标签列表
*/
protected $tags = [
// 标签定义: attr 属性列表 close 是否闭合0 或者1 默认1 alias 标签别名 level 嵌套层次
'close' => ['attr' => 'time,format', 'close' => 0], //闭合标签,默认为不闭合
'open' => ['attr' => 'name,type', 'close' => 1],
];
/**
* 这是一个闭合标签的简单演示
*/
public function tagClose($tag)
{
$format = empty($tag['format']) ? 'Y-m-d H:i:s' : $tag['format'];
$time = empty($tag['time']) ? time() : $tag['time'];
$parse = '<?php ';
$parse .= 'echo date("' . $format . '",' . $time . ');';
$parse .= ' ?>';
return $parse;
}
/**
* 这是一个非闭合标签的简单演示
*/
public function tagOpen($tag, $content)
{
$type = empty($tag['type']) ? 0 : 1; // 这个type目的是为了区分类型一般来源是数据库
$name = $tag['name']; // name是必填项这里不做判断了
$parse = '<?php ';
$parse .= '$test_arr=[[1,3,5,7,9],[2,4,6,8,10]];'; // 这里是模拟数据
$parse .= '$__LIST__ = $test_arr[' . $type . '];';
$parse .= ' ?>';
$parse .= '{volist name="__LIST__" id="' . $name . '"}';
$parse .= $content;
$parse .= '{/volist}';
return $parse;
}
}

View File

@ -0,0 +1,2 @@
{block name="title"}标题{/block}
{block name="main"}主内容{/block}

View File

@ -0,0 +1 @@
success

View File

@ -0,0 +1 @@
include

View File

@ -0,0 +1 @@
start{__CONTENT__}end

View File

@ -0,0 +1,247 @@
<?php
namespace think;
use PHPUnit\Framework\TestCase;
use tag\Demo;
class TemplateTest extends TestCase
{
public function getTemplate()
{
$config = [
'view_path' => __DIR__ . '/../template' . DIRECTORY_SEPARATOR,
'cache_path' => __DIR__ . '/../cache' . DIRECTORY_SEPARATOR,
'tpl_cache' => false,
'tpl_replace_string' => ['__STATIC__' => '/static'],
'taglib_pre_load' => Demo::class,
];
return new Template($config);
}
// 直接渲染
public function testDisplay()
{
$this->expectOutputString('hello-thinkphp');
$template = $this->getTemplate();
$content = '{$name}-{$email}';
$template->display($content, ['name' => 'hello', 'email' => 'thinkphp']);
}
// 渲染文件
public function testFetch()
{
$this->expectOutputString('success');
$template = $this->getTemplate();
$template->fetch('fetch');
}
// 布局
public function testLayout()
{
$this->expectOutputString('startsuccessend');
$template = $this->getTemplate();
$template->layout('layout');
$template->fetch('fetch');
$template->layout(false);
}
// 扩展解析
public function testExtend()
{
$this->expectOutputString('test.name');
$template = $this->getTemplate();
$template->extend('$Cms', function (array $vars) {
return '\'' . implode('.', $vars) . '\'';
});
$content = '{$Cms.test.name}';
$template->display($content);
}
// 变量
public function testParseVar()
{
$this->expectOutputString('e10adc3949ba59abbe56e057f20f883e');
$template = $this->getTemplate();
$content = '{:md5("123456")}';
$template->display($content, ['password' => '123456']);
}
// 变量使用函数
public function testParseVarFunction()
{
$this->expectOutputString('e10adc3949ba59abbe56e057f20f883e-123456-666456');
$template = $this->getTemplate();
$content = '{$password|md5}-{$password|raw}-{$password|str_replace=123,666,###}';
$template->display($content, ['password' => '123456']);
}
// 默认值
public function testParseDefaultFunction()
{
$this->expectOutputString('test');
$template = $this->getTemplate();
$content = '{$default|default="test"}';
$template->display($content);
}
// 系统变量
public function testParseThinkVar()
{
$this->expectOutputString($_SERVER['PHP_SELF'] . '-' . PHP_VERSION . '-' . PHP_VERSION);
$template = $this->getTemplate();
$content = '{$Request.server.PHP_SELF}-{$Think.const.PHP_VERSION}-{$Think.PHP_VERSION}';
$template->display($content);
}
// 数组
public function testParseArrayVar()
{
$this->expectOutputString('thinkphp<br/>thinkphp');
$template = $this->getTemplate();
$content = '{$data.name}<br/>{$data["name"]}';
$template->display($content, ['data' => ['name' => 'thinkphp']]);
}
// 对象
public function testParseObjectVar()
{
$this->expectOutputString('a-b-c-d');
$object = new class {
public string $a = 'a';
public const b = 'b';
public function c($str) {
return $str;
}
static public function d($str) {
return $str;
}
};
$template = $this->getTemplate();
$content = '{$data->a}-{$data::b}-{$data->c("c")}-{$data::d("d")}';
$template->display($content, ['data' => $object]);
}
// 运算符
public function testParseVarOperator()
{
$this->expectOutputString('2-0-2-0.5-1-1-1-4');
$template = $this->getTemplate();
$content = '{$a+1}-{$a-1}-{$a*$b}-{$a/$b}-{$a%$b}-{$a++}-{--$b}-{$a+$b+abs(-1)}';
$template->display($content, ['a' => 1, 'b' => 2]);
}
// 三元运算符
public function testParseTernaryOperator()
{
$this->expectOutputString('真-默认值-有值-NO');
$template = $this->getTemplate();
$content = '{$true?"真":"假"}-{$null ?? "默认值"}-{$one ?= "有值"}-{$zero ?: "NO"}';
$template->display($content, ['null' => null, 'zero' => 0, 'true' => true, 'one' => 1]);
}
// 单行注释
public function testParseSimpleNote()
{
$this->expectOutputString('123');
$template = $this->getTemplate();
$content = '123{// 注释内容 }';
$template->display($content);
}
// 多行注释
public function testParseMoreNote()
{
$this->expectOutputString('123');
$template = $this->getTemplate();
$content = "123{/* 这是模板\r\n注释内容*/ }";
$template->display($content);
}
// 引用标签
public function testParseInclude()
{
$this->expectOutputString('include');
$template = $this->getTemplate();
$content = '{include file="include"}';
$template->display($content);
}
// 继承标签
public function testParseExtend()
{
$this->expectOutputString("title\r\n主内容main\r\n");
$template = $this->getTemplate();
$content = "{extend name='extend' /}\r\n{block name='title'}title{/block}\r\n{block name='main'}{__block__}main{/block}";
$template->display($content);
}
// 输出替换
public function testParseReplaceString()
{
$this->expectOutputString("start/staticend");
$template = $this->getTemplate();
$content = "start__STATIC__end";
$template->display($content);
}
// 标签扩展
public function testParseDemoTag()
{
$this->expectOutputString(<<<'HTML'
<h1>闭合标签</h1>
2022-12-31 16:00:00<hr>
<h1>开放标签</h1>
0=>1<br>
1=>3<br>
2=>5<br>
3=>7<br>
4=>9<br>
<br>
0=>2<br>
1=>4<br>
2=>6<br>
3=>8<br>
4=>10<br>
HTML);
$template = $this->getTemplate();
$content = <<<'HTML'
<h1>闭合标签</h1>
{demo:close time='$demo_time'/}
<hr>
<h1>开放标签</h1>
{demo:open name='demo_name'}
{$key}=>{$demo_name}<br>
{/demo:open}
<br>
{demo:open name='demo_name' type='1'}
{$key}=>{$demo_name}<br>
{/demo:open}
HTML;
$template->display($content, ['demo_time' => 1672502400]);
}
}