PHP变量变量变量变量用法

我打电话给他们‘variable variables’,因为这句话很有趣。什么?阿仁’t变量应该…变化,改变,改变?确实,他们做到了。然而,有‘dynamic variables’在PHP中,与PHP本身(至少PHP 3. +)差不多。

网络上的最新教程,例如 PHP中的动态变量, 要么 PHP中的动态变量名称 表明它们很时尚。实际上,我还记得将它们作为一点自我发现的宝藏来学习:是的!我可以在一个脚本中生成数千个变量!是的,我还年轻。

如今,它们不再那么热。在方法上下文中添加变量缺乏责任感,应将世界隔离在一起。他们有几个人?它们包含什么?它们会干扰我自己的变量吗?

由于42%的PHP开放源代码项目都在使用它们,因此我们决定着手探索如何使用它们,以及什么可以替代它们。这是使用PHP变量变量的六种方法以及一些替代策略。

复苏register_globals

I’首先是怀旧感:使用PHP 4,早期5的每个人都记得那些hack。变量变量是模仿旧行为的最糟糕的方法。对,就那个’很好地介绍了动态变量功能的工作。

Until PHP 5.3, register_globals was a feature that automagically turned the incoming variables from an HTTP query (POST, GET, etc.) into global variables. That way, it gave immediate access to any incoming variable. Convenient. Unsafe, but convenient.

Needless to say, many applications relied on this behavior, and there was no way to convince authors to replace a simple $x call with $HTTP_POST_VARS['x']. Even when that last variable was shortened to $_POST and made super global.

因此,使用以下解决方案,变量变量节省了一天的时间。

 
<?php 
foreach($HTTP_POST_VARS as $k => $v) { 
  $$k = $v; 
} 
?>

You can easily read the code here : the name of the variable is in the key of the $HTTP_POST_VARSindex, so $$k points to the variable, whose name is $k. $v is the actual value, so this loop above creates a variable named $k, with its corresponding value.

注入未知变量

The problem with the original register_globals, was that the PHP script would start with a unknown list of pre-created variables, on top of PHP’自己的预定义变量。任何人都可以向POST目标添加一个或两个额外的变量,并在脚本内添加一个新变量。这通常意味着这些变量的初始值不再受信任。

覆盖变量

nm变量变量引入了覆盖现有变量的问题。上面显示的代码没有’检查内容,也不要检查任何预先存在的变量。这为擦除带有外部不安全值的安全变量打开了方便之门。

此外,该值本身必须在使用前进行检查。

创建任意变量

Nowadays, register_globals is an old souvenir of the not-so-good old times. It is gone, and no one misses it. Variable variables stayed, and they are still used for the same purposes, although with slightly different contexts. Hopefully, it is better those days.

目的之一是当场创建许多变量。请注意,不是来自输入值,而是来自更安全的来源。例如,配置文件或缓存。

这些来源都很安全,因为它们通常完全在应用程序中’s control. It is not an open backdoor, like $_POST was. Although, as usual, caution is always a good thing : validating those values is still the best practice.

然后,配置文件本质上是动态的:可以随时添加新指令,并且应该可以在应用程序环境中对其进行访问以方便访问。这是变量变量的典型情况:任意数量的指令列表,带有任意的名称和值。列表本身是唯一一个并非任意的列表。

在这种情况下,此代码使用变量变量:

 <?php

function foo() { 
   $is_empty = true; 
   $param = unserialize($param); 

   foreach ($param as $key => $val) { 
      if (!empty($val)) { 
          $$key = strtolower($val); 
          $is_empty = false; 
          break; 
      } 
    } 
} 
?>

指令是从外部来源注入的:这里是序列化的PHP代码。该源中的所有内容均通过循环在PHP作用域中重新创建。

这些变量的来源可以是保存数据集的任何来源。这里有些例子 :

  • JSON文件
  • INI文件
  • 序列化的PHP代码
  • 数据库
  • var_export()数组
  • YAML文件
  • 任何子流程
  • Redis哈希
  • WDDX反序列化

在配置的情况下,该循环在代码中仅出现一次。如果是缓存,则应该有第二个循环,其目的是写入。可能还有另一种可变变量用法。

也许有人失踪了?

审核那些新创建的变量时出现一个挑战。它们都是创造出来的吗?我们错过了吗?基本上,您如何进行审核并使之处于受控状态?

There is the native PHP method get_defined_vars(), which will list the created variables. This is a useful tool for debugging, albeit a bit cumbersome.

此外,当从类似数组的结构中创建变量时(请参见上面的示例),实际上,此数组比变量列表要容易得多。该数组充当变量列表的指针(或引用)。然后,使用数组函数列出它们,对它们进行计数,删除或更改它们。

 <?php

function foo(array $vars) { 
    foreach ($vars as $name => $value) { 
      $$name = $value; 
    }

    // This list include $name, $value and $vars too! 
   print_r(get_defined_vars()); 
   print_r(array_keys($vars)); 
} 
?> 

实际上,第二个需要了解的PHP函数是 提取() :它从数组产生变量,而不使用变量变量。并且它也考虑了冲突。变量冲突。

可变冲突

如前所述,在循环中使用变量变量时,我们略过了一个隐藏的问题:变量覆盖。由于变量可能会在上下文的任何位置发生变化,因此覆盖其中一个变量不会’t发出任何警告:这是正常现象。

然而,这是一个经常出现的问题。看这里 :

 <?php

foo(['name' => 1, 'vars' => 'bar']);

function foo(array $vars) { 
    foreach ($vars as $name => $value) { 
       $$name = $value;
     }

     // This list include $name, $value and $vars too! 
     print_r(get_defined_vars());

     /*Array ( 
       [vars] => bar 
       [value] => bar 
       [name] => vars 
      ) */

} 
?>

foo is called with some values to create as variables. Both will create havoc. First, 'name' is also the blind variable in the foreach. It is actually created first during the loop execution, but since $name is reused, it is then overwritten with latter values. So, arguments’变量与本地变量冲突。

Secondly, vars is also overwriting the local variables. Now, foreach() works on a copy of the source array, so the loop runs without a problem : all the variables are created as expected. But, right after the loop, $vars has been overwritten by one of the argument variables, and it is now a string, not an array anymore.

变量变量在变量名称空间和文字值之间建立了桥梁。通常,两个世界是分开的,变量保存值。值在这里创建变量,这导致那些冲突。

To avoid those conflicts, a check on variable existence is necessary. While it is possible to do it with a if (isset()) condition, 提取() does a better job. its second argument is a flag to specify the behavior in case of conflict : there are several alternative should one arise.

EXTR_OVERWRITE overwrites the previous variable, which is the default behavior; EXTRA_IF_EXISTS overwrites only previous existing variables; EXTR_SKIP means the new value is skipped, and the previous value is kept; EXTR_PREFIX_SAME adds a prefix, passed as third argument, to any conflicting variable; EXTR_PREFIX_ALL adds that prefix to all new variables.

 <?php

foo(['foo' => 1, 'vars' => 'bar']);

function foo(array $vars) { 
   extract($vars, EXTR_SKIP ); 
   foreach ($vars as $name => $value) { 
      $$name = $value; 
   }

    // This list include $name, $value and $vars too! 
    print_r(get_defined_vars());

     /* Array ( 
        [vars] => bar 
        [value] => bar 
        [name] => vars 
        [foo] => 1 )
*/
} 
?>

写入动态数据集

除了读取变量,还可以编写变量。当数据集具有多个层时,有时即使中间层具有任意名称,有时也必须访问中间层。因此,变量变量比引用变量更易于阅读。

Look at this structure, which deals with a multi-dimensional array like $issues[$lang][$type][$line] = $issue;. The variables used in index shows that those values are arbitrary. The nested loops produce a grouping of the issues, by type.

 
<?php 

// $issues[$lang][$type][$line] = $issue; 
foreach ($issues as $lang => $languageIssues) { 
    foreach ($languageIssues as $type => $languageIssue) { 
        foreach ($languageIssue as $line => $issue) { 
          array_push($$type, 'Line ' . $line . '. ' . $issue); 
        } 
     } 
} 
?> 

$type can only be a string (or a number), so it cannot hold an array. Here, the name of the type is used to access a variable with the same name, which is the final array. That way, any type of issue is created and filled as the $issues array is read.

大量创建后,必须收集这些变量。

Instead of variable variables, an array can be used here. $types[$type] acts just like a variable : it creates index on the fly, accept value pushes. And it collects immediately all the types in a convenient array.

也, 魔术方法 in a class (__get() and __set()) are another solution to collect the values in this case. Note that such class will probably use an array to store the magic properties.

批量存储临时变量

我们已经看到了从预配置变量列表中创建变量的情况。当那些动态创建的变量需要一些额外的处理时,它将导致创建更多的变量。

 
<?php 
foreach (current($data) as $_v => $_k) { 
   if ($_v != $index_key) { 
      if (!isset(${$_v . '_temp'})) ${$_v . '_temp'} = ""; 
      ${$_v . '_temp'} .= " {$_v} = CASE {$index_key} "; 
   } 
} 
?>

In the example above, the incoming variables in $data are processed to add more syntax, before building a complex SQL query. The ${$_v . '_temp'} create a new variable, which is later retrieved and combined.

The suffix approach tries to avoid the variable conflicts we mentioned before. Hopefully, none of the incoming variable will be called a and a_temp at the same moment. Should the situation happens, some of the values will overwrite each other.

To avoid this problem, switching context when processing the values would be a better idea. Apply array_map or array_walk to the $data array, to keep the processing separate. That keeps the variable namespace pollution low.

导出配置

变量变量的一个可能技巧是将其用作替代结构,或将其用作变量的开关,接近PHP 8.0中即将出现的match()。

Look at this script : the condition is processed in the loop, and the condition chooses the name of the variable. The actual HTML code has been prepared before the loop, and might hold several operations or some large text. $linkImage and $linkText acts as cached values.

Then, to display the actual value, $displayValue gets the configuration, and access the HTML code via the variable name.

<?php
// $linkImage and $linkText are set before the loop

foreach ($resources as $resource) {
    $displayValue = ($resource->type) ? 'linkImage' : 'linkText';             
    $links .= '<a href="' . $resource->link() . '">' . $$displayValue . '</a>';
}
?>

这是可变变量的巧妙用法。如我们所见,它们不需要任意数量。当然,可以用不同且较少战术的方式来处理这种情况。

永远的变量

变量变量仍然是PHP世界中一个值得注意的功能。 PHP编码器,无论是新手还是经验丰富的人,偶然发现它们,并将它们视为隐藏的宝藏。它们在某些战术情况下很有用,例如管理传出的占位符模板或传入的指令。

在应用程序内部,变量变量很容易用简单的数组或魔术类替换。这些提供了对值的更大控制,并提供了操纵它们的标准工具。特别是,它们使这些值与变量名称空间以及PHP的内部堆和设备分开。更安全。

PHP审核代码以计划在方法和函数中的内存分配。动态分配的变量很难预测。这些迫使PHP依靠默认机制来处理它们,从而降低性能。

谢谢 拉尔斯·莫里肯(Lars Moelleken),以启发这一使用可变变量的旅程。我们使用了 Exakat语料库,以测量变量变量的使用情况并检查各个细节。