我独自走进 Laravel5.5 的❤(七)

laravel 的中间件原理。

一、中间件的使用

中间件的使用也是十分之简单的鸭,仔细看看文档就可以使用得轻车熟路了。

1.1创建中间件

php artisan make:middleware FunnyMiddleware
 1.2定义中间件
  创建完成之后,我们可以看见如下的一个内容。

a. 原始状态

public function handle($request, Closure $next)
{
    return $next($request);
}

b. 前置中间件

public function handle($request, Closure $next)
{ 
    if(! session()->has('auth')) {
          abort(404);
    }
    return $next($request);
}

c.后置中间件

public function handle($request, Closure $next)
{ 
     $response = $next($request)
     if(! session('auth')->id == 1) {
          \Log('用户' . session('auth')->id . '访问了' . __CLASS__ . '::' . __FUNCTION__);
    }
    return $response;
}

 1.3 使用中间件
  a. 在路由中使用(这个部分网上例子太多了,自行看文档~)

  b.在控制器中使用(这个部分网上例子太多了,自行看文档~)

  c. 在 App\Providers\RouteServiceProvider.php 中使用,这里属于一种全局注册中间件的方法。

public function map()
{
    $this->mapApiRoutes();

    $this->mapWebRoutes();

    $this->mapAdminRoutes();//注册 mapAdminRoutes 方法
    //
}

protected function mapAdminRoutes()
{
    Route::prefix('admin')
        ->middleware('admin')
        ->namespace($this->namespace)
        ->group(base_path('route/admin.php'));
//这里就是,前缀为 admin,namespace为App\Http\Controllers,路由文件为admin.php的路由都会被绑定 'admin' 中间件。

}

然后呢,做完上面的工作, 若合乎情理,被绑定中间件的路由都会使用中间件过滤请求。

二、源码解析(lara 中间件实现原理)

又到了,让人热血澎湃的时刻。

每当分析框架源码,内心就莫名的激动,这感觉就好像轻轻地抚摸着美女套在屁屁上光滑的底裤(好吧,开慢点)。

为了便于理解,依旧以 request 处理过程作为主线。

2.1 request 被发往路由器之前

protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);
    var_dump($request);
    Facade::clearResolvedInstance('request');

    $this->bootstrap();

    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}

//是的,这个是 lara 中间件处理 request 涉及到的第一步,此函数在之前提到过,$this->bootstrap() 会绑定服务提供者

//在 return 中会返回被中间件过滤的 request 

//说是这么说,但实际上压根看不懂这是什么玩意

 2.2  关于 Illuminate\Routing\Pipeline::class
由于 return 中返回的东西是对这玩意的对象的处理,所以就先来看看这是什么鬼,和中间件有什么鬼关联。

control B 打开发现文件内容如下:

use Closure;
use Exception;
use Throwable;
use Illuminate\Http\Request;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Pipeline\Pipeline as BasePipeline;
use Symfony\Component\Debug\Exception\FatalThrowableError;

class Pipeline extends BasePipeline {

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

                $callable = $slice($stack, $pipe);

                return $callable($passable);
            } catch (Exception $e) {
                return $this->handleException($passable, $e);
            } catch (Throwable $e) {
                return $this->handleException($passable, new FatalThrowableError($e));
            }
        };
    };
}

}

 所以这是一个 Illuminate\Pipeline\Pipeline 的子类,傀儡政权。

仔细看了一下内容,发现重写了 prepareDestination、carry、handleExceotion三个方法。ok,直接看一下他爸。

/**
* Set the object being sent through the pipeline.
* 设置通过管道发送的对象
* @param  mixed  $passable
* @return $this
*/

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

    return $this;
}

/**
* Set the array of pipes.
* 将管道转为数组形式
* @param  array|mixed  $pipes
* @return $this
*/
public function through($pipes)
{
    $this->pipes = is_array($pipes) ? $pipes : func_get_args();

    return $this;
}

/**
* Run the pipeline with a final destination callback.
* 将最终的回调结果推进管道
* @param  \Closure  $destination
* @return mixed
*/
public function then(Closure $destination)
{
    $pipeline = array_reduce(
        array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
    );

    return $pipeline($this->passable);
}

 一步一步来

2.2.1 send

send($request) 把 request 传入并设置为 passable 属性,下面就是打印出来的passable ,一目了然,就是传入的request,毫无争议。

protected 'passable' => 
        object(Illuminate\Http\Request)[42]
          protected 'json' => null
          protected 'convertedFiles' => null
          protected 'userResolver' => null
          protected 'routeResolver' => null
          public 'attributes' => 
            object(Symfony\Component\HttpFoundation\ParameterBag)[44]
              ...
          public 'request' => 
            object(Symfony\Component\HttpFoundation\ParameterBag)[50]
              ...
          public 'query' => 
            object(Symfony\Component\HttpFoundation\ParameterBag)[50]
              ...
          public 'server' => 
            object(Symfony\Component\HttpFoundation\ServerBag)[46]
              ...
          public 'files' => 
            object(Symfony\Component\HttpFoundation\FileBag)[47]
              ...
          public 'cookies' => 
            object(Symfony\Component\HttpFoundation\ParameterBag)[45]
              ...
          public 'headers' => 
            object(Symfony\Component\HttpFoundation\HeaderBag)[48]
              ...
          protected 'content' => null
          protected 'languages' => null
          protected 'charsets' => null
          protected 'encodings' => null
          protected 'acceptableContentTypes' => null
          protected 'pathInfo' => null
          protected 'requestUri' => null
          protected 'baseUrl' => null
          protected 'basePath' => null
          protected 'method' => null
          protected 'format' => null
          protected 'session' => null
          protected 'locale' => null
          protected 'defaultLocale' => string 'en' (length=2)
          private 'isHostValid' (Symfony\Component\HttpFoundation\Request) => boolean true
          private 'isForwardedValid' (Symfony\Component\HttpFoundation\Request) => boolean true

 2.2.2 through

through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) 表面意思就是如果容器跳过中间件则设置通道为空数组,否则为路由绑定的中间件。

那么这个容器注入的中间件是在什么时候获得的呢?没错,就是在容器刚创建的时候,就注入了。父老乡亲们,看这里。

//  bootstrap\app.php

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

没错,Illuminate\Contracts\Http\Kernel::class 单例绑定了 App\Http\Kernel::class

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

在make 后便生成实例。

 然而,这里扩展一下,除了 app\kernel中的 alias

在 Illuminate\Foundation\Application 中还有自加载的核心组件

public function registerCoreContainerAliases()
{
    foreach ([
        'app'                  => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class,  \Psr\Container\ContainerInterface::class],
        'auth'                 => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
        'auth.driver'          => [\Illuminate\Contracts\Auth\Guard::class],
        'blade.compiler'       => [\Illuminate\View\Compilers\BladeCompiler::class],
        'cache'                => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
        'cache.store'          => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
        'config'               => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
        'cookie'               => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
        'encrypter'            => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
        'db'                   => [\Illuminate\Database\DatabaseManager::class],
        'db.connection'        => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
        'events'               => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
        'files'                => [\Illuminate\Filesystem\Filesystem::class],
        'filesystem'           => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
        'filesystem.disk'      => [\Illuminate\Contracts\Filesystem\Filesystem::class],
        'filesystem.cloud'     => [\Illuminate\Contracts\Filesystem\Cloud::class],
        'hash'                 => [\Illuminate\Contracts\Hashing\Hasher::class],
        'translator'           => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
        'log'                  => [\Illuminate\Log\Writer::class, \Illuminate\Contracts\Logging\Log::class, \Psr\Log\LoggerInterface::class],
        'mailer'               => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
        'auth.password'        => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
        'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
        'queue'                => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
        'queue.connection'     => [\Illuminate\Contracts\Queue\Queue::class],
        'queue.failer'         => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
        'redirect'             => [\Illuminate\Routing\Redirector::class],
        'redis'                => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
        'request'              => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
        'router'               => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
        'session'              => [\Illuminate\Session\SessionManager::class],
        'session.store'        => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
        'url'                  => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
        'validator'            => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
        'view'                 => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
    ] as $key => $aliases) {
        foreach ($aliases as $alias) {
            $this->alias($key, $alias);
        }
    }
}

2.2.3 then

then($this->dispatchToRouter())

先来看看 $this->dispatchToRouter()


//设置路由分发回调函数

protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance('request', $request);//这个看下面的 a
        return $this->router->dispatch($request);//这个看下面的 b
    };
}

a. Illuminate\Container\Container::class->instance

//实例化抽象类

public function instance($abstract, $instance)
{
   // 删除 abstractAlias
    $this->removeAbstractAlias($abstract); //这个看下面

    $isBound = $this->bound($abstract);

    unset($this->aliases[$abstract]);

    // We'll check to determine if this type has been bound before, and if it has
    // we will fire the rebound callbacks registered with the container and it
    // can be updated with consuming classes that have gotten resolved here.
  //我们将检查并决定这种类型是否在之前被绑定,如果是,我们将启动容器重新绑定的回调,
  // 而且我们可以使用已经映射的消费类进行更新
    $this->instances[$abstract] = $instance;

    if ($isBound) {
        $this->rebound($abstract);
    }

    return $instance;
}

Illuminate\Container\Container::class->removeAbstractAlias

protected function removeAbstractAlias($searched)
{
    if (! isset($this->aliases[$searched])) {  //如果没有设置了指定的alias 则不处理
        return;
    }
    //否则,找到已经注入的 abstractAlias(alias 的 key) 并且删除
    foreach ($this->abstractAliases as $abstract => $aliases) {
        foreach ($aliases as $index => $alias) {
            if ($alias == $searched) {
                unset($this->abstractAliases[$abstract][$index]);
            }
        }
    }
}

b. Illuminate\Router\Route::class->dispatch

/**
* Dispatch the request to the application.
* 分发请求到应用中
* @param  \Illuminate\Http\Request  $request
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public function dispatch(Request $request)
{
    $this->currentRequest = $request;

    return $this->dispatchToRoute($request);
}

/**
* Dispatch the request to a route and return the response.
* 分发请求到其中一条路由并且返回响应
* @param  \Illuminate\Http\Request  $request
* @return mixed
*/
public function dispatchToRoute(Request $request)
{
    return $this->runRoute($request, $this->findRoute($request));
}

/**
* Find the route matching a given request.
* 根据请求找到相匹配的路由
* @param  \Illuminate\Http\Request  $request
* @return \Illuminate\Routing\Route
*/
protected function findRoute($request)
{
    $this->current = $route = $this->routes->match($request);

    $this->container->instance(Route::class, $route); //这个就是上面提到的实例化函数

    return $route;
}

// Illuminate\Routing\RouteCollection::class->match

/**
* Find the first route matching a given request.
* 找到第一条匹配请求的路由
* @param  \Illuminate\Http\Request  $request
* @return \Illuminate\Routing\Route
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function match(Request $request)
{
    $routes = $this->get($request->getMethod());

    // First, we will see if we can find a matching route for this current request
    // method. If we can, great, we can just return it so that it can be called
    // by the consumer. Otherwise we will check for routes with another verb.
  //首先,我们将会根据目前的请求方法找到对应的路由,如果可以,我们可以只是
  //返回它。否则我们将会根据其他的动作寻找对应的路由
    $route = $this->matchAgainstRoutes($routes, $request);

    if (! is_null($route)) {
        return $route->bind($request);
    }

    // If no route was found we will now check if a matching route is specified by
    // another HTTP verb. If it is we will need to throw a MethodNotAllowed and
    // inform the user agent of which HTTP verb it should use for this route.
     //如果指定其他的http动作也没有路由没有匹配的路由,我们将会抛出请求方法不允许异常
    //并且通知浏览器应该使用那种http动作来请求该路由。(好像并没有这个通知)
    $others = $this->checkForAlternateVerbs($request);

    if (count($others) > 0) {
        return $this->getRouteForMethods($request, $others);
    }

    throw new NotFoundHttpException;
}

// Illuminate\Routing\Router::class->runRoute

/**
* Return the response for the given route.
* 根据指定的路由返回响应
* @param  Route  $route
* @param  Request  $request
* @return mixed
*/
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)
    );
}

/**
* Run the given route within a Stack "onion" instance.
* 在洋葱堆(Stack "onion"这玩意是啥,大神给我解释一下行不)实例里运行给定的路由
* @param  \Illuminate\Routing\Route  $route
* @param  \Illuminate\Http\Request  $request
* @return mixed
*/
protected function runRouteWithinStack(Route $route, Request $request)
{

    //这里有点奇怪,这段和Illuminate\Foundation\Http\Kernel::class::sendRequestThroughRouter中的
   // through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) 不是一样的?

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

public function then(Closure $destination)
{
    $pipeline = array_reduce(
        array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
    );

    return $pipeline($this->passable);
}

// 倒序所有管道,并利用每个管道对请求进行过滤

//array_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。

// Illuminate\Routing\Pipeline::class->carry

protected function carry()
{
    return function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            try {
                $slice = parent::carry(); //这个看下面

                $callable = $slice($stack, $pipe);

                return $callable($passable);
            } catch (Exception $e) {
                return $this->handleException($passable, $e);
            } catch (Throwable $e) {
                return $this->handleException($passable, new FatalThrowableError($e));
            }
        };
    };
}

// Illuminate\Pipeline\Pipeline::class->carry

/**
* Get a Closure that represents a slice of the application onion.
* 获取一个表示洋葱应用的切片的闭包。(所以lara的开发者是亲切地称lara容器为洋葱?)
* @return \Closure
*/
protected function carry()
{
    return function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            if (is_callable($pipe)) {
                // If the pipe is an instance of a Closure, we will just call it directly but
                // otherwise we'll resolve the pipes out of the container and call it with
                // the appropriate method and arguments, returning the results back out.

                //如果管道是一个匿名函数类的实例,我们会直接调用并且我们会在容器外映射这个管道

                //并且用适合的方法和理由来调用它并返回结果
                return $pipe($passable, $stack);
            } elseif (! is_object($pipe)) {
                list($name, $parameters) = $this->parsePipeString($pipe);

                // If the pipe is a string we will parse the string and resolve the class out
                // of the dependency injection container. We can then build a callable and
                // execute the pipe function giving in the parameters that are required.
               //如果这个管道是一个字符串,我们将会解析这个字符串并且映射出这个类的依赖并注入容器。

               //我们会建立可回调并执行管道函数,提供需要的参数
                $pipe = $this->getContainer()->make($name);

                $parameters = array_merge([$passable, $stack], $parameters);
            } else {
                // If the pipe is already an object we'll just make a callable and pass it to
                // the pipe as-is. There is no need to do any extra parsing and formatting
                // since the object we're given was already a fully instantiated object.
                //如果管道已经是一个对象,我们只会制作一个回调并且把它推进管道这样。
               //当这个对象被我们完全实例化,便没有需要去做任何额外的解析和格式化。
                $parameters = [$passable, $stack];
            }

            return method_exists($pipe, $this->method)
                            ? $pipe->{$this->method}(...$parameters)
                            : $pipe(...$parameters);
        };
    };
}
//所以,我们可以发现 lara 使用管道设计模式解决了请求动作的中间件、路由匹配、路由请求方法处理等问题。

最后,将管道处理过的响应返回浏览器。

<--------------------- End ---------------------->

以上便是中间件涉及到的流程,如果有啥不对,请指出,互相学习。

至此,

致敬知识。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!