开发者

Reference vs. value copy for PHP arrays?

开发者 https://www.devze.com 2023-01-13 05:49 出处:网络
I am missing something obvious.Here\'s a trivial piece of PHP, littered with debug echos: function echo_rows(&$res) {

I am missing something obvious. Here's a trivial piece of PHP, littered with debug echos:

function echo_rows(&$res) {
    $rows= array();
    while ($row= $res->fetch()) {
           echo $row['ccorID'] . "\r\n";
        $rows[]= $row;
           echo $rows[0]['ccorID'] . "\r\n";
    }

    echo "---.---\r\n";
    echo count($rows) . "\r\n";
    foreach($rows as $row) {
        echo $row['ccorID'] . "\r\n";
    }
    echo json_encode($rows);
}

Here's what I see in the response:

0
0
3
3
13
13
182
182
---.---
4
182
182
182
182

It seems perfectly clear to me that :-

  • I have 4 rows of data
  • $rows[]= $row; does NOT perform a value copy, despite what it says here http://uk.php.net/manual/en/language.types.array.php

Anyone got any idea what I need to do to get a 开发者_如何学运维copy of $row (which is an associative array)?

Thanks.

EDIT: Since many of you are so insistent to know what $res is, here is the class. I genuinely believe that this is more likely to confuse than enlighten (hence the omission from my OP).

class mysqlie_results {
    private $stmt;
    private $paramArray= array(); 
    private $assocArray= array(); 

    public function __construct(&$stmt) {
        $this->stmt= $stmt;
        $meta= $stmt->result_metadata(); 

        while ($colData= $meta->fetch_field()) {
            $this->paramArray[]= &$this->assocArray[$colData->name]; 
        }

        call_user_func_array(array($stmt,'bind_result'),$this->paramArray); 
        $meta->close(); 
    } 

    public function __destruct() {
        $this->stmt->free_result();
    } 

    public function fetch() { 
        return $this->stmt->fetch()? $this->assocArray : false;
    } 
} 


edit: Using object was only a guess. It turns out that the problem is caused by references as others have guessed before.

Since we don't know (yet) what $res is and what $res->fetch() actually returns let me try to replicate the behaviour.

<?php
$res = new Foo;
echo_rows($res);

function echo_rows(&$res) {
  // as provided in the question
}

class Foo extends ArrayObject {  
  protected $bar, $counter;

  public function fetch() {
    if ( ++$this->counter > 4 ) {
      return false;
    }
    else {
      $this->bar['ccorID'] = $this->counter;
      return $this->bar;
    }
  }

  public function __construct() {
    $this->bar = new ArrayObject;
    $this->counter = 0;
  }
}

prints

1
1
2
2
3
3
4
4
---.---
4
4
4
4
4
[{"ccorID":4},{"ccorID":4},{"ccorID":4},{"ccorID":4}]

The reason is, that all elements in $rows point to the same underlying object (see http://docs.php.net/language.oop5.references) since my class Foo always returns the same object and just modifies the state of this single object.


update: "what might the manual mean when it says "Use the reference operator to copy an array by reference"?"

Let's start with this script

<?php
$arr = array();
$s = '';

$b = array();
$b['foo'] = $arr;
$b['bar'] = $s;

$b['foo']['bar'] = 1;
$b['bar'] = 'xyz';

var_dump($arr, $s);

the output is

array(0) {
}
string(0) ""

i.e. $b['foo']['bar'] = 1; and $b['bar'] = 'xyz'; didn't change $arr and $s since the array contains copies of the values.

Now change the two assignments to use references

$b['foo'] = &$arr;
$b['bar'] = &$s;

and the output changes to

array(1) {
  ["bar"]=>
  int(1)
}
string(3) "xyz"

i.e. the elements are not copies.


PHP core developer Johannes Schlüter says Do not use PHP references. References in PHP are mostly a holdover from PHP 4. In PHP 5, references are not needed in most cases where you would be tempted to use them.

The PHP manual on Returning References says (emphasis is theirs):

Do not use return-by-reference to increase performance. The engine will automatically optimize this on its own. Only return references when you have a valid technical reason to do so.

If you do need to clone an array reference, then you can copy it by value this way:

$row = unserialize(serialize($row));


Let's take a closer look at

    while ($colData= $meta->fetch_field()) {
        $this->paramArray[]= &$this->assocArray[$colData->name]; 
    }

    call_user_func_array(array($stmt,'bind_result'),$this->paramArray);

There you have your reference(s) and that's causing the trouble. If you copy an array that contains a reference the result is still an array containing a reference.

Try

while ($row= $res->fetch()) {
  var_dump($row);

and you will see that $row also contains references. All those corresponding elements in paramArray, assocArray and $row actually reference the same value. And thus when you invoke return $this->stmt->fetch() it doesn't only affect paramArray but all those references, all the way down to $rows since $rows[]= $row; still only copies the array but does not "de-reference" the elements.


I'll bet that $res->fetch() is returning a reference for some reason... Try doing this to break the reference:

while ($row= $res->fetch()) {
    echo $row['ccorID'] . "\r\n";
    $rows[]= $row;
    echo $rows[0]['ccorID'] . "\r\n";
    unset($row);
}

(Noticed the added unset at the end)...


i would try array_merge(), but its not clear what the intention of this function is, quick fix just add an indexer because what you are doing is just copying the reference and on every row you just update where the reference is pointing to

0

精彩评论

暂无评论...
验证码 换一张
取 消