78

What do "=&" / "&=" operators in PHP mean? Where can I read information about them?

Searching Google doesn't help.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 3
    `=&` is NOT a "combined operator". Here is a post that explains why you should never write `=&`: https://stackoverflow.com/a/63914758/2943403 – mickmackusa Sep 16 '20 at 09:37
  • Related question with some more attempts to explain references: https://stackoverflow.com/q/1768343/157957 – IMSoP Aug 14 '22 at 09:53

2 Answers2

103

$a &= $b is short for $a = $a & $b which is the bitwise-and operator.

$a =& $b assigns $a as a reference to $b.

NikiC
  • 100,734
  • 37
  • 191
  • 225
moo
  • 7,619
  • 9
  • 42
  • 40
  • 12
    I don't think there's a `=&` operator. It's a combination of `=` (assignment) and unary `&` (referencing) operators. – Michael Krelin - hacker Sep 02 '09 at 12:46
  • Also on php.net: See the "References Explained" section. – GZipp Sep 02 '09 at 12:48
  • 3
    @hacker: Using a common name for the special character usually works well, e.g. "ampersand php". – GZipp Sep 02 '09 at 13:00
  • 1
    http://www.php.net/manual/en/language.references.whatdo.php more explanations on references. – Colin Hebert Aug 19 '10 at 22:01
  • 5
    Nitpicking: "`$a =& $b` assigns $a as a reference to $b" is wrong, since $a doesn't point to $b (or vice versa), but both point to the same place. A subtle but important difference. – Jürgen Thelen Jul 12 '13 at 09:27
  • @JürgenThelen No, `$a =& $b` literally assigns `$a` as a reference to `$b`. If the value of `$a` is changed, the value of `$b` will change. This differs from "both pointing to the same place" when it comes to objects: I could do `$a = $b = new AnObject()`, and both variables would point to the same place; however, changing where one points to would not change where the other points. That is, `$a = null` would not make `$b = null`, whereas with `=&`, it would. – Zenexer Aug 10 '13 at 18:20
  • 2
    @MichaelKrelin-hacker There's no dereference operator in PHP, per se. `&` is context-sensitive. You are right in that `=&` is not a single operator; rather, `&` is an operator when preceded by `=`, with whitespace permitted. Or perhaps you could say that `&` modifies the `=` operator. – Zenexer Aug 10 '13 at 20:16
  • @MichaelKrelin-hacker you are absolutely right and it is confusing why php.net documents them like that. It should be written as `$a = &$b;` for clarity – theking2 May 09 '22 at 17:30
  • @theking2 Although it's possible to put a space in between the two characters, it is *not* possible to describe each as a separate operator. The operation is "assign by reference", or "add to reference set", so writing it without a space makes a lot of sense. – IMSoP Aug 14 '22 at 09:37
  • 1
    @Zenexer "Both point to the same place" in the sense that *whichever name you use*, you are writing to the same variable. There is no distinction between the "reference" and the "target", the two variables are simply names bound together in a "reference set". If you bind three names together as references, you can unset any one of them and the other two will continue to be bound together, regardless of what order you assigned them in. – IMSoP Aug 14 '22 at 09:51
  • @IMSoP Yes, that's the point I was trying to get across. Unfortunately, it's difficult to articulate, as most other programming languages don't really have an equivalent construct. You've got two aliases for the same variable. By "assigns $a as a reference to $b", I mean $a (the identifier) becomes a reference to $b (the variable)--I definitely could've made that clearer. – Zenexer Aug 16 '22 at 17:50
  • @Zenexer I think that's the point that Jürgen Thelen was trying to get across as well - the identifiers $a and $b both end up pointing at the same variable (which has no name). – IMSoP Aug 16 '22 at 18:37
  • @IMSoP : AFAIK there is no difference between `=&` and `= &` as the LALR(1) parser will split between `=` and `&` as there is no token `=&` in the language. – theking2 Aug 23 '22 at 15:36
  • 1
    @theking2 If you want to get technical, the parser will emit an AST node of `ZEND_AST_ASSIGN_REF` when it sees the token sequence `variable '=' ampersand variable` but `ZEND_AST_ASSIGN` when it sees `variable '=' expr`. Neither "token" nor "AST node" maps perfectly to "operator", but importantly there is _not_ an AST node for "variable reference", and `ampersand variable` is not a valid expansion of `expr` on its own. That's what I mean by them being one combined *operation*, not something you can break down into two separate parts. It just happens to be an operation with quite flexible syntax – IMSoP Aug 23 '22 at 16:27
  • 1
    @theking2 In case you're wondering, `ampersand` is only a named token due to a messy case when parsing "intersection types" which requires some lookahead in the tokenizer. In the PHP 8.0 parser, the rule is just `variable '=' '&' variable` - https://github.com/php/php-src/blob/php-8.0.0/Zend/zend_language_parser.y#L1023 Before the AST step was added, the two rules compiled directly to different C functions, `zend_do_assign` and `zend_do_assign_ref`: https://github.com/php/php-src/blob/php-5.0.0/Zend/zend_language_parser.y#L544 Again, no "unary `&`" in sight. – IMSoP Aug 23 '22 at 16:47
  • Thanks @IMSoP this is very surprising! In that case you are right of course (why isn't `"&" variable` an expr I'm wonderin. I guess this __does__ optimize performance) – theking2 Aug 30 '22 at 09:36
  • @theking2 If `"&" variable` was a valid `expr` then you would be able to write `1 + &$z` which wouldn't make any sense - on its own, the `&$z` has no meaning, it doesn't "create a reference" or anything of the sort. The places you can use `&` are all distinct operations with the exact meaning dependent on context - you can assign by reference, pass by reference, return by reference, etc. The syntax looks similar, but it should not be read as a "unary `&` operator", just a convention that `&` means "by reference". – IMSoP Aug 30 '22 at 09:58
  • @IMSoP Comming from a c(++) background this is so counterintative. In C it would be no problem to do `&a + 1;` The effect of `$a1 =& $b1;` and `$a2 = &$b2;` in PHP is quite the same. – theking2 Aug 30 '22 at 10:19
  • 1
    @theking2 There is no "reference type" in PHP, which can be passed as an operand to other operators. In PHP, `$a =& $b;` *is* just a different way of spelling `$a = &$b;`, but it's arguably a *clearer* way of spelling it, because it is the *assignment* that is happening "by reference", not just one of the variables. It is importantly *not* the same as the equivalent in C, where you can separate it into "make a reference", and then a normal assignment, so `a = &b` would be a logical way to space it out. – IMSoP Aug 30 '22 at 10:38
50

=&

$a =& $b turns $a into an alias for $b. If the value or reference of $a is changed, the value or reference of $b will change accordingly.

This differs from "both pointing to the same place" when it comes to objects: I could do $c = $d = new AnObject(), and both variables would point to the same place; however, changing where one points would not change where the other points. That is, $c = null would not make $d = null. In the case of $a =& $b, however, $a = null would make $b = null.

Note: Officially, aliases are actually called references. The official terminology is a bit of a misnomer and is certainly ambiguous, so I've opted to use the term "alias" instead. For documentation, see php.net.

Uses and effects

With scalar values, =& is sort of like wrapping the value in an object, so that you can change the value universally among several variables. With types that are normally passed by reference (objects), =& provides a reference to a reference.

I tend to use =& when I'm working with associative arrays. Rather than rewriting $foo['bar']['foobar'] several times over, I can create an alias: $foobar =& $foo['bar']['foobar']. These even works if the index doesn't exist yet. If $foo['bar']['foobar'] doesn't exist, then isset($foobar) will be false. It's better than using a plain old variable, because I can create the alias before testing for the existence of the key without triggering an error.

Just be sure to unset (unset($foobar)) the alias when you're done. Otherwise, if you reuse the variable name later, you'll end up overwriting whatever the alias was pointing to.

You can use aliases in other ways, too--they're not limited to assignments. They work with:

  • foreach loops: foreach ($a as &$b) Assigning to $b will overwrite the corresponding value in $a. Unset $b when you're done, or you'll run into weird problems!
  • function/method parameters: function foobar(&$a) Assigning to $a within foobar will change whatever variable the caller passed as $a.
  • function/method return values: function &foobar() Whatever is returned can be modified by the caller; this is useful for passing around aliases. It's also easy to abuse.
  • arrays: $a = array(&$b) Any changes to $a[0] will now affect $b, including assignments.
  • call_user_func_array: call_user_func('foobar', array(&$a)) Assuming foobar takes a single alias parameter, foobar can now modify $a. This allows you to call functions/methods with alias parameters using call_user_func_array.

Examples

Scalars

$original = 1;
$copy = $original;
$reference =& $original;
// All three variables == 1.

$reference = 2;
// $original == 2, $reference == 2, $copy == 1

$original = 3;
// $original == 3, $reference == 3, $copy == 1

$copy = 4;
// $original == 3, $reference == 3, $copy == 4

Objects

#!/usr/bin/env php
<?php
class Object
{
        private $properties;

        public function __construct(array $properties = array())
        {
                $this->properties = $properties;
        }

        public function __isset($key)
        {
                return isset($this->properties[$key]);
        }

        public function __unset($key)
        {
                unset($this->properties[$key]);
        }

        public function __get($key)
        {
                return isset($this->$key) ? $this->properties[$key] : null;
        }

        public function __set($key, $value)
        {
                $this->properties[$key] = $value;
        }

        public function __toString()
        {
                return print_r($this->properties, true);
        }
}

function print_vars()
{
        global $original, $ref, $refref;

        echo
                '$original: ', $original,
                '$ref: ', $ref,
                '$refref: ', $refref,
                PHP_EOL;
}

$original = new Object(array('a' => 1, 'b' => 2, 'c' => 3));
$ref = $original;
$refref =& $original;
print_vars();
/*
$original: Array
(
    [a] => 1
    [b] => 2
    [c] => 3
)
$ref: Array
(
    [a] => 1
    [b] => 2
    [c] => 3
)
$refref: Array
(
    [a] => 1
    [b] => 2
    [c] => 3
)
*/

$original->a = 'duck';
$ref->b = 'moose';
$refref->c = 'cow';
print_vars();
/*
$original: Array
(
    [a] => duck
    [b] => moose
    [c] => cow
)
$ref: Array
(
    [a] => duck
    [b] => moose
    [c] => cow
)
$refref: Array
(
    [a] => duck
    [b] => moose
    [c] => cow
)
*/

// This carries over to $refref, but not $ref.
$original = new Object(array('x' => 1, 'y' => 2, 'z' => 3));
print_vars();
/*
$original: Array
(
    [x] => 1
    [y] => 2
    [z] => 3
)
$ref: Array
(
    [a] => duck
    [b] => moose
    [c] => cow
)
$refref: Array
(
    [x] => 1
    [y] => 2
    [z] => 3
)
 */

// This does *not* carry over to $original or $ref.
$ref = new Object(array('o' => 42, 'm' => 123, 'n' => 1337));
print_vars();
/*
$original: Array
(
    [x] => 1
    [y] => 2
    [z] => 3
)
$ref: Array
(
    [o] => 42
    [m] => 123
    [n] => 1337
)
$refref: Array
(
    [x] => 1
    [y] => 2
    [z] => 3
)
*/

// This *does* carry over to $original, but not $ref.
$refref = new Object(array('alpha' => 10, 'beta' => 20, 'gamma' => 30));
print_vars();
/*
$original: Array
(
    [alpha] => 10
    [beta] => 20
    [gamma] => 30
)
$ref: Array
(
    [o] => 42
    [m] => 123
    [n] => 1337
)
$refref: Array
(
    [alpha] => 10
    [beta] => 20
    [gamma] => 30
)
*/
?>

&=

&= is unrelated to =&. It comes from a set of assignment operations. Here's just a few:

  • +=
  • -=
  • *=
  • /=

See the trend here?

Binary arithmetic operators generally have assignment counterparts. Let's say @ were an arithmetic operator (it's not as of writing) such that $a @ $b generally yields a number when $a and $b are numbers. (Think: Addition, multiplication, division, etc.) How often do you need to do something like this?

$a = $a @ $b;

Pretty often. Doesn't it seem a bit unnecessary to repeat $a? Many languages, including PHP, solve this with an array of assignment operators:

$a @= $b;

Much simpler, and to a programmer accustomed to this notation, perhaps more concise and descriptive at a glance. (I certainly find it easier to read, since I'm so used to it.) So to double a variable:

$a *= 2;

Quick, easy, and relatively descriptive. Some languages, including PHP, extend this feature beyond arithmetic for an extra operation or two. Notably:

$a = $a . 'Appended text';
// Is the same as:
$a .= 'Appended text';

Very useful.

&= falls among these assignment operators, because & represents a bitwise arithmetic AND operation. There are a few others listed in the PHP documentation (see aforementioned link), all of which are common to many programming languages.

This means that $a &= $b is the same as $a = $a & $b.

Zenexer
  • 18,788
  • 9
  • 71
  • 77
  • One addtion @zenexer to your already awesome anser. `$a @= $b` is different from `$a = $a @ $b` in a signficant way and that is how ofent $a is evaluated. In the first case only one time in the second two times. This might have an impact if $a i not a `normal` variable but a property or even a functino with side effect. – theking2 May 09 '22 at 17:25
  • Great answer! I finally got it! I remember reading docs about it and failing to grasp the complete view. – Jacek Dziurdzikowski Jul 05 '22 at 11:04
  • "Alias" is not really a great term, because it still implies the operation is *directional*, but it is not. Some descriptions talk about "adding to a reference set", which is closer to the truth: both names are equally valid, and neither can be considered a reference to, or alias of, the other. – IMSoP Aug 14 '22 at 09:39