Home > PHP Framework > ThinkPHP > body text

Implementation analysis of ThinkPHP6.0 pipeline mode and middleware

藏色散人
Release: 2019-11-08 17:58:00
forward
4143 people have browsed it

Description

ThinkPHP 6.0 RC5 began to use the pipeline mode to implement middleware, which is more concise and orderly than the implementation of previous versions. This article analyzes its implementation details.

First we start from the entry file public/index.php, $http = (new App())->http;

Get an instance of the http class and call its run method :$response = $http->run();, and then its run method calls the runWithRequest method:

protected function runWithRequest(Request $request)
{
    .
    .
    .
    return $this->app->middleware->pipeline()
        ->send($request)
        ->then(function ($request) {
            return $this->dispatchToRoute($request);
        });
}
Copy after login

The execution of middleware is in the final return statement.

pipeline, through, send method

$this->app->middleware->pipeline() 的 pipeline 方法:
public function pipeline(string $type = 'global')
{
    return (new Pipeline())  
           // array_map将所有中间件转换成闭包,闭包的特点:
          // 1. 传入参数:$request,请求实例; $next,一个闭包
          // 2. 返回一个Response实例
        ->through(array_map(function ($middleware) {
            return function ($request, $next) use ($middleware) {
                list($call, $param) = $middleware;
                if (is_array($call) && is_string($call[0])) {
                    $call = [$this->app->make($call[0]), $call[1]];
                }
                 // 该语句执行中间件类实例的handle方法,传入的参数是外部传进来的$request和$next
                 // 还有一个$param是中间件接收的参数
                $response = call_user_func($call, $request, $next, $param);
                if (!$response instanceof Response) {
                    throw new LogicException('The middleware must return Response instance');
                }
                return $response;
            };
            // 将中间件排序
        }, $this->sortMiddleware($this->queue[$type] ?? [])))
        ->whenException([$this, 'handleException']);
}
Copy after login

through method code:

public function through($pipes)
{
    $this->pipes = is_array($pipes) ? $pipes : func_get_args();
    return $this;
}
Copy after login

The previous call through is the incoming array_map (...) encapsulates the middleware into closures, and through saves these closures in the $pipes attribute of the Pipeline class.

PHP’s array_map method signature:

array_map ( callable $callback , array $array1 [, array $... ] ) : array
Copy after login

$callback iterates over each element of $array and returns a new value. Therefore, the final formal characteristics of each closure in $pipes are as follows (pseudocode):

function ($request, $next) {
    $response = handle($request, $next, $param);
    return $response;
}
Copy after login
Copy after login

This closure receives two parameters, one is the request instance, and the other is the callback function, handle method After processing, the response is obtained and returned.

through returns an instance of the Pipeline class, and then calls the send method:

public function send($passable)
{
    $this->passable = $passable;
    return $this;
}
Copy after login

This method is very simple. It just saves the incoming request instance in the $passable member variable, and finally returns the Pipeline class. instance, so that other methods of the Pipeline class can be called in a chain.

then, carry method

After the send method, then call the then method:

return $this->app->middleware->pipeline()
            ->send($request)
            ->then(function ($request) {
                return $this->dispatchToRoute($request);
            });
Copy after login

The then here receives a closure as a parameter. This closure The package actually contains the execution code for the controller operations.

then method code:

public function then(Closure $destination)
{
    $pipeline = array_reduce(
        //用于迭代的数组(中间件闭包),这里将其倒序
        array_reverse($this->pipes),
        // array_reduce需要的回调函数
        $this->carry(),
        //这里是迭代的初始值
        function ($passable) use ($destination) {
            try {
                return $destination($passable);
            } catch (Throwable | Exception $e) {
                return $this->handleException($passable, $e);
            }
        });
    return $pipeline($this->passable);
}
Copy after login

carry code:

protected function carry()
{
    // 1. $stack 上次迭代得到的值,如果是第一次迭代,其值是后面的「初始值
    // 2. $pipe 本次迭代的值
    return function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            try {
                return $pipe($passable, $stack);
            } catch (Throwable | Exception $e) {
                return $this->handleException($passable, $e);
            }
        };
    };
}
Copy after login

In order to make it easier to analyze the principle, we put the carry method inside Connect it to then and remove the error capture code to get:

public function then(Closure $destination)
{
    $pipeline = array_reduce(
        array_reverse($this->pipes),
        function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                return $pipe($passable, $stack);
            };
        },
        function ($passable) use ($destination) {
            return $destination($passable);
        });
    return $pipeline($this->passable);
}
Copy after login

The key here is to understand the execution process of array_reduce and $pipeline($this->passable). These two processes can be compared to " The process of "wrapping onions" and "peeling onions".

array_reduce first iteration, the initial value of $stack is:

(A)

function ($passable) use ($destination) {
    return $destination($passable);
});
Copy after login

The return value of the callback function is:

(B)

function ($passable) use ($stack, $pipe) {
    return $pipe($passable, $stack);
};
Copy after login

Substitute A into B to get the value of $stack after the first iteration:

(C)

function ($passable) use ($stack, $pipe) {
    return $pipe($passable, 
        function ($passable) use ($destination) {
            return $destination($passable);
        })
    );
};
Copy after login

For the second iteration, in the same way, substitute C into B to get:

(D)

// 伪代码
// 每一层的$pipe都代表一个中间件闭包
function ($passable) use ($stack, $pipe) {
    return $pipe($passable,  //倒数第二层中间件
        function ($passable) use ($stack, $pipe) {
            return $pipe($passable,  //倒数第一层中间件
                function ($passable) use ($destination) {
                    return $destination($passable);  //包含控制器操作的闭包
                })
            );
        };
    );
};
Copy after login

And so on, we have Substitute as many middlewares as you want, and the last time you get $stack, return it to $pipeline. Since the middleware closures were reversed in the previous order, the closures in the front are wrapped in the inner layer, so the closures after the reverse order are later on the outside, and from the perspective of the forward order, they become the earlier ones. Middleware is the outermost layer.

After wrapping the closures layer by layer, we get a "super" closure D similar to an onion structure. The structure of this closure is as shown in the code comments above. Finally, pass the $request object to this closure and execute it: $pipeline($this->passable);, thus starting a process similar to peeling an onion. Next, let’s see how the onion is peeled.

Analysis of peeling onion process

array_map(...) Process each middleware class into a closure similar to this structure:

function ($request, $next) {
    $response = handle($request, $next, $param);
    return $response;
}
Copy after login
Copy after login

The handle is the entrance in the middleware, and its structural characteristics are as follows:

public function handle($request, $next, $param) {
    // do sth ------ M1-1 / M2-1
    $response = $next($request);
    // do sth ------ M1-2 / M2-2
    return $response;
}
Copy after login

Our "onion" above has only two layers in total, that is, there are two layers of closures of the middleware, assuming M1- 1 and M1-2 are respectively the prefix and postvalue operation points of the first middleware handle method. The same applies to the second middleware, which is M2-1 and M2-2. Now, let the program execute $pipeline($this->passable), expand it, that is, execute:

// 伪代码
function ($passable) use ($stack, $pipe) {
    return $pipe($passable,  
        function ($passable) use ($stack, $pipe) {
            return $pipe($passable,  
                function ($passable) use ($destination) {
                    return $destination($passable);  
                })
            );
        };
    );
}($this->passable)
Copy after login

At this time, the program requires the return value from:

return $pipe($passable,  
    function ($passable) use ($stack, $pipe) {
        return $pipe($passable,  
            function ($passable) use ($destination) {
                return $destination($passable);  
            })
        );
    };
);
Copy after login

, that is To execute the first middleware closure, $passable corresponds to the $request parameter of the handle method, and the next-level closure

function ($passable) use ($stack, $pipe) {
    return $pipe($passable,  
        function ($passable) use ($destination) {
            return $destination($passable);  
        })
    );
}
Copy after login

corresponds to the $next parameter of the handle method.

To execute the first closure, that is, to execute the handle method of the first closure, the process is: first execute the code at point M1-1, that is, the pre-operation, and then execute $response = $next($request);, then the program enters to execute the next closure, $next($request) is expanded, that is:

function ($passable) use ($stack, $pipe) {
    return $pipe($passable,  
        function ($passable) use ($destination) {
            return $destination($passable);  
        })
    );
}($request)
Copy after login

and so on, execute the closure, that is, execute the second The handle method of the middleware, at this time, first executes the M2-1 point, and then executes $response = $next($request). The $next closure at this time is:

function ($passable) use ($destination) {
    return $destination($passable);  
})
Copy after login

Belongs to the core of the onion— — The innermost layer, which is the closure containing the controller operations, expand it:

function ($passable) use ($destination) {
    return $destination($passable);  
})($request)
Copy after login

最终,我们从 return $destination($passable) 中返回一个 Response 类的实例,也就是,第二层的 $response = $next($request) 语句成功得到了结果,接着执行下面的语句,也就是 M2-2 点位,最后第二层闭包返回结果,也就是第一层闭包的 $response = $next($request) 语句成功得到了结果,然后执行这一层闭包该语句后面的语句,即 M1-2 点位,该点位之后,第一层闭包也成功返回结果,于是,then 方法最终得到了返回结果。

整个过程过来,程序经过的点位顺序是这样的:M1-1→M2-1→控制器操作→M2-2→M1-2→返回结果。

总结

整个过程看起来虽然复杂,但不管中间件有多少层,只要理解了前后两层中间件的这种递推关系,洋葱是怎么一层层剥开又一层层返回的,来多少层都不在话下。

The above is the detailed content of Implementation analysis of ThinkPHP6.0 pipeline mode and middleware. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:learnku.com
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template