Skip to content

PHP 类和对象之 Closure 类 #69

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之匿名函数

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions