【问题标题】:PHP dynamically accessing variable valuePHP动态访问变量值
【发布时间】:2011-12-02 12:40:26
【问题描述】:

我想动态访问变量的值,假设我有这个数组:

$aData = array(
  'test' => 123
);

打印test key 值的标准方法是:

print $aData['test'];

但是,如果我必须使用变量的字符串表示形式(出于动态目的)

$sItem = '$aData[\'test\']';

我怎样才能实现打印aData key 命名为test?下面提供的示例都不起作用

print $$sItem;
print eval($sItem);

解决办法是什么?

【问题讨论】:

    标签: php variables


    【解决方案1】:

    您的 eval 示例缺少返回值:

    print eval("return $sItem;");
    

    应该这样做:

    $aData['test'] = 'foo';
    
    $sItem = '$aData[\'test\']';
    
    print eval("return $sItem;"); # foo
    

    但不建议正常使用eval。你可以带着它进入地狱的厨房,因为 eval 是邪恶的。

    而只是解析字符串并返回值:

    $aData['test'] = 'foo';
    
    $sItem = '$aData[\'test\']';
    
    $r = sscanf($sItem, '$%[a-zA-Z][\'%[a-zA-Z]\']', $vName, $vKey);
    if ($r === 2)
    {
        $result = ${$vName}[$vKey];
    }
    else
    {
        $result = NULL;
    }
    
    print $result; # foo
    

    这也可以使用其他形式的正则表达式来完成。

    由于您的语法非常接近 PHP 并且实际上是它的一个子集,如果您想在使用 eval 之前验证输入,您可以采取一些替代方法。该方法是检查 PHP 令牌并只允许一个子集。这不会验证字符串(例如语法和是否实际设置了变量),但会使其更加严格:

    function validate_tokens($str, array $valid)
    {
        $vchk = array_flip($valid);
        $tokens = token_get_all(sprintf('<?php %s', $str));
        array_shift($tokens);
        foreach($tokens as $token)
            if (!isset($vchk[$token])) return false;
        return true;
    }
    

    您只需为该函数提供一组有效标记。这些是 PHP 令牌,在您的情况下是:

    T_LNUMBER (305) (probably)
    T_VARIABLE (309)
    T_CONSTANT_ENCAPSED_STRING (315)
    

    然后你就可以使用它,它也可以使用更复杂的键:

    $aData['test'] = 'foo';
    $aData['te\\\'[]st']['more'] = 'bar';
    
    $sItem = '$aData[\'test\']';
    $vValue = NULL;
    if (validate_tokens($sItem, array(309, 315, '[', ']')))
    {
        $vValue = eval("return $sItem;");
    }
    

    我在问题reliably convert string containing PHP array info to arrayanother answer中使用了这个。

    【讨论】:

    • @all: 谢谢大家,从我打算做的事情来看(大图),使用 eval 函数对我来说是最好的解决方案......我只是忘记使用 return
    【解决方案2】:

    如果您有(或可以获得)数组名称和键为单独的变量,则无需 eval:

    $aData = array(
      'test' => 123
    );
    
    $arrayname = 'aData';
    $keyname = 'test';
    
    print ${$arrayname}[$keyname]; // 123
    

    【讨论】:

      【解决方案3】:

      你可以像普通数组一样使用它:

      $key = "test";
      
      print $aData[$key];
      

      同样$aData 本身可以是更大数组存储中的条目。


      作为替代方案,使用正则表达式提取潜在的数组键并使用引用遍历匿名数组(应该在您的问题中提到,如果)是可能的。请参阅Set multi-dimensional array by key path from array values? 和类似主题。


      就我个人而言,我正在使用这样的构造来利用动态变量路径,例如varname[keyname](类似于 PHP 解释 GET 参数的方式)。这只是一个穿着羊皮的 eval (虽然不同意 eval 的危言耸听):

      $val = preg_replace("/^(\w)+(\[(\w+)])$/e", '$\1["\3"]', "aData[test]");
      

      【讨论】:

      • 除了您的意思可能是 print $aData[$key] 而不是 $aData[$test],这是迄今为止最好的解决方案。 +1
      • 如果您忽略所问的内容,那么这是最好的解决方案,是的! :)
      • +1。我认为这是唯一合理且可行的解决方案。无论其他人如何建议使用eval,他们都无法返回key test。可能一些正则表达式字符串解析可以实现提取密钥,但 eval 永远不会根据 OP 拥有字符串的方式来做到这一点。
      • @JanHančič 所问的是 key 而不是结果。 eval 示例都不能检索 key。 hakre 的字符串解析解决方案确实有效,但这是一个迂回的旅程。为什么你要把密钥分配给字符串变量的子字符串,然后必须遍历整个字符串来获取密钥,而你可以随时准备好密钥。
      • @JanHančič 也许,你可能是对的。如果问题是访问该值,那么您是对的。但是,我们将不得不等待 OP 的回复,看看我是否理解。 :)
      【解决方案4】:

      在您的情况下,唯一的解决方案是使用Eval()

      但是在这样做的时候请非常非常非常小心! Eval 将评估(并执行)您作为 PHP 传递给它的任何参数。因此,如果您向它提供来自用户的东西,那么任何人都可以在您的服务器上执行任何 PHP 代码,这无疑是一个大峡谷大小的安全漏洞!。

      编辑:您必须以某种方式在您的$sItem 变量中添加“打印”或“回声”。它要么必须在$sItem ($sItem = 'echo $aData[\'test\']';) 中,要么你必须像这样写你的Eval()Eval ( 'echo ' . $sData )

      【讨论】:

      • 根据他的问题是这样的。
      【解决方案5】:
      $sItem = '$aData[\'test\']';
      eval('$someVar = '.$sItem.';');
      echo $someVar;
      

      正如其他人已经解释的那样,请谨慎使用eval()

      【讨论】:

        【解决方案6】:

        你可以用这个方法

        function getRecursive($path, array $data) {
                // transform "foo['bar']" and 'foo["bar"]' to "foo[bar]"
                $path = preg_replace('@\[(?:"|\')(.+)(?:"|\')\]@Uis', '[\1]', $path);
        
                // get root
                $i = strpos($path, '[');
                $rootKey = substr($path, 0, $i);
                if (!isset($data[$rootKey])) {
                    return null;
                }
                $value = $data[$rootKey];
        
                $length = strlen($path);
                $currentKey = null;
                for (; $i < $length; ++$i) {
                    $char = $path[$i];
        
                    switch ($char) {
                        case '[':
                            if ($currentKey !== null) {
                                throw new InvalidArgumentException(sprintf('Malformed path, unexpected "[" at position %u', $i));
                            }
                            $currentKey = '';
                            break;
                        case ']':
                            if ($currentKey === null) {
                                throw new InvalidArgumentException(sprintf('Malformed path, unexpected "]" at position %u', $i));
                            }
        
                            if (!isset($value[$currentKey])) {
                                return null;
                            }
        
                            $value = $value[$currentKey];
                            if (!is_array($value)) {
                                return $value;
                            }
        
                            $currentKey = null;
                            break;
                        default:
                            if ($currentKey === null) {
                                throw new InvalidArgumentException(sprintf('Malformed path, unexpected "%s" at position %u', $char, $i));
                            }
                            $currentKey .= $char;
                            break;
                    }
                }
        
                if ($currentKey !== null) {
                    throw new InvalidArgumentException('Malformed path, must be and with "]"');
                }
        
                return $value;
            }
        

        【讨论】:

        • 这是一个好方法。然而,单个 preg_match_all 足以一次获取数组键,避免 [ 解析 ]
        • @mario 谢谢。请给我这样的,因为这样的键不能存在于数组中
        • 错过了两个+。您仍然需要探测密钥是否存在。这只是破解潜在密钥的一种更方便的方法。例如preg_match_all('/((?&lt;=^|\$)\w+|(?&lt;=\[[\'"])[^\'"]+(?=[\'"]\]))/') 之类的东西将foo["bar"] 分解为foobar
        • @mario,你测试了吗?它返回 foofoow+ 我自己修复 :)
        • 是的,测试过了。然后你忘了第二个+
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-06-04
        • 2011-11-04
        • 1970-01-01
        • 2017-07-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多