├── LICENSE ├── composer.json ├── composer.lock └── src ├── NotAnEncodedRequestException.php ├── NotAnEncodedResponseException.php ├── NotAnEncodedServerRequestException.php ├── NotAnEncodedUploadedFileException.php ├── functions.php └── functions_include.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Cees-Jan Kiewiet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wyrihaximus/json-psr7", 3 | "description": "JSON encode and decode PSR-7 requests and responses", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Cees-Jan Kiewiet", 8 | "email": "ceesjank@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "php": "^8 || ^7.4", 13 | "ancarda/psr7-string-stream": "^1.3", 14 | "cakephp/utility": "^3.7 || ^4.0", 15 | "laminas/laminas-diactoros": "^2.3", 16 | "psr/http-message": "^1.0 || ^2.0", 17 | "react/http": "^1.3", 18 | "ringcentral/psr7": "^1.2.2", 19 | "thecodingmachine/safe": "^2 || ^1.1", 20 | "wyrihaximus/json-utilities": "^1.2" 21 | }, 22 | "require-dev": { 23 | "guzzlehttp/psr7": "^1.6 || ^2.0", 24 | "nyholm/psr7": "^1.2", 25 | "slim/psr7": "^0.5.0 || ^0.6 || ^1.0", 26 | "wyrihaximus/test-utilities": "^5 || ^3.7.6" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "WyriHaximus\\": "src/" 31 | }, 32 | "files": [ 33 | "src/functions_include.php" 34 | ] 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "WyriHaximus\\Tests\\": "tests/" 39 | } 40 | }, 41 | "config": { 42 | "allow-plugins": { 43 | "composer/package-versions-deprecated": true, 44 | "infection/extension-installer": true, 45 | "dealerdirect/phpcodesniffer-composer-installer": true, 46 | "icanhazstring/composer-unused": true, 47 | "ergebnis/composer-normalize": true 48 | }, 49 | "platform": { 50 | "php": "7.4.7" 51 | }, 52 | "sort-packages": true 53 | }, 54 | "scripts": { 55 | "post-install-cmd": [ 56 | "composer normalize" 57 | ], 58 | "post-update-cmd": [ 59 | "composer normalize" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/NotAnEncodedRequestException.php: -------------------------------------------------------------------------------- 1 | getProtocolVersion(); 37 | $json['status_code'] = $response->getStatusCode(); 38 | $json['reason_phrase'] = $response->getReasonPhrase(); 39 | $json['headers'] = sort_headers($response->getHeaders()); 40 | $json['body'] = base64_encode((string) $response->getBody()); 41 | 42 | return $json; 43 | } 44 | 45 | /** 46 | * @throws NotAnEncodedResponseException 47 | */ 48 | function psr7_response_json_decode(string $json): ResponseInterface 49 | { 50 | return psr7_response_decode(json_decode($json, true)); 51 | } 52 | 53 | /** 54 | * @param array{protocol_version: string, status_code: int, reason_phrase: string, headers: array, body: string} $json 55 | * 56 | * @throws NotAnEncodedResponseException 57 | */ 58 | function psr7_response_decode(array $json): ResponseInterface 59 | { 60 | $properties = [ 61 | 'protocol_version' => 'string', 62 | 'status_code' => 'integer', 63 | 'reason_phrase' => 'string', 64 | 'headers' => 'array', 65 | 'body' => 'string', 66 | ]; 67 | 68 | validate_array($json, $properties, NotAnEncodedResponseException::class); 69 | 70 | return new Response( 71 | $json['status_code'], 72 | $json['headers'], 73 | new ReadOnlyStringStream(base64_decode($json['body'], true)), 74 | $json['protocol_version'], 75 | $json['reason_phrase'] 76 | ); 77 | } 78 | 79 | function psr7_request_json_encode(RequestInterface $request): string 80 | { 81 | return json_encode(psr7_request_encode($request)); 82 | } 83 | 84 | /** 85 | * @return array{protocol_version: string, method: string, uri: string, headers: array, body: string} 86 | */ 87 | function psr7_request_encode(RequestInterface $request): array 88 | { 89 | $json = []; 90 | $json['protocol_version'] = $request->getProtocolVersion(); 91 | $json['method'] = $request->getMethod(); 92 | $json['uri'] = (string) $request->getUri(); 93 | $json['headers'] = sort_headers($request->getHeaders()); 94 | $json['body'] = base64_encode((string) $request->getBody()); 95 | 96 | return $json; 97 | } 98 | 99 | /** 100 | * @throws NotAnEncodedRequestException 101 | */ 102 | function psr7_request_json_decode(string $json): RequestInterface 103 | { 104 | return psr7_request_decode(json_decode($json, true)); 105 | } 106 | 107 | /** 108 | * @param array{protocol_version: string, method: string, uri: string, headers: array, body: string} $json 109 | * 110 | * @throws NotAnEncodedRequestException 111 | */ 112 | function psr7_request_decode(array $json): RequestInterface 113 | { 114 | $properties = [ 115 | 'protocol_version' => 'string', 116 | 'method' => 'string', 117 | 'uri' => 'string', 118 | 'headers' => 'array', 119 | 'body' => 'string', 120 | ]; 121 | 122 | validate_array($json, $properties, NotAnEncodedRequestException::class); 123 | 124 | return new Request( 125 | $json['method'], 126 | $json['uri'], 127 | $json['headers'], 128 | new ReadOnlyStringStream(base64_decode($json['body'], true)), 129 | $json['protocol_version'] 130 | ); 131 | } 132 | 133 | function psr7_uploaded_file_json_encode(UploadedFileInterface $uploadedFile): string 134 | { 135 | return json_encode(psr7_uploaded_file_encode($uploadedFile)); 136 | } 137 | 138 | /** 139 | * @return array{stream: string, size: ?int, error: int, filename: ?string, media_type: ?string} 140 | */ 141 | function psr7_uploaded_file_encode(UploadedFileInterface $uploadedFile): array 142 | { 143 | $json = []; 144 | $json['filename'] = $uploadedFile->getClientFilename(); 145 | $json['media_type'] = $uploadedFile->getClientMediaType(); 146 | $json['error'] = $uploadedFile->getError(); 147 | $json['size'] = $uploadedFile->getSize(); 148 | $json['stream'] = base64_encode((string) $uploadedFile->getStream()); 149 | 150 | return $json; 151 | } 152 | 153 | /** 154 | * @throws NotAnEncodedUploadedFileException 155 | */ 156 | function psr7_uploaded_file_json_decode(string $json): UploadedFileInterface 157 | { 158 | return psr7_uploaded_file_decode(json_decode($json, true)); 159 | } 160 | 161 | /** 162 | * @param array{stream: string, size: int, error: int, filename: string, media_type: string} $json 163 | * 164 | * @throws NotAnEncodedUploadedFileException 165 | */ 166 | function psr7_uploaded_file_decode(array $json): UploadedFileInterface 167 | { 168 | $properties = [ 169 | 'stream' => 'string', 170 | 'size' => ['integer', 'NULL'], 171 | 'error' => 'integer', 172 | 'filename' => ['string', 'NULL'], 173 | 'media_type' => ['string', 'NULL'], 174 | ]; 175 | 176 | validate_array($json, $properties, NotAnEncodedUploadedFileException::class); 177 | 178 | /** 179 | * @psalm-suppress InternalMethod 180 | */ 181 | return new UploadedFile( 182 | new ReadOnlyStringStream(base64_decode($json['stream'], true)), 183 | $json['size'], 184 | $json['error'], 185 | $json['filename'], 186 | $json['media_type'] 187 | ); 188 | } 189 | 190 | function psr7_server_request_json_encode(ServerRequestInterface $request): string 191 | { 192 | return json_encode(psr7_server_request_encode($request)); 193 | } 194 | 195 | /** 196 | * @return array{protocol_version: string, method: string, uri: string, query_params: array, cookie_params: array, server_params: array, headers: array, attributes: array, body: string, parsed_body: (array|object|null), files: array} 197 | */ 198 | function psr7_server_request_encode(ServerRequestInterface $request): array 199 | { 200 | $json = []; 201 | $json['protocol_version'] = $request->getProtocolVersion(); 202 | $json['method'] = $request->getMethod(); 203 | $json['uri'] = (string) $request->getUri(); 204 | $json['query_params'] = $request->getQueryParams(); 205 | $json['cookie_params'] = $request->getCookieParams(); 206 | $json['server_params'] = $request->getServerParams(); 207 | $json['headers'] = sort_headers($request->getHeaders()); 208 | $json['attributes'] = $request->getAttributes(); 209 | $json['body'] = base64_encode((string) $request->getBody()); 210 | $json['parsed_body'] = $request->getParsedBody(); 211 | $json['files'] = $request->getUploadedFiles(); 212 | $json['files'] = Hash::flatten($json['files']); 213 | foreach ($json['files'] as $key => $file) { 214 | $json['files'][$key] = psr7_uploaded_file_encode($file); 215 | } 216 | 217 | return $json; 218 | } 219 | 220 | /** 221 | * @throws NotAnEncodedServerRequestException 222 | * @throws NotAnEncodedUploadedFileException 223 | */ 224 | function psr7_server_request_json_decode(string $json): ServerRequestInterface 225 | { 226 | return psr7_server_request_decode(json_decode($json, true)); 227 | } 228 | 229 | /** 230 | * @param array{protocol_version: string, method: string, uri: string, query_params: array, cookie_params: array, server_params: array, headers: array, attributes: array, body: string, parsed_body: (array|object|null), files: array} $json 231 | * 232 | * @throws NotAnEncodedServerRequestException 233 | * @throws NotAnEncodedUploadedFileException 234 | */ 235 | function psr7_server_request_decode(array $json): ServerRequestInterface 236 | { 237 | $properties = [ 238 | 'protocol_version' => 'string', 239 | 'method' => 'string', 240 | 'uri' => 'string', 241 | 'query_params' => 'array', 242 | 'cookie_params' => 'array', 243 | 'server_params' => 'array', 244 | 'headers' => 'array', 245 | 'attributes' => 'array', 246 | 'body' => 'string', 247 | 'parsed_body' => ['array', 'object', 'NULL'], 248 | 'files' => 'array', 249 | ]; 250 | 251 | validate_array($json, $properties, NotAnEncodedServerRequestException::class); 252 | 253 | /** @psalm-suppress ImplicitToStringCast */ 254 | $request = (new ServerRequest( 255 | $json['method'], 256 | $json['uri'], 257 | $json['headers'], 258 | new ReadOnlyStringStream(base64_decode($json['body'], true)), 259 | $json['protocol_version'], 260 | $json['server_params'] 261 | ))-> 262 | withParsedBody($json['parsed_body'])-> 263 | withUploadedFiles($json['files'])-> 264 | withQueryParams($json['query_params'])-> 265 | withCookieParams($json['cookie_params']); 266 | 267 | foreach ($json['attributes'] as $key => $value) { 268 | $request = $request->withAttribute($key, $value); 269 | } 270 | 271 | if (count($json['files']) > 0) { 272 | foreach ($json['files'] as $key => $file) { 273 | $json['files'][$key] = psr7_uploaded_file_decode($file); 274 | } 275 | 276 | $json['files'] = Hash::expand($json['files']); 277 | $request = $request->withUploadedFiles($json['files']); 278 | } 279 | 280 | return $request; 281 | } 282 | 283 | /** 284 | * @param array> $headers 285 | * 286 | * @return array> 287 | */ 288 | function sort_headers(array $headers): array 289 | { 290 | ksort($headers); 291 | 292 | return $headers; 293 | } 294 | -------------------------------------------------------------------------------- /src/functions_include.php: -------------------------------------------------------------------------------- 1 |