在PHP中是否可以require
任意文件,而不会将当前作用域中的任何变量泄漏到所需文件的变量名称空间中或污染全局变量作用域?
我想用PHP文件进行轻量级模板化,并且如果可以加载模板文件而不考虑其中的任何变量,那就是纯粹的想法。
我已经设置了一个我希望通过解决方案的测试。应该要求RequiredFile.php
并让它返回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()));
?>
我得到的最接近的是使用闭包,但它仍然会返回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() );
有什么想法吗?
答案 0 :(得分:21)
看看这个:
$scope = function() {
// It's very simple :)
extract( func_get_arg(1) );
require func_get_arg(0);
};
$scope( "RequiredFile.php", array() );
答案 1 :(得分:0)
我已经能够使用eval
提出一个解决方案,将变量内联为常量,从而防止它泄漏。
虽然使用eval
绝对不是一个完美的解决方案,但它确实为所需文件创建了“完全干净”的范围,这似乎是PHP本身无法做到的。
$scope = function( $file, array $scope_array ) {
extract( $scope_array ); unset( $scope_array );
eval( "unset( \$file ); require( '".str_replace( "'", "\\'", $file )."' );" );
};
$scope( "test.php", array() );
编辑:
这在技术上甚至不是一个完美的解决方案,因为它会在file
和scope_array
变量上形成“阴影”,从而阻止它们自然地传递到范围内。
EDIT2:
我可以拒绝尝试编写无阴影的解决方案。除非直接传入,否则执行的代码应该无法访问先前作用域中的$this
,全局变量或局部变量。
$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() );
答案 2 :(得分:0)
经过一番研究,这就是我想出的。唯一(干净)的解决方案是使用成员函数和实例/类变量。
你需要:
$this
而非函数参数引用所有内容。render()
将设置_render()
之后将使用的实例变量。在多线程系统中,这会创建一个竞争条件:线程A可能与线程B同时调用render(),并且其中一个数据的数据不准确。幸运的是,目前,PHP并不是多线程的。eval
。我提出的模板类:
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();
}
}
这是测试用例:
// 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
模板文件:
<?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
?>
简而言之,这个解决方案:
$this
。 ($GLOBALS
除外,见下文)。eval
或类似内容。 以下是我对上述结果的结果:
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
如果您在事件之后转储$GLOBALS
,它应该就像在通话之前那样。
唯一可能的问题是某人仍然可以执行以下操作:
unset($GLOBALS);
......你被搞砸了。并且没有办法解决这个问题。
答案 3 :(得分:0)
如果您需要一个非常简单的模板引擎,那么使用函数的方法就足够了。
告诉我,暴露$_file
变量的真正缺点是什么?
如果你需要做真正的工作,请抓住Twig并停止担心。
任何适当的模板引擎都会将你的模板编译成纯PHP,所以你不要输
速度。您还获得了显着的优势 - 更简单的语法,强制htmlspecialchars
和其他。
您可以随时在超级全局中隐藏$_file
:
$_SERVER['MY_COMPLEX_NAME'] = $_file;
unset($_file);
include($_SERVER['MY_COMPLEX_NAME']);
unset($_SERVER['MY_COMPLEX_NAME']);
子>