优秀的编程知识分享平台

网站首页 > 技术文章 正文

PHP Closure类(closure什么意思)

nanyue 2024-08-26 17:56:26 技术文章 6 ℃

Closure 类 用于代表匿名函数的类。

匿名函数(在 PHP 5.3 中被引入)会产生这个类型的对象。在过去, 这个类被认为是一个实现细节, 但现在可以依赖它做一些事情。自 PHP 5.4 起,

这个类带有一些方法, 允许在匿名函数创建后对其进行更多的控制。

这个类不能实例化, 里面主要有两个方法, 都用来复制闭包, 一个静态一个动态, 下面分别详细讲解下这两个不好理解的方法

Closure::bind
public static Closure Closure::bind ( Closure $closure , object $newthis [, mixed $newscope = 'static' ] )

参数说明:

closure 需要绑定的匿名函数。

newthis 需要绑定到匿名函数的对象, 或者 NULL 创建未绑定的闭包。

newscope 想要绑定给闭包的类作用域, 或者 'static' 表示不改变。如果传入一个对象, 则使用这个对象的类型名。

类作用域用来决定在闭包中 $this 对象的私有、保护方法 的可见性。

上面是该方法的定义, 第一个参数很好理解, 就是一个闭包函数;

第二个参数就不太好理解, 如果要复制的闭包中包含$this, 这个对象就表示这个$this, 闭包函数里面对这个对象的修改在调用结束之后也会保持一致,

比如修改了一个属性;

第三个参数就不太好理解了, 看官方的说明也是云里雾里的,

默认参数情况下, 调用$this->访问object $newthis中的属性函数的时候, 会有限制, 只能访问public属性的函数,

如果想访问protected/private属性, 就要设置为对应的类名/类实例, 就要像在类里面一样, 要访问那个类的保护/私有属性函数。

例子

<?php
class T {
    private function show()
    {
        echo "我是T里面的私有函数:show\r\n";
    }
    protected function who()
    {
        echo "我是T里面的保护函数:who\r\n";
    }
    public function name()
    {
        echo "我是T里面的公共函数:name\r\n";
    }
}
$test = new T();
$func = Closure::bind(function(){
$this->who();
$this->name();
$this->show();
}, $test);
$func();

上面的代码会报错Fatal error: Uncaught Error: Call to protected method T::who() from context 'Closure'。

加上bind第三个参数为T::class或者new T(),会正常输出每一个结果。

<?php
class T {
    private function show()
    {
        echo "我是T里面的私有函数:show\r\n";
    }
    protected function who()
    {
        echo "我是T里面的保护函数:who\r\n";
    }
    public function name()
    {
        echo "我是T里面的公共函数:name\r\n";
    }
}
$test = new T();
$func = Closure::bind(function(){
$this->who();
$this->name();
$this->show();
}, $test, new T());
$func();
?>
/*
输出结果:
我是T里面的保护函数:who 我是T里面的公共函数:name 我是T里面的私有函数:show
*/

当然了, 闭包也可以传递参数

$test = new StdClass();
var_dump($test);
echo "<br>";
$func = Closure::bind(function($obj){
    $obj->name = "燕睿涛";
}, null);
$func($test);
var_dump($test);
/*
object(stdClass)#1 (0) { }
object(stdClass)#1 (1) { ["name"]=> string(9) "燕睿涛" }
*/

另外还有个特别要说明的例子

<?php
class T {
    private function show()
    {
        echo "我是T里面的私有函数:show\n";
    }
    protected function who()
    {
        echo "我是T里面的保护函数:who\n";
    }
    public function name()
    {
        echo "我是T里面的公共函数:name\n";
    }
}
$func = Closure::bind(function ($obj) {
$obj->show();
}, null);
$test = new T();
$func($test);

上面的情况会输出什么呢, 没错, 会报错, 提示访问不了私有属性show, 这个时候, 加上第三个参数就可以了(T::class或者new T()), 看了第三个参数不光影响$this的作用域,

也可以影响参数的作用域。

Closure::bindTo

bindTo和bind功能类似, 这里只是另外一种形式, 都是复制当前闭包对象, 绑定指定的$this对象和类作用域。

参数比bind少了第一个, 后面两个一样, 当然还有一个区别就是bindTo不是静态方法, 是闭包才会存在的一个属性方法。

例子

<?php
class T {
    private function show()
    {
        echo "我是T里面的私有函数:show\n";
    }
    protected function who()
    {
        echo "我是T里面的保护函数:who\n";
    }
    public function name()
    {
        echo "我是T里面的公共函数:name\n";
    }
}
$func = function () {
$this->show();
$this->who();
$this->name();
};
$funcNew = $func->bindTo(new T(), T::class);
$funcNew();

上面函数的输出和bind的类似

我是T里面的私有函数:show

我是T里面的保护函数:who

我是T里面的公共函数:name

一个trick(戏法)

这个函数是在看composer生成的自动加载源码的时候碰到的, 在composer中用的比较特别, 下面是截取部分composer中的代码

// 文件autoload_real.php
call_user_func(\Composer\Autoload\ComposerStaticInit898ad46cb49e20577400c63254121bac::getInitializer($loader));
// 文件autoload_static.php
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit25885cdf386fdaafc0bce14bb5a7d06e::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit25885cdf386fdaafc0bce14bb5a7d06e::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit25885cdf386fdaafc0bce14bb5a7d06e::$prefixesPsr0;
$loader->classMap = ComposerStaticInit25885cdf386fdaafc0bce14bb5a7d06e::$classMap;
}, null, ClassLoader::class);
}

上面的代码比较奇特, 在call_user_func中, 第一感觉是传错参数了, 其实不然, 这里调用了一个函数, 这个函数会返回一个Closure对象, 也就是一个匿名函数, 最终传入的参数还是一个callable类型。

再看看这个返回的闭包, 里面使用了use, 这是连接闭包和外部变量的桥梁。

至于这里为什么普通传参数就可以, 是因为php5里面, 对象形参和实参数指向相同的对象, 函数里面对对象的修改会反映到对象外面。

所以, 上面这么做是没问题的, 还有另外一种形式也可以

call_user_func(\Composer\Autoload\ComposerStaticInit898ad46cb49e20577400c63254121bac::getInitializer(), $loader);
public static function getInitializer()
{
return \Closure::bind(function ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit25885cdf386fdaafc0bce14bb5a7d06e::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit25885cdf386fdaafc0bce14bb5a7d06e::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit25885cdf386fdaafc0bce14bb5a7d06e::$prefixesPsr0;
$loader->classMap = ComposerStaticInit25885cdf386fdaafc0bce14bb5a7d06e::$classMap;
}, null, ClassLoader::class);
}

Tags:

最近发表
标签列表