关闭作为班级成员?

时间:2009-01-07 10:05:10

标签: php closures

我喜欢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

[edit]在我的问题中混淆了'it'和'is'。 HEH。

更新

我下载了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

7 个答案:

答案 0 :(得分: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);
}

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

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

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

答案 1 :(得分: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)

需要注意的是外部函数不能用于私有/受保护的属性和方法。

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

答案 2 :(得分:1)

PHP可以通过create_function()。虽然语法几乎完全没用,但语法仍然是十字架。

似乎PHP5.3+正在获得closures and lambda's!

但也来自RFC

  

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

答案 3 :(得分:1)

正确的匿名函数/闭包仅在PHP 5.3中可用,暂时不会广泛使用 - some useful information。我认为不可能做你想做的事。

您可以在一定程度上使用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');

答案 4 :(得分: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);
}

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

答案 5 :(得分:0)

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

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

    基本理念在此实施:https://github.com/ptrofimov/jslikeobject

答案 6 :(得分:0)

我在我的工作中使用这个create closure()将回调分成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}));};";
         }
?>

示例:

<?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)
                                   {//...
                                   }
                                  );
         }
?>