Maison  >  Article  >  cadre php  >  Analyse de la mise en œuvre du mode pipeline et du middleware ThinkPHP6.0

Analyse de la mise en œuvre du mode pipeline et du middleware ThinkPHP6.0

藏色散人
藏色散人avant
2019-11-04 13:29:254118parcourir

Explication

ThinkPHP 6.0 RC5 a commencé à utiliser le mode pipeline pour implémenter un middleware, qui est plus concis et ordonné que l'implémentation des versions précédentes. Cet article analyse les détails de sa mise en œuvre.

Nous commençons d'abord par le fichier d'entrée public/index.php, $http = (new App())->http;

Obtenez une instance de la classe http et appelez son exécution :$response = $http->run();, puis sa méthode run appelle la méthode runWithRequest :

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

L'exécution du middleware se trouve dans l'instruction return finale.

pipeline, through, méthode d'envoi

$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']);
}

through code de méthode :

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

L'appel précédent à through est l'appel entrant array_map (...) encapsule le middleware dans des fermetures et enregistre ces fermetures dans l'attribut $pipes de la classe Pipeline.

Signature de la méthode array_map de PHP :

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

$callback parcourt chaque élément de $array et renvoie une nouvelle valeur. Par conséquent, les caractéristiques formelles finales de chaque fermeture dans $pipes sont les suivantes (pseudocode) :

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

Cette fermeture reçoit deux paramètres, l'un est l'instance de requête et l'autre est la fonction de rappel, méthode handle Après traitement , la réponse est obtenue et renvoyée.

through renvoie une instance de la classe Pipeline, puis appelle la méthode send :

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

Cette méthode est très simple. Elle enregistre simplement l'instance de requête entrante dans la variable membre $passable, et renvoie enfin l'instance de la classe Pipeline, afin que d'autres méthodes de la classe Pipeline puissent être appelées dans une chaîne.

puis, après la méthode carry

méthode send, puis appelez la méthode then :

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

Le then reçoit ici une fermeture en paramètre. Cette fermeture Le package contient en fait le code d'exécution pour les opérations du contrôleur.

puis code méthode :

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

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

Afin de faciliter l'analyse du principe, nous mettons la méthode carry à l'intérieur de Connect it to then et supprimons le code de détection d'erreur pour obtenir :

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

La clé ici est de comprendre le processus d'exécution de array_reduce et $pipeline($this->passable These). deux processus peuvent être comparés au "Le processus d'"emballage des oignons" et d'"épluchage des oignons".

La première itération de array_reduce, la valeur initiale de $stack est :

(A)

function ($passable) use ($destination) {
    return $destination($passable);
});

La valeur de retour de la fonction de rappel est :

(B)

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

Remplacer A dans B peut obtenir la valeur de $stack après la première itération :

(C )

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

Pour la deuxième itération, de la même manière, remplacez C par B pour obtenir :

(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);  //包含控制器操作的闭包
                })
            );
        };
    );
};

Et ainsi de suite , nous avons Remplacez autant de middlewares que vous le souhaitez, et la dernière fois que vous obtenez $stack, renvoyez-le à $pipeline. Étant donné que les fermetures du middleware ont été inversées dans l'ordre précédent, les fermetures à l'avant sont enveloppées dans la couche intérieure, de sorte que les fermetures après l'ordre inverse sont plus tard à l'extérieur et, du point de vue de l'ordre direct, elles deviennent les premières. Le middleware est la couche la plus externe.

Après avoir enveloppé les fermetures couche par couche, nous obtenons une "super" fermeture D similaire à une structure en oignon. La structure de cette fermeture est celle indiquée dans les commentaires de code ci-dessus. Enfin, passez l'objet $request à cette fermeture et exécutez-le : $pipeline($this->passable);, démarrant ainsi un processus similaire à l'épluchage d'un oignon. Voyons ensuite comment l'oignon est épluché.

Analyse du processus d'épluchage de l'oignon

array_map(...) Traite chaque classe middleware en une fermeture avec une structure similaire à celle-ci :

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

La poignée est l'entrée dans le middleware, et ses caractéristiques structurelles sont les suivantes :

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

L'"oignon" au-dessus de nous n'a que deux couches au total, c'est-à-dire qu'il y a deux couches de fermetures du middleware, en supposant M1-1 et M1-2 sont respectivement les points d'opération de préfixe et de post-valeur de la première méthode de gestion du middleware. Il en va de même pour le deuxième middleware, qui est M2-1 et M2-2. Maintenant, laissez le programme exécuter $pipeline($this->passable), développez-le, c'est-à-dire exécutez :

// 伪代码
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)

À ce stade, le programme nécessite la valeur de retour de :

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

, c'est-à-dire pour exécuter la première fermeture du middleware, $passable correspond au paramètre $request de la méthode handle, et la fermeture de niveau suivant

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

correspond au paramètre $next de la méthode handle.

Pour exécuter la première fermeture, c'est-à-dire pour exécuter la méthode handle de la première fermeture, le processus est le suivant : exécutez d'abord le code au point M1-1, c'est-à-dire la pré-opération, puis exécutez $response = $next($request);, puis le programme entre pour exécuter la fermeture suivante, $next($request) est développé, c'est-à-dire :

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

et ainsi de suite, exécutez la fermeture, c'est-à-dire , exécutez la deuxième méthode handle du middleware, à ce moment, exécute d'abord le point M2-1, puis exécute $response = $next($request) La fermeture $next à ce moment est :

function ($passable) use ($destination) {
    return $destination($passable);  
})
<.> Appartient au noyau de l'oignon — — La couche la plus interne, qui est la fermeture contenant les opérations du contrôleur, développez-la :

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

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

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

总结

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer