• php 闭包


    一、概念说明

    通常定义php函数时,都会指定一个函数名,这样的函数可以称为具名函数,但实际上PHP也支持定义没有函数名的函数,这类函数被称为闭包,也叫匿名函数,其本质是 Closure 类对象,类摘要如下

    // 类用final修饰,防止定义子类
    final class Closure {
        // 构造函数被定义为私有,防止匿名函数被实例化
        private __construct ( void )
        public static bind ( Closure $closure , object $newthis [, mixed $newscope = "static" ] ) : Closure
        public bindTo ( object $newthis [, mixed $newscope = "static" ] ) : Closure
        public call ( object $newthis [, mixed $... ] ) : mixed
        public static fromCallable ( callable $callable ) : Closure
        public __invoke( ...$values): mixed
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    二、成员函数说明

    2.1 __invoke

    当以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用,直接调用__invoke 也是一样会执行编写的函数体,下面是示例

    function callInvoke(){
        $func = function ($name,$age){
            echo __FUNCTION__," name=$name,age=$age\n";
        };
        $func();
        $func->__invoke("kate",100);
    }
    
    callInvoke();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    输出结果

    {closure} name=kate,age=100
    {closure} name=kate,age=100
    
    • 1
    • 2

    所以,编写的匿名函数函数体可以被认为是 __invoke 函数的函数体。

    2.2 bindTo

    返回一个新的闭包,新的闭包绑定了指定的 t h i s 对 象 和 类 作 用 域 。 也 就 是 将 匿 名 函 数 中 的 this 对象和类作用域。也就是将匿名函数中的 thisthis绑定为所给的对象,从而可以直接使用其成员变量和成员函数,反过来,相当于指定的对象临时增加了一个新的方法,只不过只能用函数调用的方式来调用,某种程度上扩展了对象的功能。

    // 定义商品类
    class Good {
        private $price;
    
        public function __construct(float $price)
        {
            $this->price = $price;
        }
    }
    
    // 定义一个匿名函数,计算商品的促销价
    $addDiscount = function(float $discount = 0.8){
        return $this->price * $discount;
    }
    
    $good = new Good(100);
    
    // 将匿名函数绑定到 $good 实例,同时指定作用域为 Good
    $count = $addDiscount->bindTo($good, Good::class); 
    $count(); // 80
    
    // 将匿名函数绑定到 $good 实例,但是不指定作用域,将无法访问 $good 的私有属性
    $count = $addDiscount->bindTo($good); 
    $count(); // 报错
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2.3 bind

    该函数是 bindTo 方法的静态版本,有两种用法:
    用法一:实现与 bindTo 方法同样的效果

    $count = \Closure::bind($addDiscount, $good, Good::class); 
    
    • 1

    用法二:将匿名函数与类(而不是对象)绑定,此时第二个参数需要设置为 null

    // 商品库存为 10
    class Good {
        static $num = 10;
    }
    
    // 每次销售后返回当前库存
    $sell = static function() {
        echo "当前库存为". --static::$num,"\n" ;
    };
    
    // 将静态匿名函数绑定到 Good 类中
    $sold = \Closure::bind($sell, null, Good::class);
    
    $sold(); 
    $sold();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    输出如下

    当前库存为 9
    当前库存为 8
    
    • 1
    • 2

    2.4 call

    call 方法是PHP 7 新增的,可以实现绑定并调用匿名函数,语法更加简洁,性能更高。下面是两种方式的示例代码。

    // call 版本
    $addDiscount->call($good, 0.5);  // 绑定并传入参数 0.5,结果为 50
    
    // bindTo 版本
    $count = $addDiscount->bindTo($good, Good::class); 
    $count(0.5); // 50
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.5 fromCallable

    PHP语言中callable 是一种类型,表示可以被直接调用,包括命名函数、闭包、类成员函数和类静态成员函数。fromCallable可以一个 callable 函数转化成匿名函数,

    class Good {
        private $price;
    
        public function __construct(float $price)
        {
            $this->price = $price;
        }
    }
    
    function addDiscount(float $discount = 0.8){
        return $this->price * $discount;
    }
    
    $closure = \Closure::fromCallable('addDiscount');
    $good = new Good(100);
    $count = $closure->bindTo($good);  
    $count = $closure->bindTo($good, Good::class);   // 报错,不能重复绑定作用域
    $count(); // 报错,无法访问私有属性
    
    // fromCallable 等价于
    $reflexion = new ReflectionFunction('addDiscount');
    $closure = $reflexion->getClosure();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    从测试来看,无论是 fromCallable 转化成的闭包,还是使用反射得到的闭包,在使用 bindTo 时,如果第二个参数指定绑定类,就会报下面的错错误

    Cannot rebind scope of closure created by ReflectionFunctionAbstract::getClosure()
    
    • 1

    也就是第二个参数必须为null,接下来又会发现,如果闭包函数访问了私有属性也会报错,这样一来,这个函数能做的事情其实很少。从下面两篇官方文档来看,此函数可以用来将类的 private/protected 成员函数转为闭包,从而在类外部直接调用。

    Closure::fromCallable
    php新特性介绍

    总结来说,这个函数挺鸡肋的。

    三、使用

    匿名函数的本质是对象,因此可将匿名函数赋值给某一变量。

    3.1 使用外部变量

    通过 use 声明的变量可以在匿名函数内部使用,如果声明的是引用,那么在匿名函数中修改变量后,外部变量会同步修改。

    $num = 1;
    $func = function() use($num){
        $num = $num + 1;
        echo $num;
    }
    $func();  // 2
    echo $num;  // 还是 1
    
    // 要让匿名函数中对外部变量的修改在闭包结束后仍然生效,需要使用引用传值
    
    $num = 1;
    $funcRef = function() use(&$num){
        $num = $num + 1;
        echo $num;
    }
    $funcRef();  // 2
    echo $num;  // 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.2 自动绑定$this到当前类

    从 PHP 5.4 开始,在类里面使用匿名函数时,匿名函数的 $this 将自动绑定到当前类

    class Foo {
        public function bar()
        {
            return function() {
                return $this;
            };
        }
    
        public function getAge(){
            return 100;
        }
    }
    
    function testThis(){
        $foo = new Foo();
        $obj = $foo->bar();
    
        var_dump($obj);//
        var_dump($obj()->getAge());
    }
    
    testThis();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    输出结果如下

    object(Closure)#2 (1) {
      ["this"]=>
      object(Foo)#1 (0) {
      }
    }
    int(100)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果不想让自动绑定生效,可以使用静态匿名函数,此时返回的闭包中不能使用 $this,否则一调用就会报下面的 Fatal error

    class FooStatic
    {
        public function bar()
        {
            return static function () {
                return $this;
            };
        }
    
        public function getAge()
        {
            return 100;
        }
    
    }
    
    function testStaticThis()
    {
        $foo = new FooStatic();
        $obj = $foo->bar(); // Closure()
        var_dump($obj);
    
        $obj(); // Fatal error: Using $this when not in object context
    }
    testStaticThis();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    因此静态匿名函数实际上是绑定了整个类,可以通过static关键字访问类静态变量,示例代码如下

    class FooStatic
    {
        static $quantity = 10;
        public function bar()
        {
            return static function () {
                echo static::$quantity,"\n";
            };
        }
    }
    
    $foo = new FooStatic();
    $obj = $foo->bar(); // Closure()
    var_dump($obj);
    
    $obj();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    输出如下

    object(Closure)#2 (0) {
    }
    10
    
    • 1
    • 2
    • 3

    参考

    php手册 https://www.php.net/manual/zh/class.closure.php
    心智极客 https://learnku.com/articles/35863

  • 相关阅读:
    vue router
    mysql的备份和恢复
    [附源码]java毕业设计疫情期间回乡人员管理系统
    数据库概论 - MySQL的简单介绍
    磁盘的架构
    MySQL学习笔记之单行函数
    查看当前所有的数据库
    cesium 实战记录(三)获取鼠标位置总结
    关于Vue中的if使用注意事项
    信息系统项目管理师教程 第四版【第2章-信息技术发展-思维导图】
  • 原文地址:https://blog.csdn.net/afterlife_union/article/details/128019382