php_nub_qq
php_nub_qq

Reputation: 16045

PHP references not working as I expect them to

Basically how I understand references work is

$a = 5;
$b = &$a;
$a = 10;
echo $b; // 10;

However in this bit of code I'm getting unexpected (for me, which probably has an explanation) result

class Room {
    
    private $users = array();
    
    public function addUser(&$user){
        $this->users[] = $user;
    }
}

$users = array(
    1 => 'Tom',
    2 => 'Hank',
    3 => 'Sam',
    4 => 'John'
);

$room = new Room();
$room->addUser($users[1]);
$room->addUser($users[3]);

unset($users[3]);

echo "<pre>" . print_r($room, true) . "</pre>";
echo "<pre>" . print_r($users, true) . "</pre>";

I expect, after unsetting $users[3], the only user inside of $room to be Tom, but that is not the case, both Tom and Sam are present in the object. Why is unset not affecting the object's property?

EDIT:

Even if I take things a step further with the example and create a class User the effect is still the same

class Room {
    
    private $users = array();
    
    public function addUser(&$user){
        $this->users[] = $user;
    }
}

class User {
    
    public $name;
    
    function __construct($name){
        $this->name = $name;
    }
}

$users = array(
    1 => new User('Tom'),
    2 => new User('Hank'),
    3 => new User('Sam'),
    4 => new User('John')
);

$room = new Room();
$room->addUser($users[1]);
$room->addUser($users[3]);

unset($users[3]);

echo "<pre>" . print_r($room, true) . "</pre>";
echo "<pre>" . print_r($users, true) . "</pre>";

Upvotes: 2

Views: 268

Answers (5)

xayer
xayer

Reputation: 479

You can change a value of arrays, like this:

CODE:

    private $users = array();

    public function addUser(&$user){
        $this->users[] = &$user;
    }
}

$users = array(
    1 => 'Tom',
    2 => 'Hank',
    3 => 'Sam',
    4 => 'John'
);

$room = new Room();
$room->addUser($users[1]);
$room->addUser($users[3]);

$users[3] = "AAA123";


echo "<pre>" . print_r($room, true) . "</pre>";
echo "<pre>" . print_r($users, true) . "</pre>";

OUTPUT:

Room Object
(
    [users:Room:private] => Array
        (
            [0] => Tom
            [1] => AAA123
        )

)

Array
(
    [1] => Tom
    [2] => Hank
    [3] => AAA123
    [4] => John
)

But delete it's not possible this way... I don't know how to explain, so just give example:

$a = 10;
$b = &$a;
unset($a);
echo $b; // 10

Then you deleting variable name, you not delete zval(container), until refcount reach 0... then "Garbage Collection" do all work and delete zval...

So method unset() remove variable name only in this case...

Upvotes: 0

y o
y o

Reputation: 1163

Unset operates on symbols, not reference targets.

That is why using unset on an undefined variable doesn't raise any kind of error.

$a = 10;
$b = &$a;
unset($b); // forget the name "$b" exists.
echo $a; // 10

If you want to unset it in both places, you have to assign null to one of the variables. This is a "hard unset", as opposed to a "soft unset" which is what you are currently doing.

Also you are not assigning a reference, you're assigning a copy.

$this->users[] = &$user;

Upvotes: 4

I think there's a slight logical problem between your desired effect and the way you try to do it.

If I understand correctly, you want to assign users to a container, then unsetting one of those user in a way that it will also be unsetted in your container. This

unset($users[3]);

unsets the value of the fourth element of your users array.

if we did $user[3] = 'foo'; the value contained in the corresponding container's entry will be set to 'foo' as well, but the container's index key itself will not get unset, or affected by the reference, because it is not part of the referenced value

If you want to unset the user, either you keep track of which index key is assigned to which user in your container and then delete users with this index key, or you set the value of $users[3] to null (or whatever suits your needs) and skip the null values when dealing with your container

Upvotes: 0

Pedro Amaral Couto
Pedro Amaral Couto

Reputation: 2115

Reference Counting Basics :

A PHP variable is stored in a container called a "zval". A zval container contains, besides the variable's type and value, two additional bits of information. The first is called "is_ref" and is a boolean value indicating whether or not the variable is part of a "reference set". (...) Since PHP allows user-land references, as created by the & operator, a zval container also has an internal reference counting mechanism to optimize memory usage. This second piece of additional information, called "refcount", contains how many variable names (also called symbols) point to this one zval container.

(...)

Variable containers get destroyed when the "refcount" reaches zero. The "refcount" gets decreased by one when any symbol linked to the variable container leaves the scope (e.g. when the function ends) or when unset() is called on a symbol.

Example with arrays:

<?php
$a = array(
    0 => 'aaa',
    1 => 'bbb',
    2 => 'ccc',
);
debug_zval_dump($a); 
// ... string(3) "bbb" refcount(1) ...

$b = array();
$b[0] = &$a[0];
$b[1] = &$a[1];

$a[1] = 'ddd';
debug_zval_dump($a);
// ... &string(3) "bbb" refcount(2) ...
debug_zval_dump($b);
// ... &string(3) "bbb" refcount(2) ...

unset($a[1]);
debug_zval_dump($a);
/*
 array(2) refcount(2){
  [0]=>
  &string(3) "aaa" refcount(2)
  [1]=>
  &string(3) "ddd" refcount(2)
}
 */
debug_zval_dump($b);
// ... string(3) "ddd" refcount(1) ...

var_dump($a);
/*
array (size=2)
  0 => &string 'aaa' (length=3)
  2 => string 'ccc' (length=3)
*/
var_dump($b);
/*
 array (size=2)
  0 => &string 'aaa' (length=3)
  1 => string 'ddd' (length=3)
 */

Upvotes: 1

Luigi Belli
Luigi Belli

Reputation: 597

Be careful.

You are passing to addUser() a reference to the string 'Tom' allocated while building the array $users.

First, addUser() should read $this->users[] =& $user;, otherwise you will be copying the value into $this->users[] instead of sharing the reference.

Now, both $users and Room::$users share the same objects, however unset($users[3]) removes the element mapped by the index 3 from the array, it does not destroy the mapped object.

Upvotes: -1

Related Questions