A collection of framework-agnostic utilities for use with PSR-7 Message implementations.
- PHP 8.1+
- ext-fileinfo,- ext-intl,- ext-json,- ext-mbstring,- ext-simplexml,- ext-zlib
- for MessageUtil::decompress():ext-brkjdev/php-ext-brotli orext-zstdkjdev/php-ext-zstd
 
requires composer
composer.json (note: replace dev-main with a version boundary, e.g. ^2.2)
{
	"require": {
		"php": "^8.1",
		"chillerlan/php-http-message-utils": "dev-main#<commit_hash>"
	}
}Profit!
The URLExtractor wraps a PSR-18 ClientInterface to extract and follow shortened URLs to their original location.
// @see https://github.com/chillerlan/php-httpinterface
$options                 = new HTTPOptions;
$options->user_agent     = 'my cool user agent 1.0';
$options->ssl_verifypeer = false;
$options->curl_options   = [
	CURLOPT_FOLLOWLOCATION => false,
	CURLOPT_MAXREDIRS      => 25,
];
$httpClient   = new CurlClient($responseFactory, $options, $logger);
$urlExtractor = new URLExtractor($httpClient, $responseFactory);
$request = $factory->createRequest('GET', 'https://t.co/ZSS6nVOcVp');
$urlExtractor->sendRequest($request); // -> response from the final location
// you can retrieve an array with all followed locations afterwards
$responses = $urlExtractor->getResponses(); // -> ResponseInterface[]
// if you just want the URL of the final location, you can use the extract method:
$url = $urlExtractor->extract('https://t.co/ZSS6nVOcVp'); // -> https://api.guildwars2.com/v2/buildThe EchoClient returns a JSON representation the original message:
$echoClient = new EchoClient($responseFactory);
$request  = $requestFactory->createRequest('GET', 'https://example.com?whatever=value');
$response = $echoClient->sendRequest($request);
$json     = json_decode($response->getBody()->getContents());Which yields an object similar to the following
{
	"headers": {
		"Host": "example.com"
	},
	"request": {
		"url": "https://example.com?whatever=value",
		"params": {
			"whatever": "value"
		},
		"method": "GET",
		"target": "/",
		"http": "1.1"
	},
	"body": ""
}The LoggingClient wraps a ClientInterface and outputs the HTTP messages in a readable way through a LoggerInterface (do NOT use in production!).
$loggingClient = new LoggingClient($httpClient, $logger);
$loggingClient->sendRequest($request); // -> log to output given via loggerThe output looks similar to the following (using monolog):
[2024-03-15 22:10:41][debug] LoggingClientTest:
----HTTP-REQUEST----
GET /get HTTP/1.1
Host: httpbin.org
[2024-03-15 22:10:41][debug] LoggingClientTest:
----HTTP-RESPONSE---
HTTP/1.1 200 OK
Date: Fri, 15 Mar 2024 21:10:40 GMT
Content-Type: application/json
Content-Length: 294
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{
  "args": {},
  "headers": {
    "Host": "httpbin.org",
    "User-Agent": "chillerlanPHPUnitHttp/1.0.0 +https://github.com/chillerlan/phpunit-http",
    "X-Amzn-Trace-Id": "Root=1-65f4b950-1f87b9e37182673438091aea"
  },
  "origin": "93.236.207.163",
  "url": "https://httpbin.org/get"
}
The following classes contain static methods for use with PSR-7 http message objects.
| method | return | info | 
|---|---|---|
| normalize(array $headers) | array | Normalizes an array of header lines to format ["Name" => "Value (, Value2, Value3, ...)", ...]An exception is being made forSet-Cookie, which holds an array of values for each cookie. For multiple cookies with the same name, only the last value will be kept. | 
| trimValues(array $values) | array | Trims whitespace from the header values | 
| normalizeHeaderName(string $name) | string | Normalizes a header name, e.g. "conTENT- lenGTh" -> "Content-Length" | 
| method | return | info | 
|---|---|---|
| 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_castconstant. By default, booleans will be left as-is (Query::BOOLEANS_AS_BOOL) and may result in empty values. If$remove_emptyis set to true, empty non-boolean and null values will be removed from the array. TheQueryclass provides the following constants for$bool_cast:BOOLEANS_AS_BOOL: unchanged boolean value (default)BOOLEANS_AS_INT: integer values 0 or 1BOOLEANS_AS_STRING: "true"/"false" stringsBOOLEANS_AS_INT_STRING: "0"/"1" strings | 
| 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. Valid values for$encodingarePHP_QUERY_RFC3986(default) andPHP_QUERY_RFC1738, any other integer value will be interpreted as "no encoding" (Query::NO_ENCODING). | 
| merge(string $uri, array $query) | string | Merges additional query parameters into an existing query string. | 
| parse(string $querystring, int $urlEncoding = null) | array | Parses a query string into an associative array, similar to parse_str(without the inconvenient usage of a by-reference result variable). | 
| recursiveRawurlencode(mixed $data) | array|string | Recursive rawurlencode | 
| method | return | info | 
|---|---|---|
| getContents(MessageInterface $message) | string | Reads the body content of a MessageInterfaceand makes sure to rewind. | 
| decodeJSON(MessageInterface $message, bool $assoc = false) | mixed | fetches the body of a MessageInterfaceand converts it to a JSON object (stdClass) or an associative array if$associs set totrueand returns the result. | 
| decodeXML(MessageInterface $message, bool $assoc = false) | mixed | fetches the body of a MessageInterfaceand converts it to aSimpleXMLElementor an associative array if$associs set totrueand returns the result. | 
| toString(MessageInterface $message, bool $appendBody = true) | string | Returns the string representation of an HTTP message. | 
| toJSON(MessageInterface $message, bool $appendBody = true) | string | Returns the string representation of an HTTP message. | 
| decompress(MessageInterface $message) | string | Decompresses the message content according to the Content-Encodingheader (compress,deflate,gzip,br,zstd) and returns the decompressed data.brandzstdwill throw aRuntimeExceptionif the respecive extensions are missing. | 
| 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 | 
| 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. | 
| 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) | 
| getCookiesFromHeader(MessageInterface $message) | array|null | Attempts to extract and parse a cookie from a "Cookie" (user-agent) header | 
| method | return | info | 
|---|---|---|
| isDefaultPort(UriInterface $uri) | bool | Checks whether the UriInterfacehas a port set and if that port is one of the default ports for the given scheme. | 
| isAbsolute(UriInterface $uri) | bool | Checks whether the URI is absolute, i.e. it has a scheme. | 
| isNetworkPathReference(UriInterface $uri) | bool | Checks whether the URI is a network-path reference. | 
| isAbsolutePathReference(UriInterface $uri) | bool | Checks whether the URI is a absolute-path reference. | 
| isRelativePathReference(UriInterface $uri) | bool | Checks whether the URI is a relative-path reference. | 
| withoutQueryValue(UriInterface $uri, string $key) | UriInterface | Removes a specific query string value. Any existing query string values that exactly match the provided $keyare removed. | 
| 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 $keyare removed and replaced with the given$key-$valuepair. A value of null will set the query string key without a value, e.g. "key" instead of "key=value". | 
| parseUrl(string $url) | ?array | UTF-8 aware \parse_url()replacement. | 
| method | return | info | 
|---|---|---|
| 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) | 
| getFromFilename(string $filename) | ?string | Get the mime type from a file name | 
| getFromContent(string $content) | ?string | Get the mime type from the given content | 
| method | return | info | 
|---|---|---|
| getContents(string $extension) | string | Reads the content from a stream and make sure we rewind | 
| 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. | 
| tryFopen(string $filename, string $mode, $context = null) | resource | Safely open a PHP resource, throws instead of raising warnings and errors | 
| tryGetContents($stream, int $length = null, int $offset = -1) | string | Safely get the contents of a stream resource, throws instead of raising warnings and errors | 
| validateMode(string $mode) | string | Checks if the given mode is valid for fopen() | 
| modeAllowsReadWrite(string $mode) | bool | Checks whether the given mode allows reading and writing | 
| modeAllowsReadOnly(string $mode) | bool | Checks whether the given mode allows only reading | 
| modeAllowsWriteOnly(string $mode) | bool | Checks whether the given mode allows only writing | 
| modeAllowsRead(string $mode) | bool | Checks whether the given mode allows reading | 
| modeAllowsWrite(string $mode) | bool | Checks whether the given mode allows writing | 
The ServerUtil object requires a set of PSR-17 factories on invocation, namely ServerRequestFactoryInterface, UriFactoryInterface, UploadedFileFactoryInterface and StreamFactoryInterface.
It provides convenience methods to create server requests, URIs and uploaded files from the superglobals.
| method | return | info | 
|---|---|---|
| createServerRequestFromGlobals() | ServerRequestInterface | Returns a ServerRequest object populated from the superglobals $_GET,$_POST,$_COOKIE,$_FILESand$_SERVER. | 
| createUriFromGlobals() | UriInterface | Creates an Uri populated with values from $_SERVER(HTTP_HOST,SERVER_NAME,SERVER_ADDR,SERVER_PORT,REQUEST_URI,QUERY_STRING). | 
| normalizeFiles(array $files) | UploadedFileInterface[] | Returns an UploadedFileinstance array. | 
| createUploadedFileFromSpec(array $value) | UploadedFileInterfaceorUploadedFileInterface[] | Creates an UploadedFile instance from a $_FILESspecification. If the specification represents an array of values, this method will delegate tonormalizeNestedFileSpec()and return that return value. | 
| normalizeNestedFileSpec(array $files):array | array | Normalizes an array of file specifications. Loops through all nested files and returns a normalized array of UploadedFileInterfaceinstances. | 
Implements a HTTP cookie
| method | return | info | 
|---|---|---|
| __construct(string $name, string $value = null) | - | |
| __toString() | string | returns the full cookie string to use in a Set-Cookieheader | 
| withNameAndValue(string $name, string $value) | Cookie | |
| withExpiry(DateTimeInterface|DateInterval|int|null $expiry) | Cookie | |
| withDomain(string|null $domain, bool $punycode = null) | Cookie | |
| withPath(string|null $path) | Cookie | |
| withSecure(bool $secure) | Cookie | |
| withHttpOnly(bool $httpOnly) | Cookie | |
| withSameSite(string|null $sameSite) | Cookie |