Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>After some research, here is what I came up with. The only (clean) solution is to use member functions and instance/class variables.</p> <p>You need to:</p> <ul> <li>Reference everything using <code>$this</code> and not function arguments.</li> <li>Unset all globals, superglobals and restore them afterwards.</li> <li>Use a <em>possible race condition</em> of some sorts. i.e.: In my example below, <code>render()</code> will set instance variables that <code>_render()</code> 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.</li> <li>Use a temporary file to include, containing a closure, to avoid the use of <code>eval</code>.</li> </ul> <p>The template class I came up with:</p> <pre><code>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-&gt;_file = $file; $this-&gt;_data = $data; $this-&gt;_render(); } // Restore the unset superglobals protected function _restore() { // Unset all variables to make sure the template don't inject anything foreach ($GLOBALS as $var =&gt; $value) { // Unset $GLOBALS and you're screwed if ($var === 'GLOBALS') continue; unset($GLOBALS[$var]); } // Restore all variables foreach ($this-&gt;_backup as $var =&gt; $value) { // Set back all global variables $GLOBALS[$var] = $value; } } // Backup the global variables and superglobals protected function _backup() { foreach ($GLOBALS as $var =&gt; $value) { // Unset $GLOBALS and you're screwed if ($var === 'GLOBALS') continue; $this-&gt;_backup[$var] = $value; unset($GLOBALS[$var]); } } // Render the template protected function _render() { $this-&gt;_backup(); $this-&gt;_tmpfile = tempnam(sys_get_temp_dir(), __CLASS__); $code = '&lt;?php $render = function() {'. 'extract('.var_export($this-&gt;_data, true).');'. 'require "'.$this-&gt;_file.'";'. '}; $render();' file_put_contents($this-&gt;_tmpfile, $code); include $this-&gt;_tmpfile; $this-&gt;_restore(); } } </code></pre> <p>And here's the test case:</p> <pre><code>// Setting some global/superglobals $_GET['get'] = 'get is still set'; $hello = 'hello is still set'; $t = new template; $t-&gt;render('template.php', array('foo'=&gt;'bar', 'this'=&gt;'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 </code></pre> <p>And the template file:</p> <pre><code>&lt;?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 ?&gt; </code></pre> <p>In short, this solution:</p> <ul> <li><strong>Hides all globals and superglobals.</strong> The variables themselves ($_GET, $_POST, etc.) can still be modified, but they will revert back to what they were previously.</li> <li><strong>Does not shadow variables.</strong> (Almost) everything can be used, <strong>including <code>$this</code></strong>. (Except for <code>$GLOBALS</code>, see below).</li> <li><strong>Does not bring anything into scope that wasn't passed.</strong></li> <li><strong>Does not lose any data nor trigger destructors,</strong> because the <em>refcount</em> never reaches zero for any variable.</li> <li><strong>Does not use <code>eval</code> or anything like that.</strong></li> </ul> <p>Here's the result I have for the above:</p> <pre><code>array(1) { ["GLOBALS"]=&gt; *RECURSION* } array(2) { ["this"]=&gt; string(11) "hello world" ["foo"]=&gt; 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 </code></pre> <p>If you dump <code>$GLOBALS</code> after the fact it should be just like it was before the call.</p> <p>The only possible issue is that someone still can execute something like:</p> <pre><code>unset($GLOBALS); </code></pre> <p>... and you're screwed. And there is no way around that.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload