模板
数据与表现层的标签分离
smarty是PHP 与 HTML代码的分离
小型模板类
$smarty 的工作流程:
把需要显示的全局变量,赋值塞到对象内部的属性上,一个数组中.
编译模板,把
{$标签}
,解析成相应的<?php echo
代码引入编译后的PHP文件
使用smarty的步骤:
smarty是一个类,要使用,需要先引入并实例化
assign赋值
display --> 编译到输出
smarty缺点:
编译模板,需要消耗时间
要把变量再重新赋值一份(又倒腾一次,放在对象的属性上)
<?php
/**
* 小型模板类
*/
/**
* 1. 把标签解析成PHP输出语句
* 2. 模板文件-->PHP文件
* 区分 模板文件和 PHP文件,把模板和编译后的结果,放置在不同的目录中.
* 用2个不同的属性来记录 不同的目录
*/
class Tmp {
public $template_dir = ''; // 模版文件所在的路径
public $compile_dir = ''; // 模板编译后存在的路径
public $tpl_var = array(); // 接受外部的变量
/**
* 存储全局变量
* @param {String} $key 变量名
* @param {Mixin} $val 变量值
*/
public function assign( $key, $val ) {
$this->tpl_var[$key] = $val;
}
/**
* 调用 compile模板,和自动引入
* @param {String} $template 模板文件名
*/
public function display( $template ) {
$comp = $this->compile($template);
include($comp);
}
/**
* 编译
* @param {String} $template 模板文件名 (需要编译的模板文件的文件名)
* @return {String} $comp 编译后的文件路径
*
* 把指定的模板内容读取,再编译成PHP文件
*
* 最终外部执行的是,编译后的文件
*/
public function compile( $template ) {
// 读取模板内容
$tmp = $this->template_dir . '/' . $template;
$scoure = file_get_contents($tmp);
// 替换模板内容
$scoure = str_replace('{$', '<?php echo $this->tpl_var[\'', $scoure);
$scoure = str_replace('}', '\'];?>', $scoure);
// 把编译后的内容保存成编译后的文件
$comp = $this->compile_dir . '/'. $template . '.php';
// 判断模板是否已经存在, 加上通过 文件修改的时间来判断,模板是否已经被修改过
if ( file_exists($comp) && filemtime($tmp) < filemtime($comp) ) {
return $comp;
}
file_put_contents($comp, $scoure);
return $comp;
}
}
?>
引入赋值和标签语法
smarty典型使用流程
<?php
/**
* 原理:
* 分析html模板中的标签,生成相应的PHP文件
* 再引入该PHP
*
* smarty流程:
* 1. 引入smarty
* 2. 实例化
* 3. 配置[最基本的要配置模板目录和编译目录]
*/
// 引入smarty
require('../smarty3/libs/Smarty.class.php');
// 实例化
$smarty = new Smarty();
// 配置
$smarty->template_dir = './templates';
$smarty->compile_dir = './compile';
// 赋值
$smarty->assign('title', 'T');
$smarty->assign('content', 'C');
// 编译
$smarty->display('temp01.html');
?>
smarty可以赋值为数值,数字等值,可以是数组.
VIEW:
<table style="border: 1px solid lightcyan;">
<tr>
<td>姓名:</td>
<td>{$name}</td>
</tr>
<tr>
<td>年龄</td>
<td>{$age}</td>
</tr>
<tr>
<td>兵器</td>
<td>{$weapon}</td>
</tr>
</table>
<table style="border: 1px solid lightcyan;">
<tr>
<td>姓名:</td>
<td>{$zf.name}</td>
</tr>
<tr>
<td>年龄</td>
<td>{$zf.age}</td>
</tr>
<tr>
<td>兵器</td>
<td>{$zf.weapon}</td>
</tr>
</table>
<table style="border: 1px solid lightcyan;">
<tr>
<td>姓名:</td>
<td>{$guanyu[0]}</td>
</tr>
<tr>
<td>年龄</td>
<td>{$guanyu[1]}</td>
</tr>
<tr>
<td>兵器</td>
<td>{$guanyu[2]}</td>
</tr>
</table>
Controller
<?php
// 引入smarty
require('../smarty3/libs/Smarty.class.php');
// 实例化
$smarty = new Smarty();
// 配置
$smarty->template_dir = './templates';
$smarty->compile_dir = './compile';
$user = array(
'name' => '刘备',
'age' => 28,
'weapon' => '双剑'
);
// 赋值
$smarty->assign($user);
$zf = array(
'name' => '张飞',
'age' => 25,
'weapon' => '矛'
);
$smarty->assign('zf', $zf);
$guanyu = array('关羽', 25, '青龙');
$smarty->assign('guanyu', $guanyu);
// 编译
$smarty->display('liubei.html');
?>
smarty模板标签与css标签防止冲突
如果smarty默认定界符 {}
与 css {}
冲突
可以使用以下二种方法解决
修改smarty默认定界符
{{}}
也可以用
{literal}{/literal}
标签,来告诉smarty,此处照常输出,不用解析
// 配置smarty的左右定界符
$smarty->left_delimiter = '{{';
$smarty->right_delimiter = '}}';
<style type="text/css">
{literal} table {background: pink;} {/literal}
</style>
模板变量来源
smarty标签变量的来源:
在模板中,{$title},则说明$title标签在被assign赋过值。
smarty的标签变量对应的来源,除了assign,还有那些?
PHP中assign分配变量
smarty的系统保留变量
从配置文件读取到的配置变量
assign
// 引入
require('../smarty3/libs/Smarty.class.php');
// 实例化
$smarty = new Smarty();
// 配置
$smarty->template_dir = './templates';
$smarty->compile_dir = './compile';
// assign 赋值
$smarty->assign('name', '罗隐');
$smarty->assign('poem', '我未成名君未嫁,可能俱是不如人');
// 编译
$smarty->display('shiju.html');
系统保留变量
系统保留变量,不用赋值,能够自动获取
<p>{$smarty.get.id}</p>
$smarty.
开头的标签,当成系统变量来解析,如:$smarty.get.id
解析成<?php echo $_GET['id'] ?>
还有以下几个系统保留变量:
$smarty.post
$smarty.session
$smarty.cookies
常量如何显示:
$smarty.const.常量名
<p>{$smarty.const.HEI}</p>
配置文件读取配置变量
注意:
配置文件,一般以
.conf
做后缀配置文件的写法是:
选项=值
配置smarty的config_dir,并把配置文件放在该目录下.
配置文件:
site=pink
tel='13164889431'
获取配置文件中的变量:
// 引入:
{config_load file='site.conf'}
// 显示
<div>
{$smarty.config.site}
</div>
<div>
{#tel#}
</div>
append
连着往某个标签赋值多个值,可以使用append
赋值:
$smarty->append('color', 'tan'); // _tpl_vars['color'][] = 'tan'
$smarty->append('color', 'pink'); // _tpl_vars['color'][] = 'pink'
使用:
<p>{$color[0]}</p>
<p>{$color[1]}</p>
源码:
$data->tpl_vars[ $tpl_var ]->value[] = $value;
把一个值压入一个数组
smarty赋值时还能够引用赋值
assignByRef('title', $title); // _tpl_vars['title'] = &title; // 引用赋值
这个功能在PHP5以后,意义不大,PHP5以后是写时赋值
对象赋值和引用
对象赋值
class Human {
public $name = 'zf';
public $age = 23;
public function say() {
return 'HELLO WORLD';
}
}
$man = new Human();
$smarty->assign('man', $man);
显示:
<div>{$man->name}</div>
<div>{$man->age}</div>
<div>{$man->say()}</div>
模板的作用:分离PHP代码,让代码简洁,所以模板中的标签,应该尽量的只负责变量的输出.不要负责太多的逻辑判断,函数调用等.
简化模板配置
通过继承来简化模板配置
<?php
/**
* 使用对象继承的方式来完成smarty的配置
*/
class MySmarty extends Smarty {
/**
* 构造函数
*/
public function __construct() {
// 调用父类的construct, 否则父类的一些设置就丢失了
parent::__construct();
$this->setTemplateDir('./templates'); // 模板文件位置
$this->setCompileDir('./compile'); // 编译文件位置
$this->setConfigDir('./conf'); // 配置文件位置
}
}
?>
标签数学运算
<div>{$age}</div>
<!-- 标签可以参与运算,但是不推荐 -->
<div>{$age + 2}</div>
<div>年纪差为:{$age - $diffAge}</div>
逻辑判断
IF标签要成对
{if $price < 10000 }
<div>{$color}</div>
{else}
<div>TAN</div>
{/if}
{if $smarty.get.today == 0 || $smarty.get.today == 7}
<div>周日</div>
{elseif $smarty.get.today == 6}
<div>周六</div>
{else}
<div>工作日</div>
{/if}
在模板中使用逻辑判断的思考:
从分工角度看:模板只负责输出 ,不负责逻辑判断。
为什么还需要有逻辑判断? 在模板上进行逻辑判断,可以极大的简化工作.
smarty循环
for循环
for循环基本应用
赋值:
$smarty->assign('start', 1);
$smarty->assign('end', 9);
显示:
{for $i = $start to $end}
{$i}
{if $i % 3 == 0}
<br/>
{/if}
{/for}
<h3>1-100所有的奇数</h3>
{for $i=$start to 100}
{if $i % 2 != 0 }
<span>{$i}</span>
{/if}
{/for}
循环总次数
$i@total
,循环索引$i@iteration
<h3>步长属性 控制</h3>
<div>
{for $i=$start to 100 step 2}
{if $i@first == $start}
<span style="color: cyan;">{$i}</span>
{elseif $i@last == $i@total}
<span style="color: tan;">{$i}</span>
{else}
{$i}
{/if}
{if $i@iteration % 3 == 0}
<br/>
{/if}
{/for}
<div>
循环总次数:{$i@total}
</div>
<div>
循环索引:$i@iteration
</div>
<div>
循环的第一次:$i@first
</div>
<div>
循环的最后一次:$i@last
</div>
</div>
foreach循环
foreach循环
典型场景,二维数组的循环
例如:新闻列表,会员列表,商品列表
赋值数据
<?php
/**
* foreach循环
* 典型场景,二维数组的循环。
* 例如:新闻列表,会员列表,商品列表
*/
require('../smarty3/libs/Smarty.class.php');
require('./MySmarty.class.php');
$smarty = new MySmarty();
// 链接数据库
$conn = mysql_connect('127.0.0.1', 'root', '');
// 设置字符集
mysql_query('set names utf8', $conn);
// 选择数据库
mysql_query('use boolshop', $conn);
// 查询数据
$sql = "select cat_id, cat_name, intro from category limit 5";
$result = mysql_query($sql, $conn);
$category = array();
while ( $row = mysql_fetch_assoc($result) ) {
$category[] = $row;
}
// 关闭数据库连接
mysql_close($conn);
// 赋值
$smarty->assign($category);
$smarty->display('foreach.html');
?>
使用foreach:
<h2>商品栏目</h2>
<table border="1">
<tr>
<td>序号</td>
<td>栏目名</td>
<td>栏目信息</td>
</tr>
// {foreach from=$category key=key item=$g}
{foreach $category as $k=>$g}
<tr>
<td>{$g.cat_id}</td>
<td>{$g.cat_name}</td>
<td>{$g.intro}</td>
</tr>
{/foreach}
</table>
循环总次数
$i@total
,循环索引$i@iteration
<h2>商品栏目</h2>
<table border="1">
<tr>
<td>序号</td>
<td>栏目名</td>
<td>栏目信息</td>
</tr>
{foreach $category as $k=>$g}
<tr {if $g@iteration % 2 == 0} bgcolor="burlywood" {/if} {if $g@first == 1} bgcolor="gray" {/if}>
<td>{$g@iteration}</td>
<td>{$g.cat_name}</td>
<td>{$g.intro}</td>
</tr>
{/foreach}
</table>
<div>索引:$g@iteration</div>
<div>首行:$g@first</div>
<div>尾行:$g@last</div>
<div>总条数:{$g@total}</div>
变量调节器
变量调节器:在模板中,修改变量的显示形式的一种功能.
变量调节器的本质是一个函数,这个函数,以标签对应的变量值为参数,然后运算,把返回值,显示在标签处.
内置变量调节器:
capitalize [首字符大写]
count_characters [字符计数]
cat [连接字符串]
count_paragraphs [计算段数]
count_sentences [计算句数]
count_words [计算词数]
date_format [格式化日期]
default [默认值]
escape [编码]
indent [缩进]
lower [小写]
nl2br [换行符替换成 <br />]
regex_replace [正则替换]
replace [替换]
spacify [插空]
string_format [字符串格式化]
strip [去除(多余空格)]
strip_tags [去除html标签]
truncate [截取]
upper [大写]
wordwrap [行宽约束]
使用变量调节器:
{foreach $goods as $key=>$g}
<tr>
<td>{$g.goods_id}</td>
<td>{$g.goods_name|truncate:15:'...'}</td>
<td>{$g.shop_price}</td>
<td>{$g.add_time|date_format:"%Y-%m-%d %H:%M:%S"}</td>
</tr>
{/foreach}
调节器的功能,在PHP中也能实现,也可以在模板中进行。比较合适在模板中进行。
体现了业务与显示的分离,尽量分离。
PHP就负责判断条件,并取出数据来。
至于显示的操作,应该尽量往"前"移(越接近用户).
MySQL-->PHP-->模板-->JavaScript
合适在MySQL存储原始数据,PHP中处理.
后台的数据尽量“原始”,不要带有样式,格式。显示的工作尽量靠前.
页面缓存
缓存,smarty重要概念。
缓存:把页面内容保存在磁盘上,下次访问相同页面,直接返回保存内容。减轻了数据库的压力。
smarty缓存的用法:
开启
配置缓存的生命周期
判断是否缓存成功并是否从数据库取出数据
输出
// 开启缓存
$smarty->caching = true;
// 设置缓存生命周期
$smarty->cache_lifetime = 3600;
// 缓存的文件目录,用户存储缓存文件
$smarty->cache_dir = './cache';
if ( !$smarty->isCached('cache.html') ) { // 判断文件是否缓存
// 链接数据库
$conn = mysql_connect('127.0.0.1', 'root', '');
// 设置字符集
mysql_query('set names utf8', $conn);
// 选择数据库
mysql_query('use boolshop', $conn);
// 查询数据
$sql = "select goods_id, goods_name, shop_price, add_time from goods limit 5";
$result = mysql_query($sql, $conn);
$goods = array();
while ( $row = mysql_fetch_assoc($result) ) {
$goods[] = $row;
}
// 关闭数据库连接
mysql_close($conn);
// 赋值
$smarty->assign('goods', $goods);
echo '走了数据库';
}
$smarty->display('cache.html');
局部缓存
smarty在页面缓存的情况下,可以设置部分内容不缓存。页面中有随机广告,时间,股票信息,不适宜缓存起来。
运行的时候,还是PHP代码,没有生成静态数据。
控制局部不缓存:
在标签中控制,该标签不缓存。
// {$标签 nocache}
<h2>{$time|date_format:"%Y-%m-%d %H:%M:%S" nocache}</h2>
控制一段标签不缓存
{nocache}
<h2>{$time|date_format:"%Y-%m-%d %H:%M:%S"}</h2>
<h2>{$time|date_format:"%Y-%m-%d %H:%M:%S"}</h2>
{/nocache}
在PHP赋值时,就控制不缓存
$smarty->assign('time2', $time, true); // 第三个参数是 nocache,为true,说明不缓存
不缓存标签,要保证总能从PHP处得到值.
调用函数
定义函数
function insert_welcome( $parm ) {
return 'WELCOME HELLO' . ', AGE:' . $parm['age'];
}
模板中使用:
<h2>{insert name="welcome" age=21}</h2>
单模版多缓存
场景:为商品模板设置缓存,当时从url接收的goods_id,当缓存后,所有商品都一样了,不合适。
能否为同一个模板,生成不同的缓存文件呢?
比如:根据ID的不同,来生成各个商品的缓存页面。
可以使用:单模板多缓存
,
原理:生成缓存的时候,可以再传一个缓存ID。如果ID不同,则生成缓存文件不同。
// 编译
$smarty->display('one_page.html', $goods_id);
一般的,哪些参数要影响页面的内容,就需要把哪些参数,当成“缓存ID”。
例如:分页:page=3
, 栏目:cat_id=3
多个参数影响:
// 编译
$one_page = $goods_id + $page; // 缓存ID,根据自定义规则计算
$smarty->display('one_page.html', $one_page);
// 缓存判断,也需要加缓存ID
if ( !$smarty->isCached('one_page'.html, $one_page) ) {
}
模板缓存:
<?php
require('../smarty3/libs/Smarty.class.php');
require('./MySmarty.class.php');
$smarty = new MySmarty();
// 开启缓存
$smarty->caching = true;
// 设置缓存生命周期
$smarty->cache_lifetime = 10;
// 缓存的文件目录,用户存储缓存文件
$smarty->cache_dir = './cache';
$goods_id = $_GET['goods_id'] + 0;
if ( !$smarty->isCached('one_page.html', $goods_id) ) {
// 链接数据库
$conn = mysql_connect('127.0.0.1', 'root', '');
// 设置字符集
mysql_query('set names utf8', $conn);
// 选择数据库
mysql_query('use boolshop', $conn);
// 查询数据
$sql = "select goods_name, shop_price, goods_desc from goods where goods_id=" . $goods_id;
$result = mysql_query($sql, $conn);
$goods = mysql_fetch_assoc($result);
// 关闭数据库连接
mysql_close($conn);
// 赋值
$smarty->assign($goods);
}
// 编译
$smarty->display('one_page.html', $goods_id);
?>
强制删除缓存
简单删除缓存.
指定模板名,不指定缓存ID,则该模板对应的缓存都会被删除.
可以通过缓存ID来控制,模板对应的指定缓存删除掉.
<?php
/**
* 强制删除缓存
*/
require('../smarty3/libs/Smarty.class.php');
require('./MySmarty.class.php');
$smarty = new MySmarty();
$goods_id = $_GET['goods_id'] + 0;
// 参数模板名,和缓存ID
$smarty->clearCache('one_page.html', $goods_id);
echo '删除缓存成功';
?>
出于调试目的,临时不缓存文件
$smarty->force_cache = true; // 强迫文件不缓存
数据对象
作用:数据分类,添加命名空间
不使用数据对象,所有数据都存储在smarty对象中.(重名就覆盖)
数据对象:把数据分类(不同类别的数据,重名不会覆盖)
<?php
require('../smarty3/libs/Smarty.class.php');
require('./MySmarty.class.php');
$smarty = new MySmarty();
$nav_top = array('首页', '商城', '男装');
$nav_footer = array('友情链接', '备案');
$smarty->assign('nav', $nav_top);
$smarty->assign('nav', $nav_footer); // 数据覆盖
/**
* smarty3引入一种新的概念,叫做数据对象。
* 数据对象,就是一个装数据用的框。
*
* 靠2个数据对象,把2个数据对象里,各赋值一个同名的`nav`,2个`nav`对象互不干扰
*/
$smarty->display('news.html');
?>
使用数据对象,命名空间
创建数据对象
$smarty->createData();
数据挂载到该数据对象上.
$smarty->dispaly();
声明使用的数据
<?php
require('../smarty3/libs/Smarty.class.php');
require('./MySmarty.class.php');
$smarty = new MySmarty();
$nav_top = array('首页', '商城', '男装');
$nav_footer = array('友情链接', '备案');
// $smarty->assign('nav', $nav_top);
// $smarty->assign('nav', $nav_footer);
/**
* smarty3引入一种新的概念,叫做数据对象。
* 数据对象,就是一个装数据用的框。
*
* 靠2个数据对象,把2个数据对象里,各赋值一个同名的`nav`,2个`nav`对象互不干扰
*/
// 创建一个数据对象
$hdata = $smarty->createData();
$fdata = $smarty->createData();
// 使用数据对象
$hdata->assign('nav', $nav_top);
$fdata->assign('nav', $nav_footer);
// display时,要声明,这次使用,哪个数据对象。
$smarty->display('header.html', $hdata);
$smarty->display('news.html');
$smarty->display('footer.html', $fdata);
?>
对象注册
场景:在模板中,smartty标签中,允许调用对象的方法,如果方法是特殊方法,比如修改密码等方法。(模板调用特殊方法)
使用对象注册的方式来解决。
作用:允许调用的方法列表。
在View视图中:
注册对象的 变量:访问方法{zf->name}. 注意不加$
,方法后不加()
<?php
require('../smarty3/libs/Smarty.class.php');
require('./MySmarty.class.php');
$smarty = new MySmarty();
class User {
public $name = 'zf';
public $age = 23;
public function say() {
return 'hello:' . $this->name;
}
public function modPass() {
return '修改密码成功';
}
}
$zf = new User();
// $smarty->assign('zf', $zf);
// 对象注册
$smarty->registerObject('zf', $zf, array('say')); // 第三个参数可以控制允许调用的方法.
$smarty->display('objLogin.html');
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>姓名:{zf->name}</h1>
<h2>年龄:{zf->age}</h2>
<p>SAY:{zf->say}</p>
<p>{zf->modPass}</p> <!-- 调用之后报错,已经不能调用该方法,限制方法不能调用 --><!-- 开发中,不建议使用 -->
</body>
</html>
模板继承
可以在父模板中,暂留{block name=""}{/block}
注意:
子模板第一句,先声明继承
{extends file=''}
子模板的目的,只是填充父模板预留的
block
父模板:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{block name='title'}父模板标题{/block}</title>
</head>
<body>
{block name='header'}{/block}
<hr />
{block name='footer'}{/block}
</body>
</html>
子模板1:
{extends file='tplExteds.html'}
{block name="title"}
<ul>
<li>嘻嘻哈哈</li>
<li>嘻嘻</li>
<li>哈哈</li>
</ul>
{/block}
{block name="header"}头部{/block}
子模板2:
{extends file='tplExted.html'}
{block name="footer"}2016年最后二天{/block}
变量调节器插件开发
调节器:
<?php
/**
* 调节颜色
* @param {String} $string Smarty自动传递给进来,待调节的变量
* @param {String} $color 参数值
*/
function Smarty_modifier_modcolor( $string, $color ) {
return '<font color="' . $color . '">' . $string . '</font>';
}
?>
在smarty源码的目录中,
plugins
, 开发。文件命名
modifier.函数名.php
定义的调节器的函数名:
Smarty_modifier_modcolor
display与fetch
display()
可以看成 echo fetch();
fecth()
仅仅计算出应输出的结果,但是不输出,只把结果返回。
一个PHP页面如何知道本身的输出结果: 使用缓冲区
。
利用fetch静态化
缓冲区的理解:
fetch是如何知道当前页面的输出内容?
输出到缓冲区,在PHP的最后,读取缓冲区,就是本页面的内容。
打开缓冲区:ob_start();
读取缓冲区:ob_get_content();
清空缓冲区:ob_clean();
读取并清空:ob_get_clenan();
静态化:缓冲区+文件写操作
<?php
// fetch 如何知道本页输出内容
if ( file_exists('./html/fetch.html') ) {
header('location: ./html/fetch.html');
exit;
}
// 启动缓冲区
ob_start();
// 数据
echo 1 , ' ';
echo 2 , ' ';
echo 4 , ' ';
// 读取缓冲区
$html = ob_get_contents();
// 清除缓冲区
ob_clean();
// 写入文件
file_put_contents('./html/fetch.html', $html);
// 输出
echo $html;
?>
模板引擎特点
所有模板的共性:解析标签,解析成PHP
标签解析的分类:
最多的就是正则替换 。 例如:smarty2.X系列,quickskin
通过字符串函数来解析 例如:dede的模板类.
字符串解析
<?php
class Mini {
protected $left_delimiter = '{';
protected $right_delimiter = '}';
protected $right_length = 1; // 右分隔符的长度
public function __constrcut() {
$this->right_length = strlen($this->right_delimiter);
}
protected $tags = array(); // 装载分析到的标签
// 编译
public function parse( $file ) {
$cont = file_get_contents('./templates/' . $file);
$offset = 0;
// 截取第一个字符 '{'
while ( $poss = strpos($cont, $this->left_delimiter, $offset) !== false ) {
// 取最后字符 '}'
$pose = strpos($cont, $this->right_delimiter, $poss);
// 截取标签
$this->tags[] = substr($cont, $poss, $pose-$poss+$this->right_length);
$offset = $pose + $this->right_length;
}
return $this->tags;
}
}
?>
变量的存储分类:
最多的是通过assign,把变量再次装载到模板对象中。
如:smarty,thinkphp只把模板解析出来,再包含模板
如:discuzd的模板,把模板解析后,只返回模板的路径,再手动包含。(省略了assign过程,速度更快,显示的粗糙暴力)
语言分类:
绝大部分PHP
C语言以扩展形式写的模板引擎(Blitz)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。