From 36858cf5166911cc9bb920cbec7b27465e335c55 Mon Sep 17 00:00:00 2001 From: Adil Date: Fri, 12 Apr 2024 19:53:29 +0600 Subject: [PATCH 1/5] feat: added new options for multipart/form-data request --- src/Services/HttpRequestService.php | 45 ++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/Services/HttpRequestService.php b/src/Services/HttpRequestService.php index a366d86..0eb06e0 100755 --- a/src/Services/HttpRequestService.php +++ b/src/Services/HttpRequestService.php @@ -129,6 +129,8 @@ public function send(string $method, string $url, array $data = [], array $heade protected function sendRequest($method, $url, array $data = [], array $headers = []): ResponseInterface { + $headers = array_change_key_case($headers); + $this->setOptions($headers); $this->setData($method, $headers, $data); @@ -222,15 +224,56 @@ protected function setData(string $method, array $headers, array $data = []): vo return; } - $headers = array_change_key_case($headers); $contentType = Arr::get($headers, 'content-type'); if (preg_match('/application\/json/', $contentType)) { $this->options['json'] = $data; } elseif (preg_match('/application\/x-www-form-urlencoded/', $contentType)) { $this->options['form_params'] = $data; + } elseif (preg_match('/multipart\/form-data/', $contentType)) { + $this->options['multipart'] = $this->getMultipartOptionReplacement($data); } else { $this->options['body'] = json_encode($data); } } + + protected function getMultipartOptionReplacement(array $data): array + { + Arr::forget($this->options, 'headers.content-type'); + + $options = []; + + foreach ($data as $key => $value) { + if (is_array($value)) { + $options = array_merge($options, $this->getArrayMultipartOptionReplacement($key, $value)); + } else { + $options[] = [ + 'name' => $key, + 'contents' => $value + ]; + } + } + + return $options; + } + + protected function getArrayMultipartOptionReplacement(string $parentKey, array $items): array + { + $options = []; + + foreach ($items as $key => $item) { + $preparedKey = "{$parentKey}[{$key}]"; + + if (is_array($item)) { + $options = array_merge($options, $this->getArrayMultipartOptionReplacement($preparedKey, $item)); + } else { + $options[] = [ + 'name' => $preparedKey, + 'contents' => $item + ]; + } + } + + return $options; + } } From b62a0994d308e57057d9560993c6b2ac52d1b757 Mon Sep 17 00:00:00 2001 From: Adil Date: Sat, 13 Apr 2024 17:44:56 +0600 Subject: [PATCH 2/5] feat: added parseMultipart content function --- composer.json | 3 +- composer.lock | 58 ++++++++++++++++++++++++++++- src/Services/HttpRequestService.php | 10 +++++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index edd6b1e..bbd201a 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "tecnickcom/tcpdf": "~6.2.22", "doctrine/dbal": "^3.6", "ext-json": "*", - "php-mock/php-mock-phpunit": "^2.9" + "php-mock/php-mock-phpunit": "^2.9", + "riverline/multipart-parser": "^2.1" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/composer.lock b/composer.lock index dc2ca29..4ba747b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "963fef6a7a25e2f0a5f48aff513f27d3", + "content-hash": "f6e2e07c87003f9316cfaf597b6a8f34", "packages": [ { "name": "brick/math", @@ -4494,6 +4494,62 @@ ], "time": "2021-09-25T23:10:38+00:00" }, + { + "name": "riverline/multipart-parser", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/Riverline/multipart-parser.git", + "reference": "7a9f4646db5181516c61b8e0225a343189beedcd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Riverline/multipart-parser/zipball/7a9f4646db5181516c61b8e0225a343189beedcd", + "reference": "7a9f4646db5181516c61b8e0225a343189beedcd", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.6.0" + }, + "require-dev": { + "laminas/laminas-diactoros": "^1.8.7 || ^2.11.1", + "phpunit/phpunit": "^5.7 || ^9.0", + "psr/http-message": "^1.0", + "symfony/psr-http-message-bridge": "^1.1 || ^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Riverline\\MultiPartParser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Romain Cambien", + "email": "romain@cambien.net" + }, + { + "name": "Riverline", + "homepage": "http://www.riverline.fr" + } + ], + "description": "One class library to parse multipart content with encoding and charset support.", + "keywords": [ + "http", + "multipart", + "parser" + ], + "support": { + "issues": "https://github.com/Riverline/multipart-parser/issues", + "source": "https://github.com/Riverline/multipart-parser/tree/2.1.2" + }, + "time": "2024-03-12T16:46:05+00:00" + }, { "name": "sebastian/cli-parser", "version": "1.0.1", diff --git a/src/Services/HttpRequestService.php b/src/Services/HttpRequestService.php index 0eb06e0..5ee15fb 100755 --- a/src/Services/HttpRequestService.php +++ b/src/Services/HttpRequestService.php @@ -7,6 +7,7 @@ use GuzzleHttp\Cookie\CookieJar; use Psr\Http\Message\ResponseInterface; use GuzzleHttp\Exception\RequestException; +use Riverline\MultiPartParser\StreamedPart; use RonasIT\Support\Exceptions\InvalidJSONFormatException; use RonasIT\Support\Exceptions\UnknownRequestMethodException; @@ -276,4 +277,13 @@ protected function getArrayMultipartOptionReplacement(string $parentKey, array $ return $options; } + + protected function parseMultipart(string $content): StreamedPart + { + $stream = fopen('php://temp', 'rw'); + fwrite($stream, $content); + rewind($stream); + + return app()->makeWith(StreamedPart::class, ['stream' => $stream]); + } } From 61001584d9e3ffccb7afac66903f08c7878b65d4 Mon Sep 17 00:00:00 2001 From: Adil Date: Sat, 13 Apr 2024 18:43:53 +0600 Subject: [PATCH 3/5] feat: tests added --- src/Services/HttpRequestService.php | 18 ++--- tests/HttpRequestServiceTest.php | 67 +++++++++++++++++++ .../HttpRequestServiceTest/multipart_content | 21 ++++++ .../parsed_multipart_content.json | 14 ++++ 4 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 tests/fixtures/HttpRequestServiceTest/multipart_content create mode 100644 tests/fixtures/HttpRequestServiceTest/parsed_multipart_content.json diff --git a/src/Services/HttpRequestService.php b/src/Services/HttpRequestService.php index 5ee15fb..ec699d5 100755 --- a/src/Services/HttpRequestService.php +++ b/src/Services/HttpRequestService.php @@ -128,6 +128,15 @@ public function send(string $method, string $url, array $data = [], array $heade } } + public function parseMultipart(string $content): StreamedPart + { + $stream = fopen('php://temp', 'rw'); + fwrite($stream, $content); + rewind($stream); + + return app()->makeWith(StreamedPart::class, ['stream' => $stream]); + } + protected function sendRequest($method, $url, array $data = [], array $headers = []): ResponseInterface { $headers = array_change_key_case($headers); @@ -277,13 +286,4 @@ protected function getArrayMultipartOptionReplacement(string $parentKey, array $ return $options; } - - protected function parseMultipart(string $content): StreamedPart - { - $stream = fopen('php://temp', 'rw'); - fwrite($stream, $content); - rewind($stream); - - return app()->makeWith(StreamedPart::class, ['stream' => $stream]); - } } diff --git a/tests/HttpRequestServiceTest.php b/tests/HttpRequestServiceTest.php index ea7aaea..d4144e9 100644 --- a/tests/HttpRequestServiceTest.php +++ b/tests/HttpRequestServiceTest.php @@ -163,6 +163,73 @@ public function testSendPutWithMultipartContentType() ]); } + public function testSendPutMultipartContentTypeWithFiles(): void + { + $this->mockGuzzleClient('put', [ + 'https://some.url.com', + [ + 'headers' => [], + 'cookies' => null, + 'allow_redirects' => true, + 'connect_timeout' => 0, + 'multipart' => [ + [ + 'name' => '0[first_file]', + 'contents' => 'first_file_content', + ], + [ + 'name' => '0[second_file][first_file]', + 'contents' => 'first_file_content', + ], + [ + 'name' => '0[second_file][second_file]', + 'contents' => 'second_file_content', + ], + [ + 'name' => '1[first_file]', + 'contents' => 'first_file_content', + ], + [ + 'name' => '1[second_file]', + 'contents' => 'second_file_content', + ] + ] + ] + ]); + + $this->httpRequestServiceClass->put('https://some.url.com', [ + [ + 'first_file' => 'first_file_content', + 'second_file' => [ + 'first_file' => 'first_file_content', + 'second_file' => 'second_file_content' + ] + ], + [ + 'first_file' => 'first_file_content', + 'second_file' => 'second_file_content' + ] + ], [ + 'Content-type' => 'multipart/form-data;' + ]); + } + + public function testParseMultipartContent(): void + { + $multipartContent = $this->getFixture('multipart_content'); + + $multipartObject = $this->httpRequestServiceClass->parseMultipart($multipartContent); + + $parsedData = []; + + foreach ($multipartObject->getParts() as $part) + { + $parsedData[] = [$part->getName(), $part->getBody()]; + } + + $this->assertEqualsFixture('parsed_multipart_content.json', $parsedData); + } + public function sendPutAsJSONData(): array { return [ diff --git a/tests/fixtures/HttpRequestServiceTest/multipart_content b/tests/fixtures/HttpRequestServiceTest/multipart_content new file mode 100644 index 0000000..4f701bf --- /dev/null +++ b/tests/fixtures/HttpRequestServiceTest/multipart_content @@ -0,0 +1,21 @@ +<< Date: Sat, 13 Apr 2024 19:05:32 +0600 Subject: [PATCH 4/5] chore: readability fix --- tests/HttpRequestServiceTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/HttpRequestServiceTest.php b/tests/HttpRequestServiceTest.php index d4144e9..5bc8722 100644 --- a/tests/HttpRequestServiceTest.php +++ b/tests/HttpRequestServiceTest.php @@ -222,8 +222,7 @@ public function testParseMultipartContent(): void $parsedData = []; - foreach ($multipartObject->getParts() as $part) - { + foreach ($multipartObject->getParts() as $part) { $parsedData[] = [$part->getName(), $part->getBody()]; } From e9ad27ac4fc75896e2a40a91dc555a26c56a67e8 Mon Sep 17 00:00:00 2001 From: Adil Date: Sat, 13 Apr 2024 19:20:28 +0600 Subject: [PATCH 5/5] fix: tests fixed --- tests/HttpRequestServiceTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/HttpRequestServiceTest.php b/tests/HttpRequestServiceTest.php index 5bc8722..d1dfe3d 100644 --- a/tests/HttpRequestServiceTest.php +++ b/tests/HttpRequestServiceTest.php @@ -144,7 +144,7 @@ public function testSendPutWithMultipartContentType() [ 'headers' => [ 'some_header' => 'some_header_value', - 'Content-type' => 'application/x-www-form-urlencoded' + 'content-type' => 'application/x-www-form-urlencoded' ], 'cookies' => null, 'allow_redirects' => true, @@ -260,7 +260,7 @@ public function testSendPutAsJSON(array $headers) $this->mockGuzzleClient('put', [ 'https://some.url.com', [ - 'headers' => $headers, + 'headers' => ['content-type' => 'application/json'], 'cookies' => null, 'allow_redirects' => true, 'connect_timeout' => 0,