Passing arguments to included file in PHP

I do not like PHP. One particular thing i dislike is how it handles scopes. There are only two of them here:

In languages as C, Python or js there are virtually infinite number of nested scopes with variable shadowing across them.

One problem that this introduces is that even loop counter of an innocent for loop litters global scope:

for ($i=1; $i<10; $i++) {
  echo $i;
}
echo $i; // 10

Another consequence is that we can't pass parameters to included file without abusing global scope:

$arg = 1; // will stay in global scope forever
include('file.php');

One could say: just use functions! Functions have their interesting scope rules in php. In one word: they introduce one new global scope! Without direct access to the surrounding one. So, if you include file from within a function call, the included file won't see any globals at all. But wait? How to do anything in this file then? Aside from pure functional math? Here we have superglobals! Like $_GET, for instance. They are magically accessible from anywhere. Also we have keyword global, which one is at first looks as a good idea. But for me it looks more like a workaround for not providing normal lexical scope in the first place.

Finally, PHP 5.3 introduced namespaces to solve exactly this problem of conflicting names. But for some mysterious reason they do not work for variables!

Workaround

So i hacked a couple of functions to be able to pass arguments to included files.

We pass array of variables we want to introduce in a new frame to a function enter_scope, do our dirty work, and then call leave_scope. enter_scope saves state of global variables. leave_scope restores it including unseting introduced variables.

function enter_scope($vars) {
  $global = &$GLOBALS;
  if (!isset($global['__scopes']))
    $global['__scopes'] = [];
  $frames = &$global['__scopes'];
  $frame = ['vars' => $vars, 'save' => []];
  $frames[] = &$frame;
  $save = &$frame['save'];
  foreach ($vars as $name => $value) {
    if (isset($global[$name])) {
      $save[$name] = $global[$name];
    }
    $global[$name] = $value;
  }
}

function leave_scope() {
  $global = &$GLOBALS;
  $frames = &$global['__scopes'];
  $frame = array_pop($frames);
  $vars = $frame['vars'];
  $save = $frame['save'];
  foreach ($vars as $name => $value) {
    if (isset($save[$name])) {
      $global[$name] = $save[$name];
    } else {
      unset($global[$name]);
    }
  }
}

Example of usage:

$a = 1;   
echo "pre  $a $b \n";
enter_scope(array('a' => 11, 'b' => 22));
include('file.php');
leave_scope();
echo "post $a $b \n";

If the file file.php just outputs variables a and b then the output will be:

pre  1  
file 11 22 
post 1      // a preserves value, b is undefined

The advantage of this method is that it does not copy all global variables to work. It preserves only those that can produce name conflicts.

Usage with for or foreach loops:

enter_scope(array('i' => 1));
for ($i=1; $i<10; $i++) {
  echo $i;
}
leave_scope();
echo $i; // undefined

Nested loops:

enter_scope(array('i' => 1));
for ($i=1; $i<10; $i++) {
  enter_scope(array('i' => 1));
  for ($i=1; $i<10; $i++) {
    echo $i; // 1 2 3 4 5 6 7 8 9
  }      
  leave_scope();
  echo $i; // 1 2 3 4 5 6 7 8 9
}
leave_scope();
echo $i; // undefined

The downside is that it is slightly slower than just abusing global scope and there is more code to write. Also the functions wouldn't work as expected if called from within a function.

 

shitpoet@gmail.com