После некоторых исследований я пришел к такому выводу. Единственное (чистое) решение - использовать функции-члены и переменные экземпляра / класса.
Вам необходимо:
- Ссылка на все, используя
$this
, а не аргументы функции.
- Сбросить все глобалы, суперглобалы и восстановить их потом.
- Используйте возможное состояние гонки некоторых сортов. т.е. в моем примере ниже,
render()
установит переменные экземпляра, которые _render()
будет использовать впоследствии. В многопоточной системе это создает условие состязания: поток A может вызывать render () одновременно с потоком B, и данные для одного из них будут неточными. К счастью, сейчас 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
?>
Короче говоря, это решение:
- Скрывает все глобальные и суперглобальные значения. Сами переменные ($ _GET, $ _POST и т. Д.) Все еще можно изменить, но они вернутся к тому, что были ранее.
- Не скрывает переменные. (Почти) можно использовать все, , включая
$this
. (За исключением $GLOBALS
, см. Ниже).
- Не вносит в область действия ничего, что не было передано.
- Не теряет ни данных, ни триггеров-деструкторов, , потому что refcount никогда не достигает нуля для любой переменной.
- Не использует
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);
... и ты облажался. И пути назад нет.