PHP-闭包
1.定义
1). 闭包和匿名函数在 PHP5.3 中被引入。
2). 闭包是指在创建时封装函数周围状态的函数,即使闭包所在的环境不存在了,闭包封装的状态依然存在,这一点和 Javascript 的闭包特性很相似。可以看我之前写的彻底弄懂 Javascript 闭包
3). 匿名函数就是没有名称的函数,匿名函数可以赋值给变量,还可以像其他任何 PHP 对象一样传递。可以将匿名函数和闭包视作相同的概念。
4). 需要注意的是闭包使用的语法和普通函数相同,但是他其实是伪装成函数的对象,是 Closure 类的实例。闭包和字符串或整数一样,是一等值类型。
定义一个闭包函数,其实就是实例化一个闭包类(Closure)对象:
$func = function()
{
echo 'hello world' . PHP_EOL;
};
var_dump($func);
输出:
object(Closure)#1 (0) {
}
类摘要:
Closure {
__construct ( void )
public static Closure bind ( Closure $closure , object $newthis [, mixed $newscope = 'static' ] )
public Closure bindTo ( object $newthis [, mixed $newscope = 'static' ] )
}
2.应用
(1)创建闭包
$closure = function ($name) {
return sprintf('hello %s', $name);
};
echo $closure('Josh');
(2)使用闭包
__invoke()魔术方法
PHP 自从 5.3 版以来就新增了一个叫做__invoke() 的魔术方法,使用该方法就可以在创建实例后,直接调用对象,我们之所以可以调用 $closure 变量,是因为这个变量的值是一个闭包,而且闭包对象实现了__invoke() 魔术方法,只要后面跟着 (),PHP 就会查找__invoke() 方法。这里简单解释下这个魔术方法:
class testClass
{
public function __invoke
{
print "hello world";
}
}
$n = new testClass;
$n();
作为回调函数
我们通常把 PHP 闭包当做函数和方法的回调使用。很多 PHP 函数都会用到回调函数,例如 array_map(),
$numbersPlusOne = array_map(function($number) {
return $number + 1;
}, [1, 2, 3]);
bindTo()方法
在 PHP 中通过调用闭包对象的 bindTo 方法或者使用 use 关键字,把状态附加到 PHP 闭包上。来看一个例子
function enclosePerson($name)
{
return function ($doCommand) use ($name) {
return sprintf('%s , %s', $name, $doCommand);
};
}
//把字符串“Clay”封装在闭包中
$clay = enclosePerson('Clay');
//传入参数,调用闭包
echo $clay('get me sweat tea!'); // Clay, get me sweat tea!
在这个例子中,函数 enclosePerson() 有一个 $name 参数,这个函数返回一个闭包对象,这个闭包封装了 $name 参数,即便返回的对象跳出了 enclosePerson() 函数的作用域,它也会记住 $name 参数的值,因为 $name 变量仍然在闭包中。
2). 使用 use 关键字可以把多个关键字传入闭包,此时要想像 PHP 函数或方法的参数一样,使用逗号分割多个参数。
3).PHP 闭包仍然是对象,可以使用 $this 关键字获取闭包的内部状态。闭包的默认状态里面有一个__invoke() 魔术方法和 bindTo() 方法。
4).bindTo() 方法为闭包增加了一些有趣的东西。我们可以使用这个方法把 Closure 对象内部状态绑定到其他对象上。bindTo() 方法的第二个参数可以指定绑定闭包的那个对象所属的 PHP 类,这样我们就可以访问这个类的受保护和私有的成员变量。看下面的代码示例:
class App
{
protected $route = array();
protected $responseStatus = '200 OK';
protected $responseContentType = 'text/html';
protected $responseBody = 'Hello world';
public function addRoute($routePath, $routeCallback)
{
$this->routes[$routePath] = $routeCallback->bindTo($this, __CLASS__);
}
public function dispatch($currentPath)
{
foreach($this->routes as $routePath => $callback) {
if ($routePath === $currentPath) {
$callback();
}
}
header('HTTP/1.1' . $this->responseStatus);
header('Content-type: ' . $this->responseContentType);
header('Content-length: ' . mb_strlen($this->responseBody));
echo $this->responseBody;
}
}
我们把路由回调绑定到了当前的 App 实例上,这样就可以在回调函数中处理 App 实例的状态了,这里我们需要重点关注addRoute方法,这个方法的参数分别是一个路由路径和一个路由回调,dispatch方法的参数是当前HTTP请求的路径,它会调用匹配的路由回调。第9行是重点所在,我们将路由回调绑定到了当前的App实例上。这么做能够在回调函数中处理App实例的状态:
$app = new App();
$app->addRoute('/users/xiaoxiao', function () {
$this->responseContentType = 'application/json;charset=utf8';
$this->responseBody = '{"name" : "xiaoxiao"}';
});
$app->dispatch('/users/xiaoxiao');
IoC 容器
匿名函数可以从父作用域继承变量,而这个父作用域是定义该闭包的函数(不一定是调用它的函数)。利用这个特性,我们可以实现一个简单的控制反转IoC容器:
class Container
{
protected static $bindings;
public static function bind($abstract, Closure $concrete)
{
static::$bindings[$abstract] = $concrete;
}
public static function make($abstract)
{
return call_user_func(static::$bindings[$abstract]);
}
}
class talk
{
public function greet($target)
{
echo 'Hello ' . $target->getName();
}
}
class A
{
public function getName()
{
return 'World';
}
}
// 创建一个talk类的实例
$talk = new talk();
// 将A类绑定至容器,命名为foo
Container::bind('foo', function() {
return new A;
});
// 通过容器取出实例
$talk->greet(Container::make('foo')); // Hello World
上述例子中,只有在通过make方法获取实例的时候,实例才被创建,这样使得我们可以实现容器。在Laravel框架底层也大量使用了闭包以及bindTo方法,利用好闭包可以实现更多的高级特性如事件触发等。
转自:
IOC https://www.cnblogs.com/DebugLZQ/archive/2013/06/05/3107957.html
闭包 https://juejin.im/post/5aac88ab51882555627d0d37
php官方的闭包解释