slim3 Request请求流程解析

1、首先我们先来看看slim的run方法

public function run($silent = false)
{
      $response = $this->container->get('response');

      try {
          ob_start();
          $response = $this->process($this->container->get('request'), $response);
      } catch (InvalidMethodException $e) {
          $response = $this->processInvalidMethod($e->getRequest(), $response);
      } finally {
          $output = ob_get_clean();
      }

      // ....
      // ....
}

其中$this->container->get('request')回去回去一个注册的request对象

// 在框架初始化时注册
if (!isset($container['request'])) { 
      $container['request'] = function ($container) {
          return Request::createFromEnvironment($container->get('environment'))
      };
 }


public function get($id)
{
    // ...
    // ...

    try {
        return $this->offsetGet($id);
    } catch (\InvalidArgumentException $exception) {
       // ...
       // ...
       // ...
    }
}


public function offsetGet($id)
{
    if (!isset($this->keys[$id])) {
        throw new UnknownIdentifierException($id);
    }

    if (
        isset($this->raw[$id])
        || !\is_object($this->values[$id])
        || isset($this->protected[$this->values[$id]])
        || !\method_exists($this->values[$id], '__invoke')
    ) {
        return $this->values[$id];
    }

    if (isset($this->factories[$this->values[$id]])) {
        return $this->values[$id]($this);
    }

    $raw = $this->values[$id];
    $val = $this->values[$id] = $raw($this);
    $this->raw[$id] = $raw;

    $this->frozen[$id] = true;

    return $val;
}

框架初始化时会判断request有没有被设置,没有则会将一个方法赋值给container的request对象,而container的get方法最终会通过offsetGet()方法去获取,这里提一个container实现了ArrayAccess 接口,这个实现的好处就是可以像数组一样去访问对象。

我们重点来看一下offsetGet()方法,上面的几个if我们放到最后看,先看下面的赋值。$this->values['request']就是我们最开始那个注册的方法,我们将它赋值给$raw, 然后在执行这个方法将返回值赋值给 $val与$this->values['request'],在将$raw放到$this->raw['request'],然后将$this->frozen['request']设置为true,说明这个id已经初始化完成了。

这时候我们再反过来查看上面的if方法,

  • isset($this->raw[$id])判断$this->raw中是否设置过该id
  • !\is_object($this->values[$id])判断是否是个对象,
  • !\method_exists($this->values[$id], '__invoke')判断对象中是否存在__invoke方法,即不能直接调用
  • isset($this->protected[$this->values[$id]]) 是否需要将方法当做参数,不执行直接返回
  • isset($this->factories[$this->values[$id]])是否是一个可直接调用的callable

即如果$this->values[$id]有如下情况,就会直接返回

  • 是一个对象
  • 之前调用过
  • 设置了保护,需要直接返回callable
  • 不可调用的对象实例
  • 设置了factories

经过上面的步骤,我们得到了request对象,接下来进入process()方法

public function process(ServerRequestInterface $request, ResponseInterface $response)
{
    // ...
    // ...
    try {
        $response = $this->callMiddlewareStack($request, $response);
    } catch (Exception $e) {
        $response = $this->handleException($e, $request, $response);
    } catch (Throwable $e) {
        $response = $this->handlePhpError($e, $request, $response);
    }

    return $response;
}

public function callMiddlewareStack(ServerRequestInterface $request, ResponseInterface $response)
{
    if (is_null($this->tip)) {
        $this->seedMiddlewareStack();
    }
    /** @var callable $start */
    $start = $this->tip;
    $this->middlewareLock = true;
    $response = $start($request, $response);
    $this->middlewareLock = false;
    return $response;
}

protected function seedMiddlewareStack(callable $kernel = null)
{
    if (!is_null($this->tip)) {
        throw new RuntimeException('MiddlewareStack can only be seeded once.');
    }
    if ($kernel === null) {
        $kernel = $this;
    }
    $this->tip = $kernel;
}

这里我们就主要看看 $this->callMiddlewareStack($request, $response)这个方法就ok。开始时现将$this即app对象赋值给$this->tip,然后将$this->tip 赋值给$start, 接着以调用函数的方法调用对象,运行app的__invoke方法。

public function __invoke(ServerRequestInterface $request, ResponseInterface $response)
{
    // Get the route info
    $routeInfo = $request->getAttribute('routeInfo');

    /** @var \Slim\Interfaces\RouterInterface $router */
    $router = $this->container->get('router');

    // If router hasn't been dispatched or the URI changed then dispatch
    if (null === $routeInfo || ($routeInfo['request'] !== [$request->getMethod(), (string) $request->getUri()])) {
        $request = $this->dispatchRouterAndPrepareRoute($request, $router);
        $routeInfo = $request->getAttribute('routeInfo');
    }

    // 找到匹配的路由
    if ($routeInfo[0] === Dispatcher::FOUND) {
        $route = $router->lookupRoute($routeInfo[1]);
        return $route->run($request, $response);
    } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
        // 方法不允许访问
        if (!$this->container->has('notAllowedHandler')) {
            throw new MethodNotAllowedException($request, $response, $routeInfo[1]);
        }
        /** @var callable $notAllowedHandler */
        $notAllowedHandler = $this->container->get('notAllowedHandler');
        return $notAllowedHandler($request, $response, $routeInfo[1]);
    }

    // 没有找到对应的处理
    if (!$this->container->has('notFoundHandler')) {
        throw new NotFoundException($request, $response);
    }
    /** @var callable $notFoundHandler */
    $notFoundHandler = $this->container->get('notFoundHandler');
    return $notFoundHandler($request, $response);
}

在__invoke中,分为三种情况

  • 找到匹配的路由,执行对应方法
  • 方法不允许访问,判断有没有注册notAllowedHandler,注册就用该handle处理,没有则报异常
  • 没有找到对应路由,判断有没有注册notFoundHandler,注册就用该handle处理,没有则报异常
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,080评论 19 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,802评论 18 399
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,386评论 11 349
  • 本文主要介绍在python中如何使用MySQL数据库。 准备工作 安装mysql Linux (Ubuntu) a...
    m2fox阅读 1,003评论 0 3
  • 当初,我们倾尽一生去爱的那个人,也许到后来的某一天,你会突然发现,当初的山盟海誓似乎并没有那么铭心刻骨,当初你挚爱...
    文婉豫阅读 621评论 2 1