Laravel
中间件 是通过pipeline
执行的。
pipeline
顾名思义,就是管道,相当于unnix
中的管道符(|
),laravel中间件大部分都是对request的过滤,这种过滤的行为相当于管道符对输出结果的处理,举个例子 ls -alh|grep acb.txt
这里grep abc.txt
就是对ls
的结果进行处理,在laravel框架中相当于一个中间件。
Laravel
中pipeline
实现的方式非常简洁、有力,不但其实现原理如此,面对开发人员,它的调用方式也十分清晰,利用匿名函数使得前置与后置的调用都很直观。不过,很多人看到源码也很迷惑,因为中间存在着非常多的回调,只要基础不够扎实,就很容易在期间产生诸多困惑。不过,逐步分析和对基础知识的补完,就会发现再复杂的框架也不过是零碎的功能有序的构建起来的
。pipeline
的终极奥义就是对PHP的一个函数array_reduce
的妙用。
array_reduce 函数介绍:
mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )
array_reduce()
将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。参数
array
输入的 array。callback mixed callback ( mixed $carry , mixed $item ) carry 携带上次迭代里的值; 如果本次迭代是第一次,那么这个值是 initial。 item 携带了本次迭代的值。
initial
如果指定了可选参数initial
,该参数将在处理开始前使用,或者当处理结束,数组为空时的最后一个结果。返回值
返回结果值。
initial
参数,array_reduce() 返回 NULL。使用举例:
<?php function sum($carry, $item) { $carry += $item; return $carry; } function product($carry, $item) { $carry *= $item; return $carry; } $a = array(1, 2, 3, 4, 5); $x = array(); var_dump(array_reduce($a, "sum")); // int(15) var_dump(array_reduce($a, "product", 10)); // int(1200), because: 10*1*2*3*4*5 var_dump(array_reduce($x, "sum", "No data to reduce")); // string(17) "No data to reduce" ?>
Laravel
在pipeline
组件中巧妙的使用了array_reduce
函数实现了中间件的效果,Laravel
的pipeline
已经非常简洁了,这里比葫芦画瓢参照pipeline
代码和接口来实现一个毫无实际利用价值的pipeline
来理解它到底是如何工作的。
Laravel
的pipeline interface
一共定义了四个方法:
<?php
interface Pipeline
{
/**
* Set the traveler object being sent on the pipeline.
*
* @param mixed $traveler
* @return $this
*/
public function send($traveler);
/**
* Set the stops of the pipeline.
*
* @param dynamic|array $stops
* @return $this
*/
public function through($stops);
/**
* Set the method to call on the stops.
*
* @param string $method
* @return $this
*/
public function via($method);
/**
* Run the pipeline with a final destination callback.
*
* @param \Closure $destination
* @return mixed
*/
public function then(Closure $destination);
}
我们分别比葫芦画瓢来写个实现,直接定义就不implements接口了:
<?php
class Pipeline
{
/**
* The object being passed through the pipeline.
*
* @var mixed
*/
protected $passable;
/**
* The array of class pipes.
*
* @var array
*/
protected $pipes = [];
/**
* Set the object being sent through the pipeline.
*
* @param mixed $passable
* @return $this
*/
public function send($passable)
{
$this->passable = $passable;
return $this;
}
/**
* Set the array of pipes.
*
* @param array|mixed $pipes
* @return $this
*/
public function through($pipes)
{
$this->pipes = is_array($pipes) ? $pipes : func_get_args();
return $this;
}
/**
* Set the method to call on the pipes.
*
* @param string $method
* @return $this
*/
public function via($method)
{
return $this;
}
/**
* Run the pipeline with a final destination callback.
*
* @param \Closure $destination
* @return mixed
*/
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
/**
* Get the final piece of the Closure onion.
*
* @param \Closure $destination
* @return \Closure
*/
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
return $destination($passable);
};
}
/**
* Get a Closure that represents a slice of the application onion.
*
* @return \Closure
*/
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);
}
};
};
}
}
我真是太他妈机智了,把框架的代码拷贝过来删除了几行就说已经实现了,我们来使用测试这个类:
test.php
<?php
require_once 'pipeline.php';
//定义pipe1 类似中间件
$pipe1= function ($passable, Closure $next) {
$passable += 1;
echo "pipe1: $passable\n";
return $next($passable);
};
$pipe2 = function ($passable, Closure $next) {
if ($passable > 7) {
return $passable;
}
$passable += 3;
echo "pipe2: $passable\n";
return $next($passable);
};
$pipe3 = function ($passable, Closure $next) {
$passable += 2;
echo "pipe3 : $passable\n";
return $next($passable);
};
//将所有要用的pipe放到一个数组
$pipes = [$pipe1, $pipe2, $pipe3];
//test function
function test($passable,$pipes)
{
echo "result: " . (new Pipeline)->send($passable)->through($pipes)->then(function ($passable) {
echo "received: $passable\n";
return 3;
}) . "\n";
}
//=========run test=========
echo "==> test 1:\n";
test(5, $pipes);
echo "==> test 2:\n";
test(7, $pipes);
运行test.php
结果如下:
==> test 1:
pipe1: 6
pipe2: 9
pipe3 : 11
received: 11
result: 3
==> test 2:
pipe1: 8
result: 8
我们看到 passable为7时根本到不了最后的function 被pipe2直接过滤掉直接return回来了。
这里我们的pipeline
中的send方法所接受的值passable即是要被过滤的条件,在Laravel
中即是request
对象,through 方法接受的pipes即是过滤方式,也就相当于Laravel
的中间件,最后then接受的参数即是自己最终对符合条件的passable想要的处理,只有符合pipe条件的passable会最终传递到then中的方法,在Laravel
中也就是只有符合中间件条件的request才会被传递到Controller。
仔细分析代码的执行过程:
先实例化pipeline 然后调用send方法将要处理的passable赋值给passable属性并返回this,接着调用through方法将pipe数组传入将pipes属性赋值并返回this,最后调用then方法传入了一个匿名函数,将值带入所以最终的then执行的代码可能是这个样子:
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination(funtion($passable){
echo "received: $passable\n";
return 3;
}));
return $pipeline($this->passable);
在array_reduce第一次迭代时,使用initial的值作为callback中的carry参数,pipe3作为item,因为array先进行了array_reverse,我们将值带入获得第一次的迭代结果为:
function ($passable) {
if ($pipe3 instanceof Closure) {
return $pipe3($passable, function ($passable) {
return $function($passable){
echo "received: $passable\n";
return 3;
};
});
}
}
然后第二次迭代的结果为:
function ($passable) {
if($pip2 instanceof Closure){
return $pip2($passable,function ($passable) {
if ($pipe3 instanceof Closure) {
return $pipe3($passable, function ($passable) {
return $function($passable){
echo "received: $passable\n";
return 3;
};
});
}
});
}
}
然后第三次迭代结果为
function ($passable) {
if($pip1 instanceof Closure){
return $pip1($passable,function ($passable) {
if($pip2 instanceof Closure){
return $pip2($passable,function ($passable) {
if ($pipe3 instanceof Closure) {
return $pipe3($passable, function ($passable) {
return $function($passable){
echo "received: $passable\n";
return 3;
};
});
}
});
}
});
}
}
这就是array_reduce的结果了,最后$pipeline($this->passable)就正式的调用被返回的这个函数。正好是按照正常的顺序一个一个执行。