Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Closures as class members?

Tags:

closures

php

I have taken a liking to jQuery/Javascript's way of extending functionality via closures. Is it possible to do something similar in PHP 5.3?

class Foo
{
    public $bar;
}

$foo = new Foo;
$foo->bar = function($baz) { echo strtoupper($baz); };
$foo->bar('lorem ipsum dolor sit amet');
// LOREM IPSUM DOLOR SIT AMET

[edit] mixed up 'it' and 'is' in my question. heh.

UPDATE

I downloaded 5.3a3 and it does work!

class Foo
{
    protected $bar;
    public $baz;

    public function __construct($closure)
    {
        $this->bar = $closure;
    }

    public function __call($method, $args)
    {
        $closure = $this->$method;
        call_user_func_array($closure, $args);
    }

}

$foo = new Foo(function($name) { echo "Hello, $name!\n"; });
$foo->bar('Mon'); 
// Hello, Mon!
$foo->baz = function($s) { echo strtoupper($s); };
$foo->baz('the quick brown fox jumps over the lazy dog');
// THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
like image 696
monzee Avatar asked Jan 07 '09 10:01

monzee


2 Answers

PHP 5.3 will allow the creation of lambda functions and closures which would allow you to implement your code example. From the PHP RFC:

function & (parameters) use (lexical vars) { body }

The function is returned as reference and can be passed as a callback. For example, creating a function:

$lambda = function () { echo "Hello World!\n"; };

Passing a lambda function as a callback:

function replace_spaces ($text) {
    $replacement = function ($matches) {
        return str_replace ($matches[1], ' ', ' ').' ';
    };
    return preg_replace_callback ('/( +) /', $replacement, $text);
}

Closures also allow you to import variables from the local scope:

$map = function ($text) use ($search, $replacement) {
    if (strpos ($text, $search) > 50) {
       return str_replace ($search, $replacement, $text);
    } else {
        return $text;
    }
};

Where $search and $replacement are both declared outside the closure, and $text is the function parameter.

like image 74
Eran Galperin Avatar answered Oct 07 '22 00:10

Eran Galperin


You can take advantage of __call method to re-route non-existent method calls. Add a registry to your class so you can add/remove methods on the fly. Calling convention for external function is that the first parameter is the object the function has been bound to.

clas Foo {

    private $registry;

    public function __call($alias, $args) {
        if (array_key_exists($alias, $this->registry)) {
            // prepend list of parameters with $this object
            array_unshift($args, $this);
            return call_user_func_array($this->registry[$alias], $args)
        }
    }

    public function register($method, $alias = null) {
        $alias or $alias = $method; // use original method name if no alias
        $this->registry[$alias] = $method;
    }

    public function unregister($alias) {
        unset($this->registry[$alias]);
    }

}

Imagine function clone_object that returns array of cloned objects. First parameter is object to be cloned, and the second is number of clones.

function clone_object($foo, $n) {
    $a = array();
    while ($n--) {
        $a[] = clone $foo;
    }
    return $a;
}

Now, this way you can inject method clone_object into class Foo and give it an alias name bar:

$f = new Foo();
$f->register('clone_object', 'bar');
$f->bar(5); // this will return array of 5 cloned objects

In the last line calling method bar will cause redirection to function clone_object. Mind that calling convention will insert parameter $this into parameters list, so (5) will be changed to ($this, 5).

The caveat is that external functions can't work on private/protected properties and methods.

Just a final word, if you can think of a solution that does not change objects on the fly, you should probably use it. The above is a black wizardry and should be used with extreme caution.

like image 30
Michał Niedźwiedzki Avatar answered Oct 06 '22 23:10

Michał Niedźwiedzki