laravelミドルウェアとは何ですか? Laravelミドルウェアの解釈(ミドルウェア)

不言
リリース: 2023-04-02 17:06:01
オリジナル
10888 人が閲覧しました

Laravel では、ミドルウェアはその名の通り、リクエストデータをインターセプトし、データを検証し、リクエストとレスポンスの間の論理的な処理を経て次のミドルウェアへの移行を許可するかどうかを決定するもので、ミドルウェアはプレフィックスとしてミドルウェア、ポストに分かれています。ミドルウェア。権限認証、ロギングなどに使用できます。

laravelミドルウェアとは何ですか? Laravelミドルウェアの解釈(ミドルウェア)

Laravel のミドルウェア (Middleware) は、アプリケーションに入る HTTP リクエスト オブジェクト (Request) をフィルタリングし、アプリケーションから出る HTTP レスポンス オブジェクト (Response) を改善する役割を果たします。アプリケーションの機能を備えており、リクエストをレイヤーごとにフィルタリングし、複数のミドルウェアを適用することで徐々に応答を改善できます。これにより、プログラムの分離が実現されますが、ミドルウェアがない場合、これらの手順をコントローラ内で実行する必要があり、コントローラが肥大化することは間違いありません。

簡単な例を挙げると、電子商取引プラットフォームでは、ユーザーはプラットフォーム上で買い物をする通常のユーザーと、店舗を開いた後の販売者ユーザーのいずれかになります。これら 2 つのタイプのユーザーのユーザー システムは次のとおりです。販売者ユーザーのみがアクセスできるコントローラーに、販売者ユーザーの ID 認証を完了するために 2 つのミドルウェアを適用するだけで済みます:

class MerchantController extends Controller$
{
    public function __construct()
    {
        $this->middleware('auth');
        $this->middleware('mechatnt_auth');
    }
}
ログイン後にコピー

認証ミドルウェアでユニバーサル ユーザー認証を実行します。成功 HTTP リクエストは、マーチャント ユーザー情報を認証するために、merchant_auth ミドルウェアに送信されます。両方のミドルウェアが渡された後、HTTP リクエストは、目的のコントローラー メソッドに入ることができます。ミドルウェアを使用すると、これらの認証コードを対応するミドルウェアに抽出でき、必要に応じて複数のミドルウェアを自由に組み合わせてHTTPリクエストをフィルタリングできます。

もう 1 つの例は、Laravel がすべてのルートに自動的に適用する VerifyCsrfToken ミドルウェアです。HTTP リクエストがアプリケーションに入り、VerifyCsrfToken ミドルウェアを通過すると、トークンが検証されます。クロスサイト リクエスト フォージェリを防ぐため、HTTP レスポンスがアプリケーションを離れる前に、適切な Cookie がレスポンスに追加されます。 (laravel 5.5以降、CSRFミドルウェアはWebルーティングにのみ自動的に適用されます)

上記の例では、リクエストをフィルタリングするものはプレミドルウェアと呼ばれ、応答を完了するものはプレミドルウェアと呼ばれます。ポストミドルウェア。プロセス全体をマークするために画像を使用できます:
laravelミドルウェアとは何ですか? Laravelミドルウェアの解釈(ミドルウェア)

上記は、laravel におけるミドルウェアの役割の概要と、どのタイプのコードをコントローラーからミドルウェアに移動する必要があるかを示しています。独自のlaravelミドルウェアを定義して使用する方法については、公式ドキュメントを参照してください。

Laravel でミドルウェアがどのように実装されているかを見てみましょう。ミドルウェアの設計には、デコレーターと呼ばれるデザイン パターンが適用されます。デコレーター パターンが何なのかまだわからない場合は、デザイン パターンを確認してください。 . 関連書籍については、こちらの記事も簡単に参照できます。

Laravel が Application をインスタンス化した後、サービス コンテナから Http Kernel オブジェクトを解析します。クラス名からも、Http Kernel が Laravel の HTTP リクエストと応答を担当するコアであることがわかります。

/**
 * @var \App\Http\Kernel $kernel
 */
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();

$kernel->terminate($request, $response);
ログイン後にコピー

index.php で、HTTP カーネルが ##bootstrap/app.php にバインドされているため、サービス コンテナから解析されることがわかります #Illuminate \Contracts\Http\Kernelインターフェイスの実装クラスApp\Http\Kernelしたがって、$kernel は実際には App\Http\Kernel クラスのオブジェクトです。 HTTP カーネルを解析した後、Laravel はアプリケーションに入るリクエスト オブジェクトを Http カーネルのハンドル メソッドに渡します。ハンドル メソッドは、アプリケーションに流入するリクエスト オブジェクトを処理し、応答オブジェクトを返す役割を果たします。

/**
 * Handle an incoming HTTP request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function handle($request)
{
    try {
        $request->enableHttpMethodParameterOverride();

        $response = $this->sendRequestThroughRouter($request);
    } catch (Exception $e) {
        $this->reportException($e);

        $response = $this->renderException($request, $e);
    } catch (Throwable $e) {
        $this->reportException($e = new FatalThrowableError($e));

        $response = $this->renderException($request, $e);
    }

    $this->app['events']->dispatch(
        new Events\RequestHandled($request, $response)
    );

    return $response;
}
ログイン後にコピー

ミドルウェア フィルタリング アプリケーションのプロセスは、

$this->sendRequestThroughRouter($request) で発生します:

/**
 * Send the given request through the middleware / router.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');

    $this->bootstrap();

    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}
ログイン後にコピー

このメソッドの前半は、初期化を適用することです。この部分は、サービスプロバイダーについて説明した前の記事で詳しく説明されています。 LaravelはリクエストオブジェクトをPipelineオブジェクト経由で送信し、PipelineではリクエストオブジェクトはHttpカーネルに定義されたミドルウェアの前処理やダイレクトクロージャ処理を経てコントローラのアクションに到達し、レスポンスオブジェクトを取得します。

パイプラインでこれらのメソッドを見てください:

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

    return $this;
}

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

    return $this;
}

public function then(Closure $destination)
{
    $firstSlice = $this->getInitialSlice($destination);
    
    //pipes 就是要通过的中间件
    $pipes = array_reverse($this->pipes);

    //$this->passable就是Request对象
    return call_user_func(
        array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
    );
}


protected function getInitialSlice(Closure $destination)
{
    return function ($passable) use ($destination) {
        return call_user_func($destination, $passable);
    };
}

//Http Kernel的dispatchToRouter是Piple管道的终点或者叫目的地
protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance('request', $request);

        return $this->router->dispatch($request);
    };
}
ログイン後にコピー

上記の関数はわかりにくいように見えます。まず、array_reduce のコールバック関数パラメーターの説明を見てみましょう:

mixed array_reduce ( array $array , callable $callback [,mixed $initial = NULL ] )
array_reduce() は、配列 array 内の各ユニットにコールバック関数 callback を繰り返し適用することで、配列を単一の値に単純化します。

callback (mixed $carry ,mixed $item )

carry
最後の反復で値をキャリーします。この反復が初めての場合、この値は初期値です。 item にはこの反復の値が含まれます。

getInitialSlice メソッドの戻り値は、callbakc 関数に渡される $carry パラメータの初期値です。この値は現在クロージャです。私は getInitialSlice メソッドと Http カーネルのdispatchToRouter メソッドを使用しています。それをマージします。 $firstSlice の値は次のとおりです:

$destination = function ($request) {
    $this->app->instance('request', $request);
    return $this->router->dispatch($request);
};

$firstSlice = function ($passable) use ($destination) {
    return call_user_func($destination, $passable);
};
ログイン後にコピー

次に、array_reduce のコールバックを見てみましょう:

//Pipeline 
protected function getSlice()
{
    return function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            try {
                $slice = parent::getSlice();

                return call_user_func($slice($stack, $pipe), $passable);
            } catch (Exception $e) {
                return $this->handleException($passable, $e);
            } catch (Throwable $e) {
                return $this->handleException($passable, new FatalThrowableError($e));
            }
        };
    };
}

//Pipleline的父类BasePipeline的getSlice方法
protected function getSlice()
{
    return function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            if ($pipe instanceof Closure) {
                return call_user_func($pipe, $passable, $stack);
            } elseif (! is_object($pipe)) {
                //解析中间件名称和参数 ('throttle:60,1')
                list($name, $parameters) = $this->parsePipeString($pipe);
                $pipe = $this->container->make($name);
                $parameters = array_merge([$passable, $stack], $parameters);
            } else{
                $parameters = [$passable, $stack];
            }
            //$this->method = handle
            return call_user_func_array([$pipe, $this->method], $parameters);
        };
    };
}
ログイン後にコピー

注: getSlice メソッドの名前は、Laravel 5.5 バージョンで変更されました。両者の間に論理的な違いはないため、バージョン 5.5 のミドルウェア コードを参照してこの記事を読むことができます。

getSlice会返回一个闭包函数, $stack在第一次调用getSlice时它的值是$firstSlice, 之后的调用中就它的值就是这里返回的值个闭包了:

$stack = function ($passable) use ($stack, $pipe) {
            try {
                $slice = parent::getSlice();

                return call_user_func($slice($stack, $pipe), $passable);
            } catch (Exception $e) {
                return $this->handleException($passable, $e);
            } catch (Throwable $e) {
                return $this->handleException($passable, new FatalThrowableError($e));
            }
 };
ログイン後にコピー

getSlice返回的闭包里又会去调用父类的getSlice方法,他返回的也是一个闭包,在闭包会里解析出中间件对象、中间件参数(无则为空数组), 然后把$passable(请求对象), $stack和中间件参数作为中间件handle方法的参数进行调用。

上面封装的有点复杂,我们简化一下,其实getSlice的返回值就是:

$stack = function ($passable) use ($stack, $pipe) {
                //解析中间件和中间件参数,中间件参数用$parameter代表,无参数时为空数组
               $parameters = array_merge([$passable, $stack], $parameters)
               return $pipe->handle($parameters)
};
ログイン後にコピー

array_reduce每次调用callback返回的闭包都会作为参数$stack传递给下一次对callback的调用,array_reduce执行完成后就会返回一个嵌套了多层闭包的闭包,每层闭包用到的外部变量$stack都是上一次之前执行reduce返回的闭包,相当于把中间件通过闭包层层包裹包成了一个洋葱。

在then方法里,等到array_reduce执行完返回最终结果后就会对这个洋葱闭包进行调用:

return call_user_func( array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable);
ログイン後にコピー

这样就能依次执行中间件handle方法,在handle方法里又会去再次调用之前说的reduce包装的洋葱闭包剩余的部分,这样一层层的把洋葱剥开直到最后。通过这种方式让请求对象依次流过了要通过的中间件,达到目的地Http Kernel 的dispatchToRouter方法。

通过剥洋葱的过程我们就能知道为什么在array_reduce之前要先对middleware数组进行反转, 因为包装是一个反向的过程, 数组$pipes中的第一个中间件会作为第一次reduce执行的结果被包装在洋葱闭包的最内层,所以只有反转后才能保证初始定义的中间件数组中第一个中间件的handle方法会被最先调用。

上面说了Pipeline传送请求对象的目的地是Http Kernel 的dispatchToRouter方法,其实到远没有到达最终的目的地,现在请求对象了只是刚通过了\App\Http\Kernel类里$middleware属性里罗列出的几个中间件:

protected $middleware = [
    \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
    \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
    \App\Http\Middleware\TrimStrings::class,
    \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    \App\Http\Middleware\TrustProxies::class,
];
ログイン後にコピー

当请求对象进入Http Kernel的dispatchToRouter方法后,请求对象在被Router dispatch派发给路由时会进行收集路由上应用的中间件和控制器里应用的中间件。

namespace Illuminate\Foundation\Http;
class Kernel implements KernelContract
{
    protected function dispatchToRouter()
    {
        return function ($request) {
            $this->app->instance('request', $request);

            return $this->router->dispatch($request);
        };
    }
}


namespace Illuminate\Routing;
class Router implements RegistrarContract, BindingRegistrar
{    
    public function dispatch(Request $request)
    {
        $this->currentRequest = $request;

        return $this->dispatchToRoute($request);
    }
    
    public function dispatchToRoute(Request $request)
    {
        return $this->runRoute($request, $this->findRoute($request));
    }
    
    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)
        );
    }
    
    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()
                        );
                    });
    
    }
}
ログイン後にコピー

收集完路由和控制器里应用的中间件后,依然是利用Pipeline对象来传送请求对象通过收集上来的这些中间件然后到达最终的目的地,在那里会执行路由对应的控制器方法生成响应对象,然后响应对象会依次来通过上面应用的所有中间件的后置操作,最终离开应用被发送给客户端。

限于篇幅和为了文章的可读性,收集路由和控制器中间件然后执行路由对应的处理方法的过程我就不在这里详述了,感兴趣的同学可以自己去看Router的源码,本文的目的还是主要为了梳理laravel是如何设计中间件的以及如何执行它们的,希望能对感兴趣的朋友有帮助。

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

Laravel控制器的解读

以上がlaravelミドルウェアとは何ですか? Laravelミドルウェアの解釈(ミドルウェア)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート