惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
F
Full Disclosure
Martin Fowler
Martin Fowler
The GitHub Blog
The GitHub Blog
L
LangChain Blog
T
The Blog of Author Tim Ferriss
D
DataBreaches.Net
GbyAI
GbyAI
Y
Y Combinator Blog
博客园 - Franky
WordPress大学
WordPress大学
Apple Machine Learning Research
Apple Machine Learning Research
A
About on SuperTechFans
Blog — PlanetScale
Blog — PlanetScale
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
G
Google Developers Blog
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
博客园 - 三生石上(FineUI控件)
IT之家
IT之家
Jina AI
Jina AI
N
Netflix TechBlog - Medium
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Recent Announcements
Recent Announcements
酷 壳 – CoolShell
酷 壳 – CoolShell
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
腾讯CDC
H
Help Net Security
H
Heimdal Security Blog
J
Java Code Geeks
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
The Exploit Database - CXSecurity.com
The Register - Security
The Register - Security
大猫的无限游戏
大猫的无限游戏
T
Threatpost
B
Blog RSS Feed
T
Threat Research - Cisco Blogs
Microsoft Security Blog
Microsoft Security Blog
S
Securelist
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
B
Blog
量子位
AWS News Blog
AWS News Blog
Project Zero
Project Zero
P
Proofpoint News Feed
Cisco Talos Blog
Cisco Talos Blog
Spread Privacy
Spread Privacy
S
Schneier on Security

青空之蓝

[青空之蓝-2023] - 色彩 | 青空之蓝 [青空之蓝-2022] - 平静 | 青空之蓝 [青空之蓝-2021] - 远望 | 青空之蓝 浅谈垃圾回收 | 青空之蓝 浅谈泛型擦除 | 青空之蓝 浅谈单点登录 | 青空之蓝 使用 Kotlin 编写 Spring 测试 | 青空之蓝 设计模式系列文章 | 青空之蓝 从零实现一个 Java 微框架 - IoC | 青空之蓝 从零实现一个 Java 微框架 - 前言 | 青空之蓝 浅谈 JVM:类加载 | 青空之蓝 浅谈 IO | 青空之蓝 浅谈并发:synchronized & ReentrantLock | 青空之蓝 浅谈并发:CAS & AQS | 青空之蓝 浅谈并发:ThreadLocal | 青空之蓝 浅谈并发:三大特性 | 青空之蓝 浅谈组合注解 & 注解别名 | 青空之蓝 [青空之蓝-2020]-迷茫 | 青空之蓝 Java 系列文章 | 青空之蓝 HTTP 系列文章 | 青空之蓝 浅谈 EatWhatYouKill | 青空之蓝 浅谈可扩展线程池 | 青空之蓝 聊聊写框架 | 青空之蓝 聊聊现状-[2020-09] | 青空之蓝 浅谈并发:锁 | 青空之蓝 浅谈并发:基础 | 青空之蓝 浅谈缓存 | 青空之蓝 无须定义类,Spring 快速注入 Json 参数 | 青空之蓝 浅谈 Proxy 和 Aop | 青空之蓝 为 Vue3 添加一个简单的 Store | 青空之蓝 从零实现一个 PHP 微框架 - 服务提供者 | 青空之蓝 WSL2 踩坑记录 | 青空之蓝 浅谈浏览器Event Loop [更新] | 青空之蓝 从零实现一个 PHP 微框架 - Bootstrap 启动加载 | 青空之蓝 从零实现一个 PHP 微框架 - IoC 容器 | 青空之蓝 从零实现一个 PHP 微框架 - PSR & Composer | 青空之蓝 从零实现一个 PHP 微框架 - 前言 | 青空之蓝 MVVM 简单实现 | 青空之蓝 浅谈 DI 和 IoC | 青空之蓝 中间件实现 [PHP] | 青空之蓝 告别 Windows 终端的难看难用,打造好用的 PowerShell | 青空之蓝 VSCode Java输出中文乱码问题解决[更新] | 青空之蓝 浅谈浏览器渲染 | 青空之蓝 Vue-Cli@2 项目迁移日志 | 青空之蓝 Laragon & Scoop 集成踩坑记录 | 青空之蓝 「一行代码」优雅管理 Windows 软件 | 青空之蓝 [青空之蓝-2019]-年度总结 | 青空之蓝 为Vue添加简单的Store | 青空之蓝 为React添加简单的Store | 青空之蓝 为Vuex添加同步Action | 青空之蓝 浅谈B+树 | 青空之蓝 浅谈跳表 | 青空之蓝 浅谈数据库索引 | 青空之蓝 MySQL事务隔离 | 青空之蓝 算法复杂度分析(1) | 青空之蓝 一年来的经验总结 | 青空之蓝 Acrylic - VSCode Extension | 青空之蓝 ace编辑器设置惯性滚动 | 青空之蓝 Java二叉树实现 | 青空之蓝 Java图实现 | 青空之蓝 为apt方式安装的nginx重新编译增加WebDAV | 青空之蓝 XK-Editor - 一个支持富文本和Markdown的编辑器 | 青空之蓝 JS生成列表树 | 青空之蓝 Laravel生成目录树 | 青空之蓝 XK-Note - 集各种神奇功能的云笔记 | 青空之蓝 PHP GD生成验证码 | 青空之蓝 PHP GD图片处理[转换格式-水印-缩略图] | 青空之蓝 Origami - 简洁轻快的WordPress主题 | 青空之蓝 为WordPress启用WorkBox | 青空之蓝 [青空之蓝-2018]-年度总结 | 青空之蓝 VSCode Java手动导入jar和源码包 | 青空之蓝 Windows IP变化自动发送邮件 | 青空之蓝 C链表实现重制版 | 青空之蓝 C 结构体的定义和使用 | 青空之蓝 图的搜索(遍历) - BFS & DFS | 青空之蓝 Java链表实现 | 青空之蓝 C 快速排序 | 青空之蓝 C 插入排序 | 青空之蓝 C 归并排序 | 青空之蓝 C语言链表实现 | 青空之蓝 VSCode配置Java调试环境[Windows] | 青空之蓝 C 选择排序 | 青空之蓝 C 冒泡排序 | 青空之蓝 VSCode配置PHP调试环境[Windows] | 青空之蓝 VSCode配置C/C++ GDB调试环境[Windows] | 青空之蓝 WordPress友情链接模板 | 青空之蓝 Intel Optane 傲腾内存体验 | 青空之蓝 Mysql双机热备实战 | 青空之蓝 博客一年记录 | 青空之蓝 为WordPress启用Service Worker | 青空之蓝 Bing每日一图API | 青空之蓝 iframe延迟加载 | 青空之蓝 写在2018年高考前 | 青空之蓝 The Fox主题汉化分享 | 青空之蓝 [青空之蓝-2017]-崭新 | 青空之蓝 本博客评论规则 | 青空之蓝 世界,您好! | 青空之蓝
从零实现一个 PHP 微框架 - 初始化请求 | 青空之蓝
Otstar Lin · 2020-07-25 · via 青空之蓝

前言

更新一波文章。

这次的内容相对简单点,初始化请求的过程包括封装 $_GET $_POST 等关联数组到 Request 对象中,用于后续流程的使用,以及从封装 Request 到路由之前的这段过程。

构造 Request

构造 Request 是通过 Application.dispatchToEmit 里的 $request = $this->make(Request::class) 初始化的,make 方法会通知容器初始化 Request 对象。

<?php
protected function dispatchToEmit(): void
{
    // 获取请求
    $request = $this->make(Request::class);

    // 处理
    $response = $this->make(RouteManager::class)->dispatch($request);

    // 发送响应
    $response->send();
}
<?php
protected function dispatchToEmit(): void
{
    // 获取请求
    $request = $this->make(Request::class);

    // 处理
    $response = $this->make(RouteManager::class)->dispatch($request);

    // 发送响应
    $response->send();
}

既然是通过容器来初始化的,那么就需要绑定该对象到容器,Request 对象是通过 RequestProvider 进行绑定的:

<?php

namespace App\Providers;

use App\Http\Request;

class RequestProvider extends Provider
{
    public function register(): void
    {
        $this->app->singleton(
            Request::class,
            function () {
                return Request::make();
            },
            'request'
        );
    }
}
<?php

namespace App\Providers;

use App\Http\Request;

class RequestProvider extends Provider
{
    public function register(): void
    {
        $this->app->singleton(
            Request::class,
            function () {
                return Request::make();
            },
            'request'
        );
    }
}

Request::make

从上面的代码可以看到初始化 Request 是通过 Request::make() 的静态工厂方法构造的:

<?php
public static function make(
    array $server = null,
    array $query = null,
    array $body = null,
    array $cookies = null,
    array $files = null
): Request {
    $files = Functions::convertFiles($files ?: $_FILES);
    $server = $server ?: $_SERVER;
    $uri =
        isset($server['HTTPS']) && $server['HTTPS'] === 'on'
            ? 'https://'
            : 'http://';
    if (isset($server['HTTP_HOST'])) {
        $uri .= $server['HTTP_HOST'];
    } else {
        $uri .=
            $server['SERVER_NAME'] .
            (isset($server['SERVER_PORT']) &&
            $server['SERVER_PORT'] !== '80' &&
            $server['SERVER_PORT'] !== '443'
                ? ':' . $server['SERVER_PORT']
                : '');
    }
    $uri .= $server['REQUEST_URI'];
    $protocol = '1.1';
    if (isset($server['SERVER_PROTOCOL'])) {
        preg_match(
            '|^(HTTP/)?(?P<version>[1-9]\d*(?:\.\d)?)$|',
            $server['SERVER_PROTOCOL'],
            $matches
        );
        $protocol = $matches['version'];
    }
    return new static(
        $server,
        $files,
        $uri,
        $server['REQUEST_METHOD'],
        'php://input',
        Functions::parseHeaders($server),
        $cookies ?: $_COOKIE,
        $query ?: $_GET,
        $body ?: $_POST,
        $protocol
    );
}
<?php
public static function make(
    array $server = null,
    array $query = null,
    array $body = null,
    array $cookies = null,
    array $files = null
): Request {
    $files = Functions::convertFiles($files ?: $_FILES);
    $server = $server ?: $_SERVER;
    $uri =
        isset($server['HTTPS']) && $server['HTTPS'] === 'on'
            ? 'https://'
            : 'http://';
    if (isset($server['HTTP_HOST'])) {
        $uri .= $server['HTTP_HOST'];
    } else {
        $uri .=
            $server['SERVER_NAME'] .
            (isset($server['SERVER_PORT']) &&
            $server['SERVER_PORT'] !== '80' &&
            $server['SERVER_PORT'] !== '443'
                ? ':' . $server['SERVER_PORT']
                : '');
    }
    $uri .= $server['REQUEST_URI'];
    $protocol = '1.1';
    if (isset($server['SERVER_PROTOCOL'])) {
        preg_match(
            '|^(HTTP/)?(?P<version>[1-9]\d*(?:\.\d)?)$|',
            $server['SERVER_PROTOCOL'],
            $matches
        );
        $protocol = $matches['version'];
    }
    return new static(
        $server,
        $files,
        $uri,
        $server['REQUEST_METHOD'],
        'php://input',
        Functions::parseHeaders($server),
        $cookies ?: $_COOKIE,
        $query ?: $_GET,
        $body ?: $_POST,
        $protocol
    );
}

首先是使用 Functions::convertFiles 方法将 $_FILES 关联数组转化到 UploadFile 数组,转化的步骤就不说明了,就是将数组的结构封装到对象(之所以要这么做是为了遵循 PSR 标准)。

<?php
public static function convertFiles(array $files): array
{
    $result = [];
    foreach ($files as $key => $value) {
        if ($value instanceof UploadedFileInterface) {
            $result[$key] = $value;
            continue;
        }
        if (
            is_array($value) &&
            isset($value['tmp_name']) &&
            is_array($value['tmp_name'])
        ) {
            $result[$key] = self::resolveStructure($value);
            continue;
        }
        if (is_array($value) && isset($value['tmp_name'])) {
            $result[$key] = new UploadFile(
                $value['tmp_name'],
                $value['size'],
                $value['error'],
                $value['name'],
                $value['type']
            );
            continue;
        }
        if (is_array($value)) {
            $result[$key] = self::convertFiles($value);
            continue;
        }
    }
    return $result;
}
<?php
public static function convertFiles(array $files): array
{
    $result = [];
    foreach ($files as $key => $value) {
        if ($value instanceof UploadedFileInterface) {
            $result[$key] = $value;
            continue;
        }
        if (
            is_array($value) &&
            isset($value['tmp_name']) &&
            is_array($value['tmp_name'])
        ) {
            $result[$key] = self::resolveStructure($value);
            continue;
        }
        if (is_array($value) && isset($value['tmp_name'])) {
            $result[$key] = new UploadFile(
                $value['tmp_name'],
                $value['size'],
                $value['error'],
                $value['name'],
                $value['type']
            );
            continue;
        }
        if (is_array($value)) {
            $result[$key] = self::convertFiles($value);
            continue;
        }
    }
    return $result;
}

然后是拼接 URL,由于 PHP 已经对 URL 进行切割,所以我们还需要拼接回去,以便后续的代码使用。以及 Protocol 的提取。

由于 PHP 将 Request header 存入了 $_SERVER 为了方便使用,我们需要把 $_SERVER 中带有 HTTP_ 前缀的字段都提取出来,这些就是 Request header,同时由于 header 是不区分大小写的,我们直接把 header 的名称转成小写即可。

<?php
public static function parseHeaders(array $server): array
{
    $headers = [];
    foreach ($server as $key => $value) {
        if (!is_string($key)) {
            continue;
        }
        if ($value === '') {
            continue;
        }
        if (strpos($key, 'HTTP_') === 0) {
            $name = str_replace('_', '-', strtolower(substr($key, 5)));
            $headers[$name] = $value;
            continue;
        }
    }
    return $headers;
}
<?php
public static function parseHeaders(array $server): array
{
    $headers = [];
    foreach ($server as $key => $value) {
        if (!is_string($key)) {
            continue;
        }
        if ($value === '') {
            continue;
        }
        if (strpos($key, 'HTTP_') === 0) {
            $name = str_replace('_', '-', strtolower(substr($key, 5)));
            $headers[$name] = $value;
            continue;
        }
    }
    return $headers;
}

new Request

有了上面的一些基础的信息,就可以正式的创建 Request 对象的:

<?php
public function __construct(
    array $server = [],
    array $files = [],
    $uri = '',
    string $method = 'GET',
    $body = 'php://input',
    array $headers = [],
    array $cookies = [],
    array $query = [],
    $parsed_body = null,
    string $protocol = '1.1'
) {
    $this->validateFiles($files);
    if ($body === 'php://input') {
        $body = new Stream($body);
    }
    $this->setMethod($method);
    if ($uri instanceof UriInterface) {
        $this->uri = $uri;
    } else {
        $this->uri = new Uri($uri);
    }
    if ($body instanceof StreamInterface) {
        $this->stream = $body;
    } else {
        $this->stream = new Stream($body, 'wb+');
    }
    $this->setHeaders($headers);
    $this->server = $server;
    $this->files = $files;
    $this->cookies = $cookies;
    $this->query = $query;
    $this->protocol = $protocol;

    $content_type = $this->header('Content-Type');
    if (
        $content_type !== null &&
        stripos($content_type, 'application/json') !== false
    ) {
        $this->parsed_body = array_merge(
            $parsed_body,
            json_decode($this->stream->getContents(), true)
        );
    } else {
        $this->parsed_body = $parsed_body;
    }

    if (!$this->hasHeader('Host') && $this->uri->getHost()) {
        $host = $this->uri->getHost();
        $host .= $this->uri->getPort() ? ':' . $this->uri->getPort() : '';
        $this->headerAlias['host'] = 'Host';
        $this->headers['Host'] = [$host];
    }
}
<?php
public function __construct(
    array $server = [],
    array $files = [],
    $uri = '',
    string $method = 'GET',
    $body = 'php://input',
    array $headers = [],
    array $cookies = [],
    array $query = [],
    $parsed_body = null,
    string $protocol = '1.1'
) {
    $this->validateFiles($files);
    if ($body === 'php://input') {
        $body = new Stream($body);
    }
    $this->setMethod($method);
    if ($uri instanceof UriInterface) {
        $this->uri = $uri;
    } else {
        $this->uri = new Uri($uri);
    }
    if ($body instanceof StreamInterface) {
        $this->stream = $body;
    } else {
        $this->stream = new Stream($body, 'wb+');
    }
    $this->setHeaders($headers);
    $this->server = $server;
    $this->files = $files;
    $this->cookies = $cookies;
    $this->query = $query;
    $this->protocol = $protocol;

    $content_type = $this->header('Content-Type');
    if (
        $content_type !== null &&
        stripos($content_type, 'application/json') !== false
    ) {
        $this->parsed_body = array_merge(
            $parsed_body,
            json_decode($this->stream->getContents(), true)
        );
    } else {
        $this->parsed_body = $parsed_body;
    }

    if (!$this->hasHeader('Host') && $this->uri->getHost()) {
        $host = $this->uri->getHost();
        $host .= $this->uri->getPort() ? ':' . $this->uri->getPort() : '';
        $this->headerAlias['host'] = 'Host';
        $this->headers['Host'] = [$host];
    }
}

首先需要对 files 进行验证,判断 files 是否实现了 UploadedFileInterface

接着就需要对 Request body 进行封装了,StreamStreamInterface 的实现类,提供了对 body 数据流的一些操作方法。

除了 filebody,我们还需要把 url 封装成 Uri 对象,该对象实现了 UriInterface,提供了对 url 的一些操作方法。

由于请求的方式可能是通过 JSON 的格式传输的,此时 $_POST 就无法获取到这些通过 JSON 传输的数据,所以,我们还需要解析 JSON。

在 PSR 标准中有说明,当请求没有 Host 头的时候,需要手动设置,保证 Request 对象中存在 Host 头。

结语

到这里初始化 Request 的部分就完成了。由于博主忙着重写 XK-Editor,所以更新 文章的速度可能会慢一点 2333。

从零实现一个 PHP 微框架 - 初始化请求

https://blog.ixk.me/post/implement-a-php-microframework-from-zero-6
  • 许可协议

    BY-NC-SA

  • 本文作者

    Otstar Lin

  • 发布于

    2020/07/25

转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!

浅谈 Proxy 和 Aop为 Vue3 添加一个简单的 Store