Access variables from the global scope within a closure - php

I am well aware that globals are evil and this is an issue I will deal with later. It's not my codebase, but I've been assigned some cleaning up tasks.
Trying to smarten up a codebase, I decided to implement simple routing by using a package known as AltoRouter - I've worked with it before and it has worked fine for my purposes.
Now, the codebase is using a large amount of variables declared in the global scope. Usually, these variables are then fetched by using the globalkeyword. But for some reason, this doesn't work when I'm working inside a closure.
Consider this simple routing example:
<?php
require 'vendor/autoload.php';
$router = new AltoRouter();
$router->map('GET', '/shops/[i:id]', function($id) {
$_GET['shop_id'] = $id;
require 'go_to_shop.php';
});
$match = $router->match();
if( $match && is_callable( $match['target'] ) ) {
call_user_func_array( $match['target'], $match['params'] );
}
This calls my closure that sets a variable and requires a file.
This produces an error:
Fatal error: Call to a member function get() on null in
/vagrant/Core/CampaignHandler.php on line 71
Now, the code being called doing this is the following (line 70-71):
// Inside a method
global $serviceContainer;
$dispatcher = $serviceContainer->get("dispatcher");
The $serviceContainer is being declared by including a file early on:
$serviceContainer = new ServiceContainer();
$serviceContainer->set("dispatcher", new EventDispatcher());
Basically, if I move the contents of the closure outside of the closure, everything works perfectly - but as soon as I'm doing it from inside the closure, all variables accessed via the global scope is empty - and I have no idea as to why.
I've tried using use on the closure, this didn't work either.
I'm mostly looking for an explanation rather than a solution.

Globals are evil for a reason. You get the error because the global is not initialized at the time when function is being called. The mess of globals and requires is the exact issue and you are already trying to deal with it.
There is no problem to use globals in closure per se. This example works perfectly fine:
global.php:
<?php
class Foo {
public function bar() { return 'bar';}
}
$foo = new Foo;
test.php:
<?php
require 'global.php';
$test = function($param) {
global $foo;
echo $param, $foo->bar();
}
call_user_func_array($test, ['baz']);
so php test.php outputs bazbar;

I'm pretty sure that the $serviceContainer variable does not exist in the global scope, but the question leaves that part out.
Can't you pass the container to the anonymous function using a use( $serviceContainer ) statement? That'd be a far cleaner solution then having to rely on globals.
function($id) use( $serviceContainer ) {
$_GET['shop_id'] = $id;
require 'go_to_shop.php';
}
Off-topic: not sure what you're doing with that id variable later on and why you're putting it back into the $_GET variable like that, but please be careful.

Please check the manual for anonymus functions–also known as closures, which in real are objects.
http://php.net/manual/en/functions.anonymous.php
Theses callables have specific functions for extending their scopes.
See: Example #3 Inheriting variables from the parent scope.
$message = 'hello';
// No "use"
$example = function () {
var_dump($message);
};
$example();
// Inherit $message
$example = function () use ($message) {
var_dump($message);
};
$example();
Sure you want to assign a value to the $_GET global var?
$_GET['shop_id'] = $id;
The shop ID in your route you can extract from altorouter parameters. (See documentation.)
$router->map( 'GET', '/', function() { .. }, 'home' );
// assuming current request url = '/'
$match = $router->match();
/*
array(3) {
["target"] => object(Closure)#2 (0) { }
["params"] => array(0) { }
["name"] => 'home'
}
*/
Or if you want to store an ID for a session use $_COOKIES or $_SESSION global variables.

Related

Trying to run app from function and i have an error when i trying to call some variable inside some functinon

Maybe this is simple thing. But I'm so confused. What's going here.
So I had made function which one calling all others files from app.
load.php
function RunApp(){
$files = array(
'/dir/PdoConn.php',
'/dir/class.php',
'/dir/function.php',
'/dir/something.php',
'/dir/template.php',
...);
foreach ($files as $include){
$path = MAIN_DIR . $include;
file_exists($path) ? require_once $path : die();
}
}
Of course in the firsts files, are connection with DB, with PDO.
So in some of those files are some functions, classes etc.
So what's happening, if I try to use $conn(pdo object) variable in some function.
Let's say:
function.php
function GetArticle($id){
global $conn;
$article = $conn->prepare("SELECT * FROM article WHERE id = ?");
$article->execute([$id]);
return $article;
}
(this function is only example, so do not care about the SQL statement)
This will throw an error:
Uncaught Error: Call to a member function prepare()
But if I do in last required file (/dir/template.php)
print_r(get_defined_vars());
It will have this in array:
[conn] => PDO Object
Of course if I delete whole function RunApp() and include those files just from that foreach, everything work perfectly.
The files are being required inside your runapp() function. So any variables they assign will be local to that function, unless the variables are declared global.
So PdoConn.php need to have global $conn; in it. And all the other files need to declare any global variables they define.

Require an arbitrary PHP file without leaking variables into scope

Is it possible in PHP to require an arbitrary file without leaking any variables from the current scope into the required file's variable namespace or polluting the global variable scope?
I'm wanting to do lightweight templating with PHP files and was wondering for purity sake if it was possible to load a template file without any variables in it's scope but the intended ones.
I have setup a test that I would like a solution to pass. It should beable to require RequiredFile.php and have it return Success, no leaking variables..
RequiredFile.php:
<?php
print array() === get_defined_vars()
? "Success, no leaking variables."
: "Failed, leaked variables: ".implode(", ",array_keys(get_defined_vars()));
?>
The closest I've gotten was using a closure, but it still returns Failed, leaked variables: _file.
$scope = function( $_file, array $scope_variables ) {
extract( $scope_variables ); unset( $scope_variables );
//No way to prevent $_file from leaking since it's used in the require call
require( $_file );
};
$scope( "RequiredFile.php", array() );
Any ideas?
Look at this:
$scope = function() {
// It's very simple :)
extract(func_get_arg(1));
require func_get_arg(0);
};
$scope("RequiredFile.php", []);
I've been able to come up with a solution using eval to inline the variable as a constant, thus preventing it from leaking.
While using eval is definitely not a perfect solution, it does create a "perfectly clean" scope for the required file, something that PHP doesn't seem to be able to do natively.
$scope = function( $file, array $scope_array ) {
extract( $scope_array ); unset( $scope_array );
eval( "unset( \$file ); require( '".str_replace( "'", "\\'", $file )."' );" );
};
$scope( "test.php", array() );
EDIT:
This technically isn't even a perfect solution as it creates a "shadow" over the file and scope_array variables, preventing them from being passed into the scope naturally.
EDIT2:
I could resist trying to write a shadow free solution. The executed code should have no access to $this, global or local variables from previous scopes, unless directly passed in.
$scope = function( $file, array $scope_array ) {
$clear_globals = function( Closure $closure ) {
$old_globals = $GLOBALS;
$GLOBALS = array();
$closure();
$GLOBALS = $old_globals;
};
$clear_globals( function() use ( $file, $scope_array ) {
//remove the only variable that will leak from the scope
$eval_code = "unset( \$eval_code );";
//we must sort the var name array so that assignments happens in order
//that forces $var = $_var before $_var = $__var;
$scope_key_array = array_keys( $scope_array );
rsort( $scope_key_array );
//build variable scope reassignment
foreach( $scope_key_array as $var_name ) {
$var_name = str_replace( "'", "\\'", $var_name );
$eval_code .= "\${'$var_name'} = \${'_{$var_name}'};";
$eval_code .= "unset( \${'_{$var_name}'} );";
}
unset( $var_name );
//extract scope into _* variable namespace
extract( $scope_array, EXTR_PREFIX_ALL, "" ); unset( $scope_array );
//add file require with inlined filename
$eval_code .= "require( '".str_replace( "'", "\\'", $file )."' );";
unset( $file );
eval( $eval_code );
} );
};
$scope( "test.php", array() );
After some research, here is what I came up with. The only (clean) solution is to use member functions and instance/class variables.
You need to:
Reference everything using $this and not function arguments.
Unset all globals, superglobals and restore them afterwards.
Use a possible race condition of some sorts. i.e.: In my example below, render() will set instance variables that _render() will use afterwards. In a multi-threaded system, this creates a race condition: thread A may call render() at the same time as thread B and the data will be inexact for one of them. Fortunately, for now, PHP isn't multi-threaded.
Use a temporary file to include, containing a closure, to avoid the use of eval.
The template class I came up with:
class template {
// Store the template data
protected $_data = array();
// Store the template filename
protected $_file, $_tmpfile;
// Store the backed up $GLOBALS and superglobals
protected $_backup;
// Render a template $file with some $data
public function render($file, $data) {
$this->_file = $file;
$this->_data = $data;
$this->_render();
}
// Restore the unset superglobals
protected function _restore() {
// Unset all variables to make sure the template don't inject anything
foreach ($GLOBALS as $var => $value) {
// Unset $GLOBALS and you're screwed
if ($var === 'GLOBALS') continue;
unset($GLOBALS[$var]);
}
// Restore all variables
foreach ($this->_backup as $var => $value) {
// Set back all global variables
$GLOBALS[$var] = $value;
}
}
// Backup the global variables and superglobals
protected function _backup() {
foreach ($GLOBALS as $var => $value) {
// Unset $GLOBALS and you're screwed
if ($var === 'GLOBALS') continue;
$this->_backup[$var] = $value;
unset($GLOBALS[$var]);
}
}
// Render the template
protected function _render() {
$this->_backup();
$this->_tmpfile = tempnam(sys_get_temp_dir(), __CLASS__);
$code = '<?php $render = function() {'.
'extract('.var_export($this->_data, true).');'.
'require "'.$this->_file.'";'.
'}; $render();'
file_put_contents($this->_tmpfile, $code);
include $this->_tmpfile;
$this->_restore();
}
}
And here's the test case:
// Setting some global/superglobals
$_GET['get'] = 'get is still set';
$hello = 'hello is still set';
$t = new template;
$t->render('template.php', array('foo'=>'bar', 'this'=>'hello world'));
// Checking if those globals/superglobals are still set
var_dump($_GET['get'], $hello);
// Those shouldn't be set anymore
var_dump($_SERVER['bar'], $GLOBALS['stack']); // undefined indices
And the template file:
<?php
var_dump($GLOBALS); // prints an empty list
$_SERVER['bar'] = 'baz'; // will be unset later
$GLOBALS['stack'] = 'overflow'; // will be unset later
var_dump(get_defined_vars()); // foo, this
?>
In short, this solution:
Hides all globals and superglobals. The variables themselves ($_GET, $_POST, etc.) can still be modified, but they will revert back to what they were previously.
Does not shadow variables. (Almost) everything can be used, including $this. (Except for $GLOBALS, see below).
Does not bring anything into scope that wasn't passed.
Does not lose any data nor trigger destructors, because the refcount never reaches zero for any variable.
Does not use eval or anything like that.
Here's the result I have for the above:
array(1) {
["GLOBALS"]=>
*RECURSION*
}
array(2) {
["this"]=>
string(11) "hello world"
["foo"]=>
string(3) "bar"
}
string(10) "get is still set"
string(12) "hello is still set"
Notice: Undefined index: bar in /var/www/temp/test.php on line 75
Call Stack:
0.0003 658056 1. {main}() /var/www/temp/test.php:0
Notice: Undefined index: stack in /var/www/temp/test.php on line 75
Call Stack:
0.0003 658056 1. {main}() /var/www/temp/test.php:0
NULL
NULL
If you dump $GLOBALS after the fact it should be just like it was before the call.
The only possible issue is that someone still can execute something like:
unset($GLOBALS);
... and you're screwed. And there is no way around that.
If you need a very simple templating engine, your approach with a function is good enough.
Tell me, what are the real disadvantages of exposing that $_file variable?
If you need to do real work, grab Twig and stop worrying.
Any proper templating engine compiles your templates into pure PHP anyway, so you don't lose
speed. You also gain significant advantages - simpler syntax, enforced htmlspecialchars and other.
You could always hide your $_file in a superglobal:
$_SERVER['MY_COMPLEX_NAME'] = $_file;
unset($_file);
include($_SERVER['MY_COMPLEX_NAME']);
unset($_SERVER['MY_COMPLEX_NAME']);

run function block in context of global namespace in PHP

So the senario is that I want to have a custom function for requiring libraries. Something like:
define('E_ROOT', str_replace('//','/',dirname(__FILE__)));
/* ... */
function e_load($fn, $allowReloading = FALSE) {
$inc = E_ROOT.'/path/here/'.$fn.'.php';
if($allowReloading)
require $inc; // !!!
else
require_once $inc; // !!!
}
The problem being that require and require_once will load the files into the namespace of the function, which doesn't help for libraries of functions, classes, et cetera. So is there a way to do this?
(Something avoiding require and require_once altogether is fine, as long as it doesn't use eval since it's banned on so many hosts.)
Thanks!
Technically include() is meant to act as though you're inserting the text of included script at that point in your PHP. Thus:
includeMe.php:
<?php
$test = "Hello, World!";
?>
includeIt.php:
<?php
include('includeMe.php');
echo $test;
?>
Should be the exact same as:
<?php
/* INSERTED FROM includeMe.php */
$test = "Hello, World!";
/* END INSERTED PORTION */
echo $test;
?>
Realizing this, the idea of making a function for dynamically including files makes about as much sense (and is about as easy to do) as having dynamic code all-together. It's possible, but it will involve a lot of meta-variables.
I'd look into Variable Variables in PHP as well as the get_defined_vars function for bringing variables into the global scope. This could be done with something like:
<?php
define('E_ROOT', str_replace('//','/',dirname(__FILE__)));
/* ... */
function e_load($fn, $allowReloading = FALSE) {
$prev_defined_vars = get_defined_vars();
$inc = E_ROOT.'/path/here/'.$fn.'.php';
if($allowReloading)
require $inc; // !!!
else
require_once $inc; // !!!
$now_defined_vars = get_defined_vars();
$new_vars = array_diff($now_defined_vars, $prev_defined_vars);
for($i = 0; $i < count($new_vars); $i++){
// Pull new variables into the global scope
global $$newvars[$i];
}
}
?>
It may be more convenient to just use require() and require_once() in place of e_load()
Note that functions and constants should always be in the global scope, so no matter where they are defined they should be callable from anywhere in your code.
The one exception to this is functions defined within a class. These are only callable within the namespace of the class.
EDIT:
I just tested this myself. Functions are declared in the global scope. I ran the following code:
<?php
function test(){
function test2(){
echo "Test2 was called!";
}
}
//test2(); <-- failed
test();
test2(); // <-- succeeded this time
?>
So the function was only defined after test() had been run, but the function was then callable from outside of test(). Therefore the only thing you should need to pull into the global scope are your variables, via the script I provided earlier.
require_once E_ROOT.$libName.'.php';
KISS
Instead of doing this...
$test = "Hello, World!";
... you could consider doing this ...
$GLOBALS[ 'test' ] = "Hello, World!";
Which is safe and consistent in both local function context, and global include context. Probably not harmful to visually remind the reader that you are expecting $test to become global. If you have a large number of globals being dragged in by your libraries maybe there's justification for wrapping it in a class (then you have the benefit of spl_autoload_register which kind of does what you are doing anyhow).

phpunit runs test twice - gets two answers. Why?

This is my phpunit test file
<?php // DemoTest - test to prove the point
function __autoload($className) {
// pick file up from current directory
$f = $className.'.php';
require_once $f;
}
class DemoTest extends PHPUnit_Framework_TestCase {
// call same test twice - det different results
function test01() {
$this->controller = new demo();
ob_start();
$this->controller->handleit();
$result = ob_get_clean();
$expect = 'Actions is an array';
$this->assertEquals($expect,$result);
}
function test02() {
$this->test01();
}
}
?>
This is the file under test
<?php // demo.php
global $actions;
$actions=array('one','two','three');
class demo {
function handleit() {
global $actions;
if (is_null($actions)) {
print "Actions is null";
} else {
print('Actions is an array');
}
}
}
?>
The result is that the second test fails because $actions is null.
My question is - why don't I get the same results for the two tests?
Is this a bug in phpunit or it is my understanding of php?
PHPUnit has a feature called "backup globals", if turned on, then at the beginning of the test all variables in global scope are backed up (a snapshot is made of current values) and after each test is completed, the values will be restored again to the original values. You can read more about that here: http://sebastian-bergmann.de/archives/797-Global-Variables-and-PHPUnit.html#content
Now lets look at your test suite.
test01 is prepared
backup is made of all global variables (at this point $actions in global scope is not set, because the code has not ran yet)
test01 runs
demo.php is included (thanks to autoload) and $actions is set in global scope
your assertion succeeds, because $actions is set in global scope
test01 is torn down. global variables are returned to their original value. $actions in global scope is destroyed at this point, because it was set inside the test, it was not part of the global state before the start of the test
test02 runs .. and fails, because there is no $actions in global scope.
Direct fix to your problem: include demo.php at the beginning of DemoTest.php, this way $actions ends up in the global scope that is backed up and restored before and after every test.
Long term fix: try to avoid the use of globals. Its just bad habit and there are always better solutions than global state using 'global'.

File private variables in PHP

Is it possible to define private variables in a PHP script so these variables are only visible in this single PHP script and nowhere else? I want to have an include file which does something without polluting the global namespace. It must work with PHP 5.2 so PHP namespaces are not an option. And no OOP is used here so I'm not searching for private class members. I'm searching for "somewhat-global" variables which are global in the current script but nowhere else.
In C I could do it with the static keyword but is there something similar in PHP?
Here is a short example of a "common.php" script:
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// Do more stuff with the $dir variable
When I include this file in some script then the $dir variable is visible in all other scripts as well and I don't want that. So how can I prevent this?
There are a few things you could do to keep $dir out of subsequent files
Example 1
set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path());
This is the most obvious.
Example 2
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// work with $dir
unset($dir);
Just unset the variable after defining it and using it. Note this will unset any variable named $dir used prior to including this script.
Example 3
define('DIR_THIS', dirname(__FILE__));
set_include_path(DIR_THIS . PATH_SEPARATOR . get_include_path());
It is less likely I suppose to redefine a global constant like this.
Example 4
function my_set_include_path {
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// Do more stuff with the $dir variable
$my_other_var = 'is trapped within this function';
}
my_set_include_path();
You can define as many variables within that function and not affect the global namespace.
Conclusion
The first method is the easiest way to solve this problem, however because you want to use $dir again, it may not be ideal. The last example will at least keep that $dir (and any others defined in that function) out of the global namespace.
The only way you're going to accomplish anything close to what you want is to wrap everything in that included file in a function, and call it. If the file needs to execute itself you could still do
<?php
run_myfile()
function run_myfile() {
...
}
?>
There is no generic way to make a variable scoped to only a file outside of namespaces, classes, or functions.
Well, I'm probably getting flailed for this, but you if you are totally desperate you could use a Registry for that. I've whipped up a small one that does without classes (since I assume from And no OOP is used here so I'm not searching for private class members. means you don't want to do it with OOP at all)
function &registry_get_instance()
{
static $data = array();
return $data;
}
The static $data variable inside is persisted inside the function scope, so you can call the function wherever you like and always get the same contents. The crucial point is returning by reference, e.g.
$registry = &registry_get_instance(); // get $data array by reference
$registry['foo'] = 'bar'; // set something to $data
unset($registry); // delete global reference to $data
print_r(&registry_get_instance()); // show $data
Obviously you'd still have $registry as a variable in the global scope when calling this method from the global scope. So, you could add some more functions to make the Registry more convenient to use, e.g. for setting data to the Registry:
function registry_set($key, $value)
{
$registry = &registry_get_instance();
$registry[$key] = $value;
}
and for getting it out again:
function registry_get($key)
{
$registry = &registry_get_instance();
if(array_key_exists($key, $registry)) {
return $registry[$key];
} else {
trigger_error(sprintf(
'Undefined Index: %s', htmlentities($key)
), E_USER_NOTICE);
}
}
and for checking if a key exists:
function registry_isset($key)
{
$registry = &registry_get_instance();
return array_key_exists($key, $registry);
}
which you could then use like:
registry_set('foo', 'bar'); // setting something to the registry
var_dump( registry_isset('foo') ); // check foo is in the registry now
echo registry_get('foo'); // prints 'bar'
echo registry_get('punt'); // raises Notice
You could populate the Registry from an include file with an additional method like this:
function registry_load_file($file)
{
if(!is_readable(realpath($file))) {
return trigger_error(sprintf(
'File is not readable: %s', htmlentities($file)
), E_USER_WARNING);
}
$config = include $file;
if(!is_array($config)) {
return trigger_error(sprintf(
'Expected file %s to return an array', htmlentities($file))
, E_USER_WARNING);
}
$registry = &registry_get_instance();
$registry += $config;
}
with the include file having to return an array:
// config.php
return array(
'setting1' => 'something'
);
and then you can do
registry_load_from_file('config.php'); // add the contents of config to registry
print_r(registry_get_instance()); // show content of registry
Of course, this is now six functions in the global scope just for not having a global variable. Don't know if it's worth it, especially since I consider static in functions and all that reference stuff doubtful practice.
Take it as a proof of concept :)
Why not just put everything in a static class? Then you only have a single "variable" that could possibly conflict with the global namespace.
class MyClass {
public static $myvar = 1;
public static $myvar2 = "xyz";
public static function myfunction() {
self::$myvar++;
self::$myvar2 = "abc";
}
}
// References to class items, if needed
MyClass::myfunction();
MyClass::$myvar += 3;
If the problem you are trying to is just:
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// Do more stuff with the $dir variable
Then the solution would be to change the include path relative to '.' in your ini settings. E.g. change:
include_path=includes:/usr/local/php
to
include_path=./includes:/usr/local/php
Note that a script does not come into scope except where you explicitly include/require it (both the _once check applies globally) however I would recommend strongly against calling include/require from within a function - its much more transparent having the includes/requires at the top of the script.
I think that the problem you are trying to solve is based on a false premise and you should look for another way of fixing it. If you want the code in an include file to behave differently depending on what includes it, then really you should seperate it out into 2 seperate files - or maybe even 3 - 2 for the different behaviours and 1 for the common.
C.

Categories