原文地址:laravel5中对pipeline的理解 http://sheefeng.com/laravel5-pipeline/
pipeline在laravel中的应用
Router和ControllerDispatcher中都用到了pipeline结构,具体如下所示:
router中的Pipeline:
/**
* Run the given route within a Stack "onion" instance.
*
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @return mixed
*/
protected function runRouteWithinStack(Route $route, Request $request)
{
$middleware = $this->gatherRouteMiddlewares($route);
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function($request) use ($route)
{
return $this->prepareResponse(
$request,
$route->run($request)
);
});
}
ControllerDispatcher中的Pipeline:
/**
* Call the given controller instance method.
*
* @param \Illuminate\Routing\Controller $instance
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @param string $method
* @return mixed
*/
protected function callWithinStack($instance, $route, $request, $method)
{
$middleware = $this->getMiddleware($instance, $method);
// Here we will make a stack onion instance to execute this request in, which gives
// us the ability to define middlewares on controllers. We will return the given
// response back out so that "after" filters can be run after the middlewares.
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function($request) use ($instance, $route, $method)
{
return $this->call($instance, $route, $method);
});
}
pipeline的存在主要是为了处理中间件逻辑.
router中的pipeline主要是首先运行选定某一个match到的route后,执行这个route所对应的的MiddleWare,最后执行这个route所对应的controller操作(在controller操作中,也会涉及到pipeline应用,如下).
ControllerDispatcher中的pipeline主要是运行Controller层面(已经选定了某个route以后)的MiddleWare,然后再最后执行controller中的具体操作($this->call($instance, $route, $method)).
理解pipeline,就要理解洋葱模型,像剥洋葱一样一层一层往里面剥.以下面语句为例:
(new Pipeline($this->container))
->send($passable)
->through($middleware)
->then($destination);
将容器$this->container注入pipeline,$passable这个变量一直在被传递,middleware数组代表要穿过的层,$destination一般是一个闭包函数,代表最终要执行的动作.
上述语句的含义就是携带着$passable这个变量,穿越层层MiddleWare,然后执行最后的$destination,如下所示:
pipeline的基本原理
下面再来看看pipeline的基本原理:
先看看pipeline的源码:
<?php namespace Illuminate\Pipeline;
use Closure;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Pipeline\Pipeline as PipelineContract;
class Pipeline implements PipelineContract {
/**
* The container implementation.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $container;
/**
* The object being passed through the pipeline.
*
* @var mixed
*/
protected $passable;
//要在洋葱模型中传递的变量,可以是一个Request对象
/**
* The array of class pipes.
*
* @var array
*/
protected $pipes = array();
//在洋葱模型中要经过的管道,一般是一些MiddleWare
/**
* The method to call on each pipe.
*
* @var string
*/
protected $method = 'handle';
/**
* Create a new class instance.
*
* @param \Illuminate\Contracts\Container\Container $container
* @return void
*/
public function __construct(Container $container)
{
$this->container = $container;
}
/**
* 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 dynamic|array $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)
{
$this->method = $method;
return $this;
}
/**
* Run the pipeline with a final destination callback.
*
* @param \Closure $destination
* @return mixed
*/
public function then(Closure $destination)
{
$firstSlice = $this->getInitialSlice($destination);
//将passable变量和destination这个闭包结合起来,生成一个新的闭包为firstSlice
$pipes = array_reverse($this->pipes);
return call_user_func(
array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
);
//核心步骤
}
/**
* Get a Closure that represents a slice of the application onion.
*
* @return \Closure
*/
protected function getSlice()
{
return function($stack, $pipe)
{
return function($passable) use ($stack, $pipe)
{
// 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.
if ($pipe instanceof Closure)
{
return call_user_func($pipe, $passable, $stack);
}
else
{
return $this->container->make($pipe)
->{$this->method}($passable, $stack);
}
};
};
}
/**
* Get the initial slice to begin the stack call.
*
* @param \Closure $destination
* @return \Closure
*/
//getInitialSlice()用来生成一个closure来实现$destination中的动作.
protected function getInitialSlice(Closure $destination)
{
return function($passable) use ($destination)
{
return call_user_func($destination, $passable);
};
}
}
上面的
return call_user_func(
array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
);
是核心步骤,不了解arrayreduce语法的人可以参考:http://php.net/manual/zh/function.array-reduce.php
getInitialSlice()用来生成一个closure来实现$destination中的动作.
getSlice()方法用来构造一个closure来不断的迭代pipes里面的每一个pipe
至于getSlice()方法中为何会向下面这样嵌套两个function,我的理解就是arrayreduce中的第二个参数是一个closure,这个closure只接受两个参数的closure,而我们这里有一个$Passable变量需要不断传递,所以需要在内部再加一层function嵌套.
return function($stack, $pipe)
{
return function($passable) use ($stack, $pipe)
{
/**中间内容忽略
};
};
}