diff --git a/accepted/PSR-7-http-message-meta.md b/accepted/PSR-7-http-message-meta.md new file mode 100644 index 000000000..c7168cd12 --- /dev/null +++ b/accepted/PSR-7-http-message-meta.md @@ -0,0 +1,649 @@ +# HTTP Message Meta Document + +## 1. Summary + +The purpose of this proposal is to provide a set of common interfaces for HTTP +messages as described in [RFC 7230](http://tools.ietf.org/html/rfc7230) and +[RFC 7231](http://tools.ietf.org/html/rfc7231), and URIs as described in +[RFC 3986](http://tools.ietf.org/html/rfc3986) (in the context of HTTP messages). + +- RFC 7230: http://www.ietf.org/rfc/rfc7230.txt +- RFC 7231: http://www.ietf.org/rfc/rfc7231.txt +- RFC 3986: http://www.ietf.org/rfc/rfc3986.txt + +All HTTP messages consist of the HTTP protocol version being used, headers, and +a message body. A _Request_ builds on the message to include the HTTP method +used to make the request, and the URI to which the request is made. A +_Response_ includes the HTTP status code and reason phrase. + +In PHP, HTTP messages are used in two contexts: + +- To send an HTTP request, via the `ext/curl` extension, PHP's native stream + layer, etc., and process the received HTTP response. In other words, HTTP + messages are used when using PHP as an _HTTP client_. +- To process an incoming HTTP request to the server, and return an HTTP response + to the client making the request. PHP can use HTTP messages when used as a + _server-side application_ to fulfill HTTP requests. + +This proposal presents an API for fully describing all parts of the various +HTTP messages within PHP. + +## 2. HTTP Messages in PHP + +PHP does not have built-in support for HTTP messages. + +### Client-side HTTP support + +PHP supports sending HTTP requests via several mechanisms: + +- [PHP streams](http://php.net/streams) +- The [cURL extension](http://php.net/curl) +- [ext/http](http://php.net/http) (v2 also attempts to address server-side support) + +PHP streams are the most convenient and ubiquitous way to send HTTP requests, +but pose a number of limitations with regards to properly configuring SSL +support, and provide a cumbersome interface around setting things such as +headers. cURL provides a complete and expanded feature-set, but, as it is not a +default extension, is often not present. The http extension suffers from the +same problem as cURL, as well as the fact that it has traditionally had far +fewer examples of usage. + +Most modern HTTP client libraries tend to abstract the implementation, to +ensure they can work on whatever environment they are executed on, and across +any of the above layers. + +### Server-side HTTP Support + +PHP uses Server APIs (SAPI) to interpret incoming HTTP requests, marshal input, +and pass off handling to scripts. The original SAPI design mirrored [Common +Gateway Interface](http://www.w3.org/CGI/), which would marshal request data +and push it into environment variables before passing delegation to a script; +the script would then pull from the environment variables in order to process +the request and return a response. + +PHP's SAPI design abstracts common input sources such as cookies, query string +arguments, and url-encoded POST content via superglobals (`$_COOKIE`, `$_GET`, +and `$_POST`, respectively), providing a layer of convenience for web developers. + +On the response side of the equation, PHP was originally developed as a +templating language, and allows intermixing HTML and PHP; any HTML portions of +a file are immediately flushed to the output buffer. Modern applications and +frameworks eschew this practice, as it can lead to issues with +regards to emitting a status line and/or response headers; they tend to +aggregate all headers and content, and emit them at once when all other +application processing is complete. Special care needs to be paid to ensure +that error reporting and other actions that send content to the output buffer +do not flush the output buffer. + +## 3. Why Bother? + +HTTP messages are used in a wide number of PHP projects -- both clients and +servers. In each case, we observe one or more of the following patterns or +situations: + +1. Projects use PHP's superglobals directly. +2. Projects will create implementations from scratch. +3. Projects may require a specific HTTP client/server library that provides + HTTP message implementations. +4. Projects may create adapters for common HTTP message implementations. + +As examples: + +1. Just about any application that began development before the rise of + frameworks, which includes a number of very popular CMS, forum, and shopping + cart systems, have historically used superglobals. +2. Frameworks such as Symfony and Zend Framework each define HTTP components + that form the basis of their MVC layers; even small, single-purpose + libraries such as oauth2-server-php provide and require their own HTTP + request/response implementations. Guzzle, Buzz, and other HTTP client + implementations each create their own HTTP message implementations as well. +3. Projects such as Silex, Stack, and Drupal 8 have hard dependencies on + Symfony's HTTP kernel. Any SDK built on Guzzle has a hard requirement on + Guzzle's HTTP message implementations. +4. Projects such as Geocoder create redundant [adapters for common + libraries](https://github.com/geocoder-php/Geocoder/tree/6a729c6869f55ad55ae641c74ac9ce7731635e6e/src/Geocoder/HttpAdapter). + +Direct usage of superglobals has a number of concerns. First, these are +mutable, which makes it possible for libraries and code to alter the values, +and thus alter state for the application. Additionally, superglobals make unit +and integration testing difficult and brittle, leading to code quality +degradation. + +In the current ecosystem of frameworks that implement HTTP message abstractions, +the net result is that projects are not capable of interoperability or +cross-pollination. In order to consume code targeting one framework from +another, the first order of business is building a bridge layer between the +HTTP message implementations. On the client-side, if a particular library does +not have an adapter you can utilize, you need to bridge the request/response +pairs if you wish to use an adapter from another library. + +Finally, when it comes to server-side responses, PHP gets in its own way: any +content emitted before a call to `header()` will result in that call becoming a +no-op; depending on error reporting settings, this can often mean headers +and/or response status are not correctly sent. One way to work around this is +to use PHP's output buffering features, but nesting of output buffers can +become problematic and difficult to debug. Frameworks and applications thus +tend to create response abstractions for aggregating headers and content that +can be emitted at once - and these abstractions are often incompatible. + +Thus, the goal of this proposal is to abstract both client- and server-side +request and response interfaces in order to promote interoperability between +projects. If projects implement these interfaces, a reasonable level of +compatibility may be assumed when adopting code from different libraries. + +It should be noted that the goal of this proposal is not to obsolete the +current interfaces utilized by existing PHP libraries. This proposal is aimed +at interoperability between PHP packages for the purpose of describing HTTP +messages. + +## 4. Scope + +### 4.1 Goals + +* Provide the interfaces needed for describing HTTP messages. +* Focus on practical applications and usability. +* Define the interfaces to model all elements of the HTTP message and URI + specifications. +* Ensure that the API does not impose arbitrary limits on HTTP messages. For + example, some HTTP message bodies can be too large to store in memory, so we + must account for this. +* Provide useful abstractions both for handling incoming requests for + server-side applications and for sending outgoing requests in HTTP clients. + +### 4.2 Non-Goals + +* This proposal does not expect all HTTP client libraries or server-side + frameworks to change their interfaces to conform. It is strictly meant for + interoperability. +* While everyone's perception of what is and is not an implementation detail + varies, this proposal should not impose implementation details. As + RFCs 7230, 7231, and 3986 do not force any particular implementation, + there will be a certain amount of invention needed to describe HTTP message + interfaces in PHP. + +## 5. Design Decisions + +### Message design + +The `MessageInterface` provides accessors for the elements common to all HTTP +messages, whether they are for requests or responses. These elements include: + +- HTTP protocol version (e.g., "1.0", "1.1") +- HTTP headers +- HTTP message body + +More specific interfaces are used to describe requests and responses, and more +specifically the context of each (client- vs. server-side). These divisions are +partly inspired by existing PHP usage, but also by other languages such as +Ruby's [Rack](https://rack.github.io), +Python's [WSGI](https://www.python.org/dev/peps/pep-0333/), +Go's [http package](http://golang.org/pkg/net/http/), +Node's [http module](http://nodejs.org/api/http.html), etc. + +### Why are there header methods on messages rather than in a header bag? + +The message itself is a container for the headers (as well as the other message +properties). How these are represented internally is an implementation detail, +but uniform access to headers is a responsibility of the message. + +### Why are URIs represented as objects? + +URIs are values, with identity defined by the value, and thus should be modeled +as value objects. + +Additionally, URIs contain a variety of segments which may be accessed many +times in a given request -- and which would require parsing the URI in order to +determine (e.g., via `parse_url()`). Modeling URIs as value objects allows +parsing once only, and simplifies access to individual segments. It also +provides convenience in client applications by allowing users to create new +instances of a base URI instance with only the segments that change (e.g., +updating the path only). + +### Why does the request interface have methods for dealing with the request-target AND compose a URI? + +RFC 7230 details the request line as containing a "request-target". Of the four +forms of request-target, only one is a URI compliant with RFC 3986; the most +common form used is origin-form, which represents the URI without the +scheme or authority information. Moreover, since all forms are valid for +purposes of requests, the proposal must accommodate each. + +`RequestInterface` thus has methods relating to the request-target. By default, +it will use the composed URI to present an origin-form request-target, and, in +the absence of a URI instance, return the string "/". Another method, +`withRequestTarget()`, allows specifying an instance with a specific +request-target, allowing users to create requests that use one of the other +valid request-target forms. + +The URI is kept as a discrete member of the request for a variety of reasons. +For both clients and servers, knowledge of the absolute URI is typically +required. In the case of clients, the URI, and specifically the scheme and +authority details, is needed in order to make the actual TCP connection. For +server-side applications, the full URI is often required in order to validate +the request or to route to an appropriate handler. + +### Why value objects? + +The proposal models messages and URIs as [value objects](http://en.wikipedia.org/wiki/Value_object). + +Messages are values where the identity is the aggregate of all parts of the +message; a change to any aspect of the message is essentially a new message. +This is the very definition of a value object. The practice by which changes +result in a new instance is termed [immutability](http://en.wikipedia.org/wiki/Immutable_object), +and is a feature designed to ensure the integrity of a given value. + +The proposal also recognizes that most clients and server-side +applications will need to be able to easily update message aspects, and, as +such, provides interface methods that will create new message instances with +the updates. These are generally prefixed with the verbiage `with` or +`without`. + +Value objects provides several benefits when modeling HTTP messages: + +- Changes in URI state cannot alter the request composing the URI instance. +- Changes in headers cannot alter the message composing them. + +In essence, modeling HTTP messages as value objects ensures the integrity of +the message state, and prevents the need for bi-directional dependencies, which +can often go out-of-sync or lead to debugging or performance issues. + +For HTTP clients, they allow consumers to build a base request with data such +as the base URI and required headers, without needing to build a brand new +request or reset request state for each message the client sends: + +```php +$uri = new Uri('http://api.example.com'); +$baseRequest = new Request($uri, null, [ + 'Authorization' => 'Bearer ' . $token, + 'Accept' => 'application/json', +]);; + +$request = $baseRequest->withUri($uri->withPath('/user'))->withMethod('GET'); +$response = $client->send($request); + +// get user id from $response + +$body = new StringStream(json_encode(['tasks' => [ + 'Code', + 'Coffee', +]]));; +$request = $baseRequest + ->withUri($uri->withPath('/tasks/user/' . $userId)) + ->withMethod('POST') + ->withHeader('Content-Type', 'application/json') + ->withBody($body); +$response = $client->send($request) + +// No need to overwrite headers or body! +$request = $baseRequest->withUri($uri->withPath('/tasks'))->withMethod('GET'); +$response = $client->send($request); +``` + +On the server-side, developers will need to: + +- Deserialize the request message body. +- Decrypt HTTP cookies. +- Write to the response. + +These operations can be accomplished with value objects as well, with a number +of benefits: + +- The original request state can be stored for retrieval by any consumer. +- A default response state can be created with default headers and/or message body. + +Most popular PHP frameworks have fully mutable HTTP messages today. The main +changes necessary in consuming true value objects are: + +- Instead of calling setter methods or setting public properties, mutator + methods will be called, and the result assigned. +- Developers must notify the application on a change in state. + +As an example, in Zend Framework 2, instead of the following: + +```php +function (MvcEvent $e) +{ + $response = $e->getResponse(); + $response->setHeaderLine('x-foo', 'bar'); +} +``` + +one would now write: + +```php +function (MvcEvent $e) +{ + $response = $e->getResponse(); + $e->setResponse( + $response->withHeader('x-foo', 'bar') + ); +} +``` + +The above combines assignment and notification in a single call. + +This practice has a side benefit of making explicit any changes to application +state being made. + +### New instances vs returning $this + +One observation made on the various `with*()` methods is that they can likely +safely `return $this;` if the argument presented will not result in a change in +the value. One rationale for doing so is performance (as this will not result in +a cloning operation). + +The various interfaces have been written with verbiage indicating that +immutability MUST be preserved, but only indicate that "an instance" must be +returned containing the new state. Since instances that represent the same value +are considered equal, returning `$this` is functionally equivalent, and thus +allowed. + +### Using streams instead of X + +`MessageInterface` uses a body value that must implement `StreamableInterface`. This +design decision was made so that developers can send and receive (and/or receive +and send) HTTP messages that contain more data than can practically be stored in +memory while still allowing the convenience of interacting with message bodies +as a string. While PHP provides a stream abstraction by way of stream wrappers, +stream resources can be cumbersome to work with: stream resources can only be +cast to a string using `stream_get_contents()` or manually reading the remainder +of a string. Adding custom behavior to a stream as it is consumed or populated +requires registering a stream filter; however, stream filters can only be added +to a stream after the filter is registered with PHP (i.e., there is no stream +filter autoloading mechanism). + +The use of a well- defined stream interface allows for the potential of +flexible stream decorators that can be added to a request or response +pre-flight to enable things like encryption, compression, ensuring that the +number of bytes downloaded reflects the number of bytes reported in the +`Content-Length` of a response, etc. Decorating streams is a well-established +[pattern in the Java](http://docs.oracle.com/javase/7/docs/api/java/io/package-tree.html) +and [Node](http://nodejs.org/api/stream.html#stream_class_stream_transform_1) +communities that allows for very flexible streams. + +The majority of the `StreamableInterface` API is based on +[Python's io module](http://docs.python.org/3.1/library/io.html), which provides +a practical and consumable API. Instead of implementing stream +capabilities using something like a `WritableStreamInterface` and +`ReadableStreamInterface`, the capabilities of a stream are provided by methods +like `isReadable()`, `isWritable()`, etc. This approach is used by Python, +[C#, C++](http://msdn.microsoft.com/en-us/library/system.io.stream.aspx), +[Ruby](http://www.ruby-doc.org/core-2.0.0/IO.html), +[Node](http://nodejs.org/api/stream.html), and likely others. + +#### What if I just want to return a file? + +In some cases, you may want to return a file from the filesystem. The typical +way to do this in PHP is one of the following: + +```php +readfile($filename); + +stream_copy_to_stream(fopen($filename, 'r'), fopen('php://output', 'w')); +``` + +Note that the above omits sending appropriate `Content-Type` and +`Content-Length` headers; the developer would need to emit these prior to +calling the above code. + +The equivalent using HTTP messages would be to use a `StreamableInterface` +implementation that accepts a filename and/or stream resource, and to provide +this to the response instance. A complete example, including setting appropriate +headers: + +```php +// where Stream is a concrete StreamableInterface: +$stream = new Stream($filename); +$finfo = new finfo(FILEINFO_MIME); +$response = $response + ->withHeader('Content-Type', $finfo->file($filename)) + ->withHeader('Content-Length', (string) filesize($filename)) + ->withBody($stream); +``` + +Emitting this response will send the file to the client. + +#### What if I want to directly emit output? + +Directly emitting output (e.g. via `echo`, `printf`, or writing to the +`php://output` stream) is generally only advisable as a performance optimization +or when emitting large data sets. If it needs to be done and you still wish +to work in an HTTP message paradigm, one approach would be to use a +callback-based `StreamableInterface` implementation, per [this +example](https://github.com/phly/psr7examples#direct-output). Wrap any code +emitting output directly in a callback, pass that to an appropriate +`StreamableInterface` implementation, and provide it to the message body: + +```php +$output = new CallbackStream(function () use ($request) { + printf("The requested URI was: %s
\n", $request->getUri()); + return ''; +}); +return (new Response()) + ->withHeader('Content-Type', 'text/html') + ->withBody($output); +``` + +#### What if I want to use an iterator for content? + +Ruby's Rack implementation uses an iterator-based approach for server-side +response message bodies. This can be emulated using an HTTP message paradigm via +an iterator-backed `StreamableInterface` approach, as [detailed in the +psr7examples repository](https://github.com/phly/psr7examples#iterators-and-generators). + +### Why are streams mutable? + +The `StreamableInterface` API includes methods such as `write()` which can +change the message content -- which directly contradicts having immutable +messages. + +The problem that arises is due to the fact that the interface is intended to +wrap a PHP stream or similar. A write operation therefore will proxy to writing +to the stream. Even if we made `StreamableInterface` immutable, once the stream +has been updated, any instance that wraps that stream will also be updated -- +making immutability impossible to enforce. + +Our recommendation is that implementations use read-only streams for +server-side requests and client-side responses. + +### Rationale for ServerRequestInterface + +The `RequestInterface` and `ResponseInterface` have essentially 1:1 +correlations with the request and response messages described in +[RFC 7230](http://www.ietf.org/rfc/rfc7230.txt). They provide interfaces for +implementing value objects that correspond to the specific HTTP message types +they model. + +For server-side applications there are other considerations for +incoming requests: + +- Access to server parameters (potentially derived from the request, but also + potentially the result of server configuration, and generally represented + via the `$_SERVER` superglobal; these are part of the PHP Server API (SAPI)). +- Access to the query string arguments (usually encapsulated in PHP via the + `$_GET` superglobal). +- Access to the parsed body (i.e., data deserialized from the incoming request + body; in PHP, this is typically the result of POST requests using + `application/x-www-form-urlencoded` content types, and encapsulated in the + `$_POST` superglobal, but for non-POST, non-form-encoded data, could be + an array or an object). +- Access to uploaded files (encapsulated in PHP via the `$_FILES` superglobal). +- Access to cookie values (encapsulated in PHP via the `$_COOKIE` superglobal). +- Access to attributes derived from the request (usually, but not limited to, + those matched against the URL path). + +Uniform access to these parameters increases the viability of interoperability +between frameworks and libraries, as they can now assume that if a request +implements `ServerRequestInterface`, they can get at these values. It also +solves problems within the PHP language itself: + +- Until 5.6.0, `php://input` was read-once; as such, instantiating multiple + request instances from multiple frameworks/libraries could lead to + inconsistent state, as the first to access `php://input` would be the only + one to receive the data. +- Unit testing against superglobals (e.g., `$_GET`, `$_FILES`, etc.) is + difficult and typically brittle. Encapsulating them inside the + `ServerRequestInterface` implementation eases testing considerations. + +### Why "parsed body" in the ServerRequestInterface? + +Arguments were made to use the terminology "BodyParams", and require the value +to be an array, with the following rationale: + +- Consistency with other server-side parameter access. +- `$_POST` is an array, and the 80% use case would target that superglobal. +- A single type makes for a strong contract, simplifying usage. + +The main argument is that if the body parameters are an array, developers have +predictable access to values: + +```php +$foo = isset($request->getBodyParams()['foo']) + ? $request->getBodyParams()['foo'] + : null; +``` + +The argument for using "parsed body" was made by examining the domain. A message +body can contain literally anything. While traditional web applications use +forms and submit data using POST, this is a use case that is quickly being +challenged in current web development trends, which are often API centric, and +thus use alternate request methods (notably PUT and PATCH), as well as +non-form-encoded content (generally JSON or XML) that _can_ be coerced to arrays +in many cases, but in many cases also _cannot_ or _should not_. + +If forcing the property representing the parsed body to be only an array, +developers then need a shared convention about where to put the results of +parsing the body. These might include: + +- A special key under the body parameters, such as `__parsed__`. +- A special named attribute, such as `__body__`. + +The end result is that a developer now has to look in multiple locations: + +```php +$data = $request->getBodyParams(); +if (isset($data['__parsed__']) && is_object($data['__parsed__'])) { + $data = $data['__parsed__']; +} + +// or: +$data = $request->getBodyParams(); +if ($request->hasAttribute('__body__')) { + $data = $request->getAttribute('__body__'); +} +``` + +The solution presented is to use the terminology "ParsedBody", which implies +that the values are the results of parsing the message body. This also means +that the return value _will_ be ambiguous; however, because this is an attribute +of the domain, this is also expected. As such, usage will become: + +```php +$data = $request->getParsedBody(); +if (! $data instanceof \stdClass) { + // raise an exception! +} +// otherwise, we have what we expected +``` + +This approach removes the limitations of forcing an array, at the expense of +ambiguity of return value. Considering that the other suggested solutions — +pushing the parsed data into a special body parameter key or into an attribute — +also suffer from ambiguity, the proposed solution is simpler as it does not +require additions to the interface specification. Ultimately, the ambiguity +enables the flexibility required when representing the results of parsing the +body. + +### Why is no functionality included for retrieving the "base path"? + +Many frameworks provide the ability to get the "base path," usually considered +the path up to and including the front controller. As an example, if the +application is served at `http://example.com/b2b/index.php`, and the current URI +used to request it is `http://example.com/b2b/index.php/customer/register`, the +functionality to retrieve the base path would return `/b2b/index.php`. This value +can then be used by routers to strip that path segment prior to attempting a +match. + +This value is often also then used for URI generation within applications; +parameters will be passed to the router, which will generate the path, and +prefix it with the base path in order to return a fully-qualified URI. Other +tools — typically view helpers, template filters, or template functions — are +used to resolve a path relative to the base path in order to generate a URI for +linking to resources such as static assets. + +On examination of several different implementations, we noticed the following: + +- The logic for determining the base path varies widely between implementations. + As an example, compare the [logic in ZF2](https://github.com/zendframework/zf2/blob/release-2.3.7/library/Zend/Http/PhpEnvironment/Request.php#L477-L575) + to the [logic in Symfony 2](https://github.com/symfony/symfony/blob/2.7/src/Symfony/Component/HttpFoundation/Request.php#L1858-L1877). +- Most implementations appear to allow manual injection of a base path to the + router and/or any facilities used for URI generation. +- The primary use cases — routing and URI generation — typically are the only + consumers of the functionality; developers usually do not need to be aware + of the base path concept as other objects take care of that detail for them. + As examples: + - A router will strip off the base path for you during routing; you do not + need to pass the modified path to the router. + - View helpers, template filters, etc. typically are injected with a base path + prior to invocation. Sometimes this is manually done, though more often it + is the result of framework wiring. +- All sources necessary for calculating the base path *are already in the + `RequestInterface` instance*, via server parameters and the URI instance. + +Our stance is that base path detection is framework and/or application +specific, and the results of detection can be easily injected into objects that +need it, and/or calculated as needed using utility functions and/or classes from +the `RequestInterface` instance itself. + +### Why does getUploadedFiles() return objects instead of arrays? + +`getUploadedFiles()` returns a tree of `Psr\Http\Message\UploadedFileInterface` +instances. This is done primarily to simplify specification: instead of +requiring paragraphs of implementation specification for an array, we specify an +interface. + +Additionally, the data in an `UploadedFileInterface` is normalized to work in +both SAPI and non-SAPI environments. This allows creation of processes to parse +the message body manually and assign contents to streams without first writing +to the filesystem, while still allowing proper handling of file uploads in SAPI +environments. + +### What about "special" header values? + +A number of header values contain unique representation requirements which can +pose problems both for consumption as well as generation; in particular, cookies +and the `Accept` header. + +This proposal does not provide any special treatment of any header types. The +base `MessageInterface` provides methods for header retrieval and setting, and +all header values are, in the end, string values. + +Developers are encouraged to write commodity libraries for interacting with +these header values, either for the purposes of parsing or generation. Users may +then consume these libraries when needing to interact with those values. +Examples of this practice already exist in libraries such as +[willdurand/Negotiation](https://github.com/willdurand/Negotiation) and +[aura/accept](https://github.com/pmjones/Aura.Accept). So long as the object +has functionality for casting the value to a string, these objects can be +used to populate the headers of an HTTP message. + +## 6. People + +### 6.1 Editor(s) + +* Matthew Weier O'Phinney + +### 6.2 Sponsors + +* Paul M. Jones +* Beau Simensen (coordinator) + +### 6.3 Contributors + +* Michael Dowling +* Larry Garfield +* Evert Pot +* Tobias Schultze +* Bernhard Schussek +* Anton Serdyuk +* Phil Sturgeon +* Chris Wilkinson diff --git a/accepted/PSR-7-http-message.md b/accepted/PSR-7-http-message.md new file mode 100644 index 000000000..13dd088da --- /dev/null +++ b/accepted/PSR-7-http-message.md @@ -0,0 +1,1872 @@ +# HTTP message interfaces + +This document describes common interfaces for representing HTTP messages as +described in [RFC 7230](http://tools.ietf.org/html/rfc7230) and +[RFC 7231](http://tools.ietf.org/html/rfc7231), and URIs for use with HTTP +messages as described in [RFC 3986](http://tools.ietf.org/html/rfc3986). + +HTTP messages are the foundation of web development. Web browsers and HTTP +clients such as cURL create HTTP request messages that are sent to a web server, +which provides an HTTP response message. Server-side code receives an HTTP +request message, and returns an HTTP response message. + +HTTP messages are typically abstracted from the end-user consumer, but as +developers, we typically need to know how they are structured and how to +access or manipulate them in order to perform our tasks, whether that might be +making a request to an HTTP API, or handling an incoming request. + +Every HTTP request message has a specific form: + +```http +POST /path HTTP/1.1 +Host: example.com + +foo=bar&baz=bat +``` + +The first line of a request is the "request line", and contains, in order, the +HTTP request method, the request target (usually either an absolute URI or a +path on the web server), and the HTTP protocol version. This is followed by one +or more HTTP headers, an empty line, and the message body. + +HTTP response messages have a similar structure: + +```http +HTTP/1.1 200 OK +Content-Type: text/plain + +This is the response body +``` + +The first line is the "status line", and contains, in order, the HTTP protocol +version, the HTTP status code, and a "reason phrase," a human-readable +description of the status code. Like the request message, this is then +followed by one or more HTTP headers, an empty line, and the message body. + +The interfaces described in this document are abstractions around HTTP messages +and the elements composing them. + +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 [RFC 2119](http://tools.ietf.org/html/rfc2119). + +### References + +- [RFC 2119](http://tools.ietf.org/html/rfc2119) +- [RFC 3986](http://tools.ietf.org/html/rfc3986) +- [RFC 7230](http://tools.ietf.org/html/rfc7230) +- [RFC 7231](http://tools.ietf.org/html/rfc7231) + + +## 1. Specification + +### 1.1 Messages + +An HTTP message is either a request from a client to a server or a response from +a server to a client. This specification defines interfaces for the HTTP messages +`Psr\Http\Message\RequestInterface` and `Psr\Http\Message\ResponseInterface` respectively. + +Both `Psr\Http\Message\RequestInterface` and `Psr\Http\Message\ResponseInterface` extend +`Psr\Http\Message\MessageInterface`. While `Psr\Http\Message\MessageInterface` MAY be +implemented directly, implementors SHOULD implement +`Psr\Http\Message\RequestInterface` and `Psr\Http\Message\ResponseInterface`. + +From here forward, the namespace `Psr\Http\Message` will be omitted when +referring to these interfaces. + +#### 1.2 HTTP Headers + +##### Case-insensitive header field names + +HTTP messages include case-insensitive header field names. Headers are retrieved +by name from classes implementing the `MessageInterface` in a case-insensitive +manner. For example, retrieving the `foo` header will return the same result as +retrieving the `FoO` header. Similarly, setting the `Foo` header will overwrite +any previously set `foo` header value. + +```php +$message = $message->withHeader('foo', 'bar'); + +echo $message->getHeaderLine('foo'); +// Outputs: bar + +echo $message->getHeaderLine('FOO'); +// Outputs: bar + +$message = $message->withHeader('fOO', 'baz'); +echo $message->getHeaderLine('foo'); +// Outputs: baz +``` + +Despite that headers may be retrieved case-insensitively, the original case +MUST be preserved by the implementation, in particular when retrieved with +`getHeaders()`. + +Non-conforming HTTP applications may depend on a certain case, so it is useful +for a user to be able to dictate the case of the HTTP headers when creating a +request or response. + +##### Headers with multiple values + +In order to accommodate headers with multiple values yet still provide the +convenience of working with headers as strings, headers can be retrieved from +an instance of a `MessageInterface` as an array or a string. Use the +`getHeaderLine()` method to retrieve a header value as a string containing all +header values of a case-insensitive header by name concatenated with a comma. +Use `getHeader()` to retrieve an array of all the header values for a +particular case-insensitive header by name. + +```php +$message = $message + ->withHeader('foo', 'bar') + ->withAddedHeader('foo', 'baz'); + +$header = $message->getHeaderLine('foo'); +// $header contains: 'bar, baz' + +$header = $message->getHeader('foo'); +// ['bar', 'baz'] +``` + +Note: Not all header values can be concatenated using a comma (e.g., +`Set-Cookie`). When working with such headers, consumers of +`MessageInterface`-based classes SHOULD rely on the `getHeader()` method +for retrieving such multi-valued headers. + +##### Host header + +In requests, the `Host` header typically mirrors the host component of the URI, as +well as the host used when establishing the TCP connection. However, the HTTP +specification allows the `Host` header to differ from each of the two. + +During construction, implementations MUST attempt to set the `Host` header from +a provided URI if no `Host` header is provided. + +`RequestInterface::withUri()` will, by default, replace the returned request's +`Host` header with a `Host` header matching the host component of the passed +`UriInterface`. + +You can opt-in to preserving the original state of the `Host` header by passing +`true` for the second (`$preserveHost`) argument. When this argument is set to +`true`, the returned request will not update the `Host` header of the returned +message -- unless the message contains no `Host` header. + +This table illustrates what `getHeaderLine('Host')` will return for a request +returned by `withUri()` with the `$preserveHost` argument set to `true` for +various initial requests and URIs. + +Request Host header[1](#rhh) | Request host component[2](#rhc) | URI host component[3](#uhc) | Result +----------------------------------------|--------------------------------------------|----------------------------------------|-------- +'' | '' | '' | '' +'' | foo.com | '' | foo.com +'' | foo.com | bar.com | foo.com +foo.com | '' | bar.com | foo.com +foo.com | bar.com | baz.com | foo.com + +- 1 `Host` header value prior to operation. +- 2 Host component of the URI composed in the request prior + to the operation. +- 3 Host component of the URI being injected via + `withUri()`. + +### 1.3 Streams + +HTTP messages consist of a start-line, headers, and a body. The body of an HTTP +message can be very small or extremely large. Attempting to represent the body +of a message as a string can easily consume more memory than intended because +the body must be stored completely in memory. Attempting to store the body of a +request or response in memory would preclude the use of that implementation from +being able to work with large message bodies. `StreamInterface` is used in +order to hide the implementation details when a stream of data is read from +or written to. For situations where a string would be an appropriate message +implementation, built-in streams such as `php://memory` and `php://temp` may be +used. + +`StreamInterface` exposes several methods that enable streams to be read +from, written to, and traversed effectively. + +Streams expose their capabilities using three methods: `isReadable()`, +`isWritable()`, and `isSeekable()`. These methods can be used by stream +collaborators to determine if a stream is capable of their requirements. + +Each stream instance will have various capabilities: it can be read-only, +write-only, or read-write. It can also allow arbitrary random access (seeking +forwards or backwards to any location), or only sequential access (for +example in the case of a socket, pipe, or callback-based stream). + +Finally, `StreamInterface` defines a `__toString()` method to simplify +retrieving or emitting the entire body contents at once. + +Unlike the request and response interfaces, `StreamInterface` does not model +immutability. In situations where an actual PHP stream is wrapped, immutability +is impossible to enforce, as any code that interacts with the resource can +potentially change its state (including cursor position, contents, and more). +Our recommendation is that implementations use read-only streams for +server-side requests and client-side responses. Consumers should be aware of +the fact that the stream instance may be mutable, and, as such, could alter +the state of the message; when in doubt, create a new stream instance and attach +it to a message to enforce state. + +### 1.4 Request Targets and URIs + +Per RFC 7230, request messages contain a "request-target" as the second segment +of the request line. The request target can be one of the following forms: + +- **origin-form**, which consists of the path, and, if present, the query + string; this is often referred to as a relative URL. Messages as transmitted + over TCP typically are of origin-form; scheme and authority data are usually + only present via CGI variables. +- **absolute-form**, which consists of the scheme, authority + ("[user-info@]host[:port]", where items in brackets are optional), path (if + present), query string (if present), and fragment (if present). This is often + referred to as an absolute URI, and is the only form to specify a URI as + detailed in RFC 3986. This form is commonly used when making requests to + HTTP proxies. +- **authority-form**, which consists of the authority only. This is typically + used in CONNECT requests only, to establish a connection between an HTTP + client and a proxy server. +- **asterisk-form**, which consists solely of the string `*`, and which is used + with the OPTIONS method to determine the general capabilities of a web server. + +Aside from these request-targets, there is often an 'effective URL' which is +separate from the request target. The effective URL is not transmitted within +an HTTP message, but it is used to determine the protocol (http/https), port +and hostname for making the request. + +The effective URL is represented by `UriInterface`. `UriInterface` models HTTP +and HTTPS URIs as specified in RFC 3986 (the primary use case). The interface +provides methods for interacting with the various URI parts, which will obviate +the need for repeated parsing of the URI. It also specifies a `__toString()` +method for casting the modeled URI to its string representation. + +When retrieving the request-target with `getRequestTarget()`, by default this +method will use the URI object and extract all the necessary components to +construct the _origin-form_. The _origin-form_ is by far the most common +request-target. + +If it's desired by an end-user to use one of the other three forms, or if the +user wants to explicitly override the request-target, it is possible to do so +with `withRequestTarget()`. + +Calling this method does not affect the URI, as it is returned from `getUri()`. + +For example, a user may want to make an asterisk-form request to a server: + +```php +$request = $request + ->withMethod('OPTIONS') + ->withRequestTarget('*') + ->withUri(new Uri('https://example.org/')); +``` + +This example may ultimately result in an HTTP request that looks like this: + +```http +OPTIONS * HTTP/1.1 +``` + +But the HTTP client will be able to use the effective URL (from `getUri()`), +to determine the protocol, hostname and TCP port. + +An HTTP client MUST ignore the values of `Uri::getPath()` and `Uri::getQuery()`, +and instead use the value returned by `getRequestTarget()`, which defaults +to concatenating these two values. + +Clients that choose to not implement 1 or more of the 4 request-target forms, +MUST still use `getRequestTarget()`. These clients MUST reject request-targets +they do not support, and MUST NOT fall back on the values from `getUri()`. + +`RequestInterface` provides methods for retrieving the request-target or +creating a new instance with the provided request-target. By default, if no +request-target is specifically composed in the instance, `getRequestTarget()` +will return the origin-form of the composed URI (or "/" if no URI is composed). +`withRequestTarget($requestTarget)` creates a new instance with the +specified request target, and thus allows developers to create request messages +that represent the other three request-target forms (absolute-form, +authority-form, and asterisk-form). When used, the composed URI instance can +still be of use, particularly in clients, where it may be used to create the +connection to the server. + +### 1.5 Server-side Requests + +`RequestInterface` provides the general representation of an HTTP request +message. However, server-side requests need additional treatment, due to the +nature of the server-side environment. Server-side processing needs to take into +account Common Gateway Interface (CGI), and, more specifically, PHP's +abstraction and extension of CGI via its Server APIs (SAPI). PHP has provided +simplification around input marshaling via superglobals such as: + +- `$_COOKIE`, which deserializes and provides simplified access for HTTP + cookies. +- `$_GET`, which deserializes and provides simplified access for query string + arguments. +- `$_POST`, which deserializes and provides simplified access for urlencoded + parameters submitted via HTTP POST; generically, it can be considered the + results of parsing the message body. +- `$_FILES`, which provides serialized metadata around file uploads. +- `$_SERVER`, which provides access to CGI/SAPI environment variables, which + commonly include the request method, the request scheme, the request URI, and + headers. + +`ServerRequestInterface` extends `RequestInterface` to provide an abstraction +around these various superglobals. This practice helps reduce coupling to the +superglobals by consumers, and encourages and promotes the ability to test +request consumers. + +The server request provides one additional property, "attributes", to allow +consumers the ability to introspect, decompose, and match the request against +application-specific rules (such as path matching, scheme matching, host +matching, etc.). As such, the server request can also provide messaging between +multiple request consumers. + +### 1.6 Uploaded files + +`ServerRequestInterface` specifies a method for retrieving a tree of upload +files in a normalized structure, with each leaf an instance of +`UploadedFileInterface`. + +The `$_FILES` superglobal has some well-known problems when dealing with arrays +of file inputs. As an example, if you have a form that submits an array of files +— e.g., the input name "files", submitting `files[0]` and `files[1]` — PHP will +represent this as: + +```php +array( + 'files' => array( + 'name' => array( + 0 => 'file0.txt', + 1 => 'file1.html', + ), + 'type' => array( + 0 => 'text/plain', + 1 => 'text/html', + ), + /* etc. */ + ), +) +``` + +instead of the expected: + +```php +array( + 'files' => array( + 0 => array( + 'name' => 'file0.txt', + 'type' => 'text/plain', + /* etc. */ + ), + 1 => array( + 'name' => 'file1.html', + 'type' => 'text/html', + /* etc. */ + ), + ), +) +``` + +The result is that consumers need to know this language implementation detail, +and write code for gathering the data for a given upload. + +Additionally, scenarios exist where `$_FILES` is not populated when file uploads +occur: + +- When the HTTP method is not `POST`. +- When unit testing. +- When operating under a non-SAPI environment, such as [ReactPHP](http://reactphp.org). + +In such cases, the data will need to be seeded differently. As examples: + +- A process might parse the message body to discover the file uploads. In such + cases, the implementation may choose *not* to write the file uploads to the + file system, but instead wrap them in a stream in order to reduce memory, + I/O, and storage overhead. +- In unit testing scenarios, developers need to be able to stub and/or mock the + file upload metadata in order to validate and verify different scenarios. + +`getUploadedFiles()` provides the normalized structure for consumers. +Implementations are expected to: + +- Aggregate all information for a given file upload, and use it to populate a + `Psr\Http\Message\UploadedFileInterface` instance. +- Re-create the submitted tree structure, with each leaf being the appropriate + `Psr\Http\Message\UploadedFileInterface` instance for the given location in + the tree. + +The tree structure referenced should mimic the naming structure in which files +were submitted. + +In the simplest example, this might be a single named form element submitted as: + +```html + +``` + +In this case, the structure in `$_FILES` would look like: + +```php +array( + 'avatar' => array( + 'tmp_name' => 'phpUxcOty', + 'name' => 'my-avatar.png', + 'size' => 90996, + 'type' => 'image/png', + 'error' => 0, + ), +) +``` + +The normalized form returned by `getUploadedFiles()` would be: + +```php +array( + 'avatar' => /* UploadedFileInterface instance */ +) +``` + +In the case of an input using array notation for the name: + +```html + +``` + +`$_FILES` ends up looking like this: + +```php +array( + 'my-form' => array( + 'details' => array( + 'avatar' => array( + 'tmp_name' => 'phpUxcOty', + 'name' => 'my-avatar.png', + 'size' => 90996, + 'type' => 'image/png', + 'error' => 0, + ), + ), + ), +) +``` + +And the corresponding tree returned by `getUploadedFiles()` should be: + +```php +array( + 'my-form' => array( + 'details' => array( + 'avatar' => /* UploadedFileInterface instance */ + ), + ), +) +``` + +In some cases, you may specify an array of files: + +```html +Upload an avatar: +Upload an avatar: +``` + +(As an example, JavaScript controls might spawn additional file upload inputs to +allow uploading multiple files at once.) + +In such a case, the specification implementation must aggregate all information +related to the file at the given index. The reason is because `$_FILES` deviates +from its normal structure in such cases: + +```php +array( + 'my-form' => array( + 'details' => array( + 'avatars' => array( + 'tmp_name' => array( + 0 => '...', + 1 => '...', + 2 => '...', + ), + 'name' => array( + 0 => '...', + 1 => '...', + 2 => '...', + ), + 'size' => array( + 0 => '...', + 1 => '...', + 2 => '...', + ), + 'type' => array( + 0 => '...', + 1 => '...', + 2 => '...', + ), + 'error' => array( + 0 => '...', + 1 => '...', + 2 => '...', + ), + ), + ), + ), +) +``` + +The above `$_FILES` array would correspond to the following structure as +returned by `getUploadedFiles()`: + +```php +array( + 'my-form' => array( + 'details' => array( + 'avatars' => array( + 0 => /* UploadedFileInterface instance */, + 1 => /* UploadedFileInterface instance */, + 2 => /* UploadedFileInterface instance */, + ), + ), + ), +) +``` + +Consumers would access index `1` of the nested array using: + +```php +$request->getUploadedFiles()['my-form']['details']['avatars'][1]; +``` + +Because the uploaded files data is derivative (derived from `$_FILES` or the +request body), a mutator method, `withUploadedFiles()`, is also present in the +interface, allowing delegation of the normalization to another process. + +In the case of the original examples, consumption resembles the following: + +```php +$file0 = $request->getUploadedFiles()['files'][0]; +$file1 = $request->getUploadedFiles()['files'][1]; + +printf( + "Received the files %s and %s", + $file0->getClientFilename(), + $file1->getClientFilename() +); + +// "Received the files file0.txt and file1.html" +``` + +This proposal also recognizes that implementations may operate in non-SAPI +environments. As such, `UploadedFileInterface` provides methods for ensuring +operations will work regardless of environment. In particular: + +- `moveTo($targetPath)` is provided as a safe and recommended alternative to calling + `move_uploaded_file()` directly on the temporary upload file. Implementations + will detect the correct operation to use based on environment. +- `getStream()` will return a `StreamInterface` instance. In non-SAPI + environments, one proposed possibility is to parse individual upload files + into `php://temp` streams instead of directly to files; in such cases, no + upload file is present. `getStream()` is therefore guaranteed to work + regardless of environment. + +As examples: + +``` +// Move a file to an upload directory +$filename = sprintf( + '%s.%s', + create_uuid(), + pathinfo($file0->getClientFilename(), PATHINFO_EXTENSION) +); +$file0->moveTo(DATA_DIR . '/' . $filename); + +// Stream a file to Amazon S3. +// Assume $s3wrapper is a PHP stream that will write to S3, and that +// Psr7StreamWrapper is a class that will decorate a StreamInterface as a PHP +// StreamWrapper. +$stream = new Psr7StreamWrapper($file1->getStream()); +stream_copy_to_stream($stream, $s3wrapper); +``` + +## 2. Package + +The interfaces and classes described are provided as part of the +[psr/http-message](https://packagist.org/packages/psr/http-message) package. + +## 3. Interfaces + +### 3.1 `Psr\Http\Message\MessageInterface` + +```php +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. + * Each key MUST be a header name, and each value MUST be an array of + * strings for that header. + */ + public function getHeaders(); + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($name); + + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader($name); + + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine($name); + + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return self + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader($name, $value); + + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return self + * @throws \InvalidArgumentException for invalid header names. + * @throws \InvalidArgumentException for invalid header values. + */ + public function withAddedHeader($name, $value); + + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return self + */ + public function withoutHeader($name); + + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody(); + + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return self + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body); +} +``` + +### 3.2 `Psr\Http\Message\RequestInterface` + +```php +getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams(); + + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return self + */ + public function withQueryParams(array $query); + + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles(); + + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array An array tree of UploadedFileInterface instances. + * @return self + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles); + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody(); + + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return self + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data); + + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return mixed[] Attributes derived from the request. + */ + public function getAttributes(); + + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute($name, $default = null); + + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return self + */ + public function withAttribute($name, $value); + + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return self + */ + public function withoutAttribute($name); +} +``` + +### 3.3 `Psr\Http\Message\ResponseInterface` + +```php + + * [user-info@]host[:port] + * + * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority(); + + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo(); + + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost(); + + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort(); + + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath(); + + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery(); + + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment(); + + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return self A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid schemes. + * @throws \InvalidArgumentException for unsupported schemes. + */ + public function withScheme($scheme); + + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return self A new instance with the specified user information. + */ + public function withUserInfo($user, $password = null); + + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return self A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost($host); + + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return self A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort($port); + + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return self A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath($path); + + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return self A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery($query); + + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return self A new instance with the specified fragment. + */ + public function withFragment($fragment); + + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3985, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString(); +} +``` + +### 3.6 `Psr\Http\Message\UploadedFileInterface` + +```php +get('/hello/{name}', function ($name) use ($app) { + return 'Hello '.$app->escape($name); +}); +``` + +### 3.2 - Расширение нескольких интерфейсов (17.10.2013) + +При расширении нескольких интерфейсов, со списком `extends` следует поступать так же как с `implements`, как описано в разделе 4.1. + diff --git a/accepted/ru/PSR-2-coding-style-guide.md b/accepted/ru/PSR-2-coding-style-guide.md new file mode 100644 index 000000000..a99d7a93e --- /dev/null +++ b/accepted/ru/PSR-2-coding-style-guide.md @@ -0,0 +1,808 @@ +Руководство по стилю кода +========================= + +Данное руководство расширяет и дополняет основной стандарт кодирования [PSR-1]. + +Цель данного руководства — уменьшить когнитивное сопротивление при +визуальном восприятии кода, написанного разными авторами. Для этого составлен +список распространённых правил и ожиданий относительно форматирования +PHP-кода. + +Представленные здесь стилистические правила получены на основе обобщения опыта +различных проектов. При сотрудничестве разных авторов над множеством проектов, +полезно применять единый набор руководящих принципов для этих проектов. +Таким образом, польза данного руководства не в правилах, как таковых, +а в их распространённости. + +Ключевые слова «НЕОБХОДИМО»/«ДОЛЖНО» («MUST»), «НЕДОПУСТИМО»/«НЕ ДОЛЖНО» («MUST NOT»), «ТРЕБУЕТСЯ» +(«REQUIRED»), «НУЖНО» («SHALL»), «НЕ ПОЗВОЛЯЕТСЯ» («SHALL NOT»), «СЛЕДУЕТ» +(«SHOULD»), «НЕ СЛЕДУЕТ» («SHOULD NOT»), «РЕКОМЕНДУЕТСЯ» («RECOMMENDED»), +«ВОЗМОЖНО» («MAY») и «НЕОБЯЗАТЕЛЬНО» («OPTIONAL») +в этом документе должны расцениваться так, как описано в [RFC 2119]. + +[RFC 2119]: http://www.ietf.org/rfc/rfc2119.txt +[PSR-0]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md +[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md + + +1. Обзор +-------- + +- НЕОБХОДИМО следовать "руководству по стилю кода" PSR [[PSR-1]]. + +- Для отступов НЕОБХОДИМО использовать 4 пробела, а не табы. + +- НЕДОПУСТИМО жёстко ограничивать длину строки; мягкое ограничение длины строки ДОЛЖНО быть 120 + символов; строки СЛЕДУЕТ делать не длиннее 80 символов. + +- НЕОБХОДИМО оставлять одну пустую строку после объявления пространства имён (`namespace`), и + НЕОБХОДИМО оставлять одну пустую строку после блока операторов `use`. + +- Открывающие фигурные скобки классов НЕОБХОДИМО переносить на следующую строку, а закрывающие фигурные скобки + переносить на следующую строку после тела. + +- Открывающие фигурные скобки методов НЕОБХОДИМО переносить на следующую строку, а закрывающие фигурные скобки + переносить на следующую строку после тела. + +- Видимость НЕОБХОДИМО объявлять для всех свойств и методов; `abstract` и + `final` НЕОБХОДИМО ставить перед модификатором видимости; `static` НЕОБХОДИМО ставить + после модификатора видимости. + +- После ключевых слов управляющих структур НЕОБХОДИМО ставить один пробел; + а после названий функций и методов НЕДОПУСТИМО. + +- Открывающие фигурные скобки управляющих структур НЕОБХОДИМО оставлять на той же строке, а закрывающие фигурные скобки + переносить на следующую строку после тела. + +- НЕДОПУСТИМО ставить пробел после открывающих круглых скобок управляющих структур, + и НЕДОПУСТИМО ставить пробел перед закрывающими круглыми скобками управляющих структур. + +### 1.1. Пример + +Этот пример даёт краткое представление о некоторых правилах, описанных ниже: + +```php + $b) { + $foo->bar($arg1); + } else { + BazClass::bar($arg2, $arg3); + } + } + + final public static function bar() + { + // тело метода + } +} +``` + +2. Общие положения +------------------ + +### 2.1 Основной стандарт кодирования + +В коде НЕОБХОДИМО следовать правилам, описанным в [PSR-1]. + +### 2.2 Файлы + +Во всех PHP файлах НЕОБХОДИМО использовать окончания строк LF (\n). + +Все PHP файлы НЕОБХОДИМО заканчивать одной пустой строкой. + +Закрывающий тэг `?>` НЕОБХОДИМО удалять из файлов, содержащих только PHP. + +### 2.3. Строки + +НЕДОПУСТИМО жёстко ограничивать длину строки. + +Мягкое ограничение длины строки ДОЛЖНО быть 120 символов; автоматизированные +проверятели стиля ДОЛЖНЫ предупреждать о нарушении мягкого ограничения, +но не считать это ошибкой. + +НЕ СЛЕДУЕТ делать строки длиннее 80 символов; те строки, что длиннее СЛЕДУЕТ +разбивать на несколько строк, не более 80 символов в каждой. + +НЕДОПУСТИМО оставлять пробелы в конце непустых строк. + +МОЖНО использовать пустые строки для улучшения читаемости и обозначения связанных блоков кода. + +НЕДОПУСТИМО писать на одной строке более одной инструкции. + +### 2.4. Отступы + +В коде НЕОБХОДИМО использовать отступ в 4 пробела, и НЕДОПУСТИМО применять табы для отступов. + +> N.b.: Использование одних пробелов, без примеси табов, помогает избежать +> проблем с диффами, патчами, историей и авторством строк. Использование пробелов +> так же позволяет легко и точно сделать межстрочное выравнивание. + +### 2.5. Ключевые слова и True/False/Null + +[Ключевые слова PHP] НЕОБХОДИМО писать в нижнем регистре. + +Константы PHP: `true`, `false`, и `null` НЕОБХОДИМО писать в нижнем регистре. + +[Ключевые слова PHP]: http://php.net/manual/en/reserved.keywords.php + + + +3. Пространства имён и оператор use +--------------------------------- + +При наличии пространства имён (`namespace`), после его объявления необходимо оставть одну пустую строку. + +При наличии операторов `use`, НЕОБХОДИМО располагать их после объявления пространства имён. + +НЕОБХОДИМО использовать один оператор `use` на одно объявление (импорт или создание псевдонима). + +НЕОБХОДИМО оставлять одну пустую строку после блока операторов `use`. + +Например: + +```php +bar($arg1); +Foo::bar($arg2, $arg3); +``` + +Список аргументов МОЖНО разбить на несколько строк, каждая из которых +с одним отступом. При этом, первый аргумент в списке НЕОБХОДИМО перенести +на следующую строку, и в каждой строке НЕОБХОДИМО указать только один аргумент. + +```php +bar( + $longArgument, + $longerArgument, + $muchLongerArgument +); +``` + +5. Управляюшие структуры +--------------------- + +Общие правила стиля для управляющих структур таковы: + +- После ключевого слова управляющей структуры НЕОБХОДИМ один пробел +- После открывающей круглой скобки пробел НЕДОПУСТИМ +- Перед закрывающей круглой скобкой пробел НЕДОПУСТИМ +- НЕОБХОДИМ один пробел между закрывающей круглой скобкой и открывающей + фигурной скобкой +- Тело управляющей структуры НЕОБХОДИМО смещать на один отступ +- Закрывающую фигурную скобку НЕОБХОДИМО располагать на следующей строке после тела + +Тело каждой управляющей структуры НЕОБХОДИМО заключать в фигурные скобки. Это +стандартизирует вид управляющих структур и уменьшает вероятность возникновения +ошибок при добавлении новых строк в тело. + + +### 5.1. `if`, `elseif`, `else` + +Управляющая структура `if` выглядит как показано ниже. Обратите внимание +на расположение пробелов, круглых и фигурных скобок; и что `else` и `elseif` +расположены на одной строке с закрывающей фигурной скобкой предыдущего тела. + +```php + $value) { + // тело foreach +} +``` + +### 5.6. `try`, `catch` + +Блок `try catch` выглядит как показано ниже. Обратите внимание на расположение +пробелов, круглых и фигурных скобок: + +```php +bar( + $arg1, + function ($arg2) use ($var1) { + // body + }, + $arg3 +); +``` + + +7. Заключение +-------------- + +Многие элементы стиля и практики были преднамеренно опущены в данном руководстве. +Вот некоторые из них: + +- Объявление глобальных переменных и глобальных констант + +- Объявление функций + +- Операторы и присваивание + +- Выравнивание внутри строк + +- Комментарии и блоки документации + +- Префиксы и суффиксы в названиях классов + +- Лучшие практики + +Будущие рекоммендации МОГУТ исправлять и расширять данное руководство, +обращаясь к этим или другим элементам стиля и практикам. + + +Дополнение A. Исследование +------------------ + +При написании данного руководства, группа авторов провела исследование по +выявлению общих практик в проектах участниках. Это исследование сохранено +здесь для потомков. + +### A.1. Данные исследования + + url,http://www.horde.org/apps/horde/docs/CODING_STANDARDS,http://pear.php.net/manual/en/standards.php,http://solarphp.com/manual/appendix-standards.style,http://framework.zend.com/manual/en/coding-standard.html,http://symfony.com/doc/2.0/contributing/code/standards.html,http://www.ppi.io/docs/coding-standards.html,https://github.com/ezsystems/ezp-next/wiki/codingstandards,http://book.cakephp.org/2.0/en/contributing/cakephp-coding-conventions.html,https://github.com/UnionOfRAD/lithium/wiki/Spec%3A-Coding,http://drupal.org/coding-standards,http://code.google.com/p/sabredav/,http://area51.phpbb.com/docs/31x/coding-guidelines.html,https://docs.google.com/a/zikula.org/document/edit?authkey=CPCU0Us&hgd=1&id=1fcqb93Sn-hR9c0mkN6m_tyWnmEvoswKBtSc0tKkZmJA,http://www.chisimba.com,n/a,https://github.com/Respect/project-info/blob/master/coding-standards-sample.php,n/a,Object Calisthenics for PHP,http://doc.nette.org/en/coding-standard,http://flow3.typo3.org,https://github.com/propelorm/Propel2/wiki/Coding-Standards,http://developer.joomla.org/coding-standards.html + voting,yes,yes,yes,yes,yes,yes,yes,yes,yes,yes,yes,yes,yes,yes,yes,no,no,no,?,yes,no,yes + indent_type,4,4,4,4,4,tab,4,tab,tab,2,4,tab,4,4,4,4,4,4,tab,tab,4,tab + line_length_limit_soft,75,75,75,75,no,85,120,120,80,80,80,no,100,80,80,?,?,120,80,120,no,150 + line_length_limit_hard,85,85,85,85,no,no,no,no,100,?,no,no,no,100,100,?,120,120,no,no,no,no + class_names,studly,studly,studly,studly,studly,studly,studly,studly,studly,studly,studly,lower_under,studly,lower,studly,studly,studly,studly,?,studly,studly,studly + class_brace_line,next,next,next,next,next,same,next,same,same,same,same,next,next,next,next,next,next,next,next,same,next,next + constant_names,upper,upper,upper,upper,upper,upper,upper,upper,upper,upper,upper,upper,upper,upper,upper,upper,upper,upper,upper,upper,upper,upper + true_false_null,lower,lower,lower,lower,lower,lower,lower,lower,lower,upper,lower,lower,lower,upper,lower,lower,lower,lower,lower,upper,lower,lower + method_names,camel,camel,camel,camel,camel,camel,camel,camel,camel,camel,camel,lower_under,camel,camel,camel,camel,camel,camel,camel,camel,camel,camel + method_brace_line,next,next,next,next,next,same,next,same,same,same,same,next,next,same,next,next,next,next,next,same,next,next + control_brace_line,same,same,same,same,same,same,next,same,same,same,same,next,same,same,next,same,same,same,same,same,same,next + control_space_after,yes,yes,yes,yes,yes,no,yes,yes,yes,yes,no,yes,yes,yes,yes,yes,yes,yes,yes,yes,yes,yes + always_use_control_braces,yes,yes,yes,yes,yes,yes,no,yes,yes,yes,no,yes,yes,yes,yes,no,yes,yes,yes,yes,yes,yes + else_elseif_line,same,same,same,same,same,same,next,same,same,next,same,next,same,next,next,same,same,same,same,same,same,next + case_break_indent_from_switch,0/1,0/1,0/1,1/2,1/2,1/2,1/2,1/1,1/1,1/2,1/2,1/1,1/2,1/2,1/2,1/2,1/2,1/2,0/1,1/1,1/2,1/2 + function_space_after,no,no,no,no,no,no,no,no,no,no,no,no,no,no,no,no,no,no,no,no,no,no + closing_php_tag_required,no,no,no,no,no,no,no,no,yes,no,no,no,no,yes,no,no,no,no,no,yes,no,no + line_endings,LF,LF,LF,LF,LF,LF,LF,LF,?,LF,?,LF,LF,LF,LF,?,,LF,?,LF,LF,LF + static_or_visibility_first,static,?,static,either,either,either,visibility,visibility,visibility,either,static,either,?,visibility,?,?,either,either,visibility,visibility,static,? + control_space_parens,no,no,no,no,no,no,yes,no,no,no,no,no,no,yes,?,no,no,no,no,no,no,no + blank_line_after_php,no,no,no,no,yes,no,no,no,no,yes,yes,no,no,yes,?,yes,yes,no,yes,no,yes,no + class_method_control_brace,next/next/same,next/next/same,next/next/same,next/next/same,next/next/same,same/same/same,next/next/next,same/same/same,same/same/same,same/same/same,same/same/same,next/next/next,next/next/same,next/same/same,next/next/next,next/next/same,next/next/same,next/next/same,next/next/same,same/same/same,next/next/same,next/next/next + +### A.2. Легенда Исследования + +`indent_type`: +Тип отступов. `tab` = "Используется таб", `2` or `4` = "число пробелов" + +`line_length_limit_soft`: +"Мягкий" лимит длины строки в символах. `?` = не определён, `no` нет лимита. + +`line_length_limit_hard`: +"Жёсткий" лимит длины строки в символах. `?` = не определён, `no` нет лимита. + +`class_names`: +Низвания классов. `lower` = только нижний регистр, `lower_under` = нижний регистр и подчёркивания, `studly` = StudlyCase. + +`class_brace_line`: +Открывающая фигурная скобка класса идёт на той же (`same`) строке, что и слово class, или на следующей (`next`)? + +`constant_names`: +Как пишутся названия констант классов? `upper` = В верхнем регистре с подчёркиваниями. + +`true_false_null`: +Ключевые слова `true`, `false` и `null` пишутся в нижнем (`lower`) или верхнем (`upper`) регистре? + +`method_names`: +Как пишутся названия методов? `camel` = `camelCase`, `lower_under` = в нижнем регистре с подчёркиваниями. + +`method_brace_line`: +Открывающая фигурная скобка метода идёт на той же (`same`) строке, что и слово class, или на следующей (`next`)? + +`control_brace_line`: +Открывающая фигурная скобка управляющей структуры идёт на той же (`same`) строке, что и слово class, или на следующей (`next`)? + +`control_space_after`: +Есть ли пробел после ключевого слова управляющей структуры? + +`always_use_control_braces`: +Всегда ли управляющие структуры используют фигурные скобки? + +`else_elseif_line`: +При использовании `else` или `elseif`, они располагаются на одной (`same`) +строке с закрывающей фигурной скобкой или на следующей (`next`)? + +`case_break_indent_from_switch`: +Сколько горизонтальных отступов у `case` и `break` относительно `switch`? + +`function_space_after`: +Есть ли у вызова функции пробел между названием функции и открывающей круглой скобкой? + +`closing_php_tag_required`: +Требуется ли звкрывающий тэг `?>` в файлах, содержащих только PHP? + +`line_endings`: +Тип окончаний строк? + +`static_or_visibility_first`: +При объявлении метода впереди идёт `static` или модификатор видимости? + +`control_space_parens`: +Есть ли пробелы внутри скобок управляющих структур? `yes` = `if ( $expr )`, `no` = `if ($expr)`. + +`blank_line_after_php`: +Есть ли пустая строка после открывающего тэга PHP? + +`class_method_control_brace`: +На какой строке пишется открывающая фигурная скобка класса, метода и управляющей структуры? + +### A.3. Результат исследования + + indent_type: + tab: 7 + 2: 1 + 4: 14 + line_length_limit_soft: + ?: 2 + no: 3 + 75: 4 + 80: 6 + 85: 1 + 100: 1 + 120: 4 + 150: 1 + line_length_limit_hard: + ?: 2 + no: 11 + 85: 4 + 100: 3 + 120: 2 + class_names: + ?: 1 + lower: 1 + lower_under: 1 + studly: 19 + class_brace_line: + next: 16 + same: 6 + constant_names: + upper: 22 + true_false_null: + lower: 19 + upper: 3 + method_names: + camel: 21 + lower_under: 1 + method_brace_line: + next: 15 + same: 7 + control_brace_line: + next: 4 + same: 18 + control_space_after: + no: 2 + yes: 20 + always_use_control_braces: + no: 3 + yes: 19 + else_elseif_line: + next: 6 + same: 16 + case_break_indent_from_switch: + 0/1: 4 + 1/1: 4 + 1/2: 14 + function_space_after: + no: 22 + closing_php_tag_required: + no: 19 + yes: 3 + line_endings: + ?: 5 + LF: 17 + static_or_visibility_first: + ?: 5 + either: 7 + static: 4 + visibility: 6 + control_space_parens: + ?: 1 + no: 19 + yes: 2 + blank_line_after_php: + ?: 1 + no: 13 + yes: 8 + class_method_control_brace: + next/next/next: 4 + next/next/same: 11 + next/same/same: 1 + same/same/same: 6 diff --git a/accepted/ru/PSR-4-autoloader-meta.md b/accepted/ru/PSR-4-autoloader-meta.md index fee8ff326..375c811e2 100644 --- a/accepted/ru/PSR-4-autoloader-meta.md +++ b/accepted/ru/PSR-4-autoloader-meta.md @@ -51,7 +51,7 @@ PSR-4 метадокумент С приходом Composer исходные файлы пакетов перестали копироваться в единственную глобальную директорию. Вместо этого они используются оттуда, куда были установлены. Это означает что в случае Composer какой-либо "главной директории" для -PHP кода, как это было в случае PEAR, нет. Вместо этого имеются несколько директорий. Каждый пакет находится в отдельой +PHP кода, как это было в случае PEAR, нет. Вместо этого имеются несколько директорий. Каждый пакет находится в отдельной директории для каждого проекта. Чтобы при этом соответствовать PSR-0, пакет Composer выглядит вот так: @@ -144,7 +144,7 @@ PHP кода, как это было в случае PEAR, нет. Вместо которые позволяют реализациям быть в большей степени взаимозаменяемыми. Несмотря на то, что это не относится к соответствию директорий, финальный черновик также -задаёт как автозагрузчики должны работать с ошибками. А именно, запрещает выкидывать +задаёт порядок работы автозагрузчиков с ошибками. А именно, запрещает выкидывать исключения и вызывать ошибки. На это есть две причины: 1. Автозагрузчики в PHP изначально разработаны чтобы использоваться вместе. То есть если один diff --git a/bylaws/002-psr-naming-conventions.md b/bylaws/002-psr-naming-conventions.md index ab023a017..bb74dcec9 100644 --- a/bylaws/002-psr-naming-conventions.md +++ b/bylaws/002-psr-naming-conventions.md @@ -4,7 +4,7 @@ Naming conventions for code released by PHP-FIG 1. Interfaces MUST be suffixed by `Interface`: e.g. `Psr\Foo\BarInterface`. 2. Abstract classes MUST be prefixed by `Abstract`: e.g. `Psr\Foo\AbstractBar`. 3. Traits MUST be suffixed by `Trait`: e.g. `Psr\Foo\BarTrait`. -4. PSR-0, 1 and 2 MUST be followed. +4. PSR-1, 2 and 4 MUST be followed. 5. The vendor namespace MUST be `Psr`. 6. There MUST be a package/second-level namespace in relation with the PSR that covers the code. diff --git a/index.md b/index.md index 8941ea2f2..0fdd8121e 100644 --- a/index.md +++ b/index.md @@ -8,45 +8,52 @@ Unless a PSR is marked as "Accepted" it is subject to change. Draft can change d ### Draft -| Num | Title | Editor | Coordinator | -|:---:|--------------------------------|-------------------------|---------------| -| 5 | [PHPDoc Standard][psr5] | Mike van Riel | Phil Sturgeon | -| 7 | [HTTP Message Interface][psr7] | Matthew Weier O'Phinney | Phil Sturgeon | -| 8 | [Huggable Interface][psr8] | Larry Garfield | Cal Evans | -| 9 | [Security Disclosure][psr9] | Lukas Kahwe Smith | Korvin Szanto | +| Num | Title | Editor | Coordinator | Sponsor | +|:---:|--------------------------------|-------------------------|----------------|----------------| +| 5 | [PHPDoc Standard][psr5] | Mike van Riel | Phil Sturgeon | Donald Gilbert | +| 8 | [Huggable Interface][psr8] | Larry Garfield | Cal Evans | Paul Jones | +| 9 | [Security Disclosure][psr9] | Lukas Kahwe Smith | Korvin Szanto | Larry Garfield | +| 10 | [Security Advisories][psr10] | Lukas Kahwe Smith | Larry Garfield | Korvin Szanto | ### Review -| Num | Title | Editor | Coordinator | -|:---:|--------------------------------|-------------------------|---------------| -| 6 | [Caching Interface][psr6] | Larry Garfield | Pádraic Brady | +| Num | Title | Editor | Coordinator | Sponsor | +|:---:|--------------------------------|-------------------------|---------------|-------------| +| 6 | [Caching Interface][psr6] | Larry Garfield | Pádraic Brady | John Mertic | + +### Voting + +| Num | Title | Editor | Coordinator | Sponsor | +|:---:|--------------------------------|-------------------------|---------------|-------------| ### Accepted -| Num | Title | Editor | Coordinator | -|:---:|-------------------------------|----------------|---------------| -| 0 | [Autoloading Standard][psr0] | _N/A_ | _N/A_ | -| 1 | [Basic Coding Standard][psr1] | _N/A_ | _N/A_ | -| 2 | [Coding Style Guide][psr2] | _N/A_ | _N/A_ | -| 3 | [Logger Interface][psr3] | Jordi Boggiano | _N/A_ | -| 4 | [Autoloading Standard][psr4] | Paul M. Jones | Phil Sturgeon | +| Num | Title | Editor | Coordinator | Sponsor | +|:---:|--------------------------------|-------------------------|---------------|----------------| +| 0 | [Autoloading Standard][psr0] | _N/A_ | _N/A_ | _N/A_ | +| 1 | [Basic Coding Standard][psr1] | _N/A_ | _N/A_ | _N/A_ | +| 2 | [Coding Style Guide][psr2] | _N/A_ | _N/A_ | _N/A_ | +| 3 | [Logger Interface][psr3] | Jordi Boggiano | _N/A_ | _N/A_ | +| 4 | [Autoloading Standard][psr4] | Paul M. Jones | Phil Sturgeon | Larry Garfield | +| 7 | [HTTP Message Interface][psr7] | Matthew Weier O'Phinney | Beau Simensen | Paul Jones | ## Numerical Index -| Status | Num | Title | Editor | Coordinator | -|--------|:---:|--------------------------------|-------------------------|---------------| -| A | 0 | [Autoloading Standard][psr0] | _N/A_ | _N/A_ | -| A | 1 | [Basic Coding Standard][psr1] | _N/A_ | _N/A_ | -| A | 2 | [Coding Style Guide][psr2] | _N/A_ | _N/A_ | -| A | 3 | [Logger Interface][psr3] | Jordi Boggiano | _N/A_ | -| A | 4 | [Autoloading Standard][psr4] | Paul M. Jones | Phil Sturgeon | -| D | 5 | [PHPDoc Standard][psr5] | Mike van Riel | Phil Sturgeon | -| R | 6 | [Caching Interface][psr6] | Larry Garfield | Pádraic Brady | -| D | 7 | [HTTP Message Interface][psr7] | Matthew Weier O'Phinney | Phil Sturgeon | -| D | 8 | [Huggable Interface][psr8] | Larry Garfield | Cal Evans | -| D | 9 | [Security Disclosure][psr9] | Lukas Kahwe Smith | Korvin Szanto | - -_**Legend:** A = Accepted | D = Draft | R = Review | X = Rejected_ +| Status | Num | Title | Editor | Coordinator | Sponsor | +|--------|:---:|--------------------------------|-------------------------|----------------|----------------| +| A | 0 | [Autoloading Standard][psr0] | _N/A_ | _N/A_ | _N/A_ | +| A | 1 | [Basic Coding Standard][psr1] | _N/A_ | _N/A_ | _N/A_ | +| A | 2 | [Coding Style Guide][psr2] | _N/A_ | _N/A_ | _N/A_ | +| A | 3 | [Logger Interface][psr3] | Jordi Boggiano | _N/A_ | _N/A_ | +| A | 4 | [Autoloading Standard][psr4] | Paul M. Jones | Phil Sturgeon | Larry Garfield | +| D | 5 | [PHPDoc Standard][psr5] | Mike van Riel | Phil Sturgeon | Donald Gilbert | +| R | 6 | [Caching Interface][psr6] | Larry Garfield | Pádraic Brady | Beau Simensen | +| A | 7 | [HTTP Message Interface][psr7] | Matthew Weier O'Phinney | Beau Simensen | Paul Jones | +| D | 8 | [Huggable Interface][psr8] | Larry Garfield | Cal Evans | Paul Jones | +| D | 9 | [Security Disclosure][psr9] | Lukas Kahwe Smith | Korvin Szanto | Larry Garfield | +| D | 10 | [Security Advisories][psr10] | Lukas Kahwe Smith | Larry Garfield | Korvin Szanto | + +_**Legend:** A = Accepted | D = Draft | R = Review | V = Voting | X = Rejected_ [psr0]: /psr/psr-0/ [psr1]: /psr/psr-1/ @@ -55,6 +62,7 @@ _**Legend:** A = Accepted | D = Draft | R = Review | X = Rejected_ [psr4]: /psr/psr-4/ [psr5]: https://github.com/phpDocumentor/fig-standards/tree/master/proposed [psr6]: https://github.com/Crell/fig-standards/blob/Cache/proposed/ -[psr7]: https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md +[psr7]: /psr/psr-7/ [psr8]: https://github.com/php-fig/fig-standards/blob/master/proposed/psr-8-hug/psr-8-hug.md [psr9]: https://github.com/php-fig/fig-standards/blob/master/proposed/security-disclosure.md +[psr10]: https://github.com/php-fig/fig-standards/pull/473 diff --git a/proposed/cache-meta.md b/proposed/cache-meta.md index 750907180..8339bae8f 100644 --- a/proposed/cache-meta.md +++ b/proposed/cache-meta.md @@ -203,7 +203,7 @@ function load_widgets(array $ids) */ -interface TaggablePoolInterface extends Psr\Cache\PoolInterface +interface TaggablePoolInterface extends Psr\Cache\CachePoolInterface { /** * Clears only those items from the pool that have the specified tag. @@ -211,7 +211,7 @@ interface TaggablePoolInterface extends Psr\Cache\PoolInterface clearByTag($tag); } -interface TaggableItemInterface extends Psr\Cache\ItemInterface +interface TaggableItemInterface extends Psr\Cache\CacheItemInterface { public function setTags(array $tags); } @@ -285,8 +285,8 @@ do so. ### 5.2 Sponsors -* Pádraic Brady (Coordinator) -* John Mertic +* Pádraic Brady, Zend Framework (Coordinator) +* Beau Simensen, Sculpin ### 5.3 Contributors diff --git a/proposed/cache.md b/proposed/cache.md index 615006598..8f27e42fb 100644 --- a/proposed/cache.md +++ b/proposed/cache.md @@ -36,9 +36,10 @@ implementation of those caching services. * **Implementing Library** - This library is responsible for implementing this standard in order to provide caching services to any Calling Library. The -Implementing Library MUST provide classes which implement the Cache\PoolInterface -and Cache\ItemInterface interfaces. Implementing Libraries MUST support at -minimum TTL functionality as described below with whole-second granularity. +Implementing Library MUST provide classes which implement the +Cache\CacheItemPoolInterface and Cache\CacheItemInterface interfaces. +Implementing Libraries MUST support at minimum TTL functionality as described +below with whole-second granularity. * **TTL** - The Time To Live (TTL) of an item is the amount of time between when that item is stored and it is considered stale. The TTL is normally defined @@ -73,7 +74,7 @@ As this is separate from isHit() there's a potential race condition between the time exists() is called and get() being called so Calling Libraries SHOULD make sure to verify isHit() on all of the get() calls. -* **Miss** - A cache miss is the opposite of a cache hit. A cache hit occurs +* **Miss** - A cache miss is the opposite of a cache hit. A cache miss occurs when a Calling Library requests an item by key and that value not found for that key, or the value was found but has expired, or the value is invalid for some other reason. An expired value MUST always be considered a cache miss. @@ -137,14 +138,15 @@ at any time. CacheItemInterface defines an item inside a cache system. Each Item object MUST be associated with a specific key, which can be set according to the -implementing system and is typically passed by the Cache\PoolInterface object. +implementing system and is typically passed by the Cache\CacheItemPoolInterface +object. The Cache\CacheItemInterface object encapsulates the storage and retrieval of -cache items. Each Cache\ItemInterface is generated by a Cache\PoolInterface -object, which is responsible for any required setup as well as associating -the object with a unique Key. Cache\ItemInterface objects MUST be able to -store and retrieve any type of PHP value defined in the Data section of this -document. +cache items. Each Cache\CacheItemInterface is generated by a +Cache\CacheItemPoolInterface object, which is responsible for any required +setup as well as associating the object with a unique Key. +Cache\CacheItemInterface objects MUST be able to store and retrieve any type of +PHP value defined in the Data section of this document. Calling Libraries MUST NOT instantiate Item objects themselves. They may only be requested from a Pool object via the getItem() method. Calling Libraries @@ -227,21 +229,31 @@ interface CacheItemInterface public function exists(); /** - * Sets the expiration for this cache item. + * Sets the expiration time for this cache item. * - * @param int|\DateTime $ttl - * - If an integer is passed, it is interpreted as the number of seconds - * after which the item MUST be considered expired. - * - If a DateTime object is passed, it is interpreted as the point in - * time after which the item MUST be considered expired. - * - If null is passed, a default value MAY be used. If none is set, - * the value should be stored permanently or for as long as the - * implementation allows. + * @param \DateTimeInterface $expiration + * The point in time after which the item MUST be considered expired. + * If null is passed explicitly, a default value MAY be used. If none is set, + * the value should be stored permanently or for as long as the + * implementation allows. * * @return static * The called object. */ - public function setExpiration($ttl = null); + public function expiresAt($expiration); + + /** + * Sets the expiration time for this cache item. + * + * @param int|\DateInterval $time + * The period of time from the present after which the item MUST be considered + * expired. An integer parameter is understood to be the time in seconds until + * expiration. + * + * @return static + * The called object. + */ + public function expiresAfter($time); /** * Returns the expiration time of a not-yet-expired cache item. @@ -276,7 +288,7 @@ interface CacheItemPoolInterface /** * Returns a Cache Item representing the specified key. * - * This method must always return an ItemInterface object, even in case of + * This method must always return a CacheItemInterface object, even in case of * a cache miss. It MUST NOT return null. * * @param string $key diff --git a/proposed/http-message-meta.md b/proposed/http-message-meta.md deleted file mode 100644 index 2efa9c02f..000000000 --- a/proposed/http-message-meta.md +++ /dev/null @@ -1,297 +0,0 @@ -HTTP Message Meta Document -========================== - -1. Summary ----------- - -The purpose of this proposal is to provide a set of common interfaces for HTTP -messages as described in [RFC 7230] and [RFC 7231]. - -[RFC 7230]: http://www.ietf.org/rfc/rfc7230.txt -[RFC 7231]: http://www.ietf.org/rfc/rfc7231.txt - -All HTTP messages consist of the HTTP protocol version being used, headers, and -a message body. A _Request_ builds on the message to include the HTTP method -used to make the request, and the URI to which the request is made. A -_Response_ includes the HTTP status code and reason phrase. - -In PHP, HTTP messages are used in two contexts: - -- To send an HTTP request from a client, such as cURL, a web browser, etc., to - be fulfilled by a server, which will return an HTTP response. In other words, - PHP can use HTTP messages when acting as an _HTTP client_. -- To process an incoming HTTP request to a server, and return an HTTP response - to the client making the request. PHP can use HTTP messages when used as a - _server-side application_ to fulfill HTTP requests. - -This proposal presents an API for describing HTTP messages in PHP in a way -that is as simple as possible while simultaneously producing no limits on -functionality. - -2. Why Bother? --------------- - -HTTP messages are used in a wide number of PHP projects -- both clients and -servers. In each case, we observe one or more of the following patterns or -situations: - -1. Projects will create implementations from scratch. -2. Projects may require a specific HTTP client/server library that provides - HTTP message implementations. -3. Projects may create adapters for common HTTP message implementations. - -As examples: - -1. Frameworks such as Symfony and Zend Framework each define HTTP components - that form the basis of their MVC layers; even small, single-purpose - libraries such as oauth2-server-php provide and require their own HTTP - request/response implementations. Guzzle, Buzz, and other HTTP client - implementations each create their own HTTP message implementations as well. -2. Projects such as Silex and Stack have hard dependencies on Symfony's HTTP - kernel. Any SDK built on Guzzle has a hard requirement on Guzzle's HTTP message - implementations. -3. Projects such as Geocoder create redundant [adapters for common - libraries](https://github.com/geocoder-php/Geocoder/tree/6a729c6869f55ad55ae641c74ac9ce7731635e6e/src/Geocoder/HttpAdapter). - -The net result is that projects are not capable of interoperability or -cross-pollination. In order to consume code targeting one framework from -another, the first order of business is building a bridge layer between the -HTTP message implementations. On the client-side, if a particular library does -not have an adapter you can utilize, you need to bridge the request/response -pairs if you wish to use an adapter from another library. - -Thus, the goal of this proposal is to abstract both client- and server-side -request and response interfaces in order to promote interoperability between -projects. If projects implement these interfaces, a reasonable level of -compatibility may be assumed when adopting code from different libraries. - -It should be noted that the goal of this proposal is not to obsolete the -current interfaces utilized by existing PHP libraries. This proposal is aimed -at interoperability between PHP packages for the purpose of describing HTTP -messages. - -3. Scope --------- - -### 3.1 Goals - -* Provide the interfaces needed for describing HTTP messages. -* Keep the interfaces as minimal as possible. -* Ensure that the API does not impose arbitrary limits on HTTP messages. For - example, some HTTP message bodies can be too large to store in memory, so we - must account for this. -* Provide useful abstractions both for handling incoming requests for - server-side applications and sending outgoing requests in HTTP clients. - -### 3.2 Non-Goals - -* This proposal does not expect all HTTP client libraries or server-side - frameworks to change their interfaces to conform. It is strictly meant for - interoperability. -* While everyone's perception of what is and is not an implementation detail - varies, this proposal should not impose implementation details. However, - because RFC 7230 and RFC 7231 do not force any particular implementation, - there will be a certain amount of invention needed to describe HTTP message - interfaces in PHP. - -4. Design Decisions -------------------- - -### Message design - -The `MessageInterface` provides accessors for the elements common to all HTTP -messages, whether they are for requests or responses. These elements include: - -- HTTP protocol version (e.g., "1.0", "1.1") -- HTTP headers -- HTTP message body - -More specific interfaces are used to describe requests and responses, and more -specifically the context of each (client- vs. server-side). These divisions are -partly inspired by existing PHP usage, but also by other languages such as -Ruby, Python, Go, Node, etc. - -#### Why are there header methods on messages rather than in a header bag? - -Moving headers to a "header bag" breaks the -[Law of Demeter](http://en.wikipedia.org/wiki/Law_of_Demeter) and exposes the -internal implementation of a message to its collaborators. In order for -something to access the headers of a message, they need to reach into the -message's header bag (`$message->getHeaders()->getHeader('Foo')`). - -Moving headers from messages into an externally mutable "header bag" exposes the -internal implementation of how a message manages its headers, and has a -side-effect that messages are no longer aware of changes to their headers. This -can lead to messages entering into an invalid or inconsistent state. - -#### Mutability of messages - -The proposal models "context-specific" mutability. This means that a message is mutable based on its context. For example: - -- Client-side requests are mutable, as they will be iterably built before being - sent. -- Client-side responses are immutable, as they represent the result of a - request that has been made; changing the result would lead to inconsistent - state when multiple processes are passed the message. -- Server-side requests are immutable, as they represent the state of the - current incoming request; changes to the message would lead to inconsistent - state when multiple processes are passed the message. -- Server-side responses are mutable, as they are built iterably before being - returned to the client. - -Real-world usage in clients requires mutable requests. As an example, most HTTP -clients allow you to modify a request pre-flight in order to implement custom -logic (for example, signing a request, compression, encryption, etc...). -Examples include: - -* Guzzle: http://guzzlephp.org/guide/plugins.html -* Buzz: https://github.com/kriswallsmith/Buzz/blob/master/lib/Buzz/Listener/BasicAuthListener.php -* Requests/PHP: https://github.com/rmccue/Requests/blob/master/docs/hooks.md - -This is not just a popular pattern in the PHP community: - -* Requests: http://docs.python-requests.org/en/latest/user/advanced/#event-hooks -* Typhoeus: https://github.com/typhoeus/typhoeus/blob/master/lib/typhoeus/request/before.rb -* RestClient: https://github.com/archiloque/rest-client#hook -* Java's HttpClient: http://hc.apache.org/httpcomponents-client-ga/httpclient/examples/org/apache/http/examples/client/ClientGZipContentCompression.java -* etc... - -On the server-side, the application will write to the response instance in -order to populate it before sending it back to the client. This is particularly -useful when considering the fact that headers can no longer be emitted once -_any_ output has been sent; aggregating headers and content in a response -object is a useful and typically necessary abstraction. - -While server-side requests are primarily immutable, we have noted one element -or property as _mutable_: "attributes". Most server-side applications utilize -processes that match the request to specific criteria -- such as path segments, -subdomains, etc. -- and then push the derived matches back into the request -itself. Since these processes need to introspect the populated request, and are -a product of the application itself, the proposal allows this property to be -mutable. Possible use cases include: - -* Body parameters are often "discovered" via deserialization of the incoming - request body, and the serialization method will need to be determined by - introspecting incoming `Content-Type` headers. -* Cookies may be encrypted, and a process may decrypt them and re-inject them - into the request for later collaborators to access. -* Routing and other tools are often used to "discover" request attributes (e.g., - decomposing the URL `/user/phil` to assign the value "phil" to the attribute - "user"). Such logic is application-specific, but still considered part of the - request state; it can only be injected after instantiation. - -Having context-specific mutability ensures the consistency of client-side -responses and server-side requests, while simultaneously allowing for the full -spectrum of operations on their counterparts, which are the product of the -applications. - -### Using streams instead of X - -`MessageInterface` uses a body value that must implement `StreamableInterface`. This -design decision was made so that developers can send and receive (and/or receive -and send) HTTP messages that contain more data than can practically be stored in -memory while still allowing the convenience of interacting with message bodies -as a string. While PHP provides a stream abstraction by way of stream wrappers, -stream resources can be cumbersome to work with: stream resources can only be -cast to a string using `stream_get_contents()` or manually reading the remainder -of a string. Adding custom behavior to a stream as it is consumed or populated -requires registering a stream filter; however, stream filters can only be added -to a stream after the filter is registered with PHP (i.e., there is no stream -filter autoloading mechanism). - -The use of a well- defined stream interface allows for the potential of -flexible stream decorators that can be added to a request or response -pre-flight to enable things like encryption, compression, ensuring that the -number of bytes downloaded reflects the number of bytes reported in the -`Content-Length` of a response, etc. Decorating streams is a well-established -[pattern in the Java](http://docs.oracle.com/javase/7/docs/api/java/io/package-tree.html) -and [Node](http://nodejs.org/api/stream.html#stream_class_stream_transform_1) -communities that allows for very flexible streams. - -The majority of the `StreamableInterface` API is based on -[Python's io module](http://docs.python.org/3.1/library/io.html), which provides -a practical and consumable API. Instead of implementing stream -capabilities using something like a `WritableStreamInterface` and -`ReadableStreamInterface`, the capabilities of a stream are provided by methods -like `isReadable()`, `isWritable()`, etc. This approach is used by Python, -[C#, C++](http://msdn.microsoft.com/en-us/library/system.io.stream.aspx), -[Ruby](http://www.ruby-doc.org/core-2.0.0/IO.html), -[Node](http://nodejs.org/api/stream.html), and likely others. - -#### Rationale for IncomingRequestInterface - -The `OutgoingRequestInterface`, `IncomingResponseInterface`, and -`OutgoingResponseInterface`, have essentially 1:1 correlations with the request -and response messages described in [RFC 7230](http://www.ietf.org/rfc/rfc7230.txt) -They provide interfaces for implementing value objects that correspond to the -specific HTTP message types they model. - -For server-side applications, however, there are other considerations for -incoming requests: - -- Access to server parameters (potentially derived from the request, but also - potentially the result of server configuration, and generally represented - via the `$_SERVER` superglobal). -- Access to the query string arguments (usually encapsulated in PHP via the - `$_GET` superglobal). -- Access to body parameters (i.e., data deserialized from the incoming request - body, and usually encapsulated by PHP in the `$_POST` superglobal). -- Access to uploaded files (usually encapsulated in PHP via the `$_FILES` - superglobal). -- Access to cookie values (usually encapsulated in PHP via the `$_COOKIE` - superglobal). -- Access to attributes derived from the request (usually against the URL path). - -Uniform access to these parameters increases the viability of interoperability -between frameworks and libraries, as they can now assume that if a request -implements `IncomingRequestInterface`, they can get at these values. It also -solves problems within the PHP language itself: - -- Until 5.6.0, `php://input` was read-once; as such, instantiating multiple - request instances from multiple frameworks/libraries could lead to - inconsistent state, as the first to access `php://input` would be the only - one to receive the data. -- Unit testing against superglobals (e.g., `$_GET`, `$_FILES`, etc.) is - difficult and typically brittle. Encapsulating them inside the - `IncomingRequestInterface` implementation eases testing considerations. - -The interface as defined provides no mutators other than for derived -attributes. The assumption is that values either (a) may be injected at -instantiation from superglobals, and (b) should not change over the course of -the incoming request. - -#### What about "special" header values? - -A number of header values contain unique representation requirements which can -pose problems both for consumption as well as generation; in particular, cookies -and the Accept header. - -This proposal does not provide any special treatment of any header types. The -base `MessageInterface` provides methods for header retrieval and setting, and -all header values are, in the end, string values. - -Developers are encouraged to write commodity libraries for interacting with -these header values, either for the purposes of parsing or generation. Users may -then consume these libraries when needing to interact with those values. -Examples of this practice already exist in libraries such as -[willdurand/Negotiation](https://github.com/willdurand/Negotiation) and -[aura/accept](https://github.com/pmjones/Aura.Accept). So long as the object -has functionality for casting the value to a string, these objects can be -used to populate the headers of an HTTP message. - -5. People ---------- - -### 5.1 Editor(s) - -* Michael Dowling -* Matthew Weier O'Phinney - -### 5.2 Sponsors - -* Phil Sturgeon (coordinator) -* Beau Simensen - -### 5.3 Contributors - -* Chris Wilkinson diff --git a/proposed/http-message.md b/proposed/http-message.md deleted file mode 100644 index e3f877fa6..000000000 --- a/proposed/http-message.md +++ /dev/null @@ -1,868 +0,0 @@ -HTTP message interfaces -======================= - -This document describes common interfaces for representing HTTP messages as -described in [RFC 7230] and [RFC 7231]. - -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 [RFC 2119]. - -[RFC 2119]: http://www.ietf.org/rfc/rfc2119.txt -[RFC 7230]: http://www.ietf.org/rfc/rfc7230.txt -[RFC 7231]: http://www.ietf.org/rfc/rfc7231.txt - -1. Specification ----------------- - -### 1.1 Messages - -An HTTP message is either a request from a client to a server or a response from -a server to a client. This specification defines two pairs of interfaces for -HTTP messages based on context: - -- Client-Side (i.e., making an HTTP request from PHP): - - `Psr\Http\Message\OutgoingRequestInterface` - - `Psr\Http\Message\IncomingResponseInterface` -- Server-Side (i.e., processing a request made to a resource handled by PHP) - - `Psr\Http\Message\IncomingRequestInterface` - - `Psr\Http\Message\OutgoingResponseInterface` - -All of the above messages extend a base `Psr\Http\Message\MessageInterface`, -which defines accessors for properties common across all implementations. While -`Psr\Http\Message\MessageInterface` MAY be implemented directly, implementors are -encouraged to implement one or both pairs of request/response interfaces, and -consumers are encouraged to typehint on the relevant request/response interfaces. - -Interfaces are segregated by context. For HTTP client applications, the request is -mutable, to allow consumers to incrementally build the request before sending it; the -response, however, is immutable, as it is the product of an operation. Similarly, for -server-side applications, the request is immutable and represents the specifics -of the HTTP request made to the server; the response, however, will be incrementally -built by the application prior to sending it back to the client. - -The `Psr\Http\Message\IncomingRequestInterface`, since it represents the incoming -PHP request environment, is intended to model the various PHP superglobals. -This practice helps reduce coupling to the superglobals by consumers, and -encourages and promotes the ability to test request consumers. The interface -purposely does not provide mutators for the various superglobal properties to -encourage treating them as immutable. However, it does provide one mutable -property, "attributes", to allow consumers the ability to introspect, -decompose, and match the request against application-specific rules (such as -path matching, cookie decryption, etc.). As such, the request can also provide -messaging between multiple request consumers. - -From here forward, the namespace `Psr\Http\Message` will be omitted when -referring to these interfaces. - -#### 1.2 HTTP Headers - -##### Case-insensitive header names - -HTTP messages include case-insensitive header names. Headers are retrieved by name from -classes implementing the `MessageInterface` interface in a case-insensitive -manner. For example, retrieving the "foo" header will return the same result as -retrieving the "FoO" header. Similarly, setting the "Foo" header will overwrite -any previously set "foo" header. - -```php -$message->setHeader('foo', 'bar'); -echo $message->getHeader('foo'); -// Outputs: bar - -echo $message->getHeader('FOO'); -// Outputs: bar - -$message->setHeader('fOO', 'baz'); -echo $message->getHeader('foo'); -// Outputs: baz -``` - -##### Headers with multiple values - -In order to accommodate headers with multiple values yet still provide the -convenience of working with headers as strings, headers can be retrieved from -an instance of a ``MessageInterface`` as an array or string. Use the -`getHeader()` method to retrieve a header value as a string containing all -header values of a header by name concatenated with a comma. -Use `getHeaderAsArray()` to retrieve an array of all the header values for a -particular header by name. - -```php -$message->setHeader('foo', 'bar'); -$message->addHeader('foo', 'baz'); - -$header = $message->getHeader('foo'); -// $header contains: 'bar, baz' - -$header = $message->getHeaderAsArray('foo'); -// $header contains: ['bar', 'baz'] -``` - -Note: Not all header values can be concatenated using a comma (e.g., -`Set-Cookie`). When working with such headers, consumers of -`MessageInterface`-based classes SHOULD rely on the `getHeaderAsArray()` method -for retrieving such multi-valued headers. - -### 1.2 Streams - -HTTP messages consist of a start-line, headers, and a body. The body of an HTTP -message can be very small or extremely large. Attempting to represent the body -of a message as a string can easily consume more memory than intended because -the body must be stored completely in memory. Attempting to store the body of a -request or response in memory would preclude the use of that implementation from -being able to work with large message bodies. `StreamableInterface` is used in -order to hide the implementation details when a stream of data is read from -or written to. For situations where a string would be an appropriate message -implementation, built-in streams such as `php://memory` and `php://temp` may be -used. - -`StreamableInterface` exposes several methods that enable streams to be read -from, written to, and traversed effectively. - -Streams expose their capabilities using three methods: `isReadable()`, -`isWritable()`, and `isSeekable()`. These methods can be used by stream -collaborators to determine if a stream is capable of their requirements. - -Each stream instance will have various capabilities: it can be read-only, -write-only, or read-write. It can also allow arbitrary random access (seeking -forwards or backwards to any location), or only sequential access (for -example in the case of a socket or pipe). - -Finally, `StreamableInterface` defines a `__toString()` method to simplify -retrieving or emitting the entire body contents at once. - -2. Package ----------- - -The interfaces and classes described are provided as part of the -[psr/http-message](https://packagist.org/packages/psr/http-message) package. - -3. Interfaces -------------- - -### 3.1 `Psr\Http\Message\MessageInterface` - -```php -getHeaders() as $name => $values) { - * echo $name . ": " . implode(", ", $values); - * } - * - * // Emit headers iteratively: - * foreach ($message->getHeaders() as $name => $values) { - * foreach ($values as $value) { - * header(sprintf('%s: %s', $name, $value), false); - * } - * } - * - * @return array Returns an associative array of the message's headers. Each - * key MUST be a header name, and each value MUST be an array of strings. - */ - public function getHeaders(); - - /** - * Checks if a header exists by the given case-insensitive name. - * - * @param string $header Case-insensitive header name. - * @return bool Returns true if any header names match the given header - * name using a case-insensitive string comparison. Returns false if - * no matching header name is found in the message. - */ - public function hasHeader($header); - - /** - * Retrieve a header by the given case-insensitive name, as a string. - * - * This method returns all of the header values of the given - * case-insensitive header name as a string concatenated together using - * a comma. - * - * NOTE: Not all header values may be appropriately represented using - * comma concatenation. - * - * @param string $header Case-insensitive header name. - * @return string - */ - public function getHeader($header); - - /** - * Retrieves a header by the given case-insensitive name as an array of strings. - * - * @param string $header Case-insensitive header name. - * @return string[] - */ - public function getHeaderAsArray($header); -} -``` - -### 3.2 Server-Side messages - -The `IncomingRequestInterface` and `OutgoingResponseInterface` describe the messages used when handling an incoming HTTP request via PHP. - -#### 3.2.1 `Psr\Http\Message\IncomingRequestInterface` - -```php -``. + +Note that projects MAY choose to host their disclosure files on a domain +other than their main project page. It is RECOMMENDED to not store the +disclosures in a VCS as this can lead to the confusions about which branch +is the relevant branch. If a VCS is used then additional steps SHOULD be taken +to clearly document to users which branch contains all vulnerabilities for +all versions. If necessary projects MAY however split vulnerability disclosure +files by major version number. In this case again this SHOULD be clearly +documented. + +## Disclosure Format + +The disclosure format is based on Atom [1], which in turn is based on XML. It +leverages the "The Common Vulnerability Reporting Framework (CVRF) v1.1" [2]. +Specifically it leverages its dictionary [3] as its base terminology. + +**TODO**: Should we also provide a JSON serialization to lower the bar for projects. +Aggregation services can then spring up to provide an Atom representation of +these disclosures in JSON format. + +The Atom extensions [4] allow a structured description of the vulnerability to +enable automated tools to determine if installed is likely affected by the +vulnerability. However human readability is considered highly important and as +such not the full CVRF is used. + +**TODO**: Review the Atom format and the supplied XSD + +Note that for each vulnerability only a single entry MUST be created. In case +any information changes the original file MUST be updated along with the last +update field. + +Any disclosure uses ``entryType`` using the following tags from the Atom +namespace (required tags are labeled with "MUST"): + +* title (short description of the vulnerability and affected versions, MUST) +* summary (description of the vulnerability) +* author (contact information, MUST) +* published (initial publication date, MUST) +* updated (date of the last update) +* link (to reference more information) +* id (project specific vulnerability id) + +In addition the following tags are added: + +* name (name of the product, MUST) +* cve (unique CVE ID) +* cwe (unique CWE ID) +* severity (low, medium high) +* affected (version(s) using composer syntax [5]) +* status (open, in progress, disputed, completed, MUST) +* remediation (textual description for how to fix an affected system) +* remediationType (workaround, mitigation, vendor fix, none available, will not fix) +* remediationLink (URL to give additional information for remediation) + +[1] https://tools.ietf.org/html/rfc4287 +[2] http://www.icasi.org/cvrf-1.1 +[3] http://www.icasi.org/cvrf-1.1-dictionary +[4] security-disclosure-publication.xsd +[5] https://getcomposer.org/doc/01-basic-usage.md#package-versions \ No newline at end of file diff --git a/proposed/security-disclosure-publication.xsd b/proposed/security-disclosure-publication.xsd new file mode 100644 index 000000000..7445db0d6 --- /dev/null +++ b/proposed/security-disclosure-publication.xsd @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The PHP FIG security disclosure remediation construct is to be used to specify a specific remediation + option for a specific vulnerability. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/proposed/security-disclosure.md b/proposed/security-disclosure.md deleted file mode 100644 index ebd4f6c18..000000000 --- a/proposed/security-disclosure.md +++ /dev/null @@ -1,24 +0,0 @@ -## Introduction - -Unfortunately with all software development, security vulnerabilities are a -fact of life that need to be addressed. It is important that when security -vulnerabilities are found that researchers have an easy channel to the -projects in question allowing them to disclose the issue to a controlled -group of people. Furthermore the process how a reported issues is then -solved and the solution and information provided to the general public must -be clear to all parties involved: the researcher, the project leads and -the user base. Especially today where PHP developers are sharing code across -projects more than ever, it also adds another dimension: how to deal with -upstream projects to ensure they have sufficient time to prepare themselves. - -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 [RFC 2119][]. - -[RFC 2119]: http://tools.ietf.org/html/rfc2119 - -## Goal - -The goal of this PSR is to give researchers, project leads, upstream project -leads and end user a defined and structured process for disclosing security -vulnerabilities. diff --git a/proposed/security-reporting-process-meta.md b/proposed/security-reporting-process-meta.md new file mode 100644 index 000000000..76edb00a3 --- /dev/null +++ b/proposed/security-reporting-process-meta.md @@ -0,0 +1,91 @@ +Security Disclosure Meta Document +================================= + +1. Summary +---------- + +There are two aspects with dealing with security issues: One is the process +by which security issues are reported and fixed in projects, the other +is how the general public is informed about the issues and any remedies +available. While PSR-10 addresses the later, this PSR, ie. PSR-9, deals with +the former. So the goal of PSR-9 is to define the process by which security +researchers and report security vulnerabilities to projects. It is important +that when security vulnerabilities are found that researchers have an easy +channel to the projects in question allowing them to disclose the issue to a +controlled group of people. + +2. Why Bother? +-------------- + +As of right now, there isn't a common standard for most parts of this process. +That is there isn't a standard where researchers can find out about the +process for handling security issues for any given project. There is also +no standard that explains to researchers what they can expect to happen if +they report a vulnerability. More importantly there is no standard on which +projects can base the security reporting process that best fits them. + +3. Scope +-------- + +## 3.1 Goals + +* A defined process for how vulnerabilities are reported, the process by which + these get fixed and finally disclosed to the public + +## 3.2 Non-Goals + +* Methods for reducing security vulnerabilities +* Publication of security issues and fixes (see PSR-10) + +4. Approaches +------------- + +Currently the most viable approach seems to be defining a base line workflow +for how security vulnerabilities go from discovery to fixing to public +disclosure. Inspiration could be drawn from this list of security disclosure +processes in various PHP and non-PHP projects: + +* http://symfony.com/doc/current/contributing/code/security.html +* http://framework.zend.com/security/ +* http://www.yiiframework.com/security/ +* https://www.drupal.org/security +* http://codex.wordpress.org/FAQ_Security +* http://www.sugarcrm.com/page/sugarcrm-security-policy/en +* http://typo3.org/teams/security/ +* http://cakephp.org/development +* http://www.concrete5.org/developers/security/ +* http://developer.joomla.org/security.html +* http://wiki.horde.org/SecurityManagement +* http://www.revive-adserver.com/support/bugs/ +* http://magento.com/security +* http://www.apache.org/security/committers.html +* https://www.mozilla.org/en-US/about/governance/policies/security-group/bugs/ +* http://www.openbsd.org/security.html + +A summary of the differences and similarities can be found here: +https://groups.google.com/d/msg/php-fig-psr-9-discussion/puGV_X0bj_M/Jr_IAS40StsJ + +5. People +--------- + +### 5.1 Editor + +* Lukas Kahwe Smith + +### 5.2 Sponsors + +* Larry Garfield (Drupal) +* Korvin Szanto (concrete5) + +### 5.3 Coordinator + +* Korvin Szanto (concrete5) + +6. Votes +-------- + + +7. Relevant Links +----------------- + +[1]: http://symfony.com/doc/current/contributing/code/security.html \ No newline at end of file diff --git a/proposed/security-reporting-process.md b/proposed/security-reporting-process.md new file mode 100644 index 000000000..47664ed9f --- /dev/null +++ b/proposed/security-reporting-process.md @@ -0,0 +1,73 @@ +## Introduction + +There are two aspects with dealing with security issues: One is the process +by which security issues are reported and fixed in projects, the other +is how the general public is informed about the issues and any remedies +available. While PSR-10 addresses the later, this PSR, ie. PSR-9, deals with +the former. So the goal of PSR-9 is to define the process by which security +researchers and report security vulnerabilities to projects. It is important +that when security vulnerabilities are found that researchers have an easy +channel to the projects in question allowing them to disclose the issue to a +controlled group of people. + +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 [RFC 2119][]. + +[RFC 2119]: http://tools.ietf.org/html/rfc2119 + +## Goal + +The goal of this PSR is to give researchers, project leads, upstream project +leads and end users a defined and structured process for disclosing security +vulnerabilities. + +## Security Disclosure Process Discovery + +Every project MUST provide a link to its security disclosure process in +an obvious place. Ideally this should be on the root page the main domain of +the given project. This MAY be a sub-domain in case it is a sub-project of a +larger initiative. The link MAY use the custom link relation +``php-vuln-reporting``, ie. for example +````. + +Projects SHOULD ideally make the location prominent itself +by either creating a dedicated sub-domain like ``http://security.example.org`` +or by making it a top level directory like ``http://example.org/security``. +Alternatively projects MAY also simply reference this document, ie. PSR-9. +By referencing PSR-9 a project basically states that they follow the +default procedures as noted in the section "Default Procedures" towards +the end of this document. Projects MUST list the variables noted at the start +of that section in this reference (ie. project name, project domain, etc.). +Projects MAY choose to list any part of the procedures that is not a MUST +which they choose to omit. + +Note that projects MAY not have a dedicated domain. For example a project +hosted on Github, Bitbucket or other service should still ensure that the +process is referenced on the landing page, ie. for example +http://github.com/example/somelib should ensure that the default branch +has a README file which references the procedures used so that it is +automatically displayed. + +If necessary projects MAY have different disclosure process +for different major version number. In this case one URL MUST be provided +for each major version. In the case a major version is no longer receiving +security fixes, instead of an URL a project MAY opt to instead simply +note that the version is no longer receiving security fixes. + +## Security Disclosure Process + +Every project MUST provide an email address in their security disclosure +process description as the ``contact email address``. Projects SHALL NOT +use contact forms. + +**TODO**: Add more things found here https://groups.google.com/d/msg/php-fig-psr-9-discussion/puGV_X0bj_M/Jr_IAS40StsJ? + +## Default Procedures + +* ``[project name]`` denotes the name on which the project uses to identify itself. +* ``[project domain]`` denotes the main (sub)domain on which the project relies. + +If not specified otherwise, the ``contact email address`` is ``security@[project domain]``. + +**TODO**: Add more things noted in the previous section \ No newline at end of file