Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

05. Pipeline 和 Middleware #5

Open
xiaohuilam opened this issue Sep 26, 2018 · 0 comments
Open

05. Pipeline 和 Middleware #5

xiaohuilam opened this issue Sep 26, 2018 · 0 comments
Labels
book The digital book for laravel learning

Comments

@xiaohuilam
Copy link
Owner

xiaohuilam commented Sep 26, 2018

Laravel 的管道机制,是其中间件 (middleware) 得以被序列执行的基础

管道机制

在开始之前我们先帖一段管理的应用代码,位于 02. HTTP Kernel Handle解析

return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());

语义化得出的分析为
携带 send 请求对象,经过中间件的处理,然后进入路由。

代码解析

  1. send 携带方法

    /**
    * Set the object being sent through the pipeline.
    *
    * @param mixed $passable
    * @return $this
    */
    public function send($passable)
    {
    $this->passable = $passable;
    return $this;
    }

    好像没啥出奇的,就是传入并储存一个对象,返回 $this

  2. through 经过方法

    /**
    * 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;
    }

    好像也没啥关键的实现,传入“被管道”的集合,返回 $this

  3. then 异步方法

    /**
    * 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);
    }

之前的都只是做登记一下就GG的作用,这个 then 才是根本大法,
使用 array_reduce 处理得到 $pipeline。这个 array_reduce 是何方神圣呢?我们查文档得到:
Iteratively reduce the array to a single value using a callback function(中文意思就是 使用回调函数,迭代地将数组减少为单个值)。

但在走到 array_reduce 之前,调用了 carry 方法 (在 5.3 及以前版本,carry 方法其实为 getSlice 方法,而过程几乎一样 来源),其内部逻辑为:

/**
* 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 (is_callable($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.
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];
}
$response = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
return $response instanceof Responsable
? $response->toResponse($this->container->make(Request::class))
: $response;
};
};
}

Laravel 的这个 carry 方法返回的是一个闭包,执行返回的闭包所返回的又是一个闭包,只不过前一个返回是 array_reduce 批量执行时拿到的,后面是处理原始对象(请求)时,处理中间件时候拿到的。

  • 闭包(前一个)的第一个参数 $stack 为 null 或者闭包对象,第二个参数 $pipe 为从1开始的调用顺序。
  • 闭包(前一个)的返回值,为管道中被执行的逻辑,也就是 middleware 的 handle。

具体 handle 代码我们举一个例子:

/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*
* @throws \Illuminate\Session\TokenMismatchException
*/
public function handle($request, Closure $next)
{
if (
$this->isReading($request) ||
$this->runningUnitTests() ||
$this->inExceptArray($request) ||
$this->tokensMatch($request)
) {
return tap($next($request), function ($response) use ($request) {
if ($this->addHttpCookie) {
$this->addCookieToResponse($request, $response);
}
});
}
throw new TokenMismatchException;
}

其中的 $next($request) 表示执行下一个中间件, $next 指的就是下面这个闭包

return function ($passable) use ($stack, $pipe) {
if (is_callable($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.
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];
}
$response = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
return $response instanceof Responsable
? $response->toResponse($this->container->make(Request::class))
: $response;
};

关于中间件执行的流程,是不是有眉目了?
image

等上面执行完成,Laravel 就要执行这个闭包了

/**
* Get the route dispatcher callback.
*
* @return \Closure
*/
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}

也就是执行路由的 dispatch 逻辑(包含被调用的逻辑一共 82 行代码)。

/**
* Dispatch the request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public function dispatch(Request $request)
{
$this->currentRequest = $request;
return $this->dispatchToRoute($request);
}
/**
* Dispatch the request to a route and return the response.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function dispatchToRoute(Request $request)
{
return $this->runRoute($request, $this->findRoute($request));
}
/**
* Find the route matching a given request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Routing\Route
*/
protected function findRoute($request)
{
$this->current = $route = $this->routes->match($request);
$this->container->instance(Route::class, $route);
return $route;
}
/**
* Return the response for the given route.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Routing\Route $route
* @return mixed
*/
protected function runRoute(Request $request, Route $route)
{
$request->setRouteResolver(function () use ($route) {
return $route;
});
$this->events->dispatch(new Events\RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
/**
* 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)
{
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}

你可能会因为这里又有中间件而诧异

return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});

其实这里的中间件和前面的不一样,前面的是在kernel 中注册的中间件。这里的中间件是在路由绑定中注册的中间件。是两部分。

于是乎,Laravel 鬼使神差的实现了中间件的管道执行机制。

接下来,请查看 09. 容器的依赖注入机制 了解 $route->run() 背后的故事。

@xiaohuilam xiaohuilam added the book The digital book for laravel learning label Sep 26, 2018
@xiaohuilam xiaohuilam changed the title 05. Pipeline 解析 05. Pipeline 和 Middleware Oct 5, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
book The digital book for laravel learning
Projects
None yet
Development

No branches or pull requests

1 participant