Heim >PHP-Framework >Denken Sie an PHP >Implementierungsanalyse des ThinkPHP6.0-Pipeline-Modus und der Middleware

Implementierungsanalyse des ThinkPHP6.0-Pipeline-Modus und der Middleware

藏色散人
藏色散人nach vorne
2019-11-04 13:29:254180Durchsuche

Erklärung

ThinkPHP 6.0 RC5 begann, den Pipeline-Modus zur Implementierung von Middleware zu verwenden, der präziser und geordneter ist als die Implementierung früherer Versionen. In diesem Artikel werden die Implementierungsdetails analysiert.

Zuerst beginnen wir mit der Eintragsdatei public/index.php, $http = (new App())->http;

Besorgen Sie sich eine Instanz der http-Klasse und rufen Sie deren Ausführung auf Methode :$response = $http->run(); und dann ruft ihre Ausführungsmethode die runWithRequest-Methode auf:

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

Die Ausführung der Middleware erfolgt in der endgültigen Return-Anweisung.

Pipeline, Through, Sendemethode

$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-Methodencode:

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

Der vorherige Aufruf von Through ist der eingehende array_map (...) kapselt die Middleware in Abschlüsse und speichert diese Abschlüsse im Attribut $pipes der Pipeline-Klasse.

Signatur der array_map-Methode von PHP:

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

$callback iteriert über jedes Element von $array und gibt einen neuen Wert zurück. Daher lauten die endgültigen formalen Merkmale jedes Abschlusses in $pipes wie folgt (Pseudocode):

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

Dieser Abschluss empfängt zwei Parameter, einer ist die Anforderungsinstanz und der andere ist die Rückruffunktion, Handle-Methode Nach der Verarbeitung , wird die Antwort abgerufen und zurückgegeben.

through gibt eine Instanz der Pipeline-Klasse zurück und ruft dann die Sendemethode auf:

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

Diese Methode ist sehr einfach. Sie speichert lediglich die eingehende Anforderungsinstanz in der $passable-Mitgliedsvariablen gibt schließlich die Instanz der Pipeline-Klasse zurück, sodass andere Methoden der Pipeline-Klasse in einer Kette aufgerufen werden können.

dann, nach der Carry-Methode

send-Methode, dann die then-Methode aufrufen:

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

The then erhält hier einen Abschluss als Parameter. Dieses Abschlusspaket enthält tatsächlich den Ausführungscode für die Controller-Operationen.

dann Methodencode:

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

Um die Analyse des Prinzips zu erleichtern, Wir fügen die Carry-Methode ein. Verbinden Sie sie mit dann und entfernen Sie den Fehlerabfangcode, um Folgendes zu erhalten:

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

Der Schlüssel hier ist, den Ausführungsprozess von array_reduce und $pipeline($this->passable These) zu verstehen Zwei Vorgänge können mit „Zwiebeln einwickeln“ und „Zwiebeln schälen“ verglichen werden.

Die erste Iteration von array_reduce, der Anfangswert von $stack ist:

(A) Der Rückgabewert der Rückruffunktion

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

ist :

(B)

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

Das Einsetzen von A in B kann den Wert von $stack nach der ersten Iteration erhalten:

(C )

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

Ersetzen Sie für die zweite Iteration auf ähnliche Weise C in B, um Folgendes zu erhalten:

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

Und so weiter , wir ersetzen so viele Middlewares, wie Sie möchten, und wenn Sie $stack das letzte Mal erhalten, geben Sie es an $pipeline zurück. Da die Middleware-Verschlüsse in der vorherigen Reihenfolge umgekehrt wurden, werden die Verschlüsse vorne in die innere Schicht eingewickelt, sodass die Verschlüsse nach der umgekehrten Reihenfolge später außen liegen und aus der Perspektive der positiven Reihenfolge zu den früheren werden . Middleware ist die äußerste Schicht.

Nachdem wir die Verschlüsse Schicht für Schicht umwickelt haben, erhalten wir einen „Super“-Verschluss D ähnlich einer Zwiebelstruktur. Die Struktur dieses Verschlusses ist wie in den Codekommentaren oben dargestellt. Übergeben Sie abschließend das $request-Objekt an diesen Abschluss und führen Sie ihn aus: $pipeline($this->passable);, wodurch ein Vorgang ähnlich dem Schälen einer Zwiebel gestartet wird. Als nächstes wollen wir sehen, wie die Zwiebel geschält wird.

Analyse des Zwiebelschälprozesses

array_map(...) Verarbeiten Sie jede Middleware-Klasse zu einem Abschluss mit einer Struktur ähnlich dieser:

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

Der Griff ist der Eingang zur Middleware und seine strukturellen Eigenschaften sind wie folgt:

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

Die „Zwiebel“ über uns hat insgesamt nur zwei Schichten, das heißt, es gibt zwei Schichten von Middleware-Verschlüssen, vorausgesetzt M1-1 und M1-2 sind die Präfix- bzw. Postwert-Operationspunkte der ersten Middleware-Handle-Methode. Dasselbe gilt für die zweite Middleware, nämlich M2-1 und M2-2. Lassen Sie das Programm nun $pipeline($this->passable) ausführen, erweitern Sie es, d. h. führen Sie Folgendes aus:

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

Zu diesem Zeitpunkt benötigt das Programm den Rückgabewert von:

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

, das heißt Um den ersten Middleware-Abschluss auszuführen, entspricht $passable dem $request-Parameter der Handle-Methode und der Abschluss der nächsten Ebene

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

entspricht dem $next-Parameter der Handle-Methode.

Um den ersten Abschluss auszuführen, dh die Handle-Methode des ersten Abschlusses auszuführen, ist der Prozess wie folgt: Führen Sie zuerst den Code an Punkt M1-1 aus, dh die Voroperation, und führen Sie ihn dann aus $response = $next($request);, dann tritt das Programm ein, um den nächsten Abschluss auszuführen, $next($request) wird erweitert, das heißt:

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

und so weiter, führt den Abschluss aus, das heißt , führe die zweite aus Die Handle-Methode der Middleware führt zu diesem Zeitpunkt zuerst den M2-1-Punkt aus und führt dann $response = $next($request) aus. Der $next-Abschluss lautet zu diesem Zeitpunkt:

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

Gehört zum Kern der Zwiebel – – Die innerste Schicht, die den Abschluss mit den Controller-Operationen darstellt, wird erweitert, um Folgendes anzuzeigen:

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→返回结果。

总结

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

Das obige ist der detaillierte Inhalt vonImplementierungsanalyse des ThinkPHP6.0-Pipeline-Modus und der Middleware. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:learnku.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen