An interesting if offbeat use for references: Creating an array with an arbitrary number of dimensions.
For example, a function that takes the result set from a database and produces a multidimensional array keyed according to one (or more) columns, which might be useful if you want your result set to be accessible in a hierarchial manner, or even if you just want your results keyed by the values of each row's primary/unique key fields.
<?php
function array_key_by($data, $keys, $dupl = false)
/*
* $data - Multidimensional array to be keyed
* $keys - List containing the index/key(s) to use.
* $dupl - How to handle rows containing the same values. TRUE stores it as an Array, FALSE overwrites the previous row.
*
* Returns a multidimensional array indexed by $keys, or NULL if error.
* The number of dimensions is equal to the number of $keys provided (+1 if $dupl=TRUE).
*/
{
// Sanity check
if (!is_array($data)) return null;
// Allow passing single key as a scalar
if (is_string($keys) or is_integer($keys)) $keys = Array($keys);
elseif (!is_array($keys)) return null;
// Our output array
$out = Array();
// Loop through each row of our input $data
foreach($data as $cx => $row) if (is_array($row))
{
// Loop through our $keys
foreach($keys as $key)
{
$value = $row[$key];
if (!isset($last)) // First $key only
{
if (!isset($out[$value])) $out[$value] = Array();
$last =& $out; // Bind $last to $out
}
else // Second and subsequent $key....
{
if (!isset($last[$value])) $last[$value] = Array();
}
// Bind $last to one dimension 'deeper'.
// First lap: was &$out, now &$out[...]
// Second lap: was &$out[...], now &$out[...][...]
// Third lap: was &$out[...][...], now &$out[...][...][...]
// (etc.)
$last =& $last[$value];
}
if (isset($last))
{
// At this point, copy the $row into our output array
if ($dupl) $last[$cx] = $row; // Keep previous
else $last = $row; // Overwrite previous
}
unset($last); // Break the reference
}
else return NULL;
// Done
return $out;
}
// A sample result set to test the function with
$data = Array(Array('name' => 'row 1', 'foo' => 'foo_a', 'bar' => 'bar_a', 'baz' => 'baz_a'),
Array('name' => 'row 2', 'foo' => 'foo_a', 'bar' => 'bar_a', 'baz' => 'baz_b'),
Array('name' => 'row 3', 'foo' => 'foo_a', 'bar' => 'bar_b', 'baz' => 'baz_c'),
Array('name' => 'row 4', 'foo' => 'foo_b', 'bar' => 'bar_c', 'baz' => 'baz_d')
);
// First, let's key it by one column (result: two-dimensional array)
print_r(array_key_by($data, 'baz'));
// Or, key it by two columns (result: 3-dimensional array)
print_r(array_key_by($data, Array('baz', 'bar')));
// We could also key it by three columns (result: 4-dimensional array)
print_r(array_key_by($data, Array('baz', 'bar', 'foo')));
?>
What References Do
There are three basic operations performed using references: assigning by reference, passing by reference, and returning by reference. This section will give an introduction to these operations, with links to further reading.
Assign By Reference
In the first of these, PHP references allow you to make two variables refer to the same content. Meaning, when you do:
<?php
$a =& $b;
?>
it means that $a and $b point to the same content.
Note: $a and $b are completely equal here. $a is not pointing to $b or vice versa. $a and $b are pointing to the same place.
Note: If arrays with references are copied, their values are not dereferenced. This is valid also for arrays passed by value to functions.
Note: If you assign, pass, or return an undefined variable by reference, it will get created.
Example #1 Using references with undefined variables
<?php
function foo(&$var) { }
foo($a); // $a is "created" and assigned to null
$b = array();
foo($b['b']);
var_dump(array_key_exists('b', $b)); // bool(true)
$c = new StdClass;
foo($c->d);
var_dump(property_exists($c, 'd')); // bool(true)
?>
The same syntax can be used with functions that return references, and with the new operator (since PHP 4.0.4 and before PHP 5.0.0):
<?php
$foo =& find_var($bar);
?>
Since PHP 5, new returns a reference automatically, so using =& in this context is deprecated and produces an E_STRICT message.
If you assign a reference to a variable declared global inside a function, the reference will be visible only inside the function. You can avoid this by using the $GLOBALS array.
Example #2 Referencing global variables inside functions
<?php
$var1 = "Example variable";
$var2 = "";
function global_references($use_globals)
{
global $var1, $var2;
if (!$use_globals) {
$var2 =& $var1; // visible only inside the function
} else {
$GLOBALS["var2"] =& $var1; // visible also in global context
}
}
global_references(false);
echo "var2 is set to '$var2'\n"; // var2 is set to ''
global_references(true);
echo "var2 is set to '$var2'\n"; // var2 is set to 'Example variable'
?>
Think about global $var; as a shortcut to $var =& $GLOBALS['var'];. Thus assigning another reference to $var only changes the local variable's reference.
Note: If you assign a value to a variable with references in a foreach statement, the references are modified too.
Example #3 References and foreach statement
<?php
$ref = 0;
$row =& $ref;
foreach (array(1, 2, 3) as $row) {
// do something
}
echo $ref; // 3 - last element of the iterated array
?>
Pass By Reference
The second thing references do is to pass variables by reference. This is done by making a local variable in a function and a variable in the calling scope referencing the same content. Example:
<?php
function foo(&$var)
{
$var++;
}
$a=5;
foo($a);
?>
will make $a to be 6. This happens because in the function foo the variable $var refers to the same content as $a. For more information on this, read the passing by reference section.
Return By Reference
The third thing references can do is return by reference.
What References Do
26-Sep-2009 05:29
09-Jun-2008 01:33
In reply to Drewseph using foo($a = 'set'); where $a is a reference formal parameter.
$a = 'set' is an expression. Expressions cannot be passed by reference, don't you just hate that, I do. If you turn on error reporting for E_NOTICE, you will be told about it.
Resolution: $a = 'set'; foo($a); this does what you want.
29-May-2008 06:15
If you set a variable before passing it to a function that takes a variable as a reference, it is much harder (if not impossible) to edit the variable within the function.
Example:
<?php
function foo(&$bar) {
$bar = "hello\n";
}
foo($unset);
echo($unset);
foo($set = "set\n");
echo($set);
?>
Output:
hello
set
It baffles me, but there you have it.
01-Apr-2008 12:56
The order in which you reference your variables matters.
<?php
$a1 = "One";
$a2 = "Two";
$b1 = "Three";
$b2 = "Four";
$b1 =& $a1;
$a2 =& $b2;
echo $a1; //Echoes "One"
echo $b1; //Echoes "One"
echo $a2; //Echoes "Four"
echo $b2; //Echoes "Four"
?>
19-Oct-2007 05:59
points to post below me.
When you're doing the references with loops, you need to unset($var).
for example
<?php
foreach($var as &$value)
{
...
}
unset($value);
?>
09-Oct-2007 04:25
Watch out for this:
foreach ($somearray as &$i) {
// update some $i...
}
...
foreach ($somearray as $i) {
// last element of $somearray is mysteriously overwritten!
}
Problem is $i contians reference to last element of $somearray after the first foreach, and the second foreach happily assigns to it!
06-Jul-2007 02:50
Solution to post "php at hood dot id dot au 04-Mar-2007 10:56":
<?php
$a1 = array('a'=>'a');
$a2 = array('a'=>'b');
foreach ($a1 as $k=>&$v)
$v = 'x';
echo $a1['a']; // will echo x
unset($GLOBALS['v']);
foreach ($a2 as $k=>$v)
{}
echo $a1['a']; // will echo x
?>
08-Jun-2007 12:59
Something that might not be obvious on the first look:
If you want to cycle through an array with references, you must not use a simple value assigning foreach control structure. You have to use an extended key-value assigning foreach or a for control structure.
A simple value assigning foreach control structure produces a copy of an object or value. The following code
$v1=0;
$arrV=array(&$v1,&$v1);
foreach ($arrV as $v)
{
$v1++;
echo $v."\n";
}
yields
0
1
which means $v in foreach is not a reference to $v1 but a copy of the object the actual element in the array was referencing to.
The codes
$v1=0;
$arrV=array(&$v1,&$v1);
foreach ($arrV as $k=>$v)
{
$v1++;
echo $arrV[$k]."\n";
}
and
$v1=0;
$arrV=array(&$v1,&$v1);
$c=count($arrV);
for ($i=0; $i<$c;$i++)
{
$v1++;
echo $arrV[$i]."\n";
}
both yield
1
2
and therefor cycle through the original objects (both $v1), which is, in terms of our aim, what we have been looking for.
(tested with php 4.1.3)
03-Apr-2007 09:11
Here's a good little example of referencing. It was the best way for me to understand, hopefully it can help others.
$b = 2;
$a =& $b;
$c = $a;
echo $c;
// Then... $c = 2
05-Mar-2007 12:56
I discovered something today using references in a foreach
<?php
$a1 = array('a'=>'a');
$a2 = array('a'=>'b');
foreach ($a1 as $k=>&$v)
$v = 'x';
echo $a1['a']; // will echo x
foreach ($a2 as $k=>$v)
{}
echo $a1['a']; // will echo b (!)
?>
After reading the manual this looks like it is meant to happen. But it confused me for a few days!
(The solution I used was to turn the second foreach into a reference too)
17-Apr-2005 04:05
I ran into something when using an expanded version of the example of pbaltz at NO_SPAM dot cs dot NO_SPAM dot wisc dot edu below.
This could be somewhat confusing although it is perfectly clear if you have read the manual carfully. It makes the fact that references always point to the content of a variable perfectly clear (at least to me).
<?php
$a = 1;
$c = 2;
$b =& $a; // $b points to 1
$a =& $c; // $a points now to 2, but $b still to 1;
echo $a, " ", $b;
// Output: 2 1
?>
15-Nov-2004 05:16
In reply to lars at riisgaardribe dot dk,
When a variable is copied, a reference is used internally until the copy is modified. Therefore you shouldn't use references at all in your situation as it doesn't save any memory usage and increases the chance of logic bugs, as you discoved.
10-Apr-2003 05:46
So to make a by-reference setter function, you need to specify reference semantics _both_ in the parameter list _and_ the assignment, like this:
class foo{
var $bar;
function setBar(&$newBar){
$this->bar =& newBar;
}
}
Forget any of the two '&'s, and $foo->bar will end up being a copy after the call to setBar.
