friendship ended with social-app. php is my new best friend
1# chillerlan/php-http-message-utils 2 3A collection of framework-agnostic utilities for use with [PSR-7 Message implementations](https://www.php-fig.org/psr/psr-7/). 4 5[![PHP Version Support][php-badge]][php] 6[![version][packagist-badge]][packagist] 7[![license][license-badge]][license] 8[![Continuous Integration][gh-action-badge]][gh-action] 9[![Coverage][coverage-badge]][coverage] 10[![Codacy][codacy-badge]][codacy] 11[![Packagist downloads][downloads-badge]][downloads] 12 13[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-http-message-utils?logo=php&color=8892BF 14[php]: https://www.php.net/supported-versions.php 15[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-http-message-utils?logo=packagist 16[packagist]: https://packagist.org/packages/chillerlan/php-http-message-utils 17[license-badge]: https://img.shields.io/github/license/chillerlan/php-http-message-utils 18[license]: https://github.com/chillerlan/php-http-message-utils/blob/main/LICENSE 19[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-http-message-utils?logo=codecov 20[coverage]: https://codecov.io/github/chillerlan/php-http-message-utils 21[codacy-badge]: https://img.shields.io/codacy/grade/70e19515c2734e0a9036d83dbbd1469c?logo=codacy 22[codacy]: https://app.codacy.com/gh/chillerlan/php-http-message-utils/dashboard 23[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-http-message-utils?logo=packagist 24[downloads]: https://packagist.org/packages/chillerlan/php-http-message-utils/stats 25[gh-action-badge]: https://img.shields.io/github/actions/workflow/status/chillerlan/php-http-message-utils/ci.yml?branch=main&logo=github 26[gh-action]: https://github.com/chillerlan/php-http-message-utils/actions/workflows/ci.yml?query=branch%3Amain 27 28 29# Documentation 30 31## Requirements 32 - PHP 8.1+ 33 - `ext-fileinfo`, `ext-intl`, `ext-json`, `ext-mbstring`, `ext-simplexml`, `ext-zlib` 34 - for `MessageUtil::decompress()`: `ext-br` [kjdev/php-ext-brotli](https://github.com/kjdev/php-ext-brotli) or `ext-zstd` [kjdev/php-ext-zstd](https://github.com/kjdev/php-ext-zstd) 35 36## Installation 37 38**requires [composer](https://getcomposer.org)** 39 40`composer.json` (note: replace `dev-main` with a [version boundary](https://getcomposer.org/doc/articles/versions.md), e.g. `^2.2`) 41```json 42{ 43 "require": { 44 "php": "^8.1", 45 "chillerlan/php-http-message-utils": "dev-main#<commit_hash>" 46 } 47} 48``` 49Profit! 50 51 52## Usage 53 54### `URLExtractor` 55 56The `URLExtractor` wraps a PSR-18 `ClientInterface` to extract and follow shortened URLs to their original location. 57 58```php 59// @see https://github.com/chillerlan/php-httpinterface 60$options = new HTTPOptions; 61$options->user_agent = 'my cool user agent 1.0'; 62$options->ssl_verifypeer = false; 63$options->curl_options = [ 64 CURLOPT_FOLLOWLOCATION => false, 65 CURLOPT_MAXREDIRS => 25, 66]; 67 68$httpClient = new CurlClient($responseFactory, $options, $logger); 69$urlExtractor = new URLExtractor($httpClient, $responseFactory); 70 71$request = $factory->createRequest('GET', 'https://t.co/ZSS6nVOcVp'); 72 73$urlExtractor->sendRequest($request); // -> response from the final location 74 75// you can retrieve an array with all followed locations afterwards 76$responses = $urlExtractor->getResponses(); // -> ResponseInterface[] 77 78// if you just want the URL of the final location, you can use the extract method: 79$url = $urlExtractor->extract('https://t.co/ZSS6nVOcVp'); // -> https://api.guildwars2.com/v2/build 80``` 81 82### `EchoClient` 83 84The `EchoClient` returns a JSON representation the original message: 85 86```php 87$echoClient = new EchoClient($responseFactory); 88 89$request = $requestFactory->createRequest('GET', 'https://example.com?whatever=value'); 90$response = $echoClient->sendRequest($request); 91$json = json_decode($response->getBody()->getContents()); 92``` 93 94Which yields an object similar to the following 95 96```json 97{ 98 "headers": { 99 "Host": "example.com" 100 }, 101 "request": { 102 "url": "https://example.com?whatever=value", 103 "params": { 104 "whatever": "value" 105 }, 106 "method": "GET", 107 "target": "/", 108 "http": "1.1" 109 }, 110 "body": "" 111} 112``` 113 114 115### `LoggingClient` 116 117The `LoggingClient` wraps a `ClientInterface` and outputs the HTTP messages in a readable way through a `LoggerInterface` (do NOT use in production!). 118 119```php 120$loggingClient = new LoggingClient($httpClient, $logger); 121 122$loggingClient->sendRequest($request); // -> log to output given via logger 123``` 124 125The output looks similar to the following (using [monolog](https://github.com/Seldaek/monolog)): 126 127``` 128[2024-03-15 22:10:41][debug] LoggingClientTest: 129----HTTP-REQUEST---- 130GET /get HTTP/1.1 131Host: httpbin.org 132 133 134[2024-03-15 22:10:41][debug] LoggingClientTest: 135----HTTP-RESPONSE--- 136HTTP/1.1 200 OK 137Date: Fri, 15 Mar 2024 21:10:40 GMT 138Content-Type: application/json 139Content-Length: 294 140Connection: keep-alive 141Server: gunicorn/19.9.0 142Access-Control-Allow-Origin: * 143Access-Control-Allow-Credentials: true 144 145{ 146 "args": {}, 147 "headers": { 148 "Host": "httpbin.org", 149 "User-Agent": "chillerlanPHPUnitHttp/1.0.0 +https://github.com/chillerlan/phpunit-http", 150 "X-Amzn-Trace-Id": "Root=1-65f4b950-1f87b9e37182673438091aea" 151 }, 152 "origin": "93.236.207.163", 153 "url": "https://httpbin.org/get" 154} 155``` 156 157 158## API 159The following classes contain static methods for use with PSR-7 http message objects. 160 161### `HeaderUtil` 162| method | return | info | 163|-------------------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 164| `normalize(array $headers)` | `array` | Normalizes an array of header lines to format `["Name" => "Value (, Value2, Value3, ...)", ...]` An exception is being made for `Set-Cookie`, which holds an array of values for each cookie. For multiple cookies with the same name, only the last value will be kept. | 165| `trimValues(array $values)` | `array` | Trims whitespace from the header values | 166| `normalizeHeaderName(string $name)` | `string` | Normalizes a header name, e.g. "conTENT- lenGTh" -> "Content-Length" | 167 168### `QueryUtil` 169| method | return | info | 170|--------------------------------------------------------------------------------------------------|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 171| `cleanParams(iterable $params, int $bool_cast = null, bool $remove_empty = true)` | `array` | Cleans/normalizes an array of query parameters, booleans will be converted according to the given `$bool_cast` constant. By default, booleans will be left as-is (`Query::BOOLEANS_AS_BOOL`) and may result in empty values. If `$remove_empty` is set to true, empty non-boolean and null values will be removed from the array. The `Query` class provides the following constants for `$bool_cast`:<br>`BOOLEANS_AS_BOOL`: unchanged boolean value (default)<br>`BOOLEANS_AS_INT`: integer values 0 or 1<br>`BOOLEANS_AS_STRING`: "true"/"false" strings<br>`BOOLEANS_AS_INT_STRING`: "0"/"1" strings | 172| `build(array $params, int $encoding = null, string $delimiter = null, string $enclosure = null)` | `string` | Builds a query string from an array of key value pairs, similar to [`http_build_query`](https://www.php.net/manual/en/function.http-build-query). Valid values for `$encoding` are `PHP_QUERY_RFC3986` (default) and `PHP_QUERY_RFC1738`, any other integer value will be interpreted as "no encoding" (`Query::NO_ENCODING`). | 173| `merge(string $uri, array $query)` | `string` | Merges additional query parameters into an existing query string. | 174| `parse(string $querystring, int $urlEncoding = null)` | `array` | Parses a query string into an associative array, similar to [`parse_str`](https://www.php.net/manual/en/function.parse-str) (without the inconvenient usage of a by-reference result variable). | 175| `recursiveRawurlencode(mixed $data)` | `array\|string` | Recursive [`rawurlencode`](https://www.php.net/manual/en/function.rawurlencode) | 176 177### `MessageUtil` 178| method | return | info | 179|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 180| `getContents(MessageInterface $message)` | `string` | Reads the body content of a `MessageInterface` and makes sure to rewind. | 181| `decodeJSON(MessageInterface $message, bool $assoc = false)` | mixed | fetches the body of a `MessageInterface` and converts it to a JSON object (`stdClass`) or an associative array if `$assoc` is set to `true` and returns the result. | 182| `decodeXML(MessageInterface $message, bool $assoc = false)` | mixed | fetches the body of a `MessageInterface` and converts it to a `SimpleXMLElement` or an associative array if `$assoc` is set to `true` and returns the result. | 183| `toString(MessageInterface $message, bool $appendBody = true)` | `string` | Returns the string representation of an HTTP message. | 184| `toJSON(MessageInterface $message, bool $appendBody = true)` | `string` | Returns the string representation of an HTTP message. | 185| `decompress(MessageInterface $message)` | `string` | Decompresses the message content according to the `Content-Encoding` header (`compress`, `deflate`, `gzip`, `br`, `zstd`) and returns the decompressed data. `br` and `zstd` will throw a `RuntimeException` if the respecive extensions are missing. | 186| `setContentLengthHeader(MessageInterface $message)` | `MessageInterface` | Sets a Content-Length header in the given message in case it does not exist and body size is not null | 187| `setContentTypeHeader(MessageInterface $message, string $filename = null, string $extension = null)` | `MessageInterface` | Tries to determine the content type from the given values and sets the Content-Type header accordingly, throws if no mime type could be guessed. | 188| `setCookie(ResponseInterface $message, string $name, string $value = null, DateTimeInterface\|DateInterval\|int $expiry = null, string $domain = null, string $path = null, bool $secure = false, bool $httpOnly = false, string $sameSite = null)` | `ResponseInterface` | Adds a Set-Cookie header to a ResponseInterface (convenience) | 189| `getCookiesFromHeader(MessageInterface $message)` | `array\|null` | Attempts to extract and parse a cookie from a "Cookie" (user-agent) header | 190 191### `UriUtil` 192| method | return | info | 193|------------------------------------------------------------------------|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 194| `isDefaultPort(UriInterface $uri)` | `bool` | Checks whether the `UriInterface` has a port set and if that port is one of the default ports for the given scheme. | 195| `isAbsolute(UriInterface $uri)` | `bool` | Checks whether the URI is absolute, i.e. it has a scheme. | 196| `isNetworkPathReference(UriInterface $uri)` | `bool` | Checks whether the URI is a network-path reference. | 197| `isAbsolutePathReference(UriInterface $uri)` | `bool` | Checks whether the URI is a absolute-path reference. | 198| `isRelativePathReference(UriInterface $uri)` | `bool` | Checks whether the URI is a relative-path reference. | 199| `withoutQueryValue(UriInterface $uri, string $key)` | `UriInterface` | Removes a specific query string value. Any existing query string values that exactly match the provided `$key` are removed. | 200| `withQueryValue(UriInterface $uri, string $key, string $value = null)` | `UriInterface` | Adds a specific query string value. Any existing query string values that exactly match the provided `$key` are removed and replaced with the given `$key`-`$value` pair. A value of null will set the query string key without a value, e.g. "key" instead of "key=value". | 201| `parseUrl(string $url)` | `?array` | UTF-8 aware `\parse_url()` replacement. | 202 203### `MimeTypeUtil` 204| method | return | info | 205|---------------------------------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 206| `getFromExtension(string $extension)` | `?string` | Get the mime type for the given file extension (checks against the constant `chillerlan\HTTP\Utils\MIMETYPES`, a list of mime types from the [apache default config](http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types)) | 207| `getFromFilename(string $filename)` | `?string` | Get the mime type from a file name | 208| `getFromContent(string $content)` | `?string` | Get the mime type from the given content | 209 210### `StreamUtil` 211| method | return | info | 212|----------------------------------------------------------------------------------------------|------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| 213| `getContents(string $extension)` | `string` | Reads the content from a stream and make sure we rewind | 214| `copyToStream(StreamInterface $source, StreamInterface $destination, int $maxLength = null)` | `int` | Copies a stream to another stream, starting from the current position of the source stream, reading to the end or until the given maxlength is hit. | 215| `tryFopen(string $filename, string $mode, $context = null)` | `resource` | Safely open a PHP resource, throws instead of raising warnings and errors | 216| `tryGetContents($stream, int $length = null, int $offset = -1)` | `string` | Safely get the contents of a stream resource, throws instead of raising warnings and errors | 217| `validateMode(string $mode)` | `string` | Checks if the given mode is valid for `fopen()` | 218| `modeAllowsReadWrite(string $mode)` | `bool` | Checks whether the given mode allows reading and writing | 219| `modeAllowsReadOnly(string $mode)` | `bool` | Checks whether the given mode allows only reading | 220| `modeAllowsWriteOnly(string $mode)` | `bool` | Checks whether the given mode allows only writing | 221| `modeAllowsRead(string $mode)` | `bool` | Checks whether the given mode allows reading | 222| `modeAllowsWrite(string $mode)` | `bool` | Checks whether the given mode allows writing | 223 224### `ServerUtil` 225The `ServerUtil` object requires a set of [PSR-17 factories](https://www.php-fig.org/psr/psr-17/) on invocation, namely `ServerRequestFactoryInterface`, `UriFactoryInterface`, `UploadedFileFactoryInterface` and `StreamFactoryInterface`. 226It provides convenience methods to create server requests, URIs and uploaded files from the [superglobals](https://www.php.net/manual/en/language.variables.superglobals.php). 227 228| method | return | info | 229|-----------------------------------------------|------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 230| `createServerRequestFromGlobals()` | `ServerRequestInterface` | Returns a ServerRequest object populated from the superglobals `$_GET`, `$_POST`, `$_COOKIE`, `$_FILES` and `$_SERVER`. | 231| `createUriFromGlobals()` | `UriInterface` | Creates an Uri populated with values from [`$_SERVER`](https://www.php.net/manual/en/reserved.variables.server) (`HTTP_HOST`, `SERVER_NAME`, `SERVER_ADDR`, `SERVER_PORT`, `REQUEST_URI`, `QUERY_STRING`). | 232| `normalizeFiles(array $files)` | `UploadedFileInterface[]` | Returns an `UploadedFile` instance array. | 233| `createUploadedFileFromSpec(array $value)` | `UploadedFileInterface` or `UploadedFileInterface[]` | Creates an UploadedFile instance from a `$_FILES` specification. If the specification represents an array of values, this method will delegate to `normalizeNestedFileSpec()` and return that return value. | 234| `normalizeNestedFileSpec(array $files):array` | `array` | Normalizes an array of file specifications. Loops through all nested files and returns a normalized array of `UploadedFileInterface` instances. | 235 236### `Cookie` 237Implements a [HTTP cookie](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1) 238 239| method | return | info | 240|------------------------------------------------------------------|----------|----------------------------------------------------------------| 241| `__construct(string $name, string $value = null)` | - | | 242| `__toString()` | `string` | returns the full cookie string to use in a `Set-Cookie` header | 243| `withNameAndValue(string $name, string $value)` | `Cookie` | | 244| `withExpiry(DateTimeInterface\|DateInterval\|int\|null $expiry)` | `Cookie` | | 245| `withDomain(string\|null $domain, bool $punycode = null)` | `Cookie` | | 246| `withPath(string\|null $path)` | `Cookie` | | 247| `withSecure(bool $secure)` | `Cookie` | | 248| `withHttpOnly(bool $httpOnly)` | `Cookie` | | 249| `withSameSite(string\|null $sameSite)` | `Cookie` | |