7

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
hakre
  • 193,403
  • 52
  • 435
  • 836
monzee
  • 423
  • 3
  • 9
  • This is pretty much a duplicate of http://stackoverflow.com/questions/146737/closures-in-php-what-precisely-are-they-and-when-would-you-need-to-use-them – troelskn Jan 09 '09 at 21:47
  • See as well: [Calling closure assigned to object property directly](http://stackoverflow.com/q/4535330/367456) – hakre Jun 02 '12 at 17:49

7 Answers7

4

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.

Eran Galperin
  • 86,251
  • 24
  • 115
  • 132
3

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.

Michał Niedźwiedzki
  • 12,859
  • 7
  • 45
  • 47
1

PHP can via create_function(). The syntax is cruciating though rendering it almost completely useless.

It seems PHP5.3+ is getting/has support for closures and lambda's!

However also from the RFC

Lambda functions / closures are not a way of dynamically extending classes by additional methods at runtime. There are several other possibilities to do this, including the already present _ _call semantic.

Martijn Laarman
  • 13,476
  • 44
  • 63
  • I guess that quote answers my question. I wonder if the 'several other possibilities' include a way that could leverage the closure syntax somehow... – monzee Jan 07 '09 at 13:24
1

Proper anonymous functions / closures are only available in PHP 5.3, which will not be widely available for a while - some useful information. I don't think it is really possible to do what you want.

You can use create_function() to an extent:

$func = create_function('$baz', ' echo strtoupper($baz); ');
$func("hello");

However the following does not work as you would expect:

$foo = new Foo;
$foo->bar = create_function('$baz', ' echo strtoupper($baz); ');
$foo->bar('lorem ipsum dolor sit amet');

You would need to do instead:

call_user_func($foo->bar, 'lorem ipsum dolor sit amet');

Also you can't use $this inside the created function because its scope would not be the same as a method.

Edit

I accidentally duplicated some of Martijn's answer as he edited his while I was writing mine. I will leave my answer intact for the sake of completeness

Another edit

You could maybe also do something like

class Foo
{
    public $bar;

    protected $anonFuncs = array();

    public function create_method($name, $params, $code) {
        if (strlen($params)) $params .= ',';
        $params .= '$self';
        $this->anonFuncs[$name] = create_function($params, $code);
    }

    public function __call($name, $args) {
        if (!isset($this->anonFuncs[$name])) {
            trigger_error("No method $name", E_USER_ERROR);
        }

        $args[] = $this;

        return call_user_func_array($this->anonFuncs[$name], $args);
    }
}

$foo = new Foo;
$foo->create_method('bar', '$baz', ' echo strtoupper($baz) . get_class($self); ');
$foo->bar('lorem ipsum dolor sit amet');
Tom Haigh
  • 57,217
  • 21
  • 114
  • 142
0

Since PHP5.4 release you can do even more:

  1. Dynamically add your methods to objects
  2. Use $this inside these methods
  3. Inherit one dynamic object from another

Basic idea is implemented here: https://github.com/ptrofimov/jslikeobject

0

I use this create_closure() in my work to seperate callbacks into Classes:

<?php
function create_closure($fun, $args, $uses)
         {$params=explode(',', $args.','.$uses);
          $str_params='';
          foreach ($params as $v)
                  {$v=trim($v, ' &$');
                   $str_params.='\''.$v.'\'=>&$'.$v.', ';
                  }
          return "return function({$args}) use ({$uses}) {{$fun}(array({$str_params}));};";
         }
?>

example:

<?php
$loop->addPeriodicTimer(1, eval(create_closure('pop_message', '$timer', '$cache_key, $n, &$response, &$redis_client')));

function pop_message($params)
         {extract($params, EXTR_REFS);
          $redis_client->ZRANGE($cache_key, 0, $n)
                            ->then(//normal
                                   function($data) use ($cache_key, $n, &$timer, &$response, &$redis_client)
                                   {//...
                                   },
                                   //exception
                                   function ($e) use (&$timer, &$response, &$redis_client)
                                   {//...
                                   }
                                  );
         }
?>
diyism
  • 12,477
  • 5
  • 46
  • 46
0

Using the create function you could do something like:

$Dynamic->lambda = create_function('$var', 'return $var." world."; ');
$classic = $Dynamic->lambda("hello"); // classic would contain "hello world."

The class code would contain something like:

public function __set($methodname, $function)
{
    $this->methods[$methodname] = $function;
}

public function __get($methodname)
{
    return $this->methods[$methodname];
}

public function __call($method, $args)
{
   $funct = $this->{$method};
   //$args[] = $this; --to add the current object as a parameter option
   return call_user_func_array($funct, $args);
}

Note: that you will not be able to use the object scope ($this variable) in your create function, as basically this is just using a object interface to create_function which is in the global namespace. However you can easily append an instance of the object to the parameter list and pull it into the class, but then again you would only have access to the public methods of the class pulled in.

null
  • 7,432
  • 4
  • 26
  • 28