“中国要复兴、富强,必须在开源软件领域起到主导作用,为了国家安全和人类发展,责无旁贷,我们须为此而奋斗”——By:云客
在系统很多地方进行设置时,事先并不知道具体的设置值,典型的比如:当用户注册时,系统可以发送一封欢迎邮件,但在设置邮件内容时并不知道用户的名字,该名字需要等到用户注册后才知道,此时可以先设置一个占位符,等到发送时进行替换即可,这样的占位符就是“token”,这样的需求有很多,又比如文件上传的保存目录如果是日期,那么也需要先使用占位符,上传保存时需要替换为真实的日期。系统统一使用“token”服务来识别和替换占位符。
占位符格式:
为了便于描述,下文“token”和“占位符”是相同意思,为了能够自动识别和替换token,需要token自带含义,因此需要具备一定格式,系统通过派发钩子的方式来收集替换值,钩子又通过token的格式进行解析并生成替换值,因此token格式尤为重要,需要有统一规范,drupal标准token格式如下:
[$type:$name]
这里$type是类型名,如node、user、comment、site、date等(通常采用模块名,但不一定是,准确讲应该称为类型名,理解为token类型,用于识别是哪一种占位符),$name是该类型下给定占位符的名字
示例如下:
[node:title]、[user:display-name]
token也可以分层级,格式如下:
[$type:$pointer:$name]
示例如下:
[node:author:mail]:被替换为节点作者的邮件
[date:custom:Y]:被替换为4位数的年份
这种层级可以理解为:第一个冒号后面的部分均是$name,只不过$name内部又继续采用了冒号分隔,这里的冒号和第一个冒号在含义上是有区别的,第一个冒号可以严格称为token分隔符,而$name里面的冒号仅供提供该token的钩子使用,其含义和层级的数量也由钩子决定
token规则是:
整个token字符串用英文方括号包裹,方括号内不允许再包括方括号,里面至少有一个英文冒号将字符分隔为两部分,第一部分字符称为类型标识符($type),标识符可以是除空白字符和方括号以外的任意字符,第一个冒号后面的全部字符称为占位符名($name),占位符名可以包含英文冒号做进一步的层级规划,可以包含方括号以外的任意字符,甚至空格,占位符名的规则和解析均由提供该token的钩子实现。
token服务:
用于识别字符串中的token并替换
服务id:token
类:Drupal\Core\Utility\Token
获取方式:\Drupal::token();
使用示例(替换目录中的日期token):
$data = [];
$destination = '[date:custom:Y]-[date:custom:m]';
$destination = trim($destination, '/');
$destination = \Drupal::token()->replace($destination, $data);
$destination = \Drupal\Component\Render\PlainTextOutput::renderFromHtml($destination);
print_r($destination);
该服务各方法说明如下:
public function replace($text, array $data = [], array $options = [], BubbleableMetadata $bubbleable_metadata = NULL)
参数如下:
$text:
包含占位符的字符串,可以是html字符串,不可以为对象
$data:
部分token的转化需要传递额外的对象,比如节点token就需要节点对象,用该数组传递所需的对象,如不需要可以留空
$options:
选项数组,用于控制方法的行为,有两个通用选项:$options['clear']如果不为空,则在没有替换值时删除占位符;$options['callback']是一个回调(在PHP7前仅允许函数名或静态方法,不能为数组),可以用该回调来调整即将使用的替换数组;不同token可以按序传递选项数组
$bubbleable_metadata:
渲染数组的可冒泡元数据对象,用于收集缓存或附属物
该方法返回替换后的字符串,调用者负责转义,如果没有传递可冒泡元数据,那么收集的可冒泡元数据将被自动添加到当前渲染上下文,如果有传递,那么由调用者处理
public function scan($text)
传入一个字符串,扫描其中所有可能包含的占位符,返回一个多维数组,第一级键名为$type,第二级键名为占位符中除开$type剩余的部分,值为完整占位符,示例如下:
如果输入值$text为:
$text='我是[a:b]和[x:y:z]';
那么输出值$var为:
$var['a']['b']='[a:b]';
$var['x']['y:z']='[x:y:z]';
如果没有扫描到token将返回空数组
public function generate($type, array $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata)
收集可冒泡渲染元数据及派发钩子收集替换信息,派发tokens钩子:
hook_tokens($type, $tokens, array $data, array $options, $bubbleable_metadata)
其中$type为占位符中类型部分,$tokens为一个数组,键名为余下的占位符部分,键值为完整占位符,其他参数与replace方法中的一样,钩子应返回一个数组,键名为完整占位符字符串,键值为替换字符串,如键值不想再被转义,应该用MarkupInterface对象表示,可返回空数组
修改钩:
hook_tokens_alter(array &$replacements, array $context, $bubbleable_metadata)
其中$replacements为全系统收集到的替换数组,$context为上下文数组,如下:
$context = [
'type' => $type,
'tokens' => $tokens,
'data' => $data,
'options' => $options,
];
含义同上
注:tokens相关钩子存放在模块的“.tokens.inc”文件中的,该文件不默认加载,那么是如何加载的呢?详见本系列钩子主题,了解钩子函数加载机制以及钩子信息钩子:system_hook_info()
public function findWithPrefix(array $tokens, $prefix, $delimiter = ':')
从占位符扫描结果中提取具备某前缀的部分,假设输入$tokens如下:
$data = array(
'author:name' => '[node:author:name]',
'title' => '[node:title]',
'created' => '[node:created]',
);
前缀$prefix为author,那么返回:
array('name' => '[node:author:name]');
public function getInfo()
返回系统所支持的全部token的元数据描述,这通过钩子“token_info”及其修改钩收集,通常实现了tokens钩子的模块应该实现该钩子
钩子实现情况:
在系统默认安装中,以下模块实现了tokens钩子,模块名及钩子函数名如下:
comment : comment_tokens
file : file_tokens
node : node_tokens
statistics : statistics_tokens
system : system_tokens
taxonomy : taxonomy_tokens
user : user_tokens
views : views_tokens
没有模块实现其修改钩,你可以用本系列配套模块“yunke_help”查看钩子实现情况
钩子“token_info”的实现情况同上
自定义token:
模块只需要实现以上的tokens和token_info钩子即可,在钩子逻辑中规划token格式
可通过以下代码测试查看token:
$data = [];
$token = "token is :\n[date:custom:Y]\n[site:url-brief]\n[site:login-url]";
$token = \Drupal::token()->replace($token, $data);
print_r($token);
die;
常用token:
[date:short]
短时间,如:10/21/2019 - 20:38
[date:medium]
中长日期,如:周一, 10/21/2019 - 20:38
[date:long]
长日期,如:星期一, 十月 21, 2019 - 20:38
[date:raw]
时间戳原始输出,如:1571661728
[date:since]
在数据参数中给出一个时间,该占位符返回一个用本地语言显示的时间差,如:
$data = ['date'=>time()-300];
$token = "[date:since] ";
$token = \Drupal::token()->replace($token, $data);
将输出类似:4 分钟 58 秒
通常用于显示已注册时间
[date:custom:Y]
四位数的年份,如2019
[date:custom:m]
带前导0的两位月份,如09
[date:custom:d]
带前导0的两位日期,如08、21
[date:custom:Y-m-d H:i:s]
将输出完整的年月日时分秒,如:“2019-10-22 19:27:01”,如你所见,在“date:custom:”后面可以使用和php函数date第一个参数相同的格式字符,这就是自定义的含义,请参考:
https://www.php.net/manual/zh/function.date.php
除此之外,drupal还很贴心的进行了本地化翻译,如:
[date:custom:F(D)]
在翻译系统有数据时,将输出“十月(周二)”,而php的date函数只能输出“October(Tue)”
[site:name]
在网站基本设置中设置的站点名
[site:slogan]
在网站基本设置中设置的站点口号或标语
[site:mail]
在网站基本设置中设置的邮件地址
[site:url]
完整的站点首页地址,带协议及域名部分
[site:url-brief]
简洁版首页地址,带域名但不带协议部分
[site:login-url]
用户登录地址
补充:
1、并非设置了token就会被替换,而是仅允许使用token的地方才会替换,如在文章正文中设置就不会被替换
2、注意占位符不要使用中文的方括号或冒号,这样将无效
3、不要将token占位符和渲染数组中的占位符($element['#attached']['placeholders'])搞混淆,这是完全不同的两个概念
反馈互动