Maison > cadre php > PensezPHP > 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

藏色散人
Libérer: 2019-11-08 17:58:00
avant
4191 Les gens l'ont consulté

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);
        });
}
Copier après la connexion

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']);
}
Copier après la connexion

through code de méthode :

public function through($pipes)
{
    $this->pipes = is_array($pipes) ? $pipes : func_get_args();
    return $this;
}
Copier après la connexion

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
Copier après la connexion

$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;
}
Copier après la connexion
Copier après la connexion

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;
}
Copier après la connexion

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);
            });
Copier après la connexion

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);
}
Copier après la connexion

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);
            }
        };
    };
}
Copier après la connexion

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);
}
Copier après la connexion

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);
});
Copier après la connexion

La valeur de retour de la fonction de rappel est :

(B)

function ($passable) use ($stack, $pipe) {
    return $pipe($passable, $stack);
};
Copier après la connexion

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);
        })
    );
};
Copier après la connexion

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);  //包含控制器操作的闭包
                })
            );
        };
    );
};
Copier après la connexion

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;
}
Copier après la connexion
Copier après la connexion

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;
}
Copier après la connexion

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)
Copier après la connexion

À 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);  
            })
        );
    };
);
Copier après la connexion

, 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);  
        })
    );
}
Copier après la connexion

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)
Copier après la connexion

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);  
})
Copier après la connexion
<. 🎜> 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)
Copier après la connexion

最终,我们从 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!

Étiquettes associées:
source:learnku.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal