php匿名函数闭包lambda,closure
php匿名函数的起源,使用.lambda,closure类使用,bind,call切换作用域.
定义
// 在js中常见的自运行函数
(function(){
echo 'hello';
})();
// 赋值
$func1=function(){};
// 箭头函数
$func2=fn()=>echo 'hello';
// 参数使用
$arr=[1,2,3];
array_walk($arr,function($v,$k){
echo $v;
});
// use 作用域
$a="hello";
$func3=function() use($a){
echo $a;
};
函数参数
在<php5.3前没匿名函数以前怎么传函数.
php中传递函数参数有两种:
- 函数名字符串
- create_function
通过函数名传参
$a="hello";
function callback(){
global $a;
echo $a;
}
/**
* 接收函数的参数
* @param callable $callback
* @return void
*/
function foo(callable $callback){
var_dump(is_string($callback)); // true
var_dump(is_callable($callback)); // true
var_dump($callback instanceof Closure);// false
var_dump($callback); // "callback"
$callback();
}
foo('callback');
如上例子,传递的是字符串"callback"接受却是callable
类型.
开发调试时很难确定是普通字符串还是函数,且只使用一次却需要定义一个函数.
在php7.2以前还有一个函数create_function
动态创建函数.
如上代码改成.
$a='hello';
function foo(callable $callback){
var_dump(is_string($callback)); // true
var_dump(is_callable($callback)); // true
var_dump($callback instanceof Closure);// false
var_dump($callback); // "?lambda_1"
$callback();
}
foo(create_function('','global $a;echo $a;'));
虽然代码结构清淅了,可以看出生成代码比较复杂,容易写错. 内部使用 eval
执行的.性能和安全都有问题.
在PHP中函数使用的是字符串描述.
可以看出来 create_function
返回的否是普通的有名函数.
了解 create_function
$func=create_function('','echo __FUNCTION__;');
$func(); // __lambda_func
var_dump($func);//lambda_1"
实际上create_function 创建出来的还是有名函数.
只是这个函数名很怪.
能不能直接运行呢?
$func=create_function('','echo __FUNCTION__;');
var_dump(function_exists('lambda_1')); // false
lambda_1();// 不存在
注意实际名称前有一段乱码.
实际上php利用特殊字符命名函数,生成用户无法输入的唯一函数.这里使用的是ascii的0值.
规则: null+lambda_n
php中的大量类似的黑科技,属于奇淫技巧很不规范.
php的函数变量名规范是 [A-z0-9_]
$func=create_function('','echo __FUNCTION__;');
$fn=chr(0)."lambda_1";
$fn();
实际上还是有名函数.
函数创建时就会在内存中,会依赖eval.
为了支持真正的匿名函数和闭包,php5.3后支持匿名函数.
匿名函数
lambda 实际是一个对象.
而不在用字符串描述函数.
同时引入了__invoke
魔术方法.
匿名函数实际就是Closure实例.
$cb=function(...$args){
echo 'hello';
};
$cb();
相当于 $cb=new Closure();
相当于下面代码:
final class Lambda {
protected $callable;
private function __construct(){}
public function __invoke(...$args)
{
return call_user_func_array($this->callable,$args);
}
public static function fromCallable(callable $cb):self{
$obj= new self();
$obj->callable=$cb;
return $obj;
}
}
$callback=Lambda::fromCallable(create_function('','echo "hello";'));
$callback();
var_dump($callback);
var_dump(is_callable($callback));
var_dump($callback instanceof Lambda);
实现closure类.
闭包
$cb=function(){};
// 相当于
$cb= new Closure;
php中的闭包不能直接获取作用域变量. 需要手动那 use.
use 更多只读.
$name="张三";
$foo=function() use(&$name){
echo $name;
$name="李四";
};
// global 读写
//$foo();// 张三
//$foo();// 李四
//echo $name; // 李四
// use 只读
//$foo();// 张三
//$foo();// 李三
//echo $name; // 张三
// &use 读写
//$foo();// 张三
//$foo();// 李四
//echo $name; // 张四
路由使用closure示例:
class App{
protected $routes=[];
protected $responseStatus='200 OK';
protected $responseContentType='text/html';
protected $responseBody='index';
public function addRoute(string $path,callable $callback){
$this->routes[$path]=$callback->bindTo($this,$this);
}
public function dispatch($path){
if(isset($this->routes[$path]) && is_callable($this->routes[$path])){
$this->routes[$path]();
}
header('HTTP/1.1 ' . $this->responseStatus);
header('Content-Type: ' . $this->responseContentType);
header('Content-Length: ' . mb_strlen($this->responseBody));
echo $this->responseBody;
}
}
$app = new App();
$app->addRoute('/',function(){});
$app->addRoute('/hello',function(){
$this->responseBody='hello';
});
$app->dispatch('/hello');
这里面用到了匿名函数和bindTo
.
可以看到匿名函数里可以调用$this
.
BindTo
绑定$this对象和作用域.生成一个新的函数.
https://www.php.net/manual/zh/closure.bindto.php
从 PHP 5.4 开始,在类里面使用匿名函数时,匿名函数的 $this 将自动绑定到当前类.
class App {
public function run(){
return function(){
// PHP 5.4 开始,可以直接使用$this
var_dump($this); // App
var_dump(self::class); //App
};
}
}
$app= new App;
$callback=$app->run();
$callback();
如果不想绑定$this
,使用 static
静态匿名函数.
class App {
public function run(){
return static function(){
var_dump($this); // error
var_dump(self::class); //App
};
}
}
$app= new App;
$callback=$app->run();
$callback();
普通匿名函数不绑定$this
.
通过bindTo
修改$this
指向A.
class App {
public function run(){
return function(){
var_dump($this); // A
var_dump(self::class); //A
};
}
}
class A{}
$obj = new A;
$app= new App;
$callback=$app->run();
$cb=$callback->bindTo($obj,A::class);
$cb();
移除和$this
和作用域.
$cb=$callback->bindTo(null,null);
只指向this.
class A{}
$obj = new A;
$cb=$callback->bindTo(obj,null);
只修改到A的作用域.
class A{}
$cb=$callback->bindTo(null,A::class);
如果设为static静态函数,则不能指向this.
除了bindTo,还有同名函数bind
Closure::bind
用法和func->bindTo一样.
class App {
public function run(){
return function(){
var_dump($this); // A
var_dump(self::class); //A
};
}
}
class A{}
$obj = new A;
$app= new App;
$callback=$app->run();
//$cb=$callback->bindTo($obj,A::class);
$cb=Closure::bind($callback,$obj,A::class);
$cb();
Call函数
call和bind的区别,call可以直接调用,bind还需要另调用.
call临时修改this,而不是复制生成函数.
相当于 call_user_func($cb,[1,2,3])
call(newThis,args);
class App {
public function run(){
return function(...$args){
var_dump($this); // A
var_dump(self::class); //A
var_dump($args); // 1,2,3
return 'ok';
};
}
}
class A{}
$obj = new A;
$app= new App;
$callback=$app->run();
//$cb=$callback->bindTo($obj,A::class);
//$cb=Closure::bind($callback,$obj,A::class);
//$ret=$cb();
$ret=$callback->call($obj,1,2,3);
// 与 call_user_func($cb,[1,2,3])一样
echo $ret;
fromCallable
把普通函数转成匿名函数
function foo(){
echo 'hello';
}
$callback=Closure::fromCallable('foo');
$callback();
同过反射获取.性能差不多.
$ref = new ReflectionFunction('foo');
$callback = $ref->getClosure();
各种应用实例
调用私有方法
私有方法不能调用怎么办?可以把私有方法转成匿名函数
class C {
private $name;
public function __construct($name) {
$this->name=$name;
}
private function test(...$args){
echo 'private:'.$this->name;
}
}
$objC=new C('张三');
// $objC->test(); // Error: Call to private method
$ref = new ReflectionClass(get_class($objC));
$test = $ref->getMethod('test')
->getClosure($objC);
$test(); // private:张三
给对象添加方法
一个类里缺少一个函数,可以动态添加函数.
如上调用私有方法,可以把匿名函数绑定到里面.
php不支持像 $obj->getName=function(){};
动态添加方法.
final class C {
private $name;// 私有属性不能继承
public function __construct($name) {
$this->name=$name;
}
// 缺少这个函数
// public function getName()
// {
// return $this->name;
// }
}
$obj=new C("阿金");
//echo $obj->getName(); // 不支持getName怎么办?
// 把this指向C对象里面.
$getName=function(){
return $this->name;
};
$getName=$getName->bindTo($obj,C::class);
echo $getName();
路由示例
类似laravel中的路由
添加addRoute
在回调中调用$this
class App{
protected $routes;
protected $responseStatus=200;
protected $responseBody='';
protected $responseContentType='text/html';
public addRoute(string $path,callable $callback)
{
// 绑定当前类
$this->routes[$path]=$callback->bindTo($this,__CLASS__);
}
public dispatch(string $path){
if(isset($this->routes[$path] && is_callable($this->routes[$path])){
$this->routes[$path]();
}
header('HTTP/1.1 ' . $this->responseStatus);
header('Content-Type: ' . $this->responseContentType);
header('Content-Length: ' . mb_strlen($this->responseBody));
echo $this->responseBody;
}
}
$app=new App();
$app->addRoute('/',function(){
// 调用this
$this->responseBody='home';
});
$app->addRoute('/hello',function(){
$this->responseBody='hello';
});
$app->dispatch('/hello'); // out: hello
模板示例
php模板渲染,需要独立的作用域.
支持局部变量和this
tpl.php 模板文件如下.
<html>
<head>
<title><?= $this->title ?></title>
<?php css_asset('main.css') ?>
</head>
<body>
<p>
name:<?= $name ?>
</p>
</body>
Template.php
class Template{
private $data=[];
public function setData($key,$val){
$this->data[$key]=$val;
}
// 渲染
function render($context, $tpl){
$data = $this->data;
$closure = function($tpl)use($data){
extract($data);
ob_start();
include $tpl;
return ob_end_flush();
};
$closure = $closure->bindTo($context, $context);
$closure($tpl);
}
}
class Post {
private $title="文章";
}
class News {
private $title="新闻";
}
$template=new Template;
$template->setData('name','arkin');
$template->render(new Post,'tpl.php');
echo PHP_EOL.'----'.PHP_EOL;
$template->render(new News,'tpl.php');
输出:
原作者:阿金
本文地址:https://hi-arkin.com/archives/php-lambda-closure.html