2011-12-01 16 views
10

我正在mysqli周围创建一个包装函数,以便我的应用程序不需要过度复杂的数据库处理代码。其中的一部分是使用mysqli :: bind_param()参数化SQL调用的一些代码。你可能知道,bind_param()需要引用。因为它是一个半通用的包装,我最终作出这一呼吁:我的PHP引用数组“神奇地”成为一组值......为什么?

call_user_func_array(array($stmt, 'bind_param'), $this->bindArgs); 

,我得到一个错误信息:

Parameter 2 to mysqli_stmt::bind_param() expected to be a reference, value given 

上面的讨论是福斯代尔那些谁也说:“你不要”在你的例子中完全不需要参考“。

我的“真实”的代码是不是有人想读一个比较复杂一点,所以我煮导致这一错误代码为以下(希望)说明性的例子:

class myclass { 
    private $myarray = array(); 

    function setArray($vals) { 
    foreach ($vals as $key => &$value) { 
     $this->myarray[] =& $value; 
    } 
    $this->dumpArray(); 
    } 
    function dumpArray() { 
    var_dump($this->myarray); 
    } 
} 

function myfunc($vals) { 
    $obj = new myclass; 
    $obj->setArray($vals); 
    $obj->dumpArray(); 
} 

myfunc(array('key1' => 'val1', 
      'key2' => 'val2')); 

问题似乎是,在myfunc()中,在对setArray()的调用和对dumpArray()的调用之间,$ obj-> myarray中的所有元素都不再是引用而是成为值。这可以通过查看输出很容易地看到:

array(2) { 
    [0]=> 
    &string(4) "val1" 
    [1]=> 
    &string(4) "val2" 
} 
array(2) { 
    [0]=> 
    string(4) "val1" 
    [1]=> 
    string(4) "val2" 
} 

注意,阵列是在这里输出的上半年“正确”的状态。如果这样做是有意义的,那么我可以在那时做我的bind_param()调用,它会起作用。不幸的是,在输出的后半部分中断了。请注意数组值类型上缺少“&”。

我的参考文献发生了什么?我怎样才能防止这种情况发生?当我真的不是语言专家时,我不喜欢称之为“PHP bug”,但是这可能是一个吗?这对我来说确实很奇怪。目前我正在使用PHP 5.3.8进行测试。


编辑:

由于超过一个人所指出的,解决方法是改变setArray()参考接受它的参数:

function setArray(&$vals) { 

我加入这个音符文件为什么这似乎工作。

PHP一般而言,尤其是mysqli,似乎对“参考”是什么有点奇怪的概念。注意这个例子:

$a = "foo"; 
$b = array(&$a); 
$c = array(&$a); 
var_dump($b); 
var_dump($c); 

首先,我敢肯定你想知道为什么我使用数组而不是标量变量 - 这是因为的var_dump()不显示是否标量的任何指示是一个参考,但它对阵列成员。

无论如何,在这一点上,$ b [0]和$ c [0]都是对$ a的引用。到现在为止还挺好。现在我们把我们的第一个扳手到作品中:

unset($a); 
var_dump($b); 
var_dump($c); 

$ B [0],$ C [0]都仍然是同样的事情引用。如果我们改变一个,两者都会改变。但是他们提到了什么?记忆中的一些未命名的位置。当然,垃圾收集可以确保我们的数据是安全的,并且会保持这种状态,直到我们停止引用它为止。

对于我们的下一个把戏,我们这样做:

unset($b); 
var_dump($c); 

现在$ C [0]仅存参考我们的数据。而且,哇!神奇的是,它不再是一个“参考”。不是通过var_dump()的度量,而是通过mysqli :: bind_param()的度量。

PHP manual表示在每条数据上都有一个单独的标志'is_ref'。然而,这个测试似乎表明,“is_ref”其实就相当于“(引用计数> 1)”

为了好玩,你可以按如下修改这个玩具例子:

$a = array("foo"); 
$b = array(&$a[0]); 
$c = array(&$a[0]); 

var_dump($a); 
var_dump($b); 
var_dump($c); 

注意所有三个数组在其成员上有参考标记,这支持'is_ref'在功能上等同于'(refcount> 1)'的想法。

为什么mysqli :: bind_param()首先会关心这种区别(或者它是call_user_func_array()......),但这似乎是我们“真正”需要确保的东西对于我们的call_user_func_array()调用中的$ this-> bindArgs的每个成员,引用计数至少为(请参阅post/question的开头部分)。最简单的方法(在这种情况下)是通过setArray()传递引用。


编辑:

对于额外的乐趣和游戏,我修改了原来的程序(这里没有显示)为它的等效离开setArray()传递的价值,并创建一个免费的额外阵列,bindArgsCopy,包含与bindArgs完全相同的东西。这意味着,是的,两个数组都包含对在第二次调用时解除分配的“临时”数据的引用。正如上面的分析预测的那样,这工作。这表明上面的分析不是var_dump()的内部工作(至少对我来说是一种解脱)的人工产物,它还表明它是引用计数的重要内容,而不是原始的“临时性”数据存储。

所以。我做了以下断言:在PHP中,为了call_user_func_array()(可能更多)的目的,说数据项是“参考”与说项目的引用计数大于或等于2相同(忽略PHP的内存优化同等数值标量)


文案注:我很想给马里奥网站信用的答案,因为他是第一个提出正确的答案,但因为他在评论中写道它,而不是一个实际的答案,我不能这样做:-(

+1

止跌” t'setArray()'方法还需要获取数组作为参考参数?否则,您只是创建对临时数组的引用。 – mario

+0

@mario:先生,您绝对正确,因为这确实解决了我的问题。这就引发了这个问题......为什么这样能解决问题?我希望对临时数组的引用仍然是一个引用,只是对由于引用本身而仅存在于内存中的某些内容的引用。我想我应该阅读关于PHP内存管理的内容,如果我能找到这样的文档。 –

+0

是的,把&vals放在setArray函数中 – malletjo

回答

4

传递数组作为参考:

function setArray(&$vals) { 
    foreach ($vals as $key => &$value) { 
     $this->myarray[] =& $value; 
    } 
    $this->dumpArray(); 
    } 

我的猜测(这可能是错误的一些细节,但希望大部分是正确的)至于为什么这使得你的代码按预期工作是当你作为一个值传递,一切都很酷,呼吁dumpArray()里面的setArray(),因为setArray()里面的$vals数组的引用依然存在。但是,当控制权返回到myfunc()时,那个临时变量就会消失,所有对它的引用都会消失。所以PHP在为它分配内存之前尽职地将数组改为字符串值而不是引用。但是,如果您将其作为参考从myfunc()中传递,那么setArray()正在使用对控制返回到myfunc()时生活的数组的引用。

2

在参数签名中添加&为我修复了它。这意味着该函数将接收原始数组的内存地址。

function setArray(&$vals) { 
// ... 
} 

CodePad

0

我刚刚在几乎完全相同的情况下遇到了同样的问题(我正在为自己的目的编写一个PDO包装)。我怀疑PHP一旦没有其他变量引用相同的数据就将PHP的引用更改为一个值,并且Rick在编辑时对您的问题的评论证实了这一疑问,所以非常感谢。

现在,别人谁遇到这样的事情,我相信我有这个简单的解决方案:简单地设置各相关数组元素对自身的引用传递数组call_user_func_array()之前。

我不确定内部发生了什么,因为它看起来应该不起作用,但元素再次成为引用(您可以使用var_dump()来查看),然后call_user_func_array()将参数传递给如预期的参考。即使数组元素仍然是一个引用,此修复程序似乎仍然有效,因此您不必首先检查。

在里克的代码,它会是这样的(一切后bind_param的第一个参数是引用,所以我跳过第一个和修复所有之后的那些的):

for ($i = 1, $count = count($this->bindArgs); $i < $count; $i++) { 
    $this->bindArgs[$i] = &$this->bindArgs[$i]; 
} 

call_user_func_array(array($stmt, 'bind_param'), $this->bindArgs); 
相关问题