Fork me on GitHub

说说laravel管道pipeline的实现


我来过,我战斗过,我不在乎结局。

Laravel中比较优雅的实现了MiddleWare. 主要用来层层递进的处理Request.

Pipeline对象

1
2
3
4
5
(new Pipeline())
->send($passable) // 待处理对象,要经过流水线的对象(比如request对象)
->through($middlewares) // 流水线上的各个环节(中间件)
->via('handle') // 指定每个环节的处理方法,比如 Authenticate::handle
->then($handler); // 尽头,最后的处理(返回时,则是源头)

then()方法

1
2
3
4
5
6
7
8
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}

$this->pipes 是我们的中间件数组:

1
2
3
4
5
6
7
8
9
[2018-01-24 08:55:58] local.INFO: array (
0 => 'App\\Http\\Middleware\\EncryptCookies',
1 => 'Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse',
2 => 'Illuminate\\Session\\Middleware\\StartSession',
3 => 'Illuminate\\View\\Middleware\\ShareErrorsFromSession',
4 => 'App\\Http\\Middleware\\VerifyCsrfToken',
5 => 'Illuminate\\Auth\\Middleware\\Authenticate',
6 => 'Illuminate\\Routing\\Middleware\\SubstituteBindings',
)

carry()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if ($pipe instanceof Closure) {
// If the pipe is an instance of a Closure, we will just call it directly but
// otherwise we'll resolve the pipes out of the container and call it with
// the appropriate method and arguments, returning the results back out.
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
list($name, $parameters) = $this->parsePipeString($pipe);
// If the pipe is a string we will parse the string and resolve the class out
// of the dependency injection container. We can then build a callable and
// execute the pipe function giving in the parameters that are required.
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
// If the pipe is already an object we'll just make a callable and pass it to
// the pipe as-is. There is no need to do any extra parsing and formatting
// since the object we're given was already a fully instantiated object.
$parameters = [$passable, $stack];
}
return $pipe->{$this->method}(...$parameters);
};
};
}

这里使用了array_reduce和闭包, 在闭包中判断, 如果是迭代就执行这个迭代.
$this->method 这个方法是handle, 也是每次我们需要自定义中间件时需要实现的方法.

简化说明

先看下面这段代码

1
2
3
4
5
6
7
8
9
$arr = ['AAAA'];
$res = array_reduce($arr, function($carry, $item){
return function () use ($carry, $item) {
if (is_null($carry)) {
return 'Carry IS NULL' . $item;
}
};
});

此时的数组长度为1,并且没有指定初始值, 故仅仅会迭代一次,返回一个闭包 use($carry = null, $item = ‘AAAA’),当我们执行($res())这个闭包时,得到的结果为Carry IS NULLAAAA

然后我们再看另外一段代码

1
2
3
4
5
6
7
8
9
10
11
12
$arr = ['AAAA', 'BBBB'];
$res = array_reduce($arr, function($carry, $item){
return function () use ($carry, $item) {
if (is_null($carry)) {
return 'Carry IS NULL' . $item;
}
if ($carry instanceof \Closure) {
return $carry() . $item;
}
};
});

我们新增了一个条件判断,若当前迭代的值是一个闭包,返回该闭包的执行结果。
当我们执行这个闭包时,满足$carry instanceof \Closure,得到结果Carry IS NULLAAAABBBB。

https://laravel-china.org/articles/5206/the-use-of-php-built-in-function-array-reduce-in-laravel