上集重点介绍了twig模板的使用,供模板设计者阅读,下集供php开发者阅读,讲解如何调用和扩展twig,这一篇安装和采用版本将衔接上集内容。

Twig_Environment:
该类的实例是twig模板系统的中心枢纽,也叫环境对象,用于保存配置、twig扩展、加载模板等,在她内部调度twig系统的各个类,可以说她是系统的对外使用接口,代表整个twig系统,因此通常用$twig变量名来表示她(在本文中凡使用该变量名,均指环境对象),初始化时接收两个参数:
加载器:负责从不同的源加载模板的内容,比如文件系统、数据库等等,不同的源用不同的加载器
选项数组:在上集中已经讲述过各选项的含义,本篇仅介绍缓存选项相关的缓存对象,见后。

加载器:
twig提供了一些默认加载器,如有特殊需求需要自己实现,默认加载器如下:

Twig_Loader_Filesystem:
用于从文件系统加载模板文件,用法如下:
$loader = new Twig_Loader_Filesystem($templateDir , $rootPath);
参数$templateDir是模板目录的绝对路径,或者是相对路径(相对于第二个参数),也可以是由她们构成的数组,此时数组中的相对路径全部是相对于第二个参数的,可以通过第二个构造参数$rootPath指定相对的根目录,如果没有提供,那么将采用执行脚本的工作目录,也就是getcwd()函数的值,如下;
$loader = new Twig_Loader_Filesystem(array($templateDir1, $templateDir2));
此时将依次查找模板文件,直到找到一个为止,实例化后也可以添加路径:
$loader->addPath($templateDir3); //追加路径
$loader->prependPath($templateDir4); //添加路径到已添加路径的前面,以便首先查找
通过以上两方法添加路径时,也可以指定名字空间,这将不同模板分到不同的组中,如:
$loader->addPath($templateDir, 'admin');
当没有指定名字空间时,默认为“main”,名字空间相当于为其下的所有目录提供了别名,可以这样加载模板:
@namespace_name/template_path
如:
$twig->render('@admin/index.html', array());
这将在名字空间“admin”下的所有目录查找“index.html”模板,名字空间在分前台模板和后台管理模板时很有用

Twig_Loader_Array:
用于从数组加载模板,使用如下:

$loader = new Twig_Loader_Array(array('index.html' => 'Hello {{ name }}!',
));

键名是模板名,键值是模板内容,这通常用于单元测试,或者在一些将所有模板存放在php文件中的小项目中

Twig_Loader_Chain:
加载器链,用于将多个加载器整合到一起,当加载模板时,依次调用这些加载器,一旦找到模板即停止查找,使用方法如下:

$loader1 = new Twig_Loader_Array(array('base.html' => '{% block content %}{% endblock %}',
));
$loader2 = new Twig_Loader_Array(array('index.html' => '{% extends "base.html" %}{% block content %}Hello {{ name }}{% endblock %}','base.html'  => 'Will never be loaded',
));$loader = new Twig_Loader_Chain(array($loader1, $loader2));
$twig = new Twig_Environment($loader);

接收由多个加载器构成的数组,传递的加载器需要实现Twig_LoaderInterface,个数不限,实例化后还可以继续添加加载器,使用该方法:
addLoader(Twig_LoaderInterface $loader)
但须注意一旦加载器实例化后,无法调整加载器的顺序

自定义加载器:
全部加载器必须实现接口:
\Twig_LoaderInterface
该接口只有三个方法:
public function getSource($name);
得到模板内容
public function getCacheKey($name);
得到提供给缓存系统使用的模板缓存键,但在缓存系统中真实的缓存id还可以结合其他内容;在文件系统加载器中,该方法返回模板文件的相对路径
public function isFresh($name, $time);
判定模板的缓存是否还有效,第二个参数应该是缓存的模板的修改时间,也就是被编译后的模板的修改时间,在内部将和源模板文件进行时间对比,保证当源模板有变化时,被编译后的缓存即失效

为了让加载器链更加快速的工作,推荐加载器也实现接口:
Twig_ExistsLoaderInterface
该接口只有一个方法:
public function exists($name);
用于判定模板是否存在

缓存对象:
缓存对象用于储存、取回被系统编译后的模板文件,并决定缓存id(对于文件系统缓存对象而言,该id就是文件路径,含文件名)、返回缓存建立时间(该时间供加载器判断缓存是否已经失效)
在上集中已经解释了提供给环境对象的选项数组中cache选项的含义,如下:
可选值有储存编译后模板的目录,或者为false以禁用编译缓存,这是默认值,也可以是缓存对象(接口:Twig_CacheInterface的实例)
实际上在内部不管该选项提供的是哪一种值,都会被转化为缓存对象,也就是接口:Twig_CacheInterface的实例,
在选项是目录路径的时候,默认使用以下缓存对象:
\Twig_Cache_Filesystem
如果禁用缓存,那么默认使用以下缓存对象:
\Twig_Cache_Null
如果需要自定义缓存,如储存到数据库时,那么需要实现接口,并将自定义的缓存对象作为选项值传入环境变量。自定义缓存对象如下:
实现接口:\Twig_CacheInterface
其方法含义如下:
generateKey($name, $className);
返回缓存id,该接口的其他方法通过该id来加载和写入编译后的模板。第一个参数是原模板的文件名,第二个为编译后的模板php类名
write($key, $content);
保存编译后的模板到缓存,第一个参数为缓存id(generateKey方法返回的值),第二个为php代码
load($key);
加载编译后的模板文件实际上是加载一个可执行的php文件,因此使用include_once,并不是读取内容字符串
getTimestamp($key);
返回被编译后的模板文件的保存时间,以供加载器用来和原模板对比判断是否失效

twig扩展Extensions:
扩展用于给twig添加新功能,通过环境对象的addExtension方法添加即可,如:
$twig->addExtension(new Twig_Extension_Sandbox());
twig自带了一些扩展,介绍如下。

核心扩展Twig_Extension_Core:
定义Twig的核心功能,如标签、过滤器、函数、测试等,自动加载该扩展,不必手动添加

转义扩展Twig_Extension_Escaper:
该扩展提供模板中全部变量自动转义、autoescape标签对模板中一段局部内容变量转义、定义过滤器raw,在实例化环境对象时自动加载该扩展,不必手动添加;在已经初始化环境对象后,如果想更改通过选项数组传入的全局转义策略,可以在扩展初始化前调用以下代码:
$escaper = new Twig_Extension_Escaper('html');
$twig->addExtension($escaper);

沙盒扩展Twig_Extension_Sandbox:
提供沙盒模式,用于执行不可信的模板代码,将其隔离执行,示例如下:

$tags = array('if');
$filters = array('upper');
$methods = array('Article' => array('getTitle', 'getBody'),
);
$properties = array('Article' => array('title', 'body'),
);
$functions = array('range');
$policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions);
$sandbox = new Twig_Extension_Sandbox($policy);
$twig->addExtension($sandbox);

如你所见,沙盒扩展需要一个策略对象,twig自带了一个策略对象:Twig_Sandbox_SecurityPolicy,她配置一个白名单,名单以外的内容都不被允许,以上示例配置了如下内容:
允许的模板标签和过滤器、可访问的对象方法和属性、可使用的函数,格式如下:
标签、过滤器、函数的值均是对应元素构成的数组,大小写敏感
允许的方法($methods变量)是一个数组,键名为类名(应该是全限定类名,被直接用于$obj instanceof $class测试),键值为方法名或方法名构成的数组,方法名大小写不敏感
允许的属性($properties变量)和允许的方法一样,但大小写敏感
自定义策略对象只需要实现接口即可,可参照以上策略对象的实现,比较简单不多介绍。
以上代码还没有真正启用沙盒模式,还需要在模板中将不安全的内容放在沙盒标签内才行:

{% sandbox %}{% include 'user.html' %}
{% endsandbox %}

如果需要对全部模板内容执行沙盒模式,那么在沙盒扩展的第二个参数传入true即可(她默认为false):
$sandbox = new Twig_Extension_Sandbox($policy,true);
如果沙盒内代码访问了不允许的内容,那么将抛出异常,该扩展没有被默认加载,如需使用需要手动加载

分析器扩展Twig_Extension_Profiler:
分析器扩展用于提供模板执行的时间、内存用量等等信息,没有默认加载,应该仅用于开发阶段,示例:
$profile = new Twig_Profiler_Profile();
$twig->addExtension(new Twig_Extension_Profiler($profile));
运行模板之后查看分析结果:
$dumper = new Twig_Profiler_Dumper_Text();
echo $dumper->dump($profile);

优化器扩展Twig_Extension_Optimizer:
用于在编译模板前优化节点树,默认加载并开启全部优化,开发者可用优化选项控制其行为,示例如下:
$optimizer = new Twig_Extension_Optimizer(Twig_NodeVisitor_Optimizer::OPTIMIZE_FOR);
$twig->addExtension($optimizer);
这会导致重新添加,覆写默认添加的选项,控制选项有如下类型:

Twig_NodeVisitor_Optimizer::OPTIMIZE_ALL:开启全部优化,默认值
Twig_NodeVisitor_Optimizer::OPTIMIZE_NONE:关闭优化,这降低模板编译时间,但加大执行时间和内存消耗    Twig_NodeVisitor_Optimizer::OPTIMIZE_FOR:优化for标签,尽可能减少变量循环
Twig_NodeVisitor_Optimizer::OPTIMIZE_RAW_FILTER:尽可能移除不必要的raw过滤器
Twig_NodeVisitor_Optimizer::OPTIMIZE_VAR_ACCESS:尽量简化模板中变量的访问和创建

自定义扩展:
twig提供强大的扩展能力,可以添加标签、过滤器、函数、全局变量、操作符、测试,甚至能扩展语法分析器本身,除扩展标签稍复杂外,其他都很简单,来看一看。

添加全局变量:
全局变量除在全部模板中(包括宏)有效外,和其他普通模板变量没什么区别,添加一个全局变量如下:

$twig = new Twig_Environment($loader);
$twig->addGlobal('text', new Text());

第一个参数是在模板中的全局变量名称,第二个为其值,可以是任意类型的值
全局变量只能在编译或渲染模板之前被添加,否则只能更新

添加过滤器:
示例如下:

$filter = new Twig_SimpleFilter('rot13', function ($string) {return str_rot13($string);
});
$twig->addFilter($filter);

其中Twig_SimpleFilter构造函数的第一个参数为在模板中将要用到的过滤器名称,第二个为一个php回调$callable,她可以是如下形式:
匿名函数、已定义的php或用户自定义的函数名、类静态方法:
$filter = new Twig_SimpleFilter('rot13', array('SomeClass', 'rot13Filter'));
$filter = new Twig_SimpleFilter('rot13', 'SomeClass::rot13Filter');
对象方法:
$filter = new Twig_SimpleFilter('rot13', array($this, 'rot13Filter'));

在调用过滤器时管道符号“|”左侧的值作为第一个参数传入,若过滤器带圆括号参数,则里面的值按顺序传递给对应参数(并非合成数组传递给第二个参数),

类Twig_SimpleFilter构造函数还存在第三个参数,以指定附加选项,是一个数组,默认值为:

array('needs_environment' => false,'needs_context' => false,'is_variadic' => false,'is_safe' => null,'is_safe_callback' => null,'pre_escape' => null,'preserves_safety' => null,'node_class' => 'Twig_Node_Expression_Filter','deprecated' => false,'alternative' => null,)

含义如下:

needs_environment:
如果过滤器需要访问环境变量,设置为true,那么第一个参数将传入环境变量,其他值依次后延
needs_context:
如果需要访问上下文,设置为true,那么第一个参数将传入上下文变量,其他值依次后延,如果同时还需要环境变量,那么上下文参数在环境变量参数后面
is_safe:
其值是一个转义策略构成的数组或为NULL,如果开启了自动转义,那么设置该值将避免返回值被指定的策略再次转义
pre_escape:
指定管道符前的变量在传入过滤器前需要先进行转义,字符串值,转义策略之一,不能使用数组指定多个,注意是管道符前的变量,如果是字面值将无效,过滤器圆括号中的额外参数不论是变量还是字面值都不会转义
is_variadic:
如果过滤器可接收任意多个参数时,将其设置为true,那么twig将额外参数合成数组后作为最后一个参数传入
deprecated:
标记这个过滤器已经被启用了
alternative:
当过滤器被标记为弃用时,可用该选项指定一个代替品
node_class:
默认值:Twig_Node_Expression_(Filter/ Function/ Test),指定一个类,用于处理节点的具体实现,比如奇偶数判断,核心并没有采用函数,而是给了一个类实现,在模板编译后的php代码中直接%2来判断
preserves_safety:
在系统中未见实质性使用,官方也未给出解释,可忽略
is_safe_callback:
定义一个回调,用于动态返回is_safe选项的内容,该回调接收Twig_Node类型的参数

动态名称过滤器:
指过滤器的名称不是固定的,twig可以实现不同过滤器名采用同一个过滤器函数,只需要在添加过滤器时动态部分用“*”号代替,如下示例:

$filter = new Twig_SimpleFilter('*_path_*', function ($name, $suffix, $arguments) {// ...
});

此时假设模板代码如:'foo'|a_path_b() 那么传递参数时将是:('a', 'b', 'foo'),动态部分依次传入,如果有环境变量和上下文参数,那么他们在环境变量和上下文参数后面传入。

添加函数:
添加函数的方法和过滤器高度相似,示例如下:

$twig = new Twig_Environment($loader);
$function = new Twig_SimpleFunction ($name, $callable, $options);
$twig->addFunction($function);

除了选项pre_escape 和 preserves_safety外,函数和过滤器有相同的功能实现,函数也支持动态函数名

动态定义未定义的过滤器或函数:
当模板中使用一个没有定义过的过滤器或函数时,默认抛出异常,但在此之前twig会尝试访问动态定义,如果没有定义才抛出,否则使用动态定义结果,示例如下:

$twig->registerUndefinedFunctionCallback(function ($name) {if (function_exists($name)) {return new Twig_SimpleFunction($name, $name);}return false;
});

上列将使所有php函数暴露给模板;过滤器动态定义请使用:registerUndefinedFilterCallback()
如果依然不想定义,必须返回false
由于该功能是在编译阶段进行的,所以没有性能损失
注意这和动态名称过滤器和函数是不一样的概念

添加测试:
这里的测试不是指单元测试,也不是在调试代码,而是指模板中的如下运用:
{% if my_value is odd %}
添加一个测试和添加过滤器、函数是高度类似的,示例如下:

$test = new Twig_SimpleTest('red', function ($color) {if ($color == 'red') {return true;}return false;
});
$twig->addTest($test);

然后在模板中就可以这样用了:

{% if my_value is red %}
是红色
{% else %}
不是红色
{% endif %}

注意:测试用于条件判断,测试函数应该总是返回布尔值

添加标签:
是指自定义类似“if”、“for”等结构,比较复杂,需要知道twig内部原理,涉及编译原理知识,使用很罕见,内容大大超出本篇范围,所以忽略不讲,但这是一件激动人心的事情,在我国计算机专业均有开设专门课程讲解编译原理,但能够实质性接触编译原理实战的机会很少,如果你希望深入研究,那么twig是一个非常合适的实战项目,这也让你大致明白php、C、java等是如何变成机器码的,这里给出一些引导:
twig模板渲染分四个步骤:
加载loader:将模板源代码加载到程序
词法分析lexer:将模板源代码解析为有用的基本单元(token流)
语法分析parser:将token流转化为节点树(抽象语法树AST:the Abstract Syntax Tree)
编译compiler:将语法树转化为php代码
更多请见:https://twig.symfony.com/doc/1.x/internals.html

添加扩展:
以上添加全局变量、过滤器、函数、测试、标签等扩展行为都是单独进行的,一个项目往往需要添加很多东西,可以把这些操作集中到一起形成一个可重用的类,这个类就是扩展,扩展对象用于更加方便的统一进行上述操作,打包功能相关的组件。
扩展类需要实现以下接口:
Twig_ExtensionInterface
通常不需要直接实现,继承抽象类Twig_Extension即可
里面的方法都对应前文介绍的添加方法,具体实现可参考核心扩展类:
Twig_Extension_Core(位于文件Twig/Extension/Core.php中)
从该类也可以看出核心提供了哪些过滤器、函数等

如果需要修改(重载)已经存在的全局变量、过滤器、函数、测试、标签等只需要重新添加他们即可,如果是直接在环境变量上添加,那么她的优先级将高于任何扩展,即便扩展被后添加

自定义定界符:
可以通过向词法分析器传递选项数组改变默认的定界符,示例如下:

$twig = new Twig_Environment($loader);
$lexer = new Twig_Lexer($twig, array('tag_comment' => array('{#', '#}'),'tag_block' => array('{%', '%}'),'tag_variable' => array('{{', '}}'),'whitespace_trim' => '-','interpolation' => array('#{', '}'),));
$twig->setLexer($lexer);

但不推荐这样用,这将使模板不通用,但有时候是很有必要的。

对象的动态属性:
当使用article.title方式访问变量时,会检查article是否存在title属性,我们也可以定义php的魔术方法,以动态返回结果,如下:

class Article
{public function __get($name){if ('title' == $name) {return 'The title';}// throw some kind of error}public function __isset($name){if ('title' == $name) {return true;}return false;}
}

验证模板语法错误:
对第三方或调试阶段的模板可以进行语法验证,以便只有通过后才能保存,示例如下:

try {$twig->parse($twig->tokenize(new Twig_Source($template)));
//模板有效
} catch (Twig_Error_Syntax $e) {// 模板有语法错误
}

使用数据库储存模板:
在官网有一个列子,请见:
https://twig.symfony.com/doc/1.x/recipes.html

补充说明:
1、开发者需要注意被编译后的模板保鲜期只和源模板有关,而与调用程序无关,这可能会带来困惑,比如调用程序添加了新的扩展,调试选项也是打开的,但输出结果未变,这极可能就是该问题导致,并非程序bug

2、DSL,是Domain Specific Languages的缩写,可以翻译为“领域特定语言”,用于给贴近业务的人员来描述业务情况,可以算是程序员和业务需求制定人员的桥梁,让不懂技术的人通过她描述业务逻辑,自然语言应该算是DSL设计的极致目标了,计算机听懂自然语言然后自动产生程序去执行,twig提供的语法就是一种DSL的实现,她让设计师通过这种语法做自己的事情,然后twig将其翻译成php程序;SQL也是一种DSL,她单独提供语法,然后数据库软件解释执行她;正则表达式也是;总的来说DSL是一种语言,提供相比于底层技术高级抽象的描述能力,而对人类而言又是自然、亲切、简单的,她涉及语法定义、编译原理等。

我是云客,【云游天下,做客四方】,联系方式见主页,欢迎转载,但须注明出处

twig模板引擎详解(下集:开发者篇)【twig模板引擎中文使用教程】相关推荐

  1. MySQL常用存储引擎详解

    MySQL常用存储引擎详解 一.什么是存储引擎 二.掌握存储引擎的重要性 三.MySQL常用的存储引擎有哪些 四.存储引擎的特性 1.InnoDB 2.MyISAM 3.MEMORY 4.ARCHIV ...

  2. 史上最简单MySQL教程详解(进阶篇)之存储引擎介绍及默认引擎设置

    什么是存储引擎? MySQL存储引擎种类 MyISAM 引擎 InnoDB引擎 存储引擎操作 查看存储引擎 存储引擎的变更 修改默认引擎 什么是存储引擎? 与其他数据库例如Oracle 和SQL Se ...

  3. js模板字符串自定义类名_详解JavaScript ES6中的模板字符串

    这篇文章主要介绍了详解JavaScript ES6中的模板字符串,JS的ES6版本带来诸多简洁化方面的重大改进,需要的朋友可以参考下 在 ES6 中引入了一种新的字符串字面量 - 模板字符串,除了使用 ...

  4. es6字符串添加html标签,JavaScript_详解JavaScript ES6中的模板字符串,在 ES6 中引入了一种新的字符 - phpStudy...

    详解JavaScript ES6中的模板字符串 在 ES6 中引入了一种新的字符串字面量 - 模板字符串,除了使用反引号 (`) 表示,它们看上去和普通的字符串没有什么区别.在最简单的情况下,他们就是 ...

  5. python 规则引擎 drools_Drools规则引擎详解-常用的drl实例

    关于Drools规则引擎,之前已经写了几篇相关文章,如果大家不熟悉的可以移步了解下: 本篇博客主要为大家列举一些常用,简单的drl文件,帮助大家更加深入地了解其drl文件地编写格式及常用的操作符 说明 ...

  6. 史上最简单MySQL教程详解(进阶篇)之索引及失效场合总结

    史上最简单MySQL教程详解(进阶篇)之索引及其失效场合总结 什么是索引及其作用 索引的种类 各存储引擎对于索引的支持 简单介绍索引的实现 索引的设置与分析 普通索引 唯一索引(Unique Inde ...

  7. 如何查看mysql数据库的引擎/MySQL数据库引擎详解

    一般情况下,mysql会默认提供多种存储引擎,你可以通过下面的查看: 看你的mysql现在已提供什么存储引擎: mysql> show engines; 看你的mysql当前默认的存储引擎: m ...

  8. 模板模式详解、模板模式怎么用、模板模式模板代码

    模板模式详解.模板模式怎么用.模板模式模板代码 文章目录 模板模式详解.模板模式怎么用.模板模式模板代码 @[toc] 模板模式定义 使用场景 优点 代码实操 模板模式定义 在模板模式(Templat ...

  9. el表达式ne什么意思_JSP中EL表达式的用法详解(必看篇)

    EL 全名为Expression Language EL 语法很简单,它最大的特点就是使用上很方便.接下来介绍EL主要的语法结构: ${sessionScope.user.sex} 所有EL都是以${ ...

  10. Django框架的模板层详解

    目录 一.模板简介 二.模板语法之变量 三.模板之过滤器 四.模板之标签 for标签 for ... empty if 标签 with 五.自定义标签和过滤器 六.模板导入和继承 模板导入: 模板继承 ...

最新文章

  1. STL之template类模板
  2. db4o官方入门教程翻译--06.集合和数组
  3. hive不在同一台机 hue_【Impala篇】---Hue从初始到安装应用
  4. Dijkstra算法(matlab实现)
  5. 有效解决0x0000011b共享打印机无法连接(适用所有win系统)
  6. 使用HttpClient和OkHttp实现模拟登录方正教务系统
  7. 方波正弦波三角波信号发生电路
  8. win7 命令行开启WiFi
  9. 惠普M1136 MFP激光打印机打印整张纸全黑
  10. SAP-PP 主生产计划MPS
  11. b站黑马Vue2后台管理项目笔记——(1)登录功能
  12. 小米手机访问电脑共享文件_小米手机不用数据线直接访问电脑上的文件的方法...
  13. simulink中detailed thyristor和thyristor的区别(针对latching current和turn-off time的对比仿真)
  14. C++,QT多个窗体切换,widget 多个页面切换跳转显示
  15. Jenkins 用标签构建配置完后。 标签不显示
  16. 很贴心的一份,适合非科班入门计算机的课程路线
  17. mysql1.7(mysql优化,mysql-mmm软件介绍,mysql高可用集群。)
  18. 用代码和想象力拥抱一个物联网时代
  19. kafka partition(分区)与 group
  20. onethink入门笔记(一)

热门文章

  1. euraka注册中心配置步骤(Maven工程导入)
  2. 2022深圳杯ACD完整论文及详细代码资料
  3. MicroATX 主板 定位孔位图
  4. colormap是MATLAB里面用来设定和获取当前色图的函数。
  5. 动手学习深度学习(总结梳理)——9. Pytorch神经网络基础
  6. (4)web安全|渗透测试|网络安全web网站源码及相关分析
  7. 最新详细版Ubuntu20.04安装教程
  8. 软件高职女生就业问题
  9. 美团 2020暑期实习生 笔试回忆
  10. Google 应用出海指南针第五期强势火热启动!