问题 PHP - 有效的变量名称


在里面 PHP手册 关于变量,我们可以阅读:

变量名称遵循与PHP中其他标签相同的规则。有效的变量名称以字母或下划线开头,后跟任意数量的字母,数字或下划线。作为正则表达式,它将表示为:'[a-zA-Z_ \ x7f- \ xff] [a-zA-Z0-9_ \ x7f- \ xff] *'

很明显,当我们尝试运行时:

$0-a = 5;
echo $0-a;

我们会得到Parse错误。这很明显。

但是在尝试某些事情时,我发现实际变量在使用这样的语法时可以包含任何字符(或者至少以数字开头并包含连字符):

${'0-a'} = 5;
echo ${'0-a'};

它没有任何问题。

还使用这样的变量变量:

$variable = '0-a';
$$variable = 5;
echo $$variable;

工作没有任何问题。

所以问题是 - 我在手册中引用的句子是不是真的,或者这可能是我所展示的不是真正的变量,或者它可能是在PHP手册中的其他地方记录的?

我已经验证了它 - 它似乎在PHP 5.6和7.1中都有效

问题是 - 使用这种结构是否安全?根据手册,它似乎根本不可能。


10442
2018-02-24 18:45


起源

好问题。为什么有人会使用那些丑陋的变量名?然而,我想解释器由于容忍而正确地使用它们,但由于它们不能完全工作(例如在简单声明中),因此它没有得到官方支持。或者有人可以说明为什么它是正确的? - Blackbam
边注;我对这个问题进行了编辑,了解您手册中的内容。如果不是,您可以进行回滚并使用实际URL来实现您的目的。 - Funk Forty Niner
谁把他们称为“过于宽泛”?我看到很多针对C和C ++的HNQ用于看似类似的构造问题,这些问题在已发表的研究中得到了答案,用于解释编译器12.4.23.0006执行的一些惊人的位翻转优化,但12.4.23.0005没有。 - MonkeyZeus


答案:


你可以选择 任何 变量的名称。 "i" 和 "foo" 是明显的选择,但是 """\n",和 "foo.bar" 是  有效。原因? PHP符号表只是一个字典:一个字符串键 零个或多个字节 映射到结构化值(称为 的zval)。有趣的是,有  访问此符号表的方法:词法变量和动态变量。

词汇变量就是你在里面读到的 “变量” 文档。词法变量在编译期间定义符号表键(即,当引擎处于lexing并解析代码时)。为了简化这个词法分析器,词法变量以a开头 $ sigil并且必须匹配正则表达式 [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*。以这种方式保持简单意味着解析器不必弄清楚,例如,是否 $foo.bar 是一个变量键入 "foo.bar" 或变量 "foo" 字符串与常量连接 bar

现在,动态变量就变得有趣了。动态变量允许您访问那些更不常见的变量名称。 PHP称之为 变量变量。 (我没有找到那个名字,因为它们的对立面在逻辑上是“常量变量”,这令人困惑。但我会在这里称它们为变量变量。)基本用法如下:

$a = 'b';
$b = 'SURPRISE!';
var_dump($$a, ${$a}); // both emit a surprise

变量变量的解析方式与词法变量不同。不是在lexing时定义符号表键,而是在at处计算符号表键 运行。逻辑是这样的:PHP词法分析器看到变量变量语法(或者 $$a 或更一般地说 ${expression}),PHP解析器延迟表达式的评估直到运行时,然后在运行时引擎使用 表达的结果键入符号表。它比词汇变量更有效,但功能更强大。

代替 ${} 你可以有一个计算结果的表达式 任何字节序列。空字符串,空字节,全部。什么都可以。这很方便,例如,在 here文档。它作为PHP变量访问远程变量也很方便。例如,JSON 允许任何角色 在密钥名称中,您可能希望将它们作为直接变量(而不是数组元素)访问:

$decoded = json_decode('{ "foo.bar" : 1 }');
foreach ($decoded as $key => $value) {
    ${$key} = $value;
}
var_dump(${'foo.bar'});

以这种方式使用变量类似于使用数组作为“符号表”,如 $array['foo.bar'],但变量变量方法是完全可以接受的,并且稍微快一点。


附录 

通过“稍微快一点”,我们到目前为止在小数点右边说它们实际上是难以区分的。直到10 ^ 8符号访问,在我的测试中差异超过1秒。

Set array key: 0.000000119529
Set var-var:   0.000000101196
Increment array key: 0.000000159856
Increment var-var:   0.000000136778

失去清晰度和惯例可能不值得。

$N = 100000000;

$elapsed = -microtime(true);
$syms = [];
for ($i = 0; $i < $N; $i++) { $syms['foo.bar'] = 1; }
printf("Set array key: %.12f\n", ($elapsed + microtime(true)) / $N);

$elapsed = -microtime(true);
for ($i = 0; $i < $N; $i++) { ${'foo.bar'} = 1; }
printf("Set var-var:   %.12f\n", ($elapsed + microtime(true)) / $N);

$elapsed = -microtime(true);
$syms['foo.bar'] = 1;
for ($i = 0; $i < $N; $i++) { $syms['foo.bar']++; }
printf("Increment array key: %.12f\n", ($elapsed + microtime(true)) / $N);

$elapsed = -microtime(true);
${'foo.bar'} = 1;
for ($i = 0; $i < $N; $i++) { ${'foo.bar'}++; }
printf("Increment var-var:   %.12f\n", ($elapsed + microtime(true)) / $N);

14
2018-02-24 19:19



所以似乎Php手册对于变量名称并不完全正确,对吧?因为那里写的实际上是关于tokenizer而不是真正的变量名? - Marcin Nabiałek
我只想把这看作是PHP手册确保新手不能轻易地为代码库的未来读者编写可怕的变量名(和代码)的方法,一旦你足够先进以了解这是可能的,那么你选择不要因为上述原因这样做。 - MonkeyZeus
我是@MonkeyZeus说的第二个。变量页面是对使用变量作为操作数据的常用方法的介绍。虽然它是 技术上 确实,令牌化器仅限于构成变量名称的内容,它是 不相干和分散注意力 讨论变量。稍后在变量变量章节中可以提到它。 - bishop