[Modern PHP] 第二章 新特性之五 闭包

闭包

闭包和匿名函数是从PHP 5.3.0开始出现的,这是我最喜欢也是用的最多的PHP功能。听到这些名称心里特别没底(至少我第一次听到时是这么认为的),但是事实上真的很好理解。它们是每个PHP开发者们的工具箱中必备的最有用的工具。

闭包作为一个函数,在创建时会封装外部的状态。即使最初创建闭包时的环境已经不存在了,封装的状态也会一直保存在闭包中。这是一个不太好掌握的概念,一旦你能够弄明白,感觉就像人生翻开了新的篇章。

匿名函数实际上就是没有名字的函数。匿名函数可以被赋值给变量,像所有其它的PHP对象一样在代码中传递。但是它终归还是函数,所以你可以调用它并且传递参数。匿名函数最大的用处是作为函数或者方法的回调。

闭包和匿名函数理论上是不同的概念。然而,PHP认为它们是一码事。所以,当我说闭包的时候也可能指的是匿名函数,反之亦然。

PHP的闭包和匿名函数在语法上和函数一样,但是别被它们弄混。他们实际上是伪装成函数的对象。如果你打印检查一个PHP闭包或者匿名函数的类型,你会发现它们都是Closure类的实例。Closure可以看作是同string和integer一样重要的数据类型。

创建

我们都知道PHP的闭包和函数看起来很像。当你像例子 2-19那样创建一个PHP闭包后,你就不会感到惊讶了。

例子 2-19 简单的闭包

<?php
$closure = function ($name) {
    return sprintf('Hello %s', $name);
};

echo $closure("Josh");
// 输出 --> "Hello Josh"

就这么简单。例子 2-19创建了一个Closure对象并将它赋值给变量$closure。它看起来像一个标准的PHP函数:它使用了相同的语法、接收参数并且有返回值。但是它没有名字。

我们可以调用$closure变量,因为$closure的是一个闭包,Closure闭包对象都实现了\_invoke()这个魔术方法。在变量名跟着一对()符号时PHP会自动查找并调用__invoke()方法。

我通常使用PHP的闭包对象作为函数和方法的回调。很多PHP的函数都会使用回调函数,例如array_map()和preg_replace_callback()。这就像为PHP匿名函数量身定做的功能!记住,就像其它任何值一样,闭包可以像参数一样被传递给其它PHP函数。在例子 2-10中我使用一个闭包对象作为array_map()函数的回调参数。

例子 2-20 array_map闭包

<?php
$numbersPlusOne = array_map(function ($number) {
    return $number +1;
}, [1,2,3]);
print_r($numbersPlusOne);
// 输出 --> [2,3,4]

看起来并不是那么让人印象深刻是吗?但是记住,在闭包功能出现之前要实现这样的功能,PHP开发者们并没有什么好的选择,他们只能创建一个具名函数并把函数名作为参数传递进去才行。这样做执行上会有些慢,最重要的是它分离了回调的实现和使用。老派的PHP开发者们使用下面的代码:

<?php
// 具名回调的实现
function incrementNumber ($number) {
    return $number + 1;
}

// 具名回调的使用
$numberPlusOne = array_map('incrementNumber', [1,2,3]);
print_r($numberPlusOne);

上面的代码固然可以正常执行,但是却没有例子 2-20中的简洁。我们并不需要一个单独以incrementNumber()命名的一次性函数作为回调。使用闭包作为回调可以创建出更简练、可读性更强的代码。

附着状态

目前为止我们演示了如何使用无名(也叫匿名)函数作为回调使用。下面让我们研究一下如何使用PHP闭包附着和封装状态。JavaScript开发者们可能会对PHP的闭包产生困惑,因为PHP的闭包不会像JavaScript一样自动将应用程序的状态封装给闭包。相反,你必须手动的调用闭包对象的bintTo()方法或者use关键词将状态附着给一个PHP闭包。

通常我们都是使用use关键词来附着闭包状态,所以让我们先以此为例(例子 2-21)。当你使用use关键词将一个变量附着给一个闭包时,被附着的变量值将一直保持为变量被附着给闭包的那一刻的值。

例子 2-21 使用use关键词附着一个闭包状态

<?php
function enclosePerson($name) {
    return function ($doCommand) use ($name) {
        return sprintf('%s, %s', $name, $doCommand);
    };
}

// 将字符串"Clay"封装进闭包
$clay = enclosePerson('Clay');

// 调用闭包
echo $clay('get me sweet tea!');
// 输出 --> "Clay, get me sweet tea!"

在例子 2-21中,具名函数enclosePerson()函数接收一个$name参数,返回一个封装了$name参数的闭包对象。即使闭包最终离开了enclosePerson()函数的作用域,但是返回的闭包对象$clay中仍然会保留$name参数被附着给闭包时的值。也就说,$name变量仍然存在在闭包中!

你可以使用use关键词给闭包传递多个参数。参数之间使用逗号分隔,就像你们平时使用PHP的函数或者方法的参数一样。

别忘了PHP闭包都是对象。每个闭包的实例都有其内部状态,我们可以像其他PHP对象那样使用$this关键词来获取这些状态。一个闭包对象的默认状态相当无趣,它包含了一个魔术方法__invoke()和一个bindTo()方法,仅此而已。

不过bindTo()方法却可以带领我们去发掘出一些有趣的实现。这个方法允许我们将闭包对象的内部状态绑定到另一个对象上。bindTo()方法的第二个参数相当关键,它可以指定闭包需要绑定到的对象的类。这样我们就可以在闭包中获取绑定后的对象中protected和private的成员变量了。

你会发现bindTo()方法经常被一些PHP框架用来将路由地址映射到匿名回调函数上。这些框架将一个匿名函数绑定到应用程序对象上。你可以在匿名函数中使用$this关键词来引用应用程序对象,就像例子 2-22中所示

例子 2-22 使用bindTo方法附着闭包状态

<?php
class App
{
    protected $routes = array();
    protected $responseStatus = '200 OK';
    protected $responseContentType = 'text/html';
    protected $responseBody = 'Hello world';

    public function addRoute($routePath, $routeCallback)
    {
        $this->routes[$routePath] = $routeCallback->bindTo($this, __CLASS__);
    }

    public function dispatch($currentPath)
    {
        foreach ($this->routes as $routePath => $callback) {
            if ($routePath === $currentPath) {
                $callback();
            }
        }

        header('HTTP/1.1 ' . $this->responseStatus);
        header('Content-type: ' . $this->responseContentType);
        header('Content-length: ' . mb_strlen($this->responseBody));
        echo $this->responseBody;
    }
}

注意addRoute方法。它接收一个路由地址(例如 /users/josh)和一个路由的回调。dispatch()方法接收一个当前HTTP请求地址并调用对应的路由回调。神奇的地方在第11行,我们将路由的回调绑定给了当前App类的实例。这样我们就可以创造一个可以操作App实例状态的回调函数了:

<?php
$app = new App();
$app->addRoute('/users/josh', function () {
    $this->responseContentType = 'application/json;charset=utf8';
    $this->responseBody = '{"name": "Josh"}';
});
$app->dispatch('/users/josh');
时间: 2024-10-10 22:01:21

[Modern PHP] 第二章 新特性之五 闭包的相关文章

[Modern PHP] 第二章 新特性之七 内置HTTP服务器

内置HTTP服务器 你知道PHP从5.4.0开始有了一个内置的web服务器吗?对于那些只知道使用Apache或者nginx去预览PHP页面的PHP开发者们来说这又是一块未被发掘的宝石.虽然你不能在产品环境中使用PHP的内置web服务器,但是这个功能对于本地开发来说真是的一个完美的工具. 无论我是否在写PHP代码,反正每天都会使用PHP的内置web服务器.我会使用它来预览Laravel和Slim Framework(译者注:框架的作者就是本书的作者Josh Lockhart)应用程序,在使用Dru

C++标准程序库读书笔记-第二章新的语言特性

1.基本类型的显式初始化 如果采用不含参数.明确的constructor(构造函数)调用语法,基本型别会被初始化为零: int i1; //undefined value int i2 = int(); //initialized with zero 这个特性可以确保我们在撰写template程序代码时,任何型别都有一个确切的初值.例如下面这个函数中,x保证被初始化为零. template <class T> void f() { T x = T(); }

EF架构学习第二章(特性控制以及验证)

特性控制 数据验证(MVC)与数据库映射 Key 主键 Required 指定列非空 DisplayFormat //显示格式 MaxLength //最大长度 Display 指定本地显示字符串 DatabaseGenerated 指定主键列创建的模式(自动增长还是自定义) NotMapped 不映射为数据列 Forengin key 映射外键 Column 指定生成列名的规则(列名 数据类型) MVC数据验证 RegularExpression 正则表达式验证 Range 范围验证 Stri

C++11新特性之五——可变参数模板

有些时候,我们定义一个函数,可能这个函数需要支持可变长参数,也就是说调用者可以传入任意个数的参数.比如C函数printf(). 我们可以这么调用. printf("name: %s, number: %d", "Obama", 1); 那么这个函数是怎么实现的呢?其实C语言支持可变长参数的. 我们举个例子, double Sum(int count, ...) { va_list ap; double sum = 0; va_start(ap, count); fo

JDK1.5/1.6/1.7新特性

开发过程中接触到了从jdk1.5---jdk1.7的使用,在不同的阶段,都使用过了jdk的一些新特性,操作起来更加方面啦!特此总结了下,与喜欢it 的朋友共勉!呵呵 以下是测试代码: JDK1.5新特性: 1.自动装箱与拆箱: Integer iObj = 3; System.out.println(iObj + 12); Integer i1 = 137(-128--127范围时,为true); Integer i2 = 137(-128--127范围时,为true); System.out.

JAVA JDK1.5-1.9新特性

JAVA JDK1.5-1.9新特性 1.5 1.自动装箱与拆箱: 2.枚举(常用来设计单例模式) 3.静态导入 4.可变参数 5.内省 1.6 1.Web服务元数据 2.脚本语言支持 3.JTable的排序和过滤 4.更简单,更强大的JAX-WS 5.轻量级Http Server 6.嵌入式数据库 Derby 1.7 1,switch中可以使用字串了 2.运用List tempList = new ArrayList<>(); 即泛型实例化类型自动推断 3.语法上支持集合,而不一定是数组 4

JDK1.5/1.6/1.7之新特性总结(转载)

原文地址:http://www.cnblogs.com/yezhenhan/archive/2011/08/16/2141510.html 如果原作者看到不想让我转载请私信我! 开发过程中接触到了从jdk1.5---jdk1.7的使用,在不同的阶段,都使用过了jdk的一些新特性,操作起来更加方面啦!特此总结了下,与喜欢it 的朋友共勉!呵呵 以下是测试代码: JDK1.5新特性: 1.自动装箱与拆箱: Integer iObj = 3; System.out.println(iObj + 12)

【转载】《Ext JS 4 First Look》翻译之一:新特性

免责声明:     本文转自网络文章,转载此文章仅为个人收藏,分享知识,如有侵权,请联系博主进行删除.     原文作者:^_^肥仔John      原文地址:http://www.cnblogs.com/fsjohnhuang/archive/2013/01/29/2880705.html 第一章 新特性 Extjs 4相对于之前的版本作出了重大的修正.其中包括全新的类系统.新平台的引入.API的修整和加强还有新组件的引入(如新的图表和图形组件).Extjs 4提供更快速.更稳定的用户体验,

Java SE 6 新特性: 编译器 API

新 API 功能简介 JDK 6 提供了在运行时调用编译器的 API,后面我们将假设把此 API 应用在 JSP 技术中.在传统的 JSP 技术中,服务器处理 JSP 通常需要进行下面 6 个步骤: 分析 JSP 代码: 生成 Java 代码: 将 Java 代码写入存储器: 启动另外一个进程并运行编译器编译 Java 代码: 将类文件写入存储器: 服务器读入类文件并运行: 但如果采用运行时编译,可以同时简化步骤 4 和 5,节约新进程的开销和写入存储器的输出开销,提高系统效率.实际上,在 JD