如何在PHP中将变量名作为字符串获取?

225

假设我有以下 PHP 代码:

$FooBar = "a string";

我需要一个像这样的函数:

print_var_name($FooBar);

输出结果如下:

FooBar

有什么方法可以实现这个吗?在PHP中是否可能实现?


10
如果你需要这个东西除了调试之外的其他用途,那么你正在做一些严重错误的事情。你的使用场景是什么? - troelskn
18
好问题。我需要相同的东西来进行调试。 - takeshin
21
+1 - 我需要这个来自动从一个模型 PHP 对象生成 XML 或 JSON 响应。不得不将对象包装在另一个命名为 rootName => modelObject 的数组中只会给响应增加不必要的深度。希望这能够被集成到语言的运行时反射能力中。 - Anurag
4
我也需要在一个日志函数中实现这个功能。我希望能够像下面这样记录:log($didTheSystemBlowUp); 在日志文件中显示为:$didTheSystemBlowUp = 'not yet, but very soon'; - SeanDowney
4
在同时调用多个变量的var_dump()函数时,如果你不想手动输出变量名以区分各个vardump的输出结果,这时这个技巧可能会很有用。 - Handsome Nerd
显示剩余3条评论
27个回答

56

我也无法想到一种高效的方法,但我想出了这个。它可以处理以下有限的用途。

耸肩

<?php

function varName( $v ) {
    $trace = debug_backtrace();
    $vLine = file( __FILE__ );
    $fLine = $vLine[ $trace[0]['line'] - 1 ];
    preg_match( "#\\$(\w+)#", $fLine, $match );
    print_r( $match );
}

$foo = "knight";
$bar = array( 1, 2, 3 );
$baz = 12345;

varName( $foo );
varName( $bar );
varName( $baz );

?>

// Returns
Array
(
    [0] => $foo
    [1] => foo
)
Array
(
    [0] => $bar
    [1] => bar
)
Array
(
    [0] => $baz
    [1] => baz
)

它的工作原理是基于调用该函数的代码行,在那里找到你传递的参数。我想它可以扩展到处理多个参数,但是像其他人说的那样,如果你能更好地解释情况,另一个解决方案可能会更好。


3
这个可以工作,但是只有当函数varName在与要查找的变量相同的文件中定义时才有效。 - rubo77
1
在这里,您可以找到一个更好的实现,它可以在多个包含文件中工作:https://dev59.com/enjZa4cB1Zd3GeqPh8WE#19788805 - rubo77

50

你可以使用get_defined_vars()函数来查找与你要查找名称的变量具有相同值的变量名称。显然,这种方法并不总是有效,因为不同的变量通常具有相同的值,但这是我能想到的唯一方法。

编辑:get_defined_vars()似乎无法正常工作,它返回'var',因为$var在函数本身中被使用。$GLOBALS似乎可以工作,所以我已经将其更改为那个。

function print_var_name($var) {
    foreach($GLOBALS as $var_name => $value) {
        if ($value === $var) {
            return $var_name;
        }
    }

    return false;
}

编辑:明确一点,PHP 中没有很好的方法来完成这个任务,这也许是因为你不应该这样做。可能有更好的方法来实现你要做的事情。


2
啊,速度有点慢;-) 我也是这么想的,但是使用 $GLOBALS 代替。那么对于相等的标量值($a = 'foo'; $b = 'foo'; assert($a === $b);),身份比较将产生 true 吗? - Argelbargel
2
有很多情况下,这段代码的行为不会如预期那样。 - troelskn
139
这段代码非常错误。检查变量是否与通过VALUE发送的变量相同是一个非常愚蠢的想法。在任何给定的时刻,无数个变量都是NULL,也有无数个变量被设为1。这太疯狂了。 - Alex Weinstein
1
尝试运行你的代码:$a = 10; $b = 10;print_r(print_var_name($b)); - Ashish Rana
尽管在许多情况下这绝对不起作用,但是对于变量名更冗长的情况,它确实非常有效,而这正是良好编码实践所应反映的。$a和$b是坏的变量名;有用,但不好。我发现这个小实用程序函数在使用较长的变量名时非常有用,并因此而+1。 - WhiteRau
显示剩余3条评论

34
你可以考虑改变你的方法,使用一个可变的变量名吗?
$var_name = "FooBar";
$$var_name = "a string";

然后你就可以了
print($var_name);

获取
FooBar

这是指向PHP变量变量手册的链接。

50
我曾经使用过一个广泛使用变量变量的系统。让我警告你,它会变得非常混乱,非常快! - Icode4food
4
大多数情况下,用户希望获取变量的名称和值。考虑“function debugvar($varname)”并打算调用它“debugvar('foo')”,那么调试将显示“foo = 123”。但是使用变量变量时,他们会得到“foo未定义”的结果。 - gcb
只看看你实际上在做什么。就在你的例子中,你实际上创建了一个变量 $FooBar,其值为 a string,请阅读你的手册。在我看来,这太可怕了。你从未给变量 $FooBar 赋值,但它却存在着。痛苦 - Toskan

28
没有人提到为什么这很困难和不明智的根本原因:
- "变量"只是指向其他东西的符号。在 PHP 中,它内部指向一个称为 "zval" 的东西,可以同时用于多个变量,因为它们有相同的值(PHP 实现了一种叫做 "写时复制" 的东西,所以 $foo = $bar 不需要立即分配额外的内存),或者因为它们已经被引用赋值(或传递给函数)(例如 $foo =& $bar)。所以 zval 没有名称。 - 当您将参数传递给函数时,您正在创建一个新变量(即使是引用也是如此)。您可以传递一些匿名内容,比如 "hello",但是一旦进入您的函数,它就是您命名的任何变量。这对于代码分离相当基本:如果函数依赖于变量以前的名称,它将更像一个 goto 而不是一个正确的分离函数。 - 全局变量通常被认为是一个坏主意。这里的很多示例假定您要 "反映" 的变量可以在 $GLOBALS 中找到,但是只有当您的代码结构糟糕并且变量没有被限定在某些函数或对象中时,才会是真的。 - 变量名称有助于程序员阅读其代码。重命名变量以更好地适应其目的是一种非常常见的重构实践,整个重点在于这不会有任何区别。
现在,我理解对于调试的愿望(尽管某些提议的用法远远超出了这个范畴),但作为一种广义解决方案,它并不像您想象的那样有用:如果您的调试函数说您的变量名为 "$file",那么这仍然可能是您代码中几十个 "$file" 变量之一,或者一个您称为 "$filename" 但正在传递给一个参数名为 "$file" 的函数的变量。
一个更有用的信息是调试函数从您的代码中哪里调用。由于您可以在编辑器中快速找到它,所以您可以看到要为自己输出的变量,甚至可以一次性将整个表达式传递给它(例如 debug('$foo + $bar = ' . ($foo + $bar)))。
为此,您可以在调试函数顶部使用以下代码片段:
$backtrace = debug_backtrace();
echo '# Debug function called from ' . $backtrace[0]['file'] . ' at line ' . $backtrace[0]['line'];

已经有一些好的答案了,所以即使面对现实,这只是持悲观态度。 - a20
@a20 所有的答案都有很大的限制,关于它们何时可以使用,何时会出现问题;没有一个简单的查找任何变量到其名称的方法,因为这实际上是不可能的。一些方法为了调试目的而进行了很多奇怪的反射,这是可以的;然而,我的观点是你最好只输出行号并自己查找源代码行 - 或者使用像XDebug这样的交互式调试器。 - IMSoP

26

这正是您想要的——它是一个即用型的“复制并粘贴”函数,可回显给定变量的名称:

function print_var_name(){
    // read backtrace
    $bt   = debug_backtrace();
    // read file
    $file = file($bt[0]['file']);
    // select exact print_var_name($varname) line
    $src  = $file[$bt[0]['line']-1];
    // search pattern
    $pat = '#(.*)'.__FUNCTION__.' *?\( *?(.*) *?\)(.*)#i';
    // extract $varname from match no 2
    $var  = preg_replace($pat, '$2', $src);
    // print to browser
    echo '<pre>' . trim($var) . ' = ' . print_r(current(func_get_args()), true) . '</pre>';
}

使用方法:print_var_name($FooBar)

打印:FooBar

提示:
现在你可以重新命名该函数,它仍将正常工作,并且可以在一行中多次使用该函数!感谢@Cliffordlife
我还添加了更好的输出!感谢@Blue-Water


3
谢谢您提供这个信息。我稍微修改了$pat的行,变成$pat = '#(.*)'.__FUNCTION__.' *?\( *?(.*) *?\)(.*)#i'; 这样我就不用关心这个调试函数的名字是什么,而且我可以准确地获取传入函数中的参数,比如$hello或者"hello"(在同一行中,我删除了匹配传入变量的$)。 - Cliffordlife
2
很棒的代码!谢谢!但是,它似乎并不在所有情况下都有效。在我的Ubuntu 18.04上测试使用PHP 7.2.19时,如果在同一行代码中多次使用,则无论是在一个表达式中还是在分开的表达式中使用,它都无法正常工作,因为它会返回该行最后一个变量的名称。如果在同一表达式中但在不同行上使用它,则可以正常工作。在不同行上使用不同表达式也可以正常工作。 - Matty
1
此外,该函数应该在一行中,不使用“var_dump”,而是结合使用print_var_name, echo, var_dump,输出为$variable); echo ' '; var_dump($variable - Bruno
1
非常适合调试。 - JayJay123
2
这是一个非常有创意的解决方案。功能很棒!此外,OP没有特别要求,因为我想他在为问题隔离做准备,但我想OP也希望输出值。因此,为了避免编写更多客户端代码,可以将函数的最后一行替换为:echo'<pre>'.trim($var).'='.print_r(current(func_get_args()), true).'</ pre>'; 这将输出FooBar = 字符串,并考虑到了数组。 - user2607743

16

在PHP.net上,Lucas提供了一种可靠的方法来检查变量是否存在。他的示例中,他遍历变量的全局变量数组(或作用域数组)的副本,将值更改为随机生成的值,并检查复制的数组中是否存在生成的值。

function variable_name( &$var, $scope=false, $prefix='UNIQUE', $suffix='VARIABLE' ){
    if($scope) {
        $vals = $scope;
    } else {
        $vals = $GLOBALS;
    }
    $old = $var;
    $var = $new = $prefix.rand().$suffix;
    $vname = FALSE;
    foreach($vals as $key => $val) {
        if($val === $new) $vname = $key;
    }
    $var = $old;
    return $vname;
}

那么尝试:

$a = 'asdf';
$b = 'asdf';
$c = FALSE;
$d = FALSE;

echo variable_name($a); // a
echo variable_name($b); // b
echo variable_name($c); // c
echo variable_name($d); // d

一定要查看他在PHP.net上的帖子:http://php.net/manual/en/language.variables.php


你如何以数组形式获取当前作用域? - Sebastián Grignoli
不错!对我来说唯一缺少的是在foreach中找到相等时的break;,并且我创建了可用的基本结构,以便在函数内定义变量时也可以自动获取变量名。如果您经常复制和粘贴或有更好的改进想法,请使用variable_name($variable, (empty(__FUNCTION__) ? false : get_defined_vars())); - odie2
这可能是完成任务的最快、最干净的方法,调试可能包括性能问题。该函数应该在foreach循环中直接返回,而不仅仅是赋值而没有中断。鉴于GLOBALS可能很大,这可以大大提高性能。 - John

15
我为了调试而制作了一个检查函数。它类似于具有类固醇效果的print_r(),就像Krumo一样,但对于对象更加有效。我想添加变量名称检测,并且灵感来自于Nick Presta在此页面上的帖子。它可以检测传递的任何表达式,不仅仅是变量名称。
以下是仅作为包装器函数的内容,用于检测传递的表达式。适用于大多数情况。如果您在同一行代码中多次调用该函数,则无法正常工作。
这个可以良好运行: die(inspect($this->getUser()->hasCredential("delete")));
inspect()是将检测传递的表达式的函数。
我们得到:$this->getUser()->hasCredential("delete")
function inspect($label, $value = "__undefin_e_d__")
{
    if($value == "__undefin_e_d__") {

        /* The first argument is not the label but the 
           variable to inspect itself, so we need a label.
           Let's try to find out it's name by peeking at 
           the source code. 
        */

        /* The reason for using an exotic string like 
           "__undefin_e_d__" instead of NULL here is that 
           inspected variables can also be NULL and I want 
           to inspect them anyway.
        */

        $value = $label;

        $bt = debug_backtrace();
        $src = file($bt[0]["file"]);
        $line = $src[ $bt[0]['line'] - 1 ];

        // let's match the function call and the last closing bracket
        preg_match( "#inspect\((.+)\)#", $line, $match );

        /* let's count brackets to see how many of them actually belongs 
           to the var name
           Eg:   die(inspect($this->getUser()->hasCredential("delete")));
                  We want:   $this->getUser()->hasCredential("delete")
        */
        $max = strlen($match[1]);
        $varname = "";
        $c = 0;
        for($i = 0; $i < $max; $i++){
            if(     $match[1]{$i} == "(" ) $c++;
            elseif( $match[1]{$i} == ")" ) $c--;
            if($c < 0) break;
            $varname .=  $match[1]{$i};
        }
        $label = $varname;
    }

    // $label now holds the name of the passed variable ($ included)
    // Eg:   inspect($hello) 
    //             => $label = "$hello"
    // or the whole expression evaluated
    // Eg:   inspect($this->getUser()->hasCredential("delete"))
    //             => $label = "$this->getUser()->hasCredential(\"delete\")"

    // now the actual function call to the inspector method, 
    // passing the var name as the label:

      // return dInspect::dump($label, $val);
         // UPDATE: I commented this line because people got confused about 
         // the dInspect class, wich has nothing to do with the issue here.

    echo("The label is: ".$label);
    echo("The value is: ".$value);

}

下面是inspector函数(以及我的dInspect类)的示例:

http://inspect.ip1.cc

该页面上的文本为西班牙语,但代码简洁易懂。


1
这不是依赖于您是否安装了NuSphere调试器吗? - Mawg says reinstate Monica
我在那里发布了这段代码的简化版本。此外,我修改了这个答案。现在它应该可以在每个PHP5实现上运行。 - Sebastián Grignoli
这种创新就是为什么每次我看到人们说“不可能做到”或“不可能”的时候,我都会微笑,包括在这种情况下的Rasmus本人。向Sebastián和其他可能对这个答案做出贡献的人致敬。 - Night Owl
1
谢谢“夜猫子”,但我坚持认为这不是万无一失的(正如答案指出的那样,如果在一行代码中多次调用我的“inspect()”函数,它将失败!)。我永远不会在生产中使用它。它只是一个调试检查器函数,永远不应该到达生产服务器。 - Sebastián Grignoli

8

来自php.net

@Alexandre - 简短解决方案

<?php
function vname(&$var, $scope=0)
{
    $old = $var;
    if (($key = array_search($var = 'unique'.rand().'value', !$scope ? $GLOBALS : $scope)) && $var = $old) return $key;  
}
?>

@Lucas - 用法

<?php
//1.  Use of a variable contained in the global scope (default):
  $my_global_variable = "My global string.";
  echo vname($my_global_variable); // Outputs:  my_global_variable

//2.  Use of a local variable:
  function my_local_func()
  {
    $my_local_variable = "My local string.";
    return vname($my_local_variable, get_defined_vars());
  }
  echo my_local_func(); // Outputs: my_local_variable

//3.  Use of an object property:
  class myclass
  {
    public function __constructor()
    {
      $this->my_object_property = "My object property  string.";
    }
  }
  $obj = new myclass;
  echo vname($obj->my_object_property, $obj); // Outputs: my_object_property
?>

5
许多回复质疑这个的实用性。然而,获取变量的引用可能非常有用。特别是在对象和$this的情况下。我的解决方案适用于对象及其属性定义的对象:
function getReference(&$var)
{
    if(is_object($var))
        $var->___uniqid = uniqid();
    else
        $var = serialize($var);
    $name = getReference_traverse($var,$GLOBALS);
    if(is_object($var))
        unset($var->___uniqid);
    else
        $var = unserialize($var);
    return "\${$name}";    
}

function getReference_traverse(&$var,$arr)
{
    if($name = array_search($var,$arr,true))
        return "{$name}";
    foreach($arr as $key=>$value)
        if(is_object($value))
            if($name = getReference_traverse($var,get_object_vars($value)))
                return "{$key}->{$name}";
}

以上是示例:

class A
{
    public function whatIs()
    {
        echo getReference($this);
    }
}

$B = 12;
$C = 12;
$D = new A;

echo getReference($B)."<br/>"; //$B
echo getReference($C)."<br/>"; //$C
$D->whatIs(); //$D

3
从上面的答案中适用于多个变量,性能良好,只需进行一次$GLOBALS扫描。
function compact_assoc(&$v1='__undefined__', &$v2='__undefined__',&$v3='__undefined__',&$v4='__undefined__',&$v5='__undefined__',&$v6='__undefined__',&$v7='__undefined__',&$v8='__undefined__',&$v9='__undefined__',&$v10='__undefined__',&$v11='__undefined__',&$v12='__undefined__',&$v13='__undefined__',&$v14='__undefined__',&$v15='__undefined__',&$v16='__undefined__',&$v17='__undefined__',&$v18='__undefined__',&$v19='__undefined__'
) {
    $defined_vars=get_defined_vars();

    $result=Array();
    $reverse_key=Array();
    $original_value=Array();
    foreach( $defined_vars as $source_key => $source_value){
        if($source_value==='__undefined__') break;
        $original_value[$source_key]=$$source_key;
        $new_test_value="PREFIX".rand()."SUFIX";
        $reverse_key[$new_test_value]=$source_key;
        $$source_key=$new_test_value;

    }
    foreach($GLOBALS as $key => &$value){
        if( is_string($value) && isset($reverse_key[$value])  ) {
            $result[$key]=&$value;
        }
    }
    foreach( $original_value as $source_key => $original_value){
        $$source_key=$original_value;
    }
    return $result;
}


$a = 'A';
$b = 'B';
$c = '999';
$myArray=Array ('id'=>'id123','name'=>'Foo');
print_r(compact_assoc($a,$b,$c,$myArray) );

//print
Array
(
    [a] => A
    [b] => B
    [c] => 999
    [myArray] => Array
        (
            [id] => id123
            [name] => Foo
        )

)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接