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

The test below demonstrates that using $_ as a variable name in this situation doesn't seem to be any different from using any other variable name. The value is still stored in the variable.

$tmp = array(1=>"one", 2=>"two", 3=>"three", 4=>"four", 5=>"five");
foreach ($tmp as $num=>$_) {
        echo "num is $num; dummy is $_<br>";
}

"_" 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.