提问人:Dany Khalife 提问时间:8/13/2013 最后编辑:CommunityDany Khalife 更新时间:4/25/2019 访问量:5209
替换字符串中的变量
Replacing variables in a string
问:
我正在用PHP开发一个多语言网站,在我的语言文件中,我经常有包含多个变量的字符串,稍后将填充这些变量以完成句子。
目前,我正在放入字符串中,并在使用时手动将每个匹配项替换为其匹配值。{VAR_NAME}
所以基本上:
{X} created a thread on {Y}
成为:
Dany created a thread on Stack Overflow
我已经想到了,但我觉得这很不方便,因为它取决于变量的顺序,这些变量可以从一种语言更改为另一种语言。sprintf
我已经检查过如何在 php 中将字符串中的变量替换为值? 现在我基本上使用这种方法。
但是我有兴趣知道 PHP 中是否有内置的(或者可能没有)方便的方法来做到这一点,因为在前面的示例中我已经有完全命名为 X 和 Y 的变量,更像是变量变量的 $$。
因此,与其对字符串进行str_replace,我可能会调用这样的函数:
$X = 'Dany';
$Y = 'Stack Overflow';
$lang['example'] = '{X} created a thread on {Y}';
echo parse($lang['example']);
也会打印出来:
Dany created a thread on Stack Overflow
谢谢!
编辑
字符串用作模板,可以多次使用不同的输入。
所以基本上这样做不会奏效,因为我会丢失模板,字符串将使用 和 的起始值进行初始化,这些值尚未确定。"{$X} ... {$Y}"
$X
$Y
答:
简单:
$X = 'Dany';
$Y = 'Stack Overflow';
$lang['example'] = "{$X} created a thread on {$Y}";
因此:
echo $lang['example'];
将输出:
Dany created a thread on Stack Overflow
按照您的要求。
更新:
根据 OP 关于使解决方案更具可移植性的评论:
每次都让一个班级为你做解析:
class MyParser {
function parse($vstr) {
return "{$x} created a thread on {$y}";
}
}
这样,如果发生以下情况:
$X = 3;
$Y = 4;
$a = new MyParser();
$lang['example'] = $a->parse($X, $Y);
echo $lang['example'];
这将返回:
3 created a thread on 4;
并且,仔细检查:
$X = 'Steve';
$Y = 10.9;
$lang['example'] = $a->parse($X, $Y);
将打印:
Steve created a thread on 10.9;
如愿以偿。
更新2:
根据 OP 关于提高可移植性的评论:
class MyParser {
function parse($vstr) {
return "{$vstr}";
}
}
$a = new MyParser();
$X = 3;
$Y = 4;
$vstr = "{$X} created a thread on {$Y}";
$a = new MyParser();
$lang['example'] = $a->parse($vstr);
echo $lang['example'];
将输出前面引用的结果。
评论
尝试
$lang['example'] = "$X created a thread on $Y";
编辑:基于最新信息
也许你需要看看 sprintf() 函数
然后,您可以将模板字符串定义为
$template_string = '%s created a thread on %s';
$X = 'Fred';
$Y = 'Sunday';
echo sprintf( $template_string, $X, $Y );
$template_string
不会更改,但稍后在代码中,当您为不同的值赋值时,您仍然可以使用$X
$Y
echo sprintf( $template_string, $X, $Y );
评论
$_SESSION
$X = 1, $Y = 2; echo $lang['example'];
$X = 3, $Y = 4; echo $lang['example'];
echo
那为什么不使用str_replace呢?如果你想把它作为模板。
echo str_replace(array('{X}', '{Y}'), array($X, $Y), $lang['example']);
对于您需要的每次出现这种情况
str_replace 最初是为此而建造的。
评论
将“变量”部分定义为一个数组,其键对应于字符串中的占位符怎么样?
$string = "{X} created a thread on {Y}";
$values = array(
'X' => "Danny",
'Y' => "Stack Overflow",
);
echo str_replace(
array_map(function($v) { return '{'.$v.'}'; }, array_keys($values)),
array_values($values),
$string
);
评论
这是一个使用变量的可移植解决方案。耶!
$string = "I need to replace {X} and {Y}";
$X = 'something';
$Y = 'something else';
preg_match_all('/\{(.*?)\}/', $string, $matches);
foreach ($matches[1] as $value)
{
$string = str_replace('{'.$value.'}', ${$value}, $string);
}
首先,设置字符串和替换项。然后,执行正则表达式以获取匹配数组({ 和 } 内的字符串,包括这些括号)。最后,使用变量变量循环这些变量,并将其替换为上面创建的变量。可爱!
只是以为我会用另一个选项更新它,即使您已将其标记为正确。您不必使用变量变量,并且可以在它的位置使用数组。
$map = array(
'X' => 'something',
'Y' => 'something else'
);
preg_match_all('/\{(.*?)\}/', $string, $matches);
foreach ($matches[1] as $value)
{
$string = str_replace('{'.$value.'}', $map[$value], $string);
}
这将允许您创建具有以下签名的函数:
public function parse($string, $map); // Probably what I'd do tbh
由于 toolmakersteve 在注释中提供了另一个选项,它消除了对循环的需求,并使用了 strtr,但需要对变量和单引号而不是双引号进行少量添加:
$string = 'I need to replace {$X} and {$Y}';
$map = array(
'{$X}' => 'something',
'{$Y}' => 'something else'
);
$string = strtr($string, $map);
评论
对于这种事情,strtr
可能是更好的选择,因为它首先替换了最长的键:
$repls = array(
'X' => 'Dany',
'Y' => 'Stack Overflow',
);
foreach($data as $key => $value)
$repls['{' . $key . '}'] = $value;
$result = strtr($text, $repls);
(想想你有 XX 和 X 等键的情况)
如果您不想使用数组,而是公开当前作用域中的所有变量:
$repls = get_defined_vars();
为什么不能在函数中使用模板字符串?
function threadTemplate($x, $y) {
return "{$x} created a thread on {$y}";
}
echo threadTemplate($foo, $bar);
评论
如果你运行的是 5.4 版本,并且你关心能够在字符串中使用 PHP 的内置变量插值,你可以使用如下方法:bindTo()
Closure
// Strings use interpolation, but have to return themselves from an anon func
$strings = [
'en' => [
'message_sent' => function() { return "You just sent a message to $this->recipient that said: $this->message."; }
],
'es' => [
'message_sent' => function() { return "Acabas de enviar un mensaje a $this->recipient que dijo: $this->message."; }
]
];
class LocalizationScope {
private $data;
public function __construct($data) {
$this->data = $data;
}
public function __get($param) {
if(isset($this->data[$param])) {
return $this->data[$param];
}
return '';
}
}
// Bind the string anon func to an object of the array data passed in and invoke (returns string)
function localize($stringCb, $data) {
return $stringCb->bindTo(new LocalizationScope($data))->__invoke();
}
// Demo
foreach($strings as $str) {
var_dump(localize($str['message_sent'], array(
'recipient' => 'Jeff Atwood',
'message' => 'The project should be done in 6 to 8 weeks.'
)));
}
//string(93) "You just sent a message to Jeff Atwood that said: The project should be done in 6 to 8 weeks."
//string(95) "Acabas de enviar un mensaje a Jeff Atwood que dijo: The project should be done in 6 to 8 weeks."
也许,它感觉有点骇人听闻,我不是特别喜欢在这种情况下使用。但是你确实得到了依赖PHP的变量插值的额外好处(它允许你做一些像转义这样的事情,这是正则表达式很难实现的)。$this
编辑:添加了 ,这增加了另一个好处:如果本地化匿名函数尝试访问未提供的数据,则不会发出警告。LocalizationScope
评论
$this
$this
use
$this
我将在这里添加一个答案,因为在我看来,当前的答案都没有真正削减芥末。我将直接深入研究并向您展示我将用于执行此操作的代码:
function parse(
/* string */ $subject,
array $variables,
/* string */ $escapeChar = '@',
/* string */ $errPlaceholder = null
) {
$esc = preg_quote($escapeChar);
$expr = "/
$esc$esc(?=$esc*+{)
| $esc{
| {(\w+)}
/x";
$callback = function($match) use($variables, $escapeChar, $errPlaceholder) {
switch ($match[0]) {
case $escapeChar . $escapeChar:
return $escapeChar;
case $escapeChar . '{':
return '{';
default:
if (isset($variables[$match[1]])) {
return $variables[$match[1]];
}
return isset($errPlaceholder) ? $errPlaceholder : $match[0];
}
};
return preg_replace_callback($expr, $callback, $subject);
}
这有什么作用?
简而言之:
- 使用指定的转义字符创建一个正则表达式,该转义字符将与三个序列之一匹配(更多内容见下文)
- 将其输入到
preg_replace_callback()
中,回调会精确处理其中两个序列,并将其他所有内容视为替换操作。 - 返回生成的字符串
正则表达式
正则表达式匹配以下三个序列中的任何一个:
- 转义字符出现两次,后跟零次或多次转义字符,后跟左大括号。仅消耗转义字符的前两次出现。这将替换为单次出现的转义字符。
- 转义字符的单次出现,后跟一个左大括号。这被字面上的打开大括号所取代。
- 左大括号,后跟一个或多个 perl 单词字符(字母数字和下划线字符),后跟右大括号。这被视为占位符,并对数组中大括号之间的名称执行查找,如果找到,则返回替换值,如果没有,则返回 的值 - 默认情况下,这是 ,被视为特殊情况,并返回原始占位符(即字符串未修改)。
$variables
$errPlaceholder
null
为什么更好?
为了理解为什么它更好,让我们看看其他答案所采取的替代方法。除了一个例外(唯一的缺点是与 PHP<5.4 的兼容性和略微不明显的行为),它们分为两类:
strtr()
- 这不提供处理转义字符的机制。如果您的输入字符串需要文本怎么办? 不考虑这一点,它将被替换为值。{X}
strtr()
$X
str_replace()
- 这与 存在相同的问题,并且还存在另一个问题。当您使用搜索/替换参数的数组参数进行调用时,它的行为就像您多次调用它一样 - 每个替换对数组一个。这意味着,如果其中一个替换字符串包含稍后出现在搜索数组中的值,则最终也会替换该值。strtr()
str_replace()
若要使用 演示此问题,请考虑以下代码:str_replace()
$pairs = array('A' => 'B', 'B' => 'C');
echo str_replace(array_keys($pairs), array_values($pairs), 'AB');
现在,您可能期望这里的输出是,但实际上它是(演示) - 这是因为第一次迭代替换为 ,而在第二次迭代中主题字符串是 - 因此这两个出现的 都被替换为 。BC
CC
A
B
BB
B
C
此问题还暴露了性能方面的考虑,这可能不会立即显现出来 - 因为每对都是单独处理的,所以操作是 ,对于每个替换对,将搜索整个字符串并处理单个替换操作。如果你有一个非常大的主题字符串和大量的替换对,那么在引擎盖下进行的一个相当大的操作。O(n)
可以说,这种性能考虑不是问题 - 在获得有意义的减速之前,您需要一个非常大的字符串和大量替换对,但它仍然值得记住。还值得记住的是,正则表达式本身有性能损失,因此通常不应将此考虑因素包含在决策过程中。
相反,我们使用 .这将访问字符串的任何给定部分,在提供的正则表达式的范围内查找一次匹配项。我添加这个限定符是因为如果你写了一个导致灾难性回溯的表达式,那么它将远远不止一次,但在这种情况下,这应该不是问题(为了帮助避免这种情况,我在表达式中做了唯一的重复所有格)。preg_replace_callback()
我们使用 instead of 允许我们在寻找替换字符串时应用自定义逻辑。preg_replace_callback()
preg_replace()
这允许您执行的操作
问题的原始示例
$X = 'Dany';
$Y = 'Stack Overflow';
$lang['example'] = '{X} created a thread on {Y}';
echo parse($lang['example']);
这变成了:
$pairs = array(
'X' = 'Dany',
'Y' = 'Stack Overflow',
);
$lang['example'] = '{X} created a thread on {Y}';
echo parse($lang['example'], $pairs);
// Dany created a thread on Stack Overflow
更高级的东西
现在假设我们有:
$lang['example'] = '{X} created a thread on {Y} and it contained {X}';
// Dany created a thread on Stack Overflow and it contained Dany
...我们希望第二个从字面上出现在生成的字符串中。使用默认转义字符 ,我们将其更改为:{X}
@
$lang['example'] = '{X} created a thread on {Y} and it contained @{X}';
// Dany created a thread on Stack Overflow and it contained {X}
好的,到目前为止看起来不错。但是,如果这应该是字面意思呢?@
$lang['example'] = '{X} created a thread on {Y} and it contained @@{X}';
// Dany created a thread on Stack Overflow and it contained @Dany
请注意,正则表达式设计为仅关注紧接在左大括号之前的转义序列。这意味着您不需要转义转义字符,除非它直接出现在占位符的前面。
关于使用数组作为参数的说明
原始代码示例使用的变量的命名方式与字符串中的占位符相同。我的使用带有命名键的数组。这有两个很好的理由:
- 清晰度和安全性 - 更容易看到最终会被替换的内容,并且您不会冒着意外替换您不想暴露的变量的风险。如果有人可以简单地输入并查看您的数据库密码,那就没有多大好处了,现在好吗?
{dbPass}
- 作用域 - 除非调用方是全局作用域,否则无法从调用作用域导入变量。这使得该函数在从另一个函数调用时毫无用处,并且从另一个作用域导入数据是非常糟糕的做法。
如果你真的想使用当前作用域中的命名变量(由于上述安全问题,我不建议这样做),你可以将对 get_defined_vars()
的调用结果传递给第二个参数。
关于选择转义字符的注意事项
您会注意到我选择了默认转义字符。你可以通过将任何字符传递给第三个参数来使用任何字符(或字符序列,它可以是多个字符)——你可能会想使用,因为这是许多语言使用的,但在你这样做之前请稍等片刻。@
\
你不想使用的原因是,许多语言都把它当作自己的转义字符,这意味着当你想在PHP字符串文字中指定你的转义字符时,你会遇到这个问题:\
$lang['example'] = '\\{X}'; // results in {X}
$lang['example'] = '\\\{X}'; // results in \Dany
$lang['example'] = '\\\\{X}'; // results in \Dany
它可能导致可读性的噩梦,以及一些具有复杂模式的不明显行为。选择任何其他语言都未使用的转义字符(例如,如果您使用此技术生成 HTML 片段,也不要用作转义字符)。&
总结一下
你正在做的事情有边缘情况。为了正确解决问题,您需要使用能够处理这些边缘情况的工具 - 当涉及到字符串操作时,该作业的工具通常是正则表达式。
评论
str_replace()
str_replace()
include()
如果 sprintf 的唯一问题是参数的顺序,则可以使用参数交换。
从文档(http://php.net/manual/en/function.sprintf.php):
$format = 'The %2$s contains %1$d monkeys';
echo sprintf($format, $num, $location);
评论
gettext 是一个广泛使用的通用本地化系统,可以完全按照您的要求执行任务。 大多数编程语言都有库,PHP 有一个内置引擎。 它由 po 文件驱动,这是基于简单文本的格式,周围有许多编辑器,并且与 sprintf 语法兼容。
它甚至具有一些功能来处理某些语言所具有的复杂复数形式。
以下是它的功能的一些示例。请注意,_() 是 gettext() 的别名:
echo _('Hello world');
将以当前选择的语言输出 Hello Worldecho sprintf(_("%s has created a thread on %s"), $name, $site);
翻译字符串,并将其交给 sprintf()echo sprintf(_("%2$s has created a thread on %1$s"), $site, $name);
与上述相同,但参数顺序已更改。
如果你有多个字符串,你绝对应该使用现有的引擎,而不是编写自己的引擎。 添加新语言只是翻译字符串列表的问题,大多数专业翻译工具也可以使用这种文件格式。
查看维基百科和 PHP 文档,了解其工作原理的基本概述:
谷歌会找到大量的文档,而你最喜欢的软件存储库很可能有一些用于管理po文件的工具。
我使用过的一些是:
- poedit:非常轻巧和简单。如果你没有太多的东西要翻译,也不想花时间思考这些东西是如何工作的,那就太好了。
- Virtaal:有点复杂,有点学习曲线,但也有一些不错的功能,让你的生活更轻松。如果你需要翻译很多,那就太好了。
- GlotPress 是一个 Web 应用程序(来自 wordpress 人),允许协作编辑翻译数据库文件。
评论
只是在使用关联数组时抛出另一种解决方案。这将遍历关联数组,并替换模板或将其留空。
例:
$list = array();
$list['X'] = 'Dany';
$list['Y'] = 'Stack Overflow';
$str = '{X} created a thread on {Y}';
$newstring = textReplaceContent($str,$list);
function textReplaceContent($contents, $list) {
while (list($key, $val) = each($list)) {
$key = "{" . $key . "}";
if ($val) {
$contents = str_replace($key, $val, $contents);
} else {
$contents = str_replace($key, "", $contents);
}
}
$final = preg_replace('/\[\w+\]/', '', $contents);
return ($final);
}
评论