PHP中的闭包是什么,何时需要使用它们?

90

我以一种良好的、最新的面向对象的方式进行编程。我经常利用PHP实现的各种面向对象编程方面,但我想知道何时需要使用闭包。有没有专家可以解释一下在什么情况下实现闭包会有用?

8个回答

89

PHP 5.3开始原生支持闭包函数。闭包函数适用于希望使用仅限于某些小型、特定目的的本地函数的情况。 关于闭包函数的RFC给出了很好的例子:

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

这样可以在replace_spaces()内部局部定义replacement函数,这样它就不会:

1)混乱全局名称空间
2)让未来三年的人们想知道为什么有一个定义在全局而只在另一个函数内使用的函数

它使事情井然有序。请注意,函数本身没有名称,它只是被定义并分配为对$replacement的引用。

但请记住,您必须等待PHP 5.3 :)


1
这是一个很棒的解释。+1 - David J Eddy
4
我很喜欢你解释为什么要使用闭包的内容。大多数人并没有真正理解这一点。+1 - Carrie Kendall
13
这是关于匿名函数的解释,而不是关于闭包的解释。如你所说,匿名函数与命名函数非常相似,只是它们不是全局的。另一方面,闭包是包含词法作用域自由变量(使用“use”声明)的函数;也就是说,它们可以复制并引用在声明它们的作用域中的值,即使其他所有内容都已被垃圾回收。 - Warbo
@Warbo 这是真的; 当时我并没有真正理解匿名函数和闭包之间的区别。只有当你理解了匿名函数,闭包才真正有意义,但是直到今天,我仍然发现“解释”闭包是什么(比如我7年前的解释;-))并没有解释它的作用域方面。 - dirtside
这就是我说的,看看 JavaScript 吧,那里经常使用闭包——但要记住,在 PHP 中,变量作用域规则是不同的。 - Rolf

17

除了技术细节外,闭包对于一种称为函数导向编程的编程风格是基本前提。在面向对象编程中,闭包大致用于与对象相同的目的;它将数据(变量)与一些代码(函数)绑定在一起,然后可以将其传递到其他地方。因此,它们影响您编写程序的方式 - 如果您不改变编写程序的方式,则它们根本没有任何影响。

在 PHP 的上下文中,它们有点奇怪,因为 PHP 已经很重视基于类的面向对象范式以及旧的过程范式。通常,拥有闭包的语言具有完全的词法作用域。为了保持向后兼容性,PHP 将不会获得这个特性,这意味着闭包在这里与其他语言有所不同。我认为我们还需要看到它们将如何被使用。


17

当你未来需要执行一个你现在已经决定的任务时,可以使用函数。

例如,如果你读取配置文件并发现其中一个参数告诉你算法的hash_methodmultiply而不是square,你可以创建一个闭包,它可以在任何需要哈希某些内容的地方使用。

这个闭包可以在(例如)config_parser()中创建;它使用config_parser()本地变量(从配置文件中)创建了一个名为do_hash_method()的函数。每当调用do_hash_method()时,它都可以访问config_parser()本地范围内的变量,即使它没有在该范围内被调用。

以下是一个好的假设性例子:

function config_parser()
{
    // Do some code here
    // $hash_method is in config_parser() local scope
    $hash_method = 'multiply';

    if ($hashing_enabled)
    {
        function do_hash_method($var)
        {
            // $hash_method is from the parent's local scope
            if ($hash_method == 'multiply')
                return $var * $var;
            else
                return $var ^ $var;
        }
    }
}


function hashme($val)
{
    // do_hash_method still knows about $hash_method
    // even though it's not in the local scope anymore
    $val = do_hash_method($val)
}

我不能简单地复制粘贴这个例子并运行它。最好提供一个我可以直接运行的例子。 - Kim Stacks
3
此答案很差。这是一个没有意义的陈述:“当你将来需要一个执行你现在决定的任务的函数时。” - Relaxing In Cyprus

10

我喜欢troelskn的帖子中提供的背景。当我想要在PHP中做像Dan Udey的示例一样的事情时,我使用OO策略模式。在我看来,这比引入一个新的全局函数,其行为在运行时确定要好得多。

http://en.wikipedia.org/wiki/Strategy_pattern

在PHP中,您还可以使用保存方法名称的变量来调用函数和方法,这非常方便。因此,对于Dan的示例,另一种方法是像这样:

class ConfigurableEncoder{
        private $algorithm = 'multiply';  //default is multiply

        public function encode($x){
                return call_user_func(array($this,$this->algorithm),$x);
        }

        public function multiply($x){
                return $x * 5;
        }

        public function add($x){
                return $x + 5;
        }

        public function setAlgorithm($algName){
                switch(strtolower($algName)){
                        case 'add':
                                $this->algorithm = 'add';
                                break;
                        case 'multiply':        //fall through
                        default:                //default is multiply
                                $this->algorithm = 'multiply';
                                break;
                }
        }
}

$raw = 5;
$encoder = new ConfigurableEncoder();                           // set to multiply
echo "raw: $raw\n";                                             // 5
echo "multiply: " . $encoder->encode($raw) . "\n";              // 25
$encoder->setAlgorithm('add');
echo "add: " . $encoder->encode($raw) . "\n";                   // 10

当然,如果您希望它在每个地方都可用,您可以将所有内容都设为静态的...


2
闭包基本上是一个函数,您在一个上下文中编写定义,但在另一个上下文中运行。JavaScript 帮助我理解这些东西,因为它们在 JavaScript 中随处可见。
在 PHP 中,由于全局变量(或“外部”变量)在函数内的作用域和可访问性方面存在差异,闭包不如在 JavaScript 中有效。然而,从 PHP 5.4 开始,闭包可以在对象内部运行时访问 $this 对象,这使它们更加有效。
这就是闭包的内容,这应该足以理解上面所写的内容。
这意味着应该可以在某个地方编写一个函数定义,并在函数定义内使用 $this 变量,然后将函数定义分配给一个变量(其他人已经给出了语法示例),然后将此变量传递给一个对象并在对象上下文中调用它,函数可以通过 $this 访问和操作对象,就好像它是对象的另一个方法一样,实际上它没有在该对象的类定义中定义,而是在其他地方定义的。
如果不是很清楚,那么不要担心,一旦开始使用它们,就会变得清晰起来。

说实话,即使对于我这个作者来说,这也不是很清楚。 基本上我的意思是:要了解闭包是什么,请在JavaScript中查看它们,但请记住,变量作用域在JavaScript和PHP之间是不同的。 - Rolf

1
这是关于PHP中闭包的示例。
// Author: HishamDalal@gamil.com
// Publish on: 2017-08-28

class users
{
    private $users = null;
    private $i = 5;

    function __construct(){
        // Get users from database
        $this->users = array('a', 'b', 'c', 'd', 'e', 'f');
    }

    function displayUsers($callback){
        for($n=0; $n<=$this->i; $n++){
            echo  $callback($this->users[$n], $n);
        }
    }

    function showUsers($callback){
        return $callback($this->users);

    }

    function getUserByID($id, $callback){
        $user = isset($this->users[$id]) ? $this->users[$id] : null;
        return $callback($user);
    }

}

$u = new users();

$u->displayUsers(function($username, $userID){
    echo "$userID -> $username<br>";
});

$u->showUsers(function($users){
    foreach($users as $user){
        echo strtoupper($user).' ';
    }

});

$x = $u->getUserByID(2, function($user){

    return "<h1>$user</h1>";
});

echo ($x);

输出:

0 -> a
1 -> b
2 -> c
3 -> d
4 -> e
5 -> f

A B C D E F 

c

1

闭包:

MDN认为它有最好的解释:

闭包是一个函数与其周围状态(词法环境)的引用组合在一起,即一个闭包允许你从内部函数中访问外部函数的作用域。

也就是说,闭包是具有访问父级作用域变量的函数。闭包允许我们方便地动态创建函数,因为在某些情况下,函数只需要在一个地方使用(回调函数、可调用参数)。

示例:

$arr = [1,2,3,3];
$outersScopeNr = 2;

// The second arg in array_filter is a closure
// It would be inconvenient to have this function in global namespace
// The use keyword lets us access a variable in an outer scope
$newArr = array_filter($arr, function ($el) use ($outersScopeNr) {
    return $el === 3 || $el === $outersScopeNr;
});

var_dump($newArr);
// array (size=3)
//  1 => int 2
//  2 => int 3
//  3 => int 3

1
基本上,闭包是具有访问外部变量的内部函数,并用作匿名函数(没有任何名称的函数)的回调函数。
 <?php
      $param='ironman';
      function sayhello(){
          $param='captain';
          $func=function () use ($param){
                $param='spiderman';
          };
       $func();
       echo  $param;
       }
      sayhello();
?>

//output captain

//and if we pass variable as a reference as(&$param) then output would be spider man;

在函数 sayhello() 中,$param='captain' 是局部变量。 在 sayhello() 上面的 $param='ironman' 是全局变量。如果你想在脚本中只有一个 $param 变量,你应该在 sayhello() 函数内调用: global $param; - vlakov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接