百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

「Laravel系列3.1」一个请求的前世今生

liebian365 2024-11-20 18:25 3 浏览 0 评论

在 Laravel 的世界中,请求和响应是非常重要的环节,虽说我们讲的是一个请求的前世今生,但这个请求最后的结果往往体现在一个响应中,所以我们就一起学习请求和响应这两方面的内容。

请求,体现在 Laravel 框架中的 request 中,这个 Request 对象在底层是 Symfony 的一个 RequestBag 包,它将贯穿整个框架几乎所有加载的对象中,能够被我们的控制器、中间件捕获调用。

请求的路径

我们分开来看请求的调用路径。这个调用路径,也就是从浏览器发出一个请求到我们要处理这个请求的路由或者控制器,看看这个请求都经历了哪些地方,走过了哪些路。

我们以路由中的处理为例,可以看到一个简单的请求走到路由中就经历过了这么多的类和方法的处理。所以我们常说 Laravel 的慢就是慢在了这里。当然,这也是之前就说过的,为了“优雅”而放弃的性能。

仔细查看左侧我们请求一路过来调用的各个文件,会发现很多的 Pipeline.php ,也会发现有很多是包含 Middleware 目录的文件。Pipeline 是管道的意思,对应的其实是 Linux 命令行中的管道的概念,而在设计模式中,对应的其实是一种 责任链模式 的实现。管道最主要的能力就是对于中间件的处理,而责任链在实现的时候,遵循的就是类似于中间件这样的一种概念,让请求依次经过每个中间件,需要处理的就处理,不需要处理的就路过。对于这里的概念,我们在后面核心架构中还会详细的讲解,大家如果对设计模式还不是很熟悉的话,可以先去复习一下 【PHP设计模式之责任链模式】https://mp.weixin.qq.com/s/ZA9vyCEkEg9_KTll-Jkcqw 。学习 Laravel ,设计模式非常重要,它里面的很多功能都是各种模式的组合实现,需要大家对设计模式有一定的理解。

在责任链中,一直不停传递的就是这个 Request 这个对象。它是通过依赖注入注入到当前这个路由的回调函数里的。关于依赖注入的问题也是我们后面再深入学习的内容,这里也只是做个了解铺垫。通过不断地注入,让这个 request 参数在中间件中不停地穿梭处理,最后到达路由或者控制器。由于我们今天的测试只是在路由进行处理,所以看不到控制器的处理,这点我们将在后面学习控制器的文章中再次学习到。

可以最后总结一下,一个请求的路径,从 入口文件index.php ,进入到 Kernel 内核之后,就是一直在 Pipeline 管道中不断地使用中间件进行处理,最终达到 路由 或者 控制器 。简单地来看,请求的传递就是这样一个链条,理论上并不长,但中间件的多少决定了它的路途是否遥远。

请求的参数

对于请求来说,我们从 request 里获取到了请求的参数,这也是我们主要要看的内容。从调试的信息来看,其实从传统的 \$_REQUEST 、$_POST、$_GET 这些全局变量中也可以获取到参数信息,为什么我们要从 request 中获取呢?

其实,许多框架都会建议从他们封装的参数获取函数中取得参数信息。一般这些框架都会对请求进行一些参数验证、数据保护过滤的操作。同时,在 Laravel 中,我们在中间件中也可以获取到这些参数,整个 request 是贯穿所有的框架对象的,也就是它在整个请求生命周期中都是存在的,并且一直是向下传递的。

我们先来看看这个请求参数是如何封装的,我们是如何获取的。

请求的封装与获取

请求参数的获取,是通过调用 request->input() 这个方法,实际调用的是 laravel/framework/src/Illuminate/Http/Concerns/InteractsWithInput.php 这个文件中的 input() 方法,它会继续调用 symfony/http-foundation/InputBag.php 中的 all() 方法。为什么是 all() 方法呢?因为我们没指定是 get 还是 post 来的数据。

在 request 中,有对应的 get() 和 post() 方法,同时也存在一个 all() 方法。普通的 all() 方法返回的是一个参数的数组,大家可以直接打印出来看一下。而 input() 方法则是从 all() 中取出一个指定名称的参数信息。input() 是非常强大的一个函数,它不仅仅是可以取 get、post 里面的数据,还可以获取 body 中格式化的 json 数据,前提是 header 头中指定请求是 application/json 格式。这个作为拓展知识,大家自己找资料尝试一下吧。

接下来就是通过 symfony/http-foundation/ParameterBag.php 中的 all() 方法获取参数的值。

整个调用过程在底层依然使用的是 Symfony 框架来进行请求的处理。并将所有的请求数据封装成一个 Bag 类型的对象。我们所有的参数都是从这个对象里面的属性中取得的。大家在调试过程中,可以看到请求参数都在 InputBag 的 parameters 属性中。

那么,这个属性是在什么时候获得参数信息的呢?

在我们的入口文件 public/index.php 中,调用了 Request::capture() 这个静态方法。在这个静态方法中,继续调用 static::createFromBase(SymfonyRequest::createFromGlobals()); 这个方法来将所有的参数变量保存到 request 中。进入 createFromBase() 方法所需要的参数,也就是 SymfonyRequest::createFromGlobals() 方法,我们将看到这样的代码。

public static function createFromGlobals()
{
    $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER);

    if ($_POST) {
        $request->request = new InputBag($_POST);
    } elseif (0 === strpos($request->headers->get('CONTENT_TYPE', ''), 'application/x-www-form-urlencoded')
        && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])
    ) {
        parse_str($request->getContent(), $data);
        $request->request = new InputBag($data);
    }

    return $request;
}

其中第一行代码就可以看到,通过 createRequestFromFactory() 这个静态方法,我们将整个请求的所有数据,包括 get、post、cookie、files、server 都 传递了进去,而在 createRequestFromFactory() 做了什么呢?将这些数据实例化成了一个 symfony/http-foundation/Request.php 对象。不同的参数分别实例化成了不同的 Bag 属性。比如我们这里的 get 请求的参数就放到了 InputBag 对应的一个 query 属性中。post 放到了 ParameterBag 对应的 request 属性中。这一切都发生在 symfony/http-foundation/Request.php 中的 initialize() 里面。

public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
{
    $this->request = new ParameterBag($request);
    $this->query = new InputBag($query);
    $this->attributes = new ParameterBag($attributes);
    $this->cookies = new InputBag($cookies);
    $this->files = new FileBag($files);
    $this->server = new ServerBag($server);
    $this->headers = new HeaderBag($this->server->getHeaders());

    $this->content = $content;
    $this->languages = null;
    $this->charsets = null;
    $this->encodings = null;
    $this->acceptableContentTypes = null;
    $this->pathInfo = null;
    $this->requestUri = null;
    $this->baseUrl = null;
    $this->basePath = null;
    $this->method = null;
    $this->format = null;
}

果然一切都是面向对象啊,不管怎么样,最后我们都是在对象的世界里去操作获取这些数据。这也是使用框架的魅力,通过对源码的分析,我们见识到了 Laravel 中对于数据的处理也全部都是通过对象的方法来进行的。

响应的返回

一个请求的最终归宿是我们代码的处理。代码处理完业务逻辑之后,需要打印数据进行展示,这个过程其实就是一次响应的过程。有请求,有响应,构成了一个完整的数据 请求/响应 模型。

对于大部分的调试来说,我们直接 echo 返回的数据就可以了,但从框架的角度来说,Laravel 中对于响应也是使用对象来操作的,这个对象就是 Response 对象。

我们在路由中可以直接 return 一个字符串,也可以 return view() 或者 return response->json() ,对应的返回的内容都是一个 Response 对象。

使用断点调试,会发现这个 return 之后会进入到 laravel/framework/src/Illuminate/Routing/Router.php 的 prepareResponse() 方法中,一路向下,会发现它进入到了 laravel/framework/src/Illuminate/Http/Response.php 中,并且实例化了一个这个对象。Response 对象继承自 Symfony 的 Response 类。

在 Response 对象中,我们会将数据保存在 ResponseBag 中,和 Reqeust 非常类似,这个 ResponseBag 中会携带响应的头信息、HTTP状态信息、数据信息,最后输出到浏览器。

在处理响应的过程中,还会进入到一些 next() 提前的中间件用于处理一些响应事件的数据,这个我们在学习中件间的时候会再提到。

$response = tap($kernel->handle(
    $request = Request::capture()
))->send();

最后,在 index.php 中,我们通过这段代码调用 symfony/http-foundation/Response.php 的 send() 方法将数据输出。

public function sendHeaders()
{
    // headers have already been sent by the developer
    if (headers_sent()) {
        return $this;
    }

    // headers
    foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
        $replace = 0 === strcasecmp($name, 'Content-Type');
        foreach ($values as $value) {
            header($name.': '.$value, $replace, $this->statusCode);
        }
    }

    // cookies
    foreach ($this->headers->getCookies() as $cookie) {
        header('Set-Cookie: '.$cookie, false, $this->statusCode);
    }

    // status
    header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);

    return $this;
}

public function sendContent()
{
    echo $this->content;

    return $this;
}

public function send()
{
    $this->sendHeaders();
    $this->sendContent();

    if (\function_exists('fastcgi_finish_request')) {
        fastcgi_finish_request();
    } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
        static::closeOutputBuffers(0, true);
    }

    return $this;
}

可以看到,在底层,归根结底还是通过 header() 和 echo 来将数据最终输出到浏览器的。

当然,我们只是研究了一下直接返回字符串的 Response 过程,其它的如 view() 返回模板的过程会更复杂一些,因为还要牵涉到视图模板数据的解析编译以及缓存。不过,整体的原理和步骤都是类似的,有兴趣的小伙伴可以自己调试一下。

总结

从一个请求的路径开始,到请求参数的底层代码分析,最后到一个响应的结束。一个请求的前世今生就被我们分析完了。当然,在这里只是点出了一些关键位置的关键代码,更详细的内容还是需要大家自己调试去的,在这个过程中,说不定还能发现更好玩的东西哦!

参考文档:

https://learnku.com/docs/laravel/8.x/responses/9370

https://learnku.com/docs/laravel/8.x/lifecycle/9360

https://learnku.com/docs/laravel/8.x/requests/9369

相关推荐

快递查询教程,批量查询物流,一键管理快递

作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...

一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递

对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...

快递查询单号查询,怎么查物流到哪了

输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...

3分钟查询物流,教你一键批量查询全部物流信息

很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...

快递单号查询,一次性查询全部物流信息

现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...

快递查询工具,批量查询多个快递快递单号的物流状态、签收时间

最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...

快递查询软件,自动识别查询快递单号查询方法

当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...

教你怎样查询快递查询单号并保存物流信息

商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...

简单几步骤查询所有快递物流信息

在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...

物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号

最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...

连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息

快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...

快递查询教程,快递单号查询,筛选更新量为1的单号

最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...

掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析

在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...

从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息

在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...

物流单号查询,在哪里查询快递

如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...

取消回复欢迎 发表评论: