Jul 8 2008
21:56 this is freakin ballin. i was so happy to see this!
Jul 10 2008
14:25 jctoast@hotmail.com
Jul 11 2008
05:31 jjjj
Jul 16 2008
14:50 Click to yabber
Jul 18 2008
20:17 Click to jabber
Jul 23 2008
14:02 how do you freaking get to the open door on 64
Jul 27 2008
23:53 awesome
Aug 5 2008
01:53 YABBERTIEM
Aug 10 2008
11:10 i wanna put this on my myspace, any clues
Aug 14 2008
22:33 <Jabber>
Aug 18 2008
07:21 Click to yabber
07:21 malllll
07:21 שלום שלום
23:10 <Jabber>
Aug 19 2008
06:18 helooo
Aug 20 2008
02:57 click to yabber

steike / code / php closures

This article is from 2004 and is kept here for historical reasons. Apparently (as of 2008) PHP will get native support for closures soon, making this ugly hack even less useful.

Why?!

A closure is a standard feature found of most modern languages. Here's one explanation, and here's another... there's a nice Wikipedia article as well.

What we would like to have

function rot13widget() {
        $a = new TextField();
        $b = new Button('Rotate!');

        $b->setClickHandler(function() {   // NOT VALID CODE!
                $a->setText(str_rot13($a->getText()));
        });
        return Layout::horizontal($a, $b);
}

$panel->add(rot13widget());
... the point being that the function object passed to onClick contains the necessary pointers to $a.

What we can get

We don't really want to add more stuff to the PHP parser. We can settle for this:
function rot13widget() {
        $a = new TextField();
        $b = new Button('Rotate!');

        $b->setClickHandler(eval(closure('
                $a->setText(str_rot13($a->getText()));
        ')));
        return Layout::horizontal($a, $b);
}
The button class would just store the function object, and call it like this:
class Button {
        function setClickHandler($o) {
                $this->clickHandler = $o;
        }
        function handleClick() {
                if($this->clickHandler)
                        $this->clickHandler->call();
        }
}
The hassle of maintaining the necessary pointers to the various widgets would be handled invisibly by the closure class.

Implementation

What we do (internally) is this: the closure() function takes the snippet of code, does a very cheesy parse of it, and returns a magic string which is then evaluated by passing it to eval(). That code grabs the necessary variables from the outer scope, and wraps them in a Closure object. That object is then returned to the function that needs it. The call above would result in this string:
new Closure(
        '$a->setText(str_rot13($a->getText()));',
        array('a' => &$a));
Here's the actual code:

Downlod this snippet as closures.txt

/**
 * Simple closure class; Erling Ellingsen, 2003.
 *
 * http://steike.com/PhpClosures
 */

class Closure {	
  var $code;	
  var $env;	
	
  function Closure($code, $env) {
    $this->code = $code;
    $this->env = $env;
  }

  function call($__args = NULL) {
    // $this will probably be clobbered by the next step, so grab our
    // code and environment now
    $__code = $this->code;
    $__env =& $this->env;

    // set up the scope we need
    // extract() doesn't do references, so we can't use that
    foreach(array_keys($__env) as $__key)
      $$__key =& $__env[$__key];

    return eval($__code);
  }

  function makeGrabber($s) {
    $zot = $seen = array();

    // basically, grab everything that looks like a variable reference.
    // noone gets hurt if we happen to grab too much.
    if(preg_match_all('/\$(\w+)/', $s, $m))
      foreach($m[1] as $var) 
        if(!$seen[$var]++)
          $zot[] = "'$var' => &\$$var";

    $grabber = join(', ', $zot);

    $escaped = preg_replace("/['\\\\]/", "\\$&", $s);

    $grabber = "return new Closure('$escaped', array($grabber));";
    return $grabber;
  }
}

function closure($s) {
  return Closure::makeGrabber($s);
}


function _test_closure($n) {
  return eval(closure('
    return $n++;
  '));
}

$a =& _test_closure(5);
$b =& _test_closure(10);

assert($a->call() == 5);
assert($b->call() == 10);
assert($a->call() == 6);

[Comment on this]