PSR 17 HTTP 工厂方法规范 - 说明文档 「起草中」
1. 概览
此 PSR 的目标是定义创建 PSR-7 对象的方法的工厂接口
2. 缘起
目前版本的 PSR-7 允许通过创建不可变副本的方式来修改绝大多数对象
但是,有两个明显的例外情况
-
StreamInterface
是一个基于资源的可变对象,只有当资源可写时才允许写入资源 -
UploadedFileInterface
是一个基于资源的只读对象,不提供任何形式的修改
前者是 PSR-7 中间件的一个重大难题,因为它可能导致响应不完整,但如果连接到响应内容的流不可寻址或者不可写入,那么可能就无法从主体已写入的错误状态中恢复
这个难题可以通过提供工厂方法创建新的流来避免。但由于缺乏 HTTP 对象工厂的正式标准,开发人员必须依靠特定的客户端实现来创建这些对象
另一个难题时是编写可重用的中间件或请求处理程序时,类库作者可能需要创建并返回响应。 但是,创建特定的实例会将该类库与特定的 PSR-7 实现紧密耦合。如果这些类库依赖于一个请求工厂方法,它们就可以不必知道 PSR-7 如何实现
因此,为工厂方法创建正式的标准将允许开发人员避免依赖于特定的实现,同时在必要时能够创建新的对象
3. 内容
3.1 目标
- 提供一组定义用于创建 PSR-7 兼容对象的方法的接口
3.2 非目标
- 不提供任何 PSR-7 工厂方法的特定实现
4. 方式
4.1 选择过程
选择哪种工厂方法定义是根据实例化后对象是否可以修改的情况而定。对于无法修改的接口,必须在实例化时定义所有对象属性
对于 UriInterface
来说,为了方便,可以传递一个完整的 URI
这些方法在使用过程中并不会发生冲突,这就应许一个类可以实现多个接口,如果需要的话
4.2 已存在的实现
当前已经存在的 PSR-7 实现都定义了它们自己的必须参数。 大多数情况下,所需的参数与本提议的工厂方法相同,甚至更不严格。
4.2.1 Diactoros
Diactoros 目前是最流行的用于服务器使用的 HTTP 消息实现
-
没有必须参数,方法和 URI 的默认值都是
null
-
没有必须参数,状态码的默认值为
200
-
没有必须参数,包含了一个单独的方法
ServerRequestFactory
用于从全局创建请求 -
必须参数为
string|resource $stream
用于消息正文
-
必须参数为
string|resource $streamOrFile
int $size
int $errorStatus
错误状态必须是 PHP 上传常量
-
没有必须参数,
string $uri
的默认值为空
总的来说,这种方式也与本提案的工厂方法类似。 某些情况下,Diactoros 提供了更多的选项,这对于有效的对象不是必需的。
与本规范的不同之处是,本规范中的上传的文件工厂允许大小和错误状态是可选的,而 Diactoros 是必选的
4.2.2 Guzzle
Guzzle 是目前最流行的实现了 HTTP 消息的客户端
-
必须参数有
string $method
string|UriInterface $uri
-
没有必须参数,状态码的默认值为
200
-
body 有一个必须的参数
resource $stream
-
没有必须参数,
string $uri
的默认值为空
因为面向客户端使用, Guzzle 不包含 ServerRequest
和 UploadedFile
实现
总的来说,这种方式也与本提案的工厂方法类似。
一个明显的区别是:Guzzle 需要使用资源来构建流,且不允许使用字符串。
但是,它包含了一个辅助函数 stream_for
,用于从一个字符串内容中创建一个流,并且提供了一个函数 try_fopen
从文件路径创建一个资源
4.2.3 Slim
Slim 是一个流行的微型框架,从版本 3.0 起开始使用 HTTP 消息
-
必须参数
string $method
,UriInterface $uri
,HeadersInterface $headers
,array $cookies
,array $serverParams
,StreamInterface $body
包含一个工厂方法
createFromEnvironment(Environment $environment)
此框架特有的,且与本规范类似的方法
createServerRequestFromArray
-
没有包含任何必须参数、状态码的默认值是
200
-
body 中有一个必须参数
resource $stream
-
必须参数
string $file
源文件
包含一个工厂方法
-
parseUploadedFiles(array $uploadedFiles)
用于从
$_FILES
或相似格式中创建一组UploadedFile
实例
同时也包含另一个工厂方法
-
createFromEnvironment(Environment $env)
这是框架所特有的方法,且调用了
parseUploadedFiles
-
必须参数有:
string $scheme
string $host
包行一个工厂方法
-
createFromString($uri)
可以用来从一个字符串中创建
Uri
Slim 仅针对服务端使用,不包含 Request
的实现。上面列出的实现是 ServerRequest
的一个实现
所有比较的方式中,Slim 与本提议的工厂最为不同。
最值得注意的是,Request
实现包含了框架独有的且 HTTP 消息规范中未定义的必须参数
Slim 包含的工厂方法普遍和本提议的工厂方法类似
4.3 潜在问题
最困难的部分是定义接口的方法签名。 由于 PSR-7 没有清晰的声明确切需要哪些值,因此必须根据接口是否具有复制和修改对象的方法来推断那些只读的属性
5. 设计决策
5.1 为什么是 PHP 7?
虽然 PSR-7 没有针对 PHP 7,但本规范的作者注意到,在编写本文时 ( 2018 年 4 月 ) ,PHP 5.6 在 15 个月前停止接收错误修正,并且将在 8 个月内不再接收安全修补程序;
PHP 7.0 本身将在 7 个月内停止接收安全修复程序( 有关当前支持详细信息,请参阅 PHP 支持的版本文档)
由于规范是长期的,作者认为规范应针对在可预见的未来将支持的版本; PHP 5 显然不是
因此,从安全角度来看,针对 PHP 7 以下版本的任何内容对用户都是不利的,因为这样做会默认使用不支持的 PHP 版本
此外,同样重要的是,PHP 7 中,我们可以为定义的接口提供返回类型提示。 这位最终用户提供了一个强大的、可预测的合约,因此他们可以认为实现返回的值将与他们期望的完全一致
5.2 为什么多个接口 ?
此建议的每个接口主要负责生产一个 PSR-7 类型,这样消费者可以精确地输入他们需要的内容:
- 如果他们需要响应,可以输入
ResponseFactoryInterface
- 如果他们需要一个 URI,可以输入
UriFactoryInterface
通过这种方式,用户可以细化他们需要的东西
这样做也允许应用程序开发人员提供基于他们正在使用的 PSR-7 实现的匿名实现,仅生成他们在特定上下文中所需的实例。 这减少了样板; 开发人员不需要为未使用的方法编写存根
5.3 为什么 ResponseFactoryInterface 接口需要 $reasonPhrase 参数 ?
ResponseFactoryInterface::createResponse()
接口包含了一个可选的字符串参数 $reasonPhrase
。
在 PSR-7 规范中, 你只能在提供状态码的同时提供一个描述短语,因为这两个数据是相关的。
本规范的作者选择模仿 PSR-7 的 ResponseInterface::withStatus()
签名来确保在创建的响应中可能存在两组数据
5.4 为什么 ServerRequestFactoryInterface 接口有 $serverParams 参数?
ServerRequestFactoryInterface::createServerRequest()
接口包含了一个可选的数组参数 $serverParams
。
这个参数存在的原因是可以在创建实例时填充服务器参数。
可通过 ServerRequestInterface
接口访问的数据中,唯一缺失的是对与服务器参数对应的数据的访问方法。
因此,这些数据必须在初始创建时就提供,这也是这个参数存在的原因
5.5 为什么没有一个工厂方法来创建 ServerRequestInterface 对象
ServerRequestFactoryInterface
的主要用例是用于从已知数据创建一个新的 ServerRequestInterface
实例
任何围绕超全局变量封装数据的解决方案都假定:
- 超全局变量存在
- 超全局变量遵循一定的结构
这两个假设并非总是如此,例如使用 Swoole,ReactPHP 等异步系统时:
- 不填充标准的超全局变量,例如
$_GET
,$_POST
,$_COOKIE
, 和$_FILES
- 不使用与标准 SAPI 相同的元素填充
$ _SERVER
( 例如 mod_php,mod-cgi 和 mod-fpm ) 来,
此外,不同标准的 SAPI 向 $_SERVER
提供的消息也不尽相同,何况,访问请求头部信息, 需要不同的方式来满足最初的请求
因此,设计一个从超全局变量中获取数据的实例的接口不在本规范的范围内,并且主要应该让实现去定制
6. 参与人员
此 PSR 由 FIG 工作中的下列人员创建
- Woody Gilk (editor), woody.gilk@gmail.com
- Matthew Weier O'Phinney (sponsor), mweierophinney@gmail.com
- Stefano Torresi
- Matthieu Napoli
- Korvin Szanto
- Glenn Eggleton
- Oscar Otero
- Tobias Nyholm
全体工作组还要感谢下列人员的帮助:
- Paul M. Jones, pmjones88@gmail.com
- Rasmus Schultz, rasmus@mindplay.dk
- Roman Tsjupa, draconyster@gmail.com
7. 表决
- Entrance Vote
- Working Group Formation
- Review Period Initiation (not yet begun)
- Acceptance Vote (not yet taken)
8. 相关文章
注意: 顺序是按照时间顺序来排列的
- PSR-7 Middleware Proposal
- PHP-FIG mailing list discussion of middleware
- ircmaxwell All About Middleware
- shadowhand All About PSR-7 Middleware
- AndrewCarterUK PSR-7 Objects Are Not Immutable
- shadowhand Dependency Inversion and PSR-7 Bodies
- PHP-FIG mailing list thread discussing factories
- PHP-FIG mailing list thread feedback on proposal