💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
**闭包**就是能够读取其他函数内部变量的函数是指有权访问另外一个函数作用域中的变量的函数。可以理解为(能够读取另一个函数作用域的变量的函数) 与其他不同的是php中的闭包就是匿名函数 闭包可以从父作用域中继承变量。 任何此类变量都应该用*use*语言结构传递进去。 PHP 7.1 起,不能传入此类变量:[superglobals](https://www.php.net/manual/zh/language.variables.predefined.php)、$this或者和参数重名。参看下面的例子: 将匿名函数放在普通函数中,也可以将匿名函数返回,这就构成了一个简单的闭包 注意:理论上讲,闭包和匿名函数是不同的概念. 不过,PHP将其视作相同的概念 ``` function demo(){ //父作用域 $name='dash'; //注意闭包函数一定要加;这是将匿名函数当作变量,PHP 会自动把此种表达式转换成内置类 Closure 的对象实例。把一个 closure 对象赋值给一个变量的方式与普通变量赋值的语法是一样的 $callback=function($param) use($name){//从父作用域中继承$name变量 echo $name.$param; }; $callback('你好!'); } demo(); ``` **实现闭包的常见形态** ``` //例一 在函数里定义一个匿名函数,并且调用它 function demo() { $callback= function( $str ) { echo $str; }; $callback( 'some string' ); //直接执行闭包函数 } demo(); //例二 在函数中把匿名函数返回,并且调用它 function demo() { $callback= function( $str ) { echo $str; }; return $callback;//返回闭包函数 } $func= demo();//获取闭包函数 $func( 'some string' );//执行闭包函数 //例三 把匿名函数当做参数传递,并且调用它 function demo( $func ) { $func( 'some string' ); } $callback = function( $str ) { echo $str; }; demo( $callback ); //也可以直接将匿名函数进行传递。如果你了解js,这种写法可能会很熟悉 demo( function( $str ) { echo $str; } ); ``` **连接闭包和外界变量的关键字:USE(即继承父作用域的变量)** ``` function getMoney() { $rmb = 1; $dollar = 6; $func = function() use ( $rmb ) { echo $rmb; echo $dollar; }; $func(); } getMoney();//输出:1 报错,找不到dorllar变量 ``` use所引用的也只不过是变量的一个副本 ``` function getMoney() { $rmb = 1; $func = function() use ( $rmb ) { echo $rmb; //把$rmb的值加1 $rmb++; }; $func(); echo $rmb; } getMoney();//输出:1 1 function getMoney() { $rmb = 1; $func = function() use ( &$rmb ) { echo $rmb; //把$rmb的值加1 $rmb++; }; $func(); echo $rmb; } getMoney();//输出:1 2 //如果将匿名函数返回给外界,匿名函数会保存use所引用的变量,而外界则不能得到这些变量,这样形成‘闭包'这个概念可能会更清晰一些。 function getMoneyFunc() { $rmb = 1; $func = function() use ( &$rmb ) { echo $rmb; //把$rmb的值加1 $rmb++; }; return $func; } $getMoney = getMoneyFunc();//外界则不能得到$rmb变量 $getMoney(); $getMoney(); $getMoney(); //输出:1 2 3 ``` **经典案例:购物车类统计价格** ``` // 一个基本的购物车,包括一些已经添加的商品和每种商品的数量。 // 其中有一个方法用来计算购物车中所有商品的总价格,该方法使 // 用了一个 closure 作为回调函数。 class Cart { const PRICE_BUTTER = 1.00; const PRICE_MILK = 3.00; const PRICE_EGGS = 6.95; //商品集合 键为商品值为数量 protected $products = array(); //添加商品 public function add($product, $quantity) { $this->products[$product] = $quantity; } //获取商品数量 public function getQuantity($product) { return isset($this->products[$product]) ? $this->products[$product] : FALSE; } //计算总价 public function getTotal($tax) { $total = 0.00; //constant()获取常量的值 $callback = function ($quantity, $product) use ($tax, &$total) { $pricePerItem = constant(__CLASS__ . "::PRICE_" . strtoupper($product));//获取当前商品价格 $total += ($pricePerItem * $quantity) * ($tax + 1.0); }; //array_walk($arr ,function($arr_value,$arr_key){});//对arr中每个array_value进行回调 array_walk($this->products, $callback); return round($total, 2); } } $my_cart = new Cart; // 往购物车里添加条目 $my_cart->add('butter', 1); $my_cart->add('milk', 3); $my_cart->add('eggs', 6); // 打出出总价格,其中有 5% 的销售税. print $my_cart->getTotal(0.05) . "\n"; // 最后结果是 54.29 ``` >[info]总结 PHP闭包的特性并没有太大惊喜,其实用CLASS就可以实现类似甚至强大得多的功能,更不能和js的闭包相提并论,只能期待PHP以后对闭 包支持的改进。不过匿名函数`function(){}`还是挺有用的,比如在使用preg_replace_callback等之类的函数可以不用在外部声明回调函数了。 ## **Closure类**: 匿名函数(在 PHP 5.3 中被引入)会产生这个类型的对象 自 PHP 5.4 起,这个类带有一些方法,允许在匿名函数创建后对其进行更多的控制 ``` Closure { __construct ( void ) — 用于禁止实例化的构造函数 public static Closure bind (Closure $closure , object $newthis [, mixed $newscope = 'static'] — 复制一个闭包,绑定指定的$this对象和类作用域。 public Closure bindTo (object $newthis [, mixed $newscope = 'static' ]) — 复制当前闭包对象,绑定指定的$this对象和类作用域。 public call ( object $newthis [, mixed $... ] ) : mixed -- 绑定并调用闭包【临时将对象scope绑定到闭包并调用它的简写方法】(php7) public static fromCallable ( callable $callable ) : Closure -- 将一个可调用的包转换为一个闭包(php7?) } ``` 参数说明: * **closure**: 需要绑定的匿名函数。 * **newthis**: 需要绑定到匿名函数的对象,或者 NULL 创建未绑定的闭包(**给闭包绑定`$this`对象的**)。 参数为object还是null,取决于第一个参数闭包中是否用到了`$this`的上下文环境。(绑定的对象决定了函数中的$this的取值) 若闭包中用到了`$this`,则第2个参数不可为null,只能为object实例对象;若闭包中用到了静态访问(::操作符),第2个参数就可以为null,也可以为object **先要搞清楚类属性的可访问权限:** * **public**(在子类中可以通过self::var调用public方法或属性,parent::method调用父类方法,在实例中可以能过$obj->var 来调用 public类型的方法或属性) * **protect**(子类可访问,类外不可以访问; 在子类中可以通过self::var调用protected方法或属性,parent::method调用父类方法在实例中不能通过$obj->var 来调用 protected类型的方法或属性) * **private**(子类不可访问 属性或方法只能在该类中使用,在该类的实例、子类中、子类的实例中都不能) * 不能通过 $this 访问静态变量,静态方法里面也不可用 $this在类外不能通过 类名::私有静态变量,只能在类里面通过self,或者static 访问私有静态变量 * **newscope**: 想要绑定给闭包的类作用域,或者 'static' 表示不改变(**给闭包绑定作用域的**)。如果传入一个对象,则使用这个对象的类型名。 类作用域用来决定在闭包中 $this 对象的 私有、保护方法 的可见性。如果闭包中访问的是 private 属性,就需要第3个参数提升闭包的访问权限,若闭包中访问的是public属性,第三个参数可以不用。只有需要改变访问权限时才要(备注:可以传入类名或类的实例,默认值是 'static', 表示不改变。) 上面的例子的Closure只是全局的的匿名函数,那我们现在想指定一个类有一个匿名函数。也可以理解说,这个匿名函数的访问范围不再是全局的了,而是一个类的访问范围。 那么我们就需要将“一个匿名函数绑定到一个类中” *bind*是*bindTo*的静态版本,因此只说*bind*吧 ``` class Person{ private $name = '王力宏'; protected $age = '30'; private static $weight = '70kg'; public $address = '中国'; public static $height = '180cm'; } $obj = new Person(); //echo $obj->name;//报错 在类外部不能获取私有属性 //echo $obj->age;//报错 在类外部不能获取受保护的属性 //echo Person::$weight; //报错 在类外部不能获取私有的静态属性 echo $obj->address;//中国 公共属性 echo Person::$height;//180cm 公共静态属性 $fun = function(){ $obj = new Person(); //实例对象可以获得公有属性address, 但是$obj->name等私有属性肯定不行 上面例子已列出报错 return $obj->address; } echo $fun();//中国 $fun2 = function(){ // 类可以直接访问公有静态属性,但Person::$weight肯定不行,因为weight为私有属性 return Person::$height; } echo $fun2();//正常 输出 180cm //下面获取私有属性试试 $fun = function(){ return $this->name; } //或者 $fun = function(){ return Person::$height; } echo $fun();//会报错的 因为里面有个$this,程序压根不知道你这个$this是代表那个对象 或 那个类 ``` 这里我们就需要给闭包绑定$this对象,将Person的对象绑定到闭包的$this ``` class Person{ public $address = '中国'; public static $height = '180cm'; protected $age = '30'; private $name = '王力宏'; private static $weight = '70kg'; } //public $fun1 = function(){ return $this->address; } ; //将闭包的$this绑定到Person $address = Closure::bind($fun1,new Person()); echo $address();//中国 //该函数返回一个全新的 Closure匿名的函数,和原来匿名函数$fun一模一样,只是其中$this被指向了Person实例对象,这样就能访问address属性了。 //public static $fun2 = function(){ //return static::$height; return Person::$height; } ; //给闭包绑定了Person类的作用域,以便闭包找到Person类 //静态无this 所以不指定第二个参数 $height = Closure::bind($fun2,null,'Person'); echo $height();//中国 //protected或者private $fun3 = function(){ return $this->name.'<=>'.$this->age; } ; //将闭包的$this绑定到Person, name是私有属性,需要第3个参数 同理age属性也需要第三个属性 $nameAndage = Closure::bind($fun3,new Person(),'Person'); echo $nameAndage();//王力宏<=>30 //private static $fun4 = function(){ //return self::$weight;//第三个参数不指定作用域,是找不到self指向的那个类的 return Person::$weight;//不绑定的话肯定报错私有和保护的属性在外部是不能调用的 } ; //给闭包绑定了Person类的作用域 //静态无this 所以不指定第二个参数 $weight = Closure::bind($fun4,null,'Person'); echo $weight();//70kg ``` ### **总结:** 第二个参数为null则获取不到闭包的$this **第二个参数可以是类实例或 null(默认)** 第三个参数null只能获取public权限的属性或者方法(获取私有保护或静态的需要设置第三个参数)**第三个参数可以是类实例,可以是类字符串,或 static(默认)** 绑定的几种情况 1、只绑定$this对象. (public访问权限的属性或方法) 2、只绑定类作用域. (静态的属性或方法) 3、同时绑定$this对象和类作用域 (protected,private访问权限的属性或方法) 4、都不绑定.(这样一来只是纯粹的复制`Closure::bind($fun4,null)`与`clone $fun4`一样, 文档说法是使用cloning代替bind或bindTo) ## **BindTo与Bind差不多** 完全解除绑定使用`bindTo(null, null)` ``` class Person{ public $address = '中国'; public static $height = '180cm'; protected $age = '30'; private $name = '王力宏'; private static $weight = '70kg'; } //闭包返回的是Closure对象所以可以直接用bindTo $func = function ($name, $age) { $this->name = $name; $this->age = $age; return $this->name.'<=>'.$this->age; }; $fun5=$func ->bindTo(new Person,'Person'); echo $fun5('dash',30); //简写为 $fun5 = (function ($name, $age) { $this->name = $name; $this->age = $age; return $this->name.'<=>'.$this->age; })->bindTo(new Person,'Person'); echo $fun5('dash',30); //更近一步简写 (function ($name, $age) { $this->name = $name; $this->age = $age; return $this->name.'<=>'.$this->age; })->bindTo(new Person,'Person')('dash',30); ``` ``` class A { function __construct ( $val ) { $this -> val = $val ; } function getClosure () { //返回一个匿名函数,即Closure对象 return function() { return $this -> val ; }; } } $ob1 = new A ( 1 ); $ob2 = new A ( 2 ); $cl = $ob1 -> getClosure (); echo $cl ();//1 //绑定obj2对象到闭包 $c2 = $cl -> bindTo ( $ob2 ); echo $c2 ();//2 ``` 实现类似javascript的功能 ``` trait DynamicDefinition { //调用不存在的成员触发 public function __call($name, $args) { if (is_callable($this->$name)) { return call_user_func_array($this->$name, $args); } else { throw new \RuntimeException("Method {$name} does not exist"); } } //设置不存在的成员触发 public function __set($name,Closure $value) { $this->$name = is_callable($value)? $value->bindTo($this, $this): $value; } } class Foo { use DynamicDefinition; private $privateValue = 'I am private'; private $name='dash'; } $foo = new Foo; $foo->bar = function() { return $this->privateValue; }; print $foo->bar();//返回:I am private $foo->age = function($age){ return $this->name.'的年龄是'.$age; }; echo $foo->age(18);//dash的年龄是18 ``` 我们可以使用bindTo的概念来编写一个非常小的模板引擎 ``` //############# //tpl.php //############ <h1><?php echo $this->title;?></h1> //############# //index.php //############ class Article{ private $title = "文章标题"; } class Post{ private $title = "帖子标题"; } class Template{ function render($context, $tpl){ $closure = function($tpl){ ob_start(); include $tpl; return ob_end_flush(); }; $closure = $closure->bindTo($context, $context); $closure($tpl); } } $art = new Article(); $post = new Post(); $template = new Template(); $template->render($art, 'tpl.php');//文章标题 $template->render($post, 'tpl.php');//帖子标题 ``` ## **php7新增call方法** ``` class Person { private $name = 1; } // PHP 7 之前版本的代码 $getClosure = function() { return $this->name; }; $getName = $getClosure->bindTo(new Person(), 'Person'); // 中间层闭包 echo $getName(); // PHP 7+ 及更高版本的代码 $getName = function() { return $this->name; }; echo $getName->call(new Person); ``` ## **php7.1新增Closure::fromCallable() 将callables转为闭包** Closure新增了一个静态方法,用于将callable快速地 转为一个Closure 对象。 ``` class Test { public function exposeFunction() { return Closure::fromCallable([$this, 'privateFunction']); } private function privateFunction($param) { var_dump($param); } } //((new Test)->exposeFunction())('some value'); $privFunc = (new Test)->exposeFunction(); $privFunc('some value');//some value ```