7

我喜欢 jQuery/Javascript 通过闭包扩展功能的方式。是否可以在 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

[编辑] 在我的问题中混淆了“它”和“是”。呵呵。

更新

我下载了 5.3a3,它确实有效!

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
4

7 回答 7

4

PHP 5.3 将允许创建 lambda 函数和闭包,这将允许您实现代码示例。来自PHP RFC

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

该函数作为引用返回,并且可以作为回调传递。例如,创建一个函数:

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

将 lambda 函数作为回调传递:

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

闭包还允许您从本地范围导入变量:

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

其中 $search 和 $replacement 都在闭包之外声明,$text 是函数参数。

于 2009-01-07T11:12:54.030 回答
3

您可以利用__call方法来重新路由不存在的方法调用。将注册表添加到您的类,以便您可以即时添加/删除方法。外部函数的调用约定是第一个参数是函数绑定的对象。

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]);
    }

}

想象一下clone_object返回克隆对象数组的函数。第一个参数是要克隆的对象,第二个参数是克隆的数量。

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

现在,您可以通过这种方式将方法注入clone_object到类中Foo并为其命名bar

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

在最后一行调用方法bar将导致重定向到函数clone_object。请注意,调用约定会将参数$this插入参数列表,因此(5)将更改为($this, 5).

需要注意的是,外部函数不能在私有/受保护的属性和方法上工作。

最后一句话,如果您能想到一个不会动态更改对象的解决方案,您可能应该使用它。以上是黑色巫术,应谨慎使用。

于 2009-01-07T11:51:21.597 回答
1

PHP 可以通过create_function()。尽管使语法几乎完全无用,但语法却很痛苦。

似乎PHP5.3+正在获得/支持闭包和 lambda!

然而也来自RFC

Lambda 函数/闭包不是在运行时通过其他方法动态扩展类的方法。还有其他几种可能性可以做到这一点,包括已经存在的 __call 语义。

于 2009-01-07T10:44:20.343 回答
1

正确的匿名函数/闭包仅在 PHP 5.3 中可用,暂时不会广泛使用 -一些有用的信息。我不认为真的可以做你想做的事。

您可以create_function()在一定程度上使用:

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

但是,以下内容无法按您的预期工作:

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

您需要改为:

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

您也不能$this在创建的函数内部使用,因为它的范围与方法不同。

编辑

我不小心复制了一些 Martijn 的答案,因为他在我写我的时候编辑了他的答案。为了完整起见,我将完整保留我的答案

另一个编辑

你也许也可以做类似的事情

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');
于 2009-01-07T10:52:42.977 回答
0

使用 create 函数,您可以执行以下操作:

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

类代码将包含以下内容:

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);
}

注意:您将无法$this在 create 函数中使用对象范围(变量),因为基本上这只是使用全局命名空间中 create_function 的对象接口。但是,您可以轻松地将对象的实例附加到参数列表并将其拉入类中,但同样您只能访问拉入的类的公共方法。

于 2009-01-08T08:49:09.497 回答
0

自 PHP5.4 发布以来,您可以做的更多:

  1. 动态地将您的方法添加到对象
  2. 在这些方法中使用$this
  3. 从另一个继承一个动态对象

基本思想在这里实现:https ://github.com/ptrofimov/jslikeobject

于 2013-01-10T07:11:30.250 回答
0

我在工作中使用这个 create_closure() 将回调分离到类中:

<?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}));};";
         }
?>

例子:

<?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)
                                   {//...
                                   }
                                  );
         }
?>
于 2014-06-10T05:00:56.490 回答