Skip to content

PHP 类和对象之 Closure 类 #69

@huliuqing

Description

@huliuqing

Closure 类

一 匿名类

PHP 匿名类文档

1.1 概要

PHP 7 起支持匿名类

语法

<?php

$closure = new class {

};

1.2 特性

  • ① 可以继承(extends)其它类,实现(implements)接口,或使用 Trait
  • ② 可以传递参数到匿名类的构造器
  • ③ 普通类(Class)嵌套匿名类,匿名类无法直接访问
    • protected 级别可见属性或方法,可以让匿名类继承普通类访问
    • private 属性,可以通过匿名类的构造函数参数传递进匿名类
  • ④ 声明同一个匿名类,所创建的对象都是这个匿名类实例
<?php

interface Wing
{
    public function fly();
}

class Car
{
    private $hasGasoline = true;
    
    protected $wheels = 3;
    
    protected function canDrive()
    {
        if ($this->hasGasoline) {
           echo "Can drive.<br/>";
        }
    }
    
    public function getWheels()
    {
        return $this->wheels;
    }
    
    public function mountWing()
    {
        return new class($this->hasGasoline) extends Car implements Wing{
            
            public $hasGasolineClone = false;
        
            public function __construct($hasGasoline)
            {
                $this->hasGasolineClone = $hasGasoline;
            }
            
            public function fly()
            {
                if ($this->hasGasolineClone) {
                    echo "I have wing and gasoline, i can fly." . PHP_EOL;
                }
                
                if ($this->wheels == 3) {
                    echo "Car just have 3 wheels need one more.". PHP_EOL;
                    ++$this->wheels;
                }
                
                if ($this->wheels == 4) {
                    echo "Car have 4 wheels.". PHP_EOL;
                }
            }
        };
    }
}

function d($data)
{
    echo "-- START --" . PHP_EOL;
    var_dump($data);
    echo "-- END --" . PHP_EOL;
}

$car = new Car();


d($car->getWheels());
$car->mountWing()->fly();
d($car->getWheels());

二 Closure 类

名词解释:

  • callback: 回调函数(PHP 4 引入)
  • anonymous function: 匿名函数(PHP 5.3 引入)
  • closure: 闭包(PHP 5.3 引入)
  • callable: 可回调

在 PHP 4 最早稱 callback,PHP 5.3 引入 closure 與 anonymous function ,PHP 5.4 則新增 callable type hint。
所以在 PHP 中,callback、closure、anonymous function,與 callable,事實上指的是同一件事情,但因為底層用的都是 Closure 物件,通常統稱為 closure。
-- from 如何使用 Closure

2.1 匿名函数(anonymous function,或称闭包(closure))

匿名函数

2.1.1 概念

允许创建临时的没有指定函数名的函数

2.1.2 匿名函数用法

2.1.3 匿名函数原理

匿名函数是通过 closure 类实现的。

即在定义匿名函数时,php 内部会将匿名函数自动转换成为 closure 类的对象实例

2.1.4 匿名函数语法与特性

  • 定义
<?php
function($message)
{
    var_dump($message);
// 注意结束分号 ; 
};
  • 继承父作用域变量

使用 use 语言结构传递(继承)父作用域变量(PHP 7.1 起,不能传递 superglobals, $this 或和匿名函数参数重名的变量)。

① 不使用 use

<?php
$greeting = "hello";

$greet = function(){
    printf("%s world", $greeting);
};

$greet();
// PHP Notice:  Undefined variable: greeting in /home/cg/root/main.php on line 5

② 使用 use 传递(继承)父作用域变量

<?php
$greeting = "hello";

$greet = function() use ($greeting) {
    printf("%s world", $greeting);
};

$greet();
// print: hello world

③ 匿名函数获取父作用域变量的值,在定义变量时即确定,而非调用时确定

<?php
$greeting = "hello";

$greet = function() use ($greeting) {    
    printf("%s world", $greeting);
};

$greeting = "hi";// 赋新值

$greet();
//print: hello world

④ 引用传递父作用域变量

<?php
$greeting = "hello";

$greet = function() use (&$greeting) {
    printf("%s ", $greeting);
};

$greet();//print: hello

$greeting = "world";

$greet();//print: world
  • 使用 $this 伪引用
    PHP 5.4 以后,在类中定义匿名函数,将自动将 $this 类的伪引用绑定到匿名函数,可在匿名函数内使用 $this。
<?php
class Test
{
    public function testing()
    {
        return function() {
            var_dump($this);
        };
    }
}

$object = new Test;
$function = $object->testing();
$function();

//object(Test)#1 (0) {
//}
}   

闭包父作用域: 定义该闭包的函数,而非调用它的函数;所以,使用父作用域变量,需在定义匿名函数之前声明

  • 静态匿名函数(static anonymous function)

定义

<?php
static function()
{

};

静态匿名函数用户与普通匿名函数一致,区别在于类中定义静态匿名函数时,类不会将 $this 绑定到静态匿名函数

<?php
class Test
{
    public function testing()
    {
        return static function() {
            var_dump($this);
        };
    }
}

$object = new Test;
$function = $object->testing();
$function();

//PHP Fatal error:  Uncaught Error: Using $this when not in object context in /home/cg/root/main.php:7 
}   

2.2 Closure 类

Closure Document

在 2.1.3 匿名函数原理一节中说过,所有的匿名函数在声明时会自动转换成 Closure 类的实例。

简言之,匿名函数就是Closure 类

<<?php
$anonymous = function() {

};

var_dump($anonymous instanceof Closure);
//print: bool(true)

2.2.1 Closure 类方法

  • __construct(): 用于禁止实例化一个 closure 类对象
  • bind(Closure $closure, object $obj [, mixed $scope = "static"]): 绑定指定的 $obj 对象和和作用域( $score )到静态闭包( $closure )
  • bindTo(object $obj [, mixed $scope = "static"]): 绑定指定的 $obj 对象和和作用域( $score )到非静态闭包( $closure )
  • call(object $obj [, mixed $... ]): 临时绑定 $obj 对象到闭包($closure),同时并以给定的参数执行闭包调用
  • fromCallable(callable $callable)

2.2.2 Closure 类方法详解

bind 方法

bind 方法和 bindTo 方法,功能作用完全一样,唯一区别就是,bind 是 bindTo 方法的静态版本

功能: 是让 object $obj 对象能够在匿名函数作用域内使用

先上示例

<?php
class A {
    private static $sfoo = 1;
    private $ifoo = 2;
}

$cl1 = static function() {
    return A::$sfoo;
};

$cl2 = function() {
    return $this->ifoo;
};

$bcl1 = Closure::bind($cl1, null, 'A');
$bcl2 = Closure::bind($cl2, new A(), 'A');
echo $bcl1(), "\n";
echo $bcl2(), "\n";
?>

示例详解:
① 示例组成: 实例中一共由三部分组成,类 A 声明、静态匿名函数 $cl1 和 非静态匿名函数 $cl2 声明、执行 Closure:: bind 绑定

② 类 A 用于声明静态属性 $sfoo 和非静态属性 $ifoo

<?php
class A {
    private static $sfoo = 1;
    private $ifoo = 2;
}

③ 静态匿名函数 $cl1 函数体返回 A::$sfoo 的结果;匿名函数 $cl2 返回 $this->ifoo的结果

<?php
$cl1 = static function() {
    return A::$sfoo;
};

$cl2 = function() {
    return $this->ifoo;
};

③ 执行 Closure:: bind 绑定

<?php
$bcl1 = Closure::bind($cl1, null, 'A');
$bcl2 = Closure::bind($cl2, new A(), 'A');
  • 通过 $bcl1 = Closure::bind($cl1, null, 'A'); 我们发现
  • a. 第一个参数 $cl1 为 ② 中定义的静态匿名函数,而 $cl1 中使用了 A::$sfoo;
  • b. 即 A::$sfoo; 对应了第三个参数 'A',表示 Closure::bind 操作,将类 A 的作用域绑定到了匿名行数 $cl1
  • c. 而由于 $cl1 使用了类 A的静态方法,因而第二个参数为 null,表示无需实例化类 A 且仅能在 $cl1 函数中使用类 A 的静态方法及属性
  • d. 提出相关代码示例
<?php
class A {
    private static $sfoo = 1;
    private $ifoo = 2;
}

$cl1 = static function() {
    return A::$sfoo;
};

$bcl1 = Closure::bind($cl1, null, 'A');
echo $bcl1(), "\n";//print: 1

//---------------
// 更改第三个参数 'A' 为 'B',会提示报错 Class 'B' not found,可见 b 点观点正确
// 表示 Closure::bind 操作,将类 A 的作用域绑定到了匿名行数 $cl1

//$bcl2 = Closure::bind($cl1, null, 'B');
//echo $bcl2(), "\n";//Exception: PHP Warning:  Class 'B' not found in /home/cg/root/main.php on line 14

//---------------
// 文档 http://php.net/manual/en/class.closure.php
// 有关 $newscrope = 'static' 的说明 The class scope to which associate the closure is to be associated, or 'static' to keep the current one
// 这个意思是说使用当前作用域的类(即 A)
// 但是执行时会报错 Uncaught Error: Cannot access private property A::$sfoo...
// 修改可见性为 public 且仅为public,在 'static' 作用域下才能执行,结果为 1

//$bcl3 = Closure::bind($cl1, null, 'static');
//echo $bcl3(), "\n";


?>
  • 通过 $bcl2 = Closure::bind($cl2, new A(), 'A'); 我们了解到
  • a. 第一个参数由静态匿名函数 $cl1 变成了 非静态匿名函数 $cl2
  • b. 由此,第二个参数由 null 变成了 new A();这是因为在非静态匿名函数 $cl2 内部使用了 $this,因此需要实例化类 A
<?php
class A {
    private static $sfoo = 1;
    private $ifoo = 2;
}

$cl2 = function() {
    return $this->ifoo;
};

$bcl2 = Closure::bind($cl2, new A(), 'A');
echo $bcl2(), "\n";//print: 2


//$bcl3 = Closure::bind($cl2, new A(), 'static');
//echo $bcl3(), "\n";//当 A $ifoo 属性控制访问更改为 public 输出 2
?>

bindTo 方法

<?php
// 实例功能同 $bcl1 = Closure::bind($cl1, null, 'A');

class A {
    public static $sfoo = 1;
    public $ifoo = 2;
}

$cl1 = static function () {
    return A::$sfoo;
};

$bcl1 = $cl1->bindTo(null, A::class);

echo $bcl1();
<?php
// 实例功能同 $bcl2 = Closure::bind($cl2, new A(), 'A');

class A {
    public static $sfoo = 1;
    public $ifoo = 2;
}

$cl1 = function () {
    return $this->ifoo;
};

$bcl1 = $cl1->bindTo(new A(), A::class);

echo $bcl1();

三 参考资料

匿名函数
Closure 类
Closure::bind
todo 深入探討 bindTo()
todo 如何使用 Closure?
https://stackoverflow.com/questions/39884308/closurebindto-how-its-work
深入理解PHP之匿名函数

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions