This package provides interoperable interfaces for encapsulating readable server-side request values in PHP 8.4 or later, in order to reduce the global mutable state problems that exist with PHP superglobals. It reflects and refines the common practices of over a dozen different userland projects.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (RFC 2119, RFC 8174).
This package defines the following interfaces:
- Request to represent the incoming request.
- Upload to represent an uploaded file.
- Url to represent the request URL.
- Body to represent mutable request or upload body content.
- Factory to create instances of the above.
Notes:
-
The interfaces define readable properties, not getter methods. PHP superglobals are presented as variables and not as functions; using properties instead of methods maintains symmetry with the language. In addition, using things like array access and null-coalesce against a property looks more usually idiomatic in PHP than with a getter method; it is the difference between
$request->query['foo'] ?? 'bar'
and$request->getQuery()['foo'] ?? 'bar'
or$request->query->get('foo', 'bar')
. -
The interfaces define property hooks for
get
but notset
. The interfaces only guarantee readability; writability is outside the scope of this package.
The Request interface represents copies of the PHP superglobals (or their equivalents) and values derived from them. It defines these properties:
-
CookiesArray $cookies { get; }
corresponds to a copy of the$_COOKIES
superglobal array or its equivalent. -
FilesArray $files { get; }
corresponds to a copy of the$_FILES
superglobal array or its equivalent. -
HeadersArray $headers { get; }
corresponds to an array of the request headers, usually derived from$_SERVER
or its equivalent. Each array key MUST be the header field name in lower-kebab-case. -
InputArray $input { get; }
corresponds to an array of the request body values, usually a copy of the$_POST
superglobal array or its equivalent (such as a parsed or decoded representation of the request body). -
MethodString $method { get; }
corresponds to the request method, usually derived from$_SERVER
or its equivalent. -
QueryArray $query { get; }
corresponds to an array of the request query values, usually a copy of$_GET
or its equivalent. -
ServerArray $server { get; }
corresponds to a copy of the$_SERVER
superglobal array or its equivalent. -
UploadsArray $uploads { get; }
is an array of Upload instances, usually derived from$_FILES
or its equivalent. The$uploads
index structure MUST correspond to the structure in which the uploaded files were indexed; cf. README-UPLOADS.md. -
Url $url { get; }
is a Url instance corresponding to this request, usually derived from$_SERVER
or its equivalent.
It also provides these custom PHPStan types to aid static analysis:
-
CookiesArray
:array<string, string>
-
FilesArray
:mixed[]
-- Implementations MUST honor thismixed[]
type as the recursive pseudo-typearray<array-key, FilesArrayGroup|FilesArrayItem|FilesArray>
. -
FilesArrayGroup
:array{ tmp_name:string[], error:int[], name?:string[], full_path?:string[], type?:string[], size?:int[], }
-
FilesArrayItem
:array{ tmp_name:string, error:int, name?:string, full_path?:string, type?:string, size?:int, }
-
HeadersArray
:array<lowercase-string, string>
-
InputArray
:mixed[]
-- Implementations MUST honor thismixed[]
type as the recursive pseudo-type<array-key, null|scalar|InputArray>
. -
MethodString
:uppercase-string
-
QueryArray
:mixed[]
-- Implementations MUST honor thismixed[]
type as the recursive pseudo-type<array-key, string|QueryArray>
. -
ServerArray
:array<string, string>
-
UploadsArray
:mixed[]
-- Implementations MUST honor thismixed[]
type as the recursive pseudo-typearray<array-key, Upload|UploadsArray>
.
Notes:
-
The
$method
property is a string and not a Method interface. Usually the reason for a Method interface is to defineis(string $method) : bool
to make sure the comparison values use matching cases. However, the customMethodString
type isuppercase-string
, which means static analysis should catch mismatched casing. -
The
FilesArray
,InputArray
,QueryArray
, andUploadsArray
types aremixed[]
only because they are recursive. Currently, static analysis tools such as PHPStan cannot process recursive types. Implementations MUST honor thesemixed[]
types as the more strict, but not analyzable, recursive pseudo-type provided with their respective type descriptions. -
The
QueryArray
type allows onlystring
, whileInputArray
allows anyscalar
. TheQueryArray
values correspond to$_GET
, which is composed only of strings. However,InputArray
corresponds to any parsed or decoded form of the request content body; different parsing strategies, such asjson_decode()
, may return various scalar types. -
The
ServerArray
type isarray<string, string>
and notarray<uppercase-string, string>
. Some servers add$_SERVER
keys in mixed case. For example, Microsoft IIS addsIIS_WasUrlRewritten
.
The Url interface represents the URL of the request. It defines these properties and methods:
-
?string $scheme { get; }
corresponds to thescheme
key fromparse_url()
. -
?string $host { get; }
corresponds to thehost
key fromparse_url()
. -
?int $port { get; }
corresponds to theport
key fromparse_url()
. -
?string $user { get; }
corresponds to theuser
key fromparse_url()
. -
?string $pass { get; }
corresponds to thepass
key fromparse_url()
. -
?string $path { get; }
corresponds to thepath
key fromparse_url()
. -
?string $query { get; }
corresponds to thequery
key fromparse_url()
. -
?string $fragment { get; }
corresponds to thefragment
key fromparse_url()
. -
__toString() : string
returns the full URL as a string.
It also provides this custom PHPStan type to aid static analysis:
-
UrlArray
:array{ scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:?string, query:?string, fragment:?string }
Notes:
-
This is a Url interface, not a Uri interface. This is because the protocol (i.e., the
$scheme
) is intended to be included in the properties. Cf. The Real Difference Between a URL and a URI: "A URL is a more specific version of a URI, so if the protocol is given or implied you should probably use URL." -
The Url properties, and the
UrlArray
elements, are taken from theparse_url()
array structure.
The Upload interface represents a single uploaded file. It defines these properties and methods:
-
string $tmpName { get; }
corresponds to the'tmp_name'
key in aFilesArrayItem
(usually from$_FILES
). -
int $error { get; }
corresponds to the'error'
key in aFilesArrayItem
(usually from$_FILES
). -
?string $name { get; }
corresponds to the'name'
key in aFilesArrayItem
(usually from$_FILES
). -
?string $fullPath { get; }
corresponds to the'full_path'
key in aFilesArrayItem
(usually from$_FILES
). -
?string $type { get; }
corresponds to the'type'
key in aFilesArrayItem
(usually from$_FILES
). -
?int $size { get; }
corresponds to the'size'
key in aFilesArrayItem
(usually from$_FILES
). -
move(string $to) : bool
moves the uploaded file to another location, usually viamove_uploaded_file()
.
(Cf. https://www.php.net/manual/en/features.file-upload.post-method.php.)
The Body interface represents the raw content of a Request or an Upload. It defines these properties and methods:
-
?BodyResource $body { get; }
is a stream resource of the raw content. For a Request, this SHOULD refer tophp://input
but MAY refer to some other stream, whereas for an Upload it SHOULD refer to the$tmpName
property but MAY refer to some other stream. -
__toString() : string
MUST return the entire$body
resource as a string.
It also provides this custom PHPStan type to aid static analysis:
BodyResource
:resource
of type (stream)
Implementations of Body MUST NOT be advertised as readonly or immutable. Thus, any implementation of Request or Upload that also implements Body MUST NOT be advertised as readonly or immutable.
The Body interface MAY be implemented independently from a Request or Upload.
Notes:
-
The Body interface is separated from the other interfaces. Whereas readonly or immutable Request and Upload objects can be implemented easily, readonly and immutability on a stream resource is (practically speaking) so difficult to achieve as to be impossible. Thus, implementors who want a truly readonly or immutable Request or Upload can do so, though without access to the Body as a resource. Implementors who need access to a Body can implement it as part of a mutable Request or Upload. Alternatively, it can be an independent mutable Body alongside (but separate from) a readonly or immutable Request or Upload.
-
The
$body
resource might be manipulated externally. As with any stream resource, the state of the$body
resource is mutable. Consumers might modify it, close it, leave the pointer in an unexpected location, and so on. This is why Body implementations must not be advertised as readonly or immutable.
The Factory interface defines the following methods.
-
newRequest()
returns a new Request instance:/** * @param ?CookiesArray $cookies * @param ?FilesArray $files * @param ?HeadersArray $headers * @param ?InputArray $input * @param ?InputArray $input * @param ?MethodString $method * @param ?QueryArray $query * @param ?ServerArray $server * @param ?UploadsArray $uploads * @param ?BodyResource $body * @return Request|(Request&Body) */ public function newRequest( ?array $cookies = null, ?array $files = null, ?array $headers = null, ?array $input = null, ?string $method = null, ?array $query = null, ?array $server = null, ?array $uploads = null, ?Url $url = null, mixed $body = null, ) : Request;
-
newUpload()
returns a new Upload instance:/** * @param ?BodyResource $body * @return Upload|(Upload&Body) */ public function newUpload( string $tmpName, int $error, ?string $name = null, ?string $fullPath = null, ?string $type = null, ?int $size = null, mixed $body = null, ) : Upload;
-
newUrl()
returns a new Url instance:public function newUrl( ?string $scheme = null, ?string $host = null, ?int $port = null, ?string $user = null, ?string $pass = null, ?string $path = null, ?string $query = null, ?string $fragment = null, ) : Url;
-
newBody()
returns a new independent Body instance:/** * @param BodyResource $body */ public function newBody(mixed $body) : Body;
Implementations otherwise advertised as readonly or immutable SHOULD throw a BadMethodCallException for this method, but MAY return an independent Body implementation advertised as mutable.
Notes:
-
All
newRequest()
andnewUrl()
arguments are optional. The arguments are intended to override whatever defaults the implementation may provide; i.e., providing no arguments SHOULD return the default implementation object, such as one created from the superglobals. -
The first two
newUpload()
arguments are required. An Upload MUST have at least a$tmpName
and an$error
code; all other values are optional. -
The
newBody()
method MUST NOT return an implementation advertised as readonly or immutable. Whereas readonly or immutable implementations of Request and Upload are not allowed to implement Body, a separate Body implementation is allowed, so long as it is advertised as mutable. Thus, factories for otherwise readonly or immutable implementations are allowed to return an independent mutable Body implementation. -
The
newBody()
method$body
parameter is not nullable. An independent Body implementation is not expected to have a default resource to draw from.
Implementations advertised as readonly or immutable MUST be deeply readonly or immutable; they MUST NOT encapsulate any references, resources, mutable objects, objects or arrays encapsulating references or resources or mutable objects, and so on.
Implementations MAY contain additional properties and methods not defined in these interfaces; implementations advertised as readonly or immutable MUST make those additional elements deeply readonly or immutable.
Notes:
-
Reflection does not invalidate advertisements of readonly or immutable implementations. The ability of a consumer to use Reflection to mutate an implementation advertised as readonly or immutable does not constitute a failure to comply with RequestInterop.
-
Reference implementations may be found at https://github.com/request-interop/impl.
The pre-PSR-7 versions of Aura, Cake, Code Igniter, Horde, Joomla, Klein, Lithium, MediaWiki, Nette, Phalcon, Symfony, Yaf, Yii, and Zend. See this project comparison for more information.
In short:
-
ServerRequestInterface attempts to model the incoming HTTP request message, plus application-specific context, with shallow and inconsistent immutability requirements.
-
RequestInterop attempts to model the PHP superglobals, provides no space for application context, and requires that readonly or immutable implementations to be deeply so.
A longer answer is at README-PSR-7.md.
How is RequestInterop different from the Server-Side Request and Response Objects RFC?
This package is an intellectual descendant of that RFC, similar in form but much reduced in scope: only the superglobal-equivalent arrays, the method string, the URL, and the uploads array properties remain. (Notably, the URL array is now a Url interface.)