Skip to content
Advertisement

Use of php variable $_ (dollar sign followed by an underscore)

Is that really true that i can use $_ as a dummy variable in foreach loop if there is no need for $value in foreach($array as $key => $value)? I could not find any useful information that proves this except PHP syntax formatting.

There’s a special case for foreach loops when the value is not used inside the loop. In this case the dummy variable $_ (underscore) is used:

foreach ($GLOBALS['TCA'] as $table => $_) { // Do something with $table }

This is done for performance reasons, as it is faster than calling array_keys() and looping on its result.

Advertisement

Answer

“_” is a valid variable name character, so you can use it as you would any other variable and has no special significance; this isn’t Perl.

<?php
    $_ = "Hello";
    $__ = "World";
    $___ = "foo";

    print "{$_}, {$__}, {$___}n";
?>

will output “Hello, World, foo” as expected. Also,

foreach ( [ 'a' => 'Alpha', 'b' => 'Beta', 'c' => 'Gamma' ] as $letter => $_ ) {
    print $letter;
}
print $_;

will output “abcGamma”, showing that the $_ variable remains defined after being used in the foreach; it’s not some weird kind of “local” variable.

As for the performances, I don’t think it makes much difference, but that’s your call. Rather, I’d try and not use global variables, to avoid polluting the global scope.

Tests and rants more or less at random

n.b. a recent PHP required, I think

feel free to correct/add/suggest improvements

define('INNER_LOOP', 10000);
define('OUTER_LOOP', 10);

$TCA    = [
    'customers' => '',
    'relations' => '',
    'invoices'  => '',
    'books'     => '',
    'parts'     => '',
    'records'   => '',
    'calories'  => '',
    'bounties'  => '',
    'cats'      => '',
    'cowabunga' => '',
    'amenities' => '',
];

$tests  = [
    "foreach access to global" => function() {
        global $TCA;
        for ($i = 0; $i < INNER_LOOP; $i++) {
            foreach ($TCA as $table => $_) {
                $t = $table . 'x';
            }
        }
    },
    "foreach access to GLOBALS" => function() {
        for ($i = 0; $i < INNER_LOOP; $i++) {
            foreach ($GLOBALS['TCA'] AS $table => $_) {
                $t = $table . 'x';
            }
        }
    },
    "passing parameter" => function($TCA) {
        for ($i = 0; $i < INNER_LOOP; $i++) {
            foreach ($TCA AS $table => $_) {
                $t = $table . 'x';
            }
        }
    },
    "passing parameter and array_keys" => function($TCA) {
        $keys = array_keys($TCA);
        for ($i = 0; $i < INNER_LOOP; $i++) {
            foreach ($keys AS $table) {
                $t = $table . 'x';
            }
        }
    },
    "walking passed parameter w/lambda" => function($TCA) {
        for ($i = 0; $i < INNER_LOOP; $i++) {
            array_map(
                function($table) {
                    $t = $table . 'x';
                },
                array_keys($TCA)
            );
        }
    },
    "walking passed parameter w/ anon func" => function($TCA) {
        $handler = function($table) {
                    $t = $table . 'x';
                };
        $keys = array_keys($TCA);
        for ($i = 0; $i < INNER_LOOP; $i++) {
            array_map($handler, $keys);
        }
    },


];

function timeFunc($function, $obj) {
  $time   = microtime(true);
  for ($i = 0; $i < OUTER_LOOP; $i++) {
    $function($obj);
  }
  return (microtime(true) - $time);
}

foreach ($tests as $name => $test) {
    print "$name: " . timeFunc($test, $TCA) . "n";
    flush();
}

These are my results, formatted and sorted:

- passing parameter and array_keys:      0.04573917388916
- foreach access to global:              0.067629098892212
- passing parameter:                     0.08098292350769
- foreach access to GLOBALS:             0.082289934158325
- walking passed parameter w/ anon func: 1.6233508586884
- walking passed parameter w/lambda:     1.6796138286591

Two things need noting: between the fastest and slowest I have a difference of about forty times. But the difference over one hundred thousand calls is 1.63 seconds, which means 16.3 microseconds for a single call between the faster and the slower versions.

So if one of these versions show promise of saving you, say, five minutes a year of head-scratching, bug-hunting or customer support, it’s likely that going for that version will prove a worthwhile investment.

If, on the other hand, you really need something called several billion times, so that those paltry microseconds add up to something worth tackling, then probably you’d be better off investing some time in porting (or having ported) that section of code to a language which is either inherently faster or can be made to massively parallelize – maybe C, or Erlang; or re-thinking the architecture (e.g. daemonize a process to save on the overhead, use stored procedures to offload the hassle to the RDBMS, cache results, …).

UPDATE FOR PHP 7.2

These are the results for PHP 7.2.19 on a newer 64bit machine:

passing parameter and array_keys        0.57718586921692
foreach access to global                0.65028595924377
passing parameter                       0.65098810195923
foreach access to GLOBALS               0.69678092002869
walking passed parameter w/ anon func   0.84391593933105
walking passed parameter w/lambda       1.0423438549042

Note that the difference between fastest and slowest is now less than a factor of 2; therefore, the argument for “go with the clearest, easiest to understand code” is now even stronger.

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement