PHP V5.3 中的新特性,第 2 部分: 闭包及 lambda 函数

闭包函数和 lambda 函数绝对不是新出现的概念;它们均来自函数编程领域。函数编程 是一种编程风格,它将关注点从执行命令转移到表达式计算。这些表达式是使用函数构成的,结合这些函数可以得到我们要查找的结果。这种编程风格最常用于学术目的,但是也可以在人工智能与数学领域中见到,并且可以在用 Erlang、Haskell 及 Scheme 等语言编写的商业应用程序中找到。

闭包 最初是在 20 世纪 60 年代作为 Scheme 的一部分开发的,Scheme 是最著名的函数编程语言之一。Lambda 函数和闭包通常出现在允许将函数处理为第一类值(First-class value)的语言中,这意味着函数可以动态创建并作为参数传递给其他语言。

从那时起,闭包及 lambda 函数已经找到了走出函数编程世界并进入 JavaScript、Python 和 Ruby 等语言的方法。JavaScript 是支持闭包和 lambda 函数的最常见语言之一。JavaScript 实际使用这些函数作为支持面向对象的编程方法,把函数嵌套到其他函数中以用作私有成员。清单 1 提供了 JavaScript 如何使用闭包的示例。

清单 1. 使用闭包构建 JavaScript 对象

var Example = function()
{
    this.public = function()
    {
        return "This is a public method";
    }; 

    var private = function()
    {
        return "This is a private method";
    };
};

Example.public()  // returns "This is a public method"
Example.private() // error - doesn't work

 

如清单 1 中所示,Example 对象的成员函数被定义为闭包。由于私有方法作用于局部变量(与绑定到使用此关键字的 Example 对象的公共方法相反),因此从外部看不到它。

现在我们已经了解了这些概念的历史,让我们查看 PHP 中的 lambda 函数。lambda 函数的概念是闭包的基础,并且提供了一种比 PHP 中已有的 create_function() 函数改进了很多的动态创建函数的方法。

Lambda 函数

Lambda 函数(或者通常所谓的 “匿名函数”)是可以随时定义的简单抛弃型函数,并且通常都与变量绑定。函数本身仅存在于定义函数的变量范围内,因此当该变量超出范围时,函数也超出范围。lambda 函数的理念源于 20 世纪 30 年代的数学研究。它被称为 lambda 演算,用于研究函数定义与应用程序以及递归概念。lambda 演算用于开发函数编程语言,例如 Lisp 和 Scheme。

对于大多数实例来说,尤其对于接受回调函数的许多 PHP 函数来说,Lambda 函数非常方便。array_map() 就是这样一种函数,它允许我们遍历数组并将回调函数应用到数组的每个元素上。在早期版本的 PHP 中,这些函数的最大问题是没有一种清晰的方式定义回调函数;我们坚持使用以下三种解决方法的其中一种:

  1. 我们可以在代码中的其他位置定义回调函数,因此我们知道它可用。这有些麻烦,因为它把调用的实现部分移到了其他位置,这样做对于可读性与可维护性极为不便,尤其是不打算在其他位置使用此函数时。
  2. 我们可以在同一个代码块中定义回调函数,但是使用一个名称。虽然这样做有助于把内容放在一起,但是需要在定义周围添加if 块以避免名称空间冲突。清单 2 展示了这种方法。

    清单 2. 在同一个代码块中定义指定的回调

    function quoteWords()
    {
         if (!function_exists ('quoteWordsHelper')) {
             function quoteWordsHelper($string) {
                 return preg_replace('/(\w)/','"$1"',$string);
             }
          }
          return array_map('quoteWordsHelper', $text);
    }
  3. 我们可以使用 create_function()(从 V4 开始就是 PHP 的一部分)在运行时创建函数。虽然在功能上此函数执行了所需操作,但是它有一些缺点。一个主要缺点是,它在运行时而非编译时编译,不允许操作码缓存来缓存函数。它的语法智能(syntax-wise)也非常糟糕,并且大多数 IDE 中的字符串高亮显示功能完全不起作用。

虽然接受回调函数的函数功能十分强大,但是没有一种好方法可以执行一次性回调函数,而无需执行一些非常笨拙的工作。使用 PHP V5.3,我们可以使用 lambda 函数以更规则的方法重新执行以上示例。

清单 3. 使用 lambda 函数用于回调的 quoteWords()

function quoteWords()
{
     return array_map('quoteWordsHelper',
            function ($string) {
                return preg_replace('/(\w)/','"$1"',$string);
            });
}

 

我们看到了定义这些函数的更规则的语法,这可以通过操作码缓存来优化性能。我们也已经得到了改进的可读性以及与字符串高亮显示功能的兼容性。让我们在此基础上了解如何在 PHP 中使用闭包。

 

回页首

闭包

Lambda 函数本身并没有添加以前不能执行的功能。正如我们所见,我们可以使用 create_function() 执行所有这项操作,尽管后果是使用更糟糕的语法并且性能更加不理想。但是它们仍然是抛弃型函数并且不维护任何类型的状态,这限制了我们可以用它们执行的操作。因此出现了闭包并使 lambda 函数得到增强。

闭包是在它自己的环境中执行计算的函数,它有一个或多个绑定变量可以在调用函数时访问。它们来自函数编程世界,其中涉及大量概念。闭包类似于 lambda 函数,但是在与定义闭包的外部环境中的变量进行交互方面更加智能。

让我们看一看如何在 PHP 中定义闭包。清单 4 显示了从外部环境导入变量并将其简单地输出到屏幕上的闭包示例。

清单 4. 简单闭包示例

$string = "Hello World!";
$closure = function() use ($string) { echo $string; };

$closure();

Output:
Hello World!

 

从外部环境中导入的变量是在闭包函数定义的 use 子句中指定的。默认情况下,它们是由值传递的,意味着如果要更新传递到闭包函数定义内的值,则不更新外部值。但是,我们可以通过在变量前放置 & 运算符来完成此操作,这种方法在函数定义中用于表示按引用传递。清单 5 显示了这种方法的示例。

清单 5. 通过引用传递变量的闭包

$x = 1
$closure = function() use (&$x) { ++$x; }

echo $x . "\n";
$closure();
echo $x . "\n";
$closure();
echo $x . "\n";

Output:
1
2
3

 

可以看到,闭包使用外部变量 $x 并在每次调用闭包时递增该变量。我们可以将按值和按引用传递的变量轻松地混合到 use 子句中,并且可以顺利地处理这些变量。我们也可以拥有直接返回闭包的函数,如清单 6 所示。在本例中,闭包的生命周期实际上比定义闭包的方法长。

清单 6. 函数返回的闭包

function getAppender($baseString)
{
      return function($appendString) use ($baseString) { return $baseString .
$appendString; };
}

 

 

回页首

闭包与对象

闭包不但是过程式编程的有用工具,而且是面向对象编程的有用工具。在这种情况下使用闭包与在类外部使用闭包实现的目的相同:包含在小范围内绑定的特定函数。在对象外部与在对象内部使用一样简单。

在对象内定义时,非常方便的一点是闭包通过 $this 变量拥有对对象的完全访问权,而无需显式导入。清单 7 演示了该示例。

清单 7. 对象内的闭包

class Dog
{
    private $_name;
    protected $_color;

    public function __construct($name, $color)
    {
         $this->_name = $name;
         $this->_color = $color;
    }

    public function greet($greeting)
    {
         return function() use ($greeting) {
             echo "$greeting, I am a {$this->_color} dog named
{$this->_name}.";
         };
    }
}

$dog = new Dog("Rover","red");
$dog->greet("Hello");

Output:
Hello, I am a red dog named Rover.

 

在这里,我们在闭包内显式使用提供给 greet() 方法的欢迎词,闭包在该方法内定义。我们还在闭包内获取狗的颜色和名字,传递到构造函数中并存储到对象中。

在类中定义的闭包基本上与在对象外部定义的闭包相同。惟一的不同之处在于通过 $this 变量自动导入对象。我们可以通过将闭包定义为静态闭包禁用此行为。

清单 8. 静态闭包

class House
{
     public function paint($color)
     {
         return static function() use ($color) { echo "Painting the
house $color...."; };
     }
}

$house = new House();
$house->paint('red');

Output:
Painting the house red....

 

此示例类似于清单 5 中定义的 Dog 类。最大的差别是在闭包内不使用对象的任何属性,因为它被定义为静态类。

在对象内使用静态闭包与使用非静态闭包相比的最大优点是节省内存。由于无需将对象导入闭包中,因此可以节省大量内存,尤其是在拥有许多不需要此功能的闭包时。

针对对象的另一个优点是添加名为 __invoke() 的魔术方法,此方法允许对象本身被调用为闭包。如果定义了此方法,则在该上下文中调用对象时将使用此方法。清单 9 演示了示例。

清单 9. 使用 __invoke() 方法


class Dog
{
    public function __invoke()
    {
         echo "I am a dog!";
    }
}

$dog = new Dog();
$dog();

 

将清单 9 中所示的对象引用调用为变量将自动调用 __invoke() 魔术方法,使类本身用作闭包。

闭包可以很好地与面向对象的代码以及面向过程的代码整合。让我们看一看闭包如何与 PHP 的强大 Reflection API 交互。

 

回页首

闭包与反射

PHP 有一个有用的反射 API,它允许我们对类、接口、函数和方法执行反向工程。按照设计,闭包是匿名函数,这意味着它们不显示在反射 API 中。

但是,新 getClosure() 方法已经添加到 PHP 中的 ReflectionMethod 和 ReflectionFunction 类中,可以从指定的函数或方法动态创建闭包。它在此上下文中用作宏,通过闭包调用函数方法将在定义它的上下文中执行函数调用。清单 10 显示了此方法如何运行。

清单 10. 使用 getClosure() 方法

class Counter
{
      private $x;

      public function __construct()
      {
           $this->x = 0;
      }

      public function increment()
      {
           $this->x++;
      }

      public function currentValue()
      {
           echo $this->x . "\n";
      }
}
$class = new ReflectionClass('Counter');
$method = $class->getMethod('currentValue');
$closure = $method->getClosure()
$closure();
$class->increment();
$closure();
Output:
0
1

 

这种方法的一个有趣的副作用是允许通过闭包访问类的私有成员和受保护成员,这有利于对类执行单元测试。清单 11 展示了对类的私有方法的访问。

清单 11. 访问类的私有方法

class Example
{
     ....
     private static function secret()
     {
          echo "I'm an method that's hiding!";
     }
     ...
} 

$class = new ReflectionClass('Example');
$method = $class->getMethod('secret');
$closure = $method->getClosure()
$closure();
Output:
I'm an method that's hiding!

 

此外,可以使用反射 API 来内省(introspect)闭包本身,如清单 12 所示。只需将对闭包的变量引用传递到 ReflectionMethod 类的构造函数中。

清单 12. 使用反射 API 内省闭包

$closure = function ($x, $y = 1) {};
$m = new ReflectionMethod($closure);
Reflection::export ($m);
Output:
Method [ <internal> public method __invoke ] {
  - Parameters [2] {
    Parameter #0 [ <required> $x ]
    Parameter #1 [ <optional> $y ]
  }
}

 

关于向后兼容性值得注意的一点是,PHP 引擎现在保留类名 Closure 并用于存储闭包,因此使用该名称的所有类都需要重命名。

正如我们所见,反射 API 能够通过现有函数和方法动态创建闭包,从而为闭包提供强大的支持。它们还可以像普通函数一样内省到闭包中。

 

回页首

为什么使用闭包?

如在 lambda 函数的示例中所示,闭包的最明显用法之一是少数 PHP 函数接受回调函数作为参数。但是,在需要把逻辑封装到自己的范围内的情况下,闭包会十分有用。重构旧代码以进行简化并提高可读性就是这样一个例子。查看以下示例,该示例显示了在运行一些 SQL 查询时使用的记录程序。

清单 13. 记录 SQL 查询的代码

$db = mysqli_connect("server","user","pass");
Logger::log('debug','database','Connected to database');
$db->query('insert into parts (part, description) values ('Hammer','Pounds nails');
Logger::log('debug','database','Insert Hammer into to parts table');
$db->query('insert into parts (part, description) values
      ('Drill','Puts holes in wood');
Logger::log('debug','database','Insert Drill into to parts table');
$db->query('insert into parts (part, description) values ('Saw','Cuts wood');
Logger::log('debug','database','Insert Saw into to parts table');

 

从清单 13 中可以看出执行操作的重复程度。对 Logger::log() 执行的每次调用都有相同的前两个实参。为了解决此问题,我们可以把该方法调用放入闭包并转而针对该闭包执行调用。得到的代码如下所示:

清单 14. 记录 SQL 查询的重构代码

$logdb = function ($string) { Logger::log('debug','database',$string); };
$db = mysqli_connect("server","user","pass");
$logdb('Connected to database');
$db->query('insert into parts (part, description) values ('Hammer','Pounds nails');
$logdb('Insert Hammer into to parts table');
$db->query('insert into parts (part, description) values
       ('Drill','Puts holes in wood');
$logdb('Insert Drill into to parts table');
$db->query('insert into parts (part, description) values ('Saw','Cuts wood');
$logdb('Insert Saw into to parts table');

 

代码不但在外观上更加规则,而且更易于更改 SQL 查询日志的日志级别,因为现在只需要在一个位置进行更改。

 

回页首

结束语

本文演示了闭包在 PHP V5.3 代码中作为函数编程构造时多么有用。我们讨论了 lambda 函数及闭包与这些函数相比的优点。对象与闭包可以很好地结合使用,比如我们在面向对象的代码内对闭包的特殊处理。我们看到了如何使用反射 API 创建动态闭包,以及如何内省现有的闭包。

时间: 2024-12-30 06:17:13

PHP V5.3 中的新特性,第 2 部分: 闭包及 lambda 函数的相关文章

PHP V5.3 中的新特性,第 5 部分: 从 PHP V5.2 升级到 PHP V5.3

简介 本系列着重介绍 PHP V5.3 中的新特性,例如名称空间.闭包.对象管理.面向对象编程和 Phar.虽然这些动人的新特性作为该语言的增补广受欢迎,但 PHP V5.3 同时也是为进一步优化 PHP 而设计的.它构建在流行.稳定的 PHP V5.2 的基础上,并对该语言作了增强,使之更加强大.在本文中,了解 PHP V5.3 中的变化,以及从 PHP V5.2 升级到 PHP V5.3 时需要考虑的一些事情.   回页首 语法变化 该语言新增了名称空间和闭包(在 第 2 部分 和 第 3

PHP V5.3 中的新特性,第 1 部分: 对象接口的变化

PHP V5 和面向对象编程 与 PHP V4 提供的特性相比,2004 年发布的 PHP V5 在面向对象编程(OOP)和设计方面向前迈出了很大的一步.它提供了一些必要的改进,例如类可见性.合适的构造函数和解构函数.输入提示和类反射(class-reflection)API.它为在 PHP 中进行高级的面向对象编程敞开了大门,并允许实现更加简单的设计模式,以及更好的设计类和 API. PHP V5.3 在 OOP 方面提供了大量渐进式补充.这些改进一直集中在语法补充和性能改进方面.首先,我们将

PHP V5.3 中的新特性,第 3 部分: 名称空间

很多语言都提供了名称空间特性,包括 C++ 和 Java 编程语言.引入名称空间是为了帮助组织大型的代码库,因为在大型代码库中,应用程序经常会出现函数名或类名重叠问题,这会引起其他问题.使用名称空间可以帮助识别代码提供的函数或实用程序,甚至可以帮助指定其来源.一个例子就是 C# 中的 System 名称空间,它包含有 .NET 框架提供的所有函数和类. 在其他未提供正式名称空间的语言中(比如 PHP V5.2 以及更早版本),人们常常通过在类或函数名中使用特定的命名约定来发挥名称空间的作用.比如

PHP V5.3 中的新特性,第 4 部分: 创建并使用 Phar 归档

Phar 归档的概念来自 Java 技术的 JAR 归档,它允许使用单个文件打包应用程序,这个文件中包含运行应用程序所需的所有东西.该文件不同于单个可执行文件,后者通常由编程语言生成,比如 C,因为该文件实际上是一个归档文件而非编译过的应用程序.因此 JAR 文件实际上包含组成应用程序的文件,但是考虑到安全性,不对这些文件进行仔细区分.Phar 扩展正是基于类似的理念,但是在设计时主要针对 PHP 的 Web 环境.同样,与 JAR 归档不同的是,Phar 归档可由 PHP 本身处理,因此不需要

ASP 3.0中的新特性

    假如读者已经熟悉了ASP 2.0,并正在寻找3.0版本中的实际改变的列表,那么将在下面发现这些信息.假如读者是一个ASP的初学者,可以越过本章到下一章,那里循序渐进地介绍了ASP对象和它们的用法.    ASP 3.0新特性概要    在ASP 3.0中,有一些新的特性或经历较大的变化或改进的特性.    1. 无脚本的ASP    如早先提到的, ASP处理不包括任何脚本的.asp页的速度是很快的,假如你正在创建的站点或Web应用程序文件最终可能使用ASP,最好让这些文件使用.asp文

VBSctipt 5.0中的新特性

VBSctipt 5.0中的新特性 能够在ASP中应用的特性包括了那些由脚本引擎所提供的特性,这意味着VBScript的改进也可在ASP中应用.VBScript的改进如下所述: 1. 在脚本中使用类在VBScript中实现完整的VB类(class)模型,但明显的例外是在ASP服务器端的脚本事件.可以在脚本中创建类,使它们的属性和方法能够和用于页面的其余代码,例如:Class MyClass Private m_HalfValue 'Local variable to hold value of

iOS 各版本中的新特性(What&amp;#39;s New in iOS)- 目录翻译完成

iOS 各版本中的新特性(What's New in iOS) 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 介绍 Introduction文档组织结构 Organization of Thi

iOS7 中的新特性

iOS7 中的新特性 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 介绍 Introduction文档组织结构 Organization of This Document  iOS 7.0 用

WebSphere Application Server V7中的新特性

IBM WebSphere Application Server V7 中包括一些功能强大的新特性和显著的增强功能,以帮助您实现更高的工作效率.更强的安全性.更紧密的集成和简化的管理.了解这个新版本中的关键特性,这些特性使得该版本可以为您的面向服务的体系结构提供灵活而可靠的基础. 引言 IBM WebSphere Application Server 为面向服务的体系结构(Service Oriented Architecture,SOA)应用程序交付敏捷.可靠的基础,以使应用程序与业务和 IT