The purpose of this PSR is to provide factory interfaces that define methods to create PSR-7 objects.
The current specification for PSR-7 allows for most objects to be modified by creating immutable copies. However, there are two notable exceptions:
StreamInterface
is a mutable object based on a resource that only allows
the resource to be written to when the resource is writable.UploadedFileInterface
is a read-only object based on a resource that offers
no modification capabilities.The former is a significant pain point for PSR-7 middleware, as it can leave the response in an incomplete state. If the stream attached to the response body is not seekable or not writable, there is no way to recover from an error condition in which the body has already been written to.
This scenario can be avoided by providing a factory to create new streams. Due to the lack of a formal standard for HTTP object factories, a developer must rely on a specific vendor implementation in order to create these objects.
Another pain point is when writing re-usable middleware or request handlers. In such cases, package authors may need to create and return a response. However, creating discrete instances then ties the package to a specific PSR-7 implementation. If these packages rely on the request factory interface instead, they can remain agnostic of the PSR-7 implementation.
Creating a formal standard for factories will allow developers to avoid dependencies on specific implementations while having the ability to create new objects when necessary.
The factory method definition has been chosen based on whether or not the object can be modified after instantiation. For interfaces that cannot be modified, all of the object properties must be defined at the time of instantiation.
In the case of UriInterface
a complete URI may be passed for convenience.
The method names used will not conflict. This allows for a single class to implement multiple interfaces when appropriate.
All of the current implementations of PSR-7 have defined their own requirements. In most cases, the required parameters are the same or less strict than the proposed factory methods.
Diactoros was one of the first HTTP Messages implementations for server usage, and was developed parallel to the PSR-7 specification.
Request
No required parameters, method and URI default to null
.Response
No required parameters, status code defaults to 200
.ServerRequest
No required parameters. Contains a separate
ServerRequestFactory
for creating requests from globals.Stream
Requires string|resource $stream
for the body.UploadedFile
Requires string|resource $streamOrFile
, int $size
,
int $errorStatus
. Error status must be a PHP upload constant.Uri
No required parameters, string $uri
is empty by default.Overall this approach is quite similar to the proposed factories. In some cases, more options are given by Diactoros which are not required for a valid object. The proposed uploaded file factory allows for size and error status to be optional.
Guzzle is an HTTP Messages implementation that focuses on client usage.
Request
Requires both string $method
and string|UriInterface $uri
.Response
No required parameters, status code defaults to 200
.Stream
Requires resource $stream
for the body.Uri
No required parameters, string $uri
is empty by default.Being geared towards client usage, Guzzle does not contain a ServerRequest
or
UploadedFile
implementation.
Overall this approach is also quite similar to the proposed factories. One notable
difference is that Guzzle requires streams to be constructed with a resource and
does not allow a string. However, it does contain a helper function stream_for
that will create a stream from a string of content and a function try_fopen
that will create a resource from a file path.
Slim is a micro-framework that makes use of HTTP Messages from version 3.0 forward.
Request
Requires string $method
, UriInterface $uri
,
HeadersInterface $headers
, array $cookies
, array $serverParams
, and
StreamInterface $body
. Contains a factory method createFromEnvironment(Environment $environment)
that is framework specific but analogous to the proposed createServerRequestFromArray
.Response
No required parameters, status code defaults to 200
.Stream
Requires resource $stream
for the body.UploadedFile
Requires string $file
for the source file.
Contains a factory method parseUploadedFiles(array $uploadedFiles)
for creating
an array of UploadedFile
instances from $_FILES
or similar format. Also contains
a factory method createFromEnvironment(Environment $env)
that is framework specific
and makes use of parseUploadedFiles
.Uri
Requires string $scheme
and string $host
. Contains a factory
method createFromString($uri)
that can be used to create a Uri
from a string.Being geared towards server usage only, Slim does not contain an implementation
of Request
. The implementation listed above is an implementation of ServerRequest
.
Of the compared approaches, Slim is most different from the proposed factories.
Most notably, the Request
implementation contains requirements specific
to the framework that are not defined in HTTP Messages specification. The factory
methods that are included are generally similar with the proposed factories.
The most difficult task in establishing this standard will be defining the method signatures for the interfaces. As there is no clear declaration in PSR-7 as to what values are explicitly required, the properties that are read-only must be inferred based on whether the interfaces have methods to copy-and-modify the object.
While PSR-7 does not target PHP 7, the authors of this specification note that, at the time of writing (April 2018), PHP 5.6 stopped receiving bugfixes 15 months ago, and will no longer receive security patches in 8 months; PHP 7.0 itself will stop receiving security fixes in 7 months (see the PHP supported versions document for current support details). Since specifications are meant to be long-term, the authors feel the specification should target versions that will be supported for the foreseeable future; PHP 5 will not. As such, from a security standpoint, targeting anything under PHP 7 is a disservice to users, as doing so would be tacit approval of usage of unsupported PHP versions.
Additionally, and equally importantly, PHP 7 gives us the ability to provide return type hints to interfaces we define. This guarantees a strong, predicatable contract for end users, as they can assume that the values returned by implementations will be exactly what they expect.
Each proposed interface is (primarily) responsible for producing one PSR-7 type.
This allows consumers to typehint on exactly what they need: if they need a
response, they typehint on ResponseFactoryInterface
; if they need a URI, they
typehint on UriFactoryInterface
. In this way, users can be granular about what
they need.
Doing so also allows application developers to provide anonymous implementations based on the PSR-7 implementation they are using, producing only the instances they need for the specific context. This reduces boilerplate; developers do not need to write stubs for unused methods.
ResponseFactoryInterface::createResponse()
includes an optional string
argument, $reasonPhrase
. In the PSR-7 specification, you can only provide a
reason phrase at the same time you provide a status code, as the two are related
pieces of data. The authors of this specification have chosen to mimic the PSR-7
ResponseInterface::withStatus()
signature to ensure both sets of data may be
present in the response created.
ServerRequestFactoryInterface::createServerRequest()
includes an optional
$serverParams
array argument. The reason this is provided is to ensure that an
instance can be created with the server params populated. Of the data accessible
via the ServerRequestInterface
, the only data that does not have a mutator
method is the one corresponding to the server params. As such, this data MUST be
provided at initial creation. For this reason, it exists as an argument to the
factory method.
The primary use case of ServerRequestFactoryInterface
is for creating a new
ServerRequestInterface
instance from known data. Any solution around
marshaling data from superglobals assumes that:
These two assumptions are not always true. When using asynchronous systems such as Swoole, ReactPHP, and others:
$_GET
, $_POST
, $_COOKIE
,
and $_FILES
$_SERVER
with the same elements as a standard SAPI (such as
mod_php, mod-cgi, and mod-fpm)Moreover, different standard SAPIs provide different information to $_SERVER
and access to request headers, requiring different approaches for initial
population of the request.
As such, designing an interface for population of an instance from superglobals is out of scope of this specification, and should largely be implementation-specfic.
The primary use case of RequestFactoryInterface
is to create a request, and
the only required values for any request are the request method and a URI. While
RequestFactoryInterface::createRequest()
can accept a UriInterface
instance,
it also allows a string.
The rationale is two-fold. First, the majority use case is to create a request
instance; creation of the URI instance is secondary. Requiring a UriInterface
means users would either need to also have access to a UriFactoryInterface
, or
the RequestFactoryInterface
would have a hard requirement on a
UriFactoryInterface
. The first complicates usage for consumers of the factory,
the second complicates usage for either developers of the factory, or those
creating the factory instance.
Second, UriFactoryInterface
provides exactly one way to create a
UriInterface
instance, and that is from a string URI. If creation of the URI
is based on a string, there's no reason for the RequestFactoryInterface
not to
allow the same semantics. Additionally, every PSR-7 implementation surveyed at
the time this proposal was developed allowed a string URI when creating a
RequestInterface
instance, as the value was then passed to whatever
UriInterface
implementation they provided. As such, accepting a string is
expedient and follows existing semantics.
This PSR was produced by a FIG Working Group with the following members:
The working group would also like to acknowledge the contributions of:
Note: Order descending chronologically.
Prior to PHP 8.4, it was allowed to declare a type accepting null
by omitting
the nullable part of the type if the default value of the property or parameter
was set to null. This implicit type declaration is now deprecated and all
types should be declared explicitly.
This change also requires the minimum PHP version required by this PSR to be updated to 7.1, as nullable types were introduced in this version. Apart from this change, no breaking change is introduced by this update and the behavior of the interfaces remains the same.