ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 新版模板引擎特性和使用方法 新版的sp框架,已经集成了一个非常轻量级的模板引擎,通过120行左右的代码,实现兼容Smarty开发中最常用的语法,是代替Smarty的首选。 > 绝大部分开发过程中,我们用到Smarty引擎的功能只是Smarty的百分之一代码量不到,并且Smarty越来越臃肿。所以我们开发了新的模板引擎,并且内置在框架内,仅仅120行的代码,实现了日常开发全部用到的模板功能。 ### 特性 **编译** - 模板在第一次框架执行时,会被编译成php文件并保存下来,之后除非模板文件有修改,否则会一直使用编译后的php文件,极大节省了资源。 - 模板编译成php文件更方便opcode缓存,性能非常好。 - 当模板的其中一部分被修改后,会触发局部编译,仅仅针对部分页面进行编译,也很好地节省了资源。 **目录** - 编译目录在protected/tmp,模板目录在protected/view。protected/view也称为模板根目录。 - 目录均为默认配置,只有在SAE环境下需要配置'view' => array('compile_dir'=>SAE_TMP_PATH),具体参考本手册。 **自带防跨站脚本攻击XSS** - 每个输出到页面的模板变量,默认都会被进行htmlspecialchars()的转换,以保证输入脚本不会被执行。 - 只有在单个变量后面加入nofilter属性后才会被取消转换原样输出,但这时候就需要开发者谨慎使用了。 > php自带的htmlspecialchars()函数会将'&','"',"'",'<','>'转换成对应的HTML标记。 ### 使用模板 **赋值** 在控制器内,可以简单地使用$this->foo=bar的方法将变量值传给模板使用。 如 $this->foo = "bar"; 那么模板内就可以使用<{$foo}>变量了。 **显示模板** 控制器内通过$this->display("模板文件名")的方式进行模板的显示。 如 模板是 protected/view/guestbook.html 文件,要在控制器显示即可用以下代码: $this->display("guestbook.html"); 就可以显示出来。 > 这里protected/view也称为模板根目录。 如果是模板目录内还有子目录,即可在display里面带上子目录。 如 模板是 protected/view/main/index.html 文件,那么: $this->display("main/index.html"); > display()使用的路径均以模板根目录为开始。 **自动显示模板** sp框架的模板自动输出可以让我们不需要使用display语句就可以将模板输出, 对代码本身而言是简化了不少,对开发者而言也更方便了。 > 旧版框架广受好评的自动模板输出,新版也继承了,而且是内置的,不需要配置。 在模板根目录里面,把模板名称设置为“控制器名_方法名.html”,对应控制器/方法就不需要display(),而直接输出此模板。 如模板目录内有名为:“main_index.html”的模板,那么MainController/actionIndex()里面,不需要调用$this->display()方法,也会自动输出“main_index.html”的模板。 - 模板名必须是“控制器_方法名.html”。无需配置,框架会自动检测有无匹配名称的模板,进行显示。 - 如果控制器方法内已经调用过$this->display()来显示模板,那么即使存在对应名称的模板,也不会自动显示。 - 该功能也同时支持modules模块开发,但需要多一级路径,如admin模块下的MainController/actionIndex(),那么对应的自动显示的模板文件路径是:protected/view/admin/main_index.html。注意这里多了admin一级目录。 [例子下载](images/8.zip) **自动显示模板的最佳实践**是:在比较简单甚至没有内容只是显示模板的页面上,可以尽量多地使用自动模板(如关于网站、介绍我们等页面)。如果是模板赋值较多,逻辑较复杂的页面,建议是尽量使用$this->diplay()进行显示,这样逻辑更清晰。 **layout布局** layout布局也是比较方便于使用模板的辅助功能,主要是解决页面之间共同的大结构的模板问题。 > 旧版框架里面,一般解决页面共用结构,是通过在各模板上面,前面include一个header.html,后面再include一个footer.html来实现的。 > 不过这样首先体验很糟糕,毕竟每个页面都需要写上下两个include,忘记了就麻烦。而且要在不同的模板动态调整大结构也是比较麻烦的,比如说使用不同的header.html。 > 当然,通过一些小技巧,旧版框架还是能实现这个layout布局的。 新版自带layout布局的模式,就可以很好解决此问题。 layout布局,可以通过一个可灵活变动的布局模板,然后自动成为其他模板的大结构。 - 布局模板可以在控制器内定义,生效访问根据控制器本身的范围,比如说MainController那么只有Main控制器里面的方法才会生效,如果是BaseController那么但凡继承BaseController都会生效(除非单独的控制器自己再覆盖定义一个)。 - 生效范围内的模板,都不再需要写外面的结构,可以直接写里面的结构。 - 模板输出的时候,布局模板会在外面,嵌套了里面的各个模板。 - **当$layout被设置成空的时候,那么当前页面就不会启用布局模板。**对于一些比较特殊的页面,如Ajax请求带模板的页面,比较方便哦。 [例子下载](images/8.zip)(和上面的自动显示模板的例子是同一个) 使用layout布局我们先要准备两个事情: 1. 要赋值给控制器的$layout这个成员变量,值是一个模板的文件名。如例子里面是在BaseController.php里面,$layout="layout.html"。 2. 在模板目录,我们要创建刚才赋值的文件名的文件。例子里面我们创建了protected/view/layout.html。 我们看看layout.html里面内容是什么: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>layout演示</title> <link href="/i/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <br /> <div class="container"> <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="#"> Layout演示 </a> </div> </div> </nav> <{include file=$__template_file}> </div> </body> </html> 主要关注 <{include file=$__template_file}>这句,这是布局模板的关键所在。 然后我们看看其他的模板: main_index.html <div class="jumbotron"> <h1>Hello, world!</h1> <p>This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p> <p><a class="btn btn-primary btn-lg" href="<{url c="view" a="index"}>" role="button">Learn more</a></p> </div> 可以发现main_index.html和view_index.html都只有中间的HTML,没有包括头尾的HTML。(也没有header.html和footer.html什么的) 在页面输出的时候,我们查看源码,如http://localhost/main/index <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>layout演示</title> <link href="/i/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <br /> <div class="container"> <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="#"> Layout演示 </a> </div> </div> </nav> <div class="jumbotron"> <h1>Hello, world!</h1> <p>This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p> <p><a class="btn btn-primary btn-lg" href="http://localhost/view/index" role="button">Learn more</a></p> </div> </div> </body> </html> 可以发现layout.html的内容已经在页面上,而且main_index.html的内容是嵌在中间的。 ![layout示意图](https://box.kancloud.cn/309f813c6abd65e30b73588f878c8ce5_556x562.jpg) > 例子里面需要注意一下的是BaseController.php文件的$layout变量。 ### 模板语法 本章介绍新版模板引擎的全部功能,我们会发现新版模板引擎的语法和Smarty比较像,而且包含了日常开发中用到的Smarty的功能。 **限定符** 模板引擎的限定符是<{和}>,而且一般不能进行修改,除非直接使用View类。 限定符的意思是在模板页面里面,在<{ 和 }> 中间的代码,均被视为模板语法,会被模板引擎进行编译。 > 也可以把限定符内的内容理解成类似php的语法代码,在最终显示的页面上,是看不到这些代码的,只能看到这些代码输出的结果。 模板引擎的全部语法,都是指在模板内的限定符中间编写的代码规则。 **注释** 模板内的注释写法如下: <{*这里是一个注释*}> 注释的作用只是写在代码页面上供提示之用,不会执行也不会输出到页面上。 **变量显示** 在控制器内通过$this->foo=bar的方式传值的变量,在模板内都可以直接通过$foo的方式使用。 如MainController.php <?php class MainController extends BaseController { function actionIndex(){ $this->myval = "123"; $this->display("main_index.html"); } } 在main_index.html模板即可通过: 传值是:<{$myval}> 结果是:传值是123 这里的变量可以是一切php的变量,包括数字/字符串/数组等。 **变量自动过滤及避免过滤** 从控制器中传递到模板的变量,如果直接显示将默认进行HTML转码,功能类似PHP函数htmlspecialchars(); '&' (和符号) 转成 '&amp;' '"' (双引号) 转成 '&quot;' "'" (单引号) 转成 '&#039;' (或者 &apos;) '<' (小于号) 转成 '&lt;' '>' (大于号) 转成 '&gt;' 如MainController.php <?php class MainController extends BaseController { function actionIndex(){ $this->myval = "<script>alert('攻击代码');</script>"; $this->display("main_index.html"); } } 在main_index.html模板: 传值是:<{$myval}> 显示的HTML源码是: 传值是:&lt;script&gt;alert(&#039;攻击代码&#039;);&lt;/script&gt; 有时我们也需要让变量直接显示成HTML,而不进行过滤的,那么在保证变量本身安全的前提下,我们可以通过nofilter语法来避免过滤。 还是上述的例子,但是在模板内是: 传值是:<{$myval nofilter}> 加入了nofilter的修饰符,然后显示是: ![nofilter](https://box.kancloud.cn/5274dc4d4b3a5f9c4f5ddb64c33795f4_550x260.jpg) 显示的HTML源码是: 传值是:<script>alert('攻击代码');</script> **变量赋值** 模板内可以通过等号进行变量赋值,如: <{$foo = $myval + 2}> <{$foo}> 那么$foo会输出125 (123+2)。 **使用常量** PHP常量可以两种方式在模板内使用,请自行选择一种: ***使用#语法*** 模板中可以使用#语法来使用常量:如<{#ROOT}>,例子: MainController.php <?php class MainController extends BaseController { function actionIndex(){ define("MY_CONSTANT", "show the money"); $this->display("main_index.html"); } } 在main_index.html模板: 显示常量:<{#MY_CONSTANT}> ***使用传值方式使用常量*** 如果不想使用#方式来显示常量,可以通过赋值的方式来使用。 > 有时候在一个模板内使用太多自定义的语法是不太好的,会增加团队学习成本,故常量的显示方法可以自行选择。 例子: MainController.php <?php class MainController extends BaseController { function actionIndex(){ define("MY_CONSTANT", "show the money"); $this->constant = MY_CONSTANT; $this->display("main_index.html"); } } 在main_index.html模板: 显示常量:<{$constant}> 这里先将全部用户定义常量传给模板变量,然后在模板内直接使用。传值这个步骤可以放控制器,要做到全局也可以放BaseController。如: <?php class BaseController extends Controller{ public $layout = "layout.html"; // 通过继承将display重写,使得可以传入常量的数组 function display($tpl_name, $return = false){ $this->constant = MY_CONSTANT; parent::display($tpl_name, $return); } } **数组点号** 变量输出时,如果是数组,那么可以通过传统数组的方括号来显示值,也可以通过点号来显示。 如MainController.php <?php class MainController extends BaseController { function actionIndex(){ $this->myval = array("num"=>10086); $this->display("main_index.html"); } } 那么在模板内可以使用以下两种显示方式: <{$myval["num"]}> 等同于 <{$myval.num}> 显示的结果是:10086 等同于 10086 **循环foreach** 新版模板引擎支持php的foreach语法,但是稍微有点不一样。 > <{/foreach}>是foreach的结束符 如MainController.php <?php class MainController extends BaseController { function actionIndex(){ $this->myarr = array( "one" => "100", "two" => "200", "three" => "300", ); $this->display("main_index.html"); } } main_index.html <{foreach $myarr as $k => $v}> <p><{$k}> => <{$v}></p> <{/foreach}> 输出: ![foreach结果](https://box.kancloud.cn/b86f76812804c1136e0a898471d0562b_136x103.jpg) 当然,不要key的数组foreach也是可以的: <{foreach $myarr as $v}> <p><{$v}></p> <{/foreach}> ![foreach结果](https://box.kancloud.cn/34a1c0de54949c2161ac78e4362c7336_79x104.jpg) **foreach的自带值** 模板的foreach有一些比较特殊的值,方便平时编程使用的。 自带值|意义|作用 ---|---|--- $v@index|循环索引,从0开始按循环次数递增|用于判断当前循环次数,如隔行换底色等 $v@iteration|循环次数,从1开始递增,等同于$v@index + 1|用于显示序号 $v@first|当第一次循环,值是true,之后一直是false|用于判断当前循环是否循环的最开始第一次,如制作表格的表头之类的 $v@last|当循环到最后一次,值为true,未到最后则是false|用于判断当前循环是否最后一次循环,比如说有时候循环最后一行的收尾处理 $v@total|循环数组的总次数,等于与count(数组)|显示总数,在一开始就知道总数挺方便的 示例: <{foreach $myarr as $v}> <p>第<{$v@iteration}>个值:<{$v}> <{if $v@first == true}> (这里是开始一行) <{/if}> <{if $v@last == true}> (这里是最后一行) <{/if}> </p> <{/foreach}> 结果: ![foreach结果](https://box.kancloud.cn/38aa7bd3b3c903151c440e22540370e1_301x105.jpg) **多维数组的显示** 示例: <?php class MainController extends BaseController { function actionIndex(){ $this->myarr = array( array( array( 'name' => 'apple', 'count' => '1000', ), array( 'name' => 'banana', 'count' => '2000' ), ), array( array( 'name' => 'cat', 'count' => '5000', ), array( 'name' => 'dog', 'count' => '100' ), ), ); $this->display("main_index.html"); } } 模板: <{foreach $myarr as $arr1}> 第<{$arr1@iteration}>列 <{foreach $arr1 as $arr2}> <{foreach $arr2 as $k => $v}> <p><{$k}>:<{$v}></p> <{/foreach}> <{/foreach}> <{/foreach}> 结果: ![多维数组结果](https://box.kancloud.cn/157bc20c86817fdbc54549123fe3d563_199x287.jpg) **break,continue** foreach循环里面可以使用php语法的break和continue,使用方法是: <{break}> 和 <{continue}> 作用跟php内使用完全一致。 **if判断** 新版模板引擎支持if判断,包括if,elseif,else,<{/if}>(结束符)。 如: <?php class MainController extends BaseController { function actionIndex(){ $this->myval = 500; $this->mybool = false; $this->display("main_index.html"); } } 模板: <p> <{if $myval > 1000}> myval大于1000 <{elseif $myval > 100}> myval小于等于1000,大于100 <{else}> myval小于等于100 <{/if}> </p> <p> <{if $mybool}> mybool是true <{else}> mybool是false <{/if}> </p> 结果: ![foreach结果](https://box.kancloud.cn/9a2356c20c3d1324dfb9d054fabab0f2_229x78.jpg) **include包含模板** 模板中可以通过include语法进行模板的包含。 - 包含的模板路径以模板根目录为基础,一般是protected/view。 - 包含的模板里面不能有包含原来模板的语句,否则会造成死循环。 语法: <{include file="inner.html"}> 一般include是用于包含公共HTML片段,使得不需要相同的代码写多次,而且修改也能比较方便地修改一个地方即可。 **函数调用方法** 新版框架可以直接调用php函数输出。不再需要像旧版一样需要注册函数。 > 理论上,注册一个函数来使用是很不合理的事情,毕竟模板内也是php,也能执行php函数。 **一般建议模板内调用的函数,都是可以直接输出结果的。** 示例: <?php class MainController extends BaseController { function actionIndex(){ // strtotime可以通过字符串取得时间戳 // 这里取上个星期天的时间戳 $this->mytime = strtotime("last sunday"); $this->display("main_index.html"); } } 模板调用date()函数输出: 上个星期天是<{date("Y年m月d日", $mytime)}> 结果: ![foreach结果](https://box.kancloud.cn/356ffe94efc34a8a797561e0393b7a3a_295x49.jpg) **URL地址构造函数url()** 新版框架还支持另一种函数调用方式,我们通过最常用的url()地址构造函数来讲解一下: 在模板内使用url()函数是这样的: <a href="<{url c="main" a="index"}>">返回首页</a> 这里有一些特点: - url()函数并不是通过类似date()函数的函数调用的。 - 参数是类似键值对(key-value)的方式赋值。 观察一下url()函数的代码,会发现: function url($c = 'main', $a = 'index', $param = array()){ if(is_array($c)){ $param = $c; $c = $param['c']; unset($param['c']); $a = $param['a']; unset($param['a']); } ... 参数$c做了一个特殊的处理:判断$c(第一个参数)是否数组,如果是的话,将数组内和参数同名键的值取出来赋值给同名参数。 也就是说,第一个参数$c实际上是一个带了完整三个参数的数组。 所以,如果需要写一个类似url()的函数,那么它的第一个参数数组,就是全部的参数的数组。 而且这个函数在模板调用时,就可以直接通过键值对的方式来赋值参数了。 有时我们需要对URL的中文进行URLdecode处理,如某些情况下的JS或者ajax传递汉字或特殊值参数时,会发现<{url}>的调用将汉字或特殊值转换成%20的URL编码后的样子,不利于JS的编写和参数构造。 这个时候我们可以通过函数的方式来将URL解码,那么输出到页面上“汉字”就会保持原样。 <a href="<{urldecode(url("search", "index", array("search" => "汉字")))}>">返回首页</a> 这里是直接用了urldecode()函数,而url()函数也是用了它的原始的版本。 **临时目录不可写错误** 很多时候在linux上使用新版框架会产生以下错误提示: Err: Directory "somedir/tmp" is not writable or readable 原因是protected/tmp目录不可写导致的。 解决方法是:将protected/tmp目录的权限设置成777。 **修改限定符** 在新版中,左右限定符(<{和}>)是默认的。因其可以在页面上很好地跟Javascript脚本做区分。不过如果希望自行修改限定符,可以通过以下方法: // 这里是BaseController里面的init()方法 function init(){ $compile_dir = isset($GLOBALS['view']['compile_dir']) ? $GLOBALS['view']['compile_dir'] : APP_DIR.DS.'protected'.DS.'tmp'; $this->_v = new View(APP_DIR.DS.'protected'.DS.'view', $compile_dir, '{', '}'); // ..其他代码.. } **模板中CSS和JS的路径** > 图片、CSS和Javascript文件,我们通常称其为“媒体文件”。 媒体文件在页面上使用,可以有两种路径:相对路径、绝对路径。 ***相对路径***:指的是媒体文件相对于浏览器访问的当前目录的路径。 比如说: http://www.speedphp.com/bbs/forum-6-1.html 这个网址浏览器的当前访问目录,是: http://www.speedphp.com/bbs/ 如果该网页上面有这样一张图片: <img src="images/logo.gif" alt="" /> 那么,这张图片就称为“相对路径”,可以认为,通过以下地址就可以访问到这张图片: http://www.speedphp.com/bbs/images/logo.gif 还有更多例子: <script type="text/javascript" src="js/jquery.js"></script> <link rel="stylesheet" href="css/style.css" type="text/css" media="screen" /> <img src="images/logo.gif" alt="" /> “./”点斜杠是相对路径:(注意是点+斜杠) <script type="text/javascript" src="./js/jquery.js"></script> <link rel="stylesheet" href="./css/style.css" type="text/css" media="screen" /> <img src="./images/logo.gif" alt="" /> 相对路径在使用中会有个缺点,当前页面的访问目录如果修改,那么页面上的媒体文件路径也可能要修改,不然就会找不到媒体文件。 所以,一般建议只在当前页面访问目录能确定不会修改的情况下,才直接在模板上面使用相对路径。 > 有些时候,当一套speedphp的程序,从原来的未开启urlrewrite到开启后,产生页面上图片或者css文件错乱的情况,就有可能是因为使用了相对路径;解决的方案是修改成绝对路径,或者重新调整媒体文件的路径。 ***绝对路径***:指的是媒体文件相对网站根目录的路径。 绝对路径有两种情况,单斜杠开头的媒体文件地址,或者是http://开头的完整的媒体文件地址。 以下都是绝对路径的例子: <script type="text/javascript" src="/js/jquery.js"></script> <link rel="stylesheet" href="/css/style.css" type="text/css" media="screen" /> <img src="/images/logo.gif" alt="" /> <script type="text/javascript" src="http://www.speedphp.com/js/jquery.js"></script> <link rel="stylesheet" href="http://www.speedphp.com/css/style.css" type="text/css" media="screen" /> <img src="http://www.speedphp.com/images/logo.gif" alt="" /> 由于在绝对路径中,媒体文件是相对于网站根目录,所以无论在哪个页面上,用绝对路径都能找到这个媒体文件。 如果对于媒体文件地址比较困惑的情况,建议直接用绝对路径。 > url()函数产生的网址,也是http://开头的绝对路径地址。