From f0bce589ebc9d14864673cba202c731ae15a2b06 Mon Sep 17 00:00:00 2001 From: KevinFrydman Date: Mon, 2 Oct 2023 16:01:22 +0200 Subject: [PATCH 01/10] Add UploadOrderDocument operation to SF SDK --- src/Api/Order/OrderOperation.php | 103 ++++++++++++++++-- src/Api/Order/UploadOrderDocument.php | 54 +++++++++ .../Order/UploadOrderDocumentCollection.php | 19 ++++ src/Hal/HalLink.php | 5 + 4 files changed, 169 insertions(+), 12 deletions(-) create mode 100644 src/Api/Order/UploadOrderDocument.php create mode 100644 src/Api/Order/UploadOrderDocumentCollection.php diff --git a/src/Api/Order/OrderOperation.php b/src/Api/Order/OrderOperation.php index ebf696d5..a206ebae 100644 --- a/src/Api/Order/OrderOperation.php +++ b/src/Api/Order/OrderOperation.php @@ -11,13 +11,14 @@ class OrderOperation extends Operation\AbstractBulkOperation /** * Operation types */ - const TYPE_ACCEPT = 'accept'; - const TYPE_CANCEL = 'cancel'; - const TYPE_REFUSE = 'refuse'; - const TYPE_SHIP = 'ship'; - const TYPE_REFUND = 'refund'; - const TYPE_ACKNOWLEDGE = 'acknowledge'; - const TYPE_UNACKNOWLEDGE = 'unacknowledge'; + const TYPE_ACCEPT = 'accept'; + const TYPE_CANCEL = 'cancel'; + const TYPE_REFUSE = 'refuse'; + const TYPE_SHIP = 'ship'; + const TYPE_REFUND = 'refund'; + const TYPE_ACKNOWLEDGE = 'acknowledge'; + const TYPE_UNACKNOWLEDGE = 'unacknowledge'; + const TYPE_UPLOAD_DOCUMENTS = 'upload-documents'; /** * @var array @@ -30,6 +31,7 @@ class OrderOperation extends Operation\AbstractBulkOperation self::TYPE_REFUND, self::TYPE_ACKNOWLEDGE, self::TYPE_UNACKNOWLEDGE, + self::TYPE_UPLOAD_DOCUMENTS, ]; /** @@ -179,6 +181,22 @@ public function unacknowledge($reference, $channelName) return $this; } + public function uploadDocuments( + $reference, + $channelName, + UploadOrderDocumentCollection $collection + ): self + { + $this->addOperation( + $reference, + $channelName, + self::TYPE_UPLOAD_DOCUMENTS, + ['documents' => $collection->getDocuments()] + ); + + return $this; + } + /** * Execute all declared operations * @@ -225,14 +243,75 @@ function (Hal\HalResource $batch) use ($resources) { private function createRequestGenerator($type, Hal\HalLink $link, \ArrayAccess $requests) { return function (array $chunk) use ($type, $link, &$requests) { - $requests[] = $link->createRequest( - 'POST', - ['operation' => $type], - ['order' => $chunk] - ); + if ($type === self::TYPE_UPLOAD_DOCUMENTS) { + $requests[] = $this->createUploadDocumentRequest($link, $chunk); + } else { + $requests[] = $link->createRequest( + 'POST', + ['operation' => $type], + ['order' => $chunk] + ); + } }; } + /** + * Create custom request generation callback for uploadDocument + * + * @param Hal\HalLink $link + * @param array $requests + * + * @return \Psr\Http\Message\RequestInterface + */ + private function createUploadDocumentRequest(Hal\HalLink $link, array $chunk) + { + $client = $link->getHalClient(); + + $files = []; + $payload = [ + 'order' => [], + ]; + + foreach ($chunk as $operation) { + $reference = $operation['reference']; + $channelName = $operation['channelName']; + + $documents = []; + + foreach ($operation['documents'] as $document) { + /** @var UploadOrderDocument $document */ + $files[] = [ + 'name' => 'files[]', + 'contents' => fopen($document->getPath(), 'rb'), + ]; + $documents[] = ['type' => $document->getPath()]; + } + + $payload['order'][] = [ + 'reference' => $reference, + 'channelName' => $channelName, + 'documents' => $documents, + ]; + } + + return $client->createRequest( + 'POST', + $link->getUri(['operation' => self::TYPE_UPLOAD_DOCUMENTS]), + [ + 'Content-Type' => 'multipart/form-data', + ], + [ + 'multipart' => [ + $files, + [ + 'name' => 'body', + 'contents' => json_encode($payload), + ], + ], + ] + ); + } + /** * Add operation to queue * diff --git a/src/Api/Order/UploadOrderDocument.php b/src/Api/Order/UploadOrderDocument.php new file mode 100644 index 00000000..61f5bc28 --- /dev/null +++ b/src/Api/Order/UploadOrderDocument.php @@ -0,0 +1,54 @@ +isAllowedType($type)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Only %s types are accepted', + implode(', ', self::TYPES) + )); + } + + $this->path = $path; + $this->type = $type; + } + + public function getPath(): string + { + return $this->path; + } + + public function getType(): string + { + return $this->type; + } + + private function isAllowedType(string $type): bool + { + return in_array($type, self::TYPES); + } +} diff --git a/src/Api/Order/UploadOrderDocumentCollection.php b/src/Api/Order/UploadOrderDocumentCollection.php new file mode 100644 index 00000000..63eb477c --- /dev/null +++ b/src/Api/Order/UploadOrderDocumentCollection.php @@ -0,0 +1,19 @@ +documents[] = $document; + } + + public function getDocuments(): array + { + return $this->documents; + } +} diff --git a/src/Hal/HalLink.php b/src/Hal/HalLink.php index 82e3002f..54529f2b 100644 --- a/src/Hal/HalLink.php +++ b/src/Hal/HalLink.php @@ -302,4 +302,9 @@ private function createExceptionCallback(callable $callback = null) call_user_func($callback, $exception); }; } + + public function getHalClient() + { + return $this->client; + } } From 2de4bf6d40eba5b65abbf0a28cc0250f4b10ecae Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 14 Nov 2023 17:56:46 +0100 Subject: [PATCH 02/10] - Handle 20 files limit for upload documents - Add documentation - Simplify code --- docs/manual/resources/order.md | 20 +++ src/Api/Order/Document/AbstractDocument.php | 34 +++++ src/Api/Order/Document/Invoice.php | 11 ++ src/Api/Order/OrderOperation.php | 121 +++++++++--------- src/Api/Order/UploadOrderDocument.php | 54 -------- .../Order/UploadOrderDocumentCollection.php | 19 --- src/Hal/HalLink.php | 17 ++- 7 files changed, 137 insertions(+), 139 deletions(-) create mode 100644 src/Api/Order/Document/AbstractDocument.php create mode 100644 src/Api/Order/Document/Invoice.php delete mode 100644 src/Api/Order/UploadOrderDocument.php delete mode 100644 src/Api/Order/UploadOrderDocumentCollection.php diff --git a/docs/manual/resources/order.md b/docs/manual/resources/order.md index f321f96b..1f1eee16 100644 --- a/docs/manual/resources/order.md +++ b/docs/manual/resources/order.md @@ -259,3 +259,23 @@ $operation $orderApi->execute($operation); ``` + +### Upload documents + +To upload order documents, you need the following parameters : +1. [mandatory] `$reference` : Order reference (eg: 'reference1') +2. [mandatory] `$channelName` : The channel where the order is from (eg: 'amazon') +3. [mandatory] `$documents` : One or more documents to upload + +Example : + +```php +namespace ShoppingFeed\Sdk\Api\Order; + +$operation = new OrderOperation(); +$operation + ->uploadDocument('ref1', 'amazon', new Document\Invoice('/tmp/amazon_ref1_invoice.pdf')) + ->uploadDocument('ref2', 'amazon', new Document\Invoice('/tmp/amazon_ref2_invoice.pdf')); + +$orderApi->execute($operation); +``` diff --git a/src/Api/Order/Document/AbstractDocument.php b/src/Api/Order/Document/AbstractDocument.php new file mode 100644 index 00000000..9a85641a --- /dev/null +++ b/src/Api/Order/Document/AbstractDocument.php @@ -0,0 +1,34 @@ +path = $path; + $this->type = $type; + } + + public function getPath(): string + { + return $this->path; + } + + public function getType(): string + { + return $this->type; + } +} diff --git a/src/Api/Order/Document/Invoice.php b/src/Api/Order/Document/Invoice.php new file mode 100644 index 00000000..37fe5425 --- /dev/null +++ b/src/Api/Order/Document/Invoice.php @@ -0,0 +1,11 @@ +addOperation( $reference, $channelName, self::TYPE_UPLOAD_DOCUMENTS, - ['documents' => $collection->getDocuments()] + ['document' => $document] ); return $this; @@ -210,10 +217,7 @@ public function execute(Hal\HalLink $link) $resources = new \ArrayObject(); foreach ($this->allowedOperationTypes as $type) { - $this->eachBatch( - $this->createRequestGenerator($type, $link, $requests), - $type - ); + $this->populateRequests($type, $link, $requests); } $link->batchSend( @@ -231,85 +235,78 @@ function (Hal\HalResource $batch) use ($resources) { ); } - /** - * Create request generation callback - * - * @param string $type - * @param Hal\HalLink $link - * @param array $requests - * - * @return \Closure - */ - private function createRequestGenerator($type, Hal\HalLink $link, \ArrayAccess $requests) + private function populateRequests($type, Hal\HalLink $link, \ArrayAccess $requests): void { - return function (array $chunk) use ($type, $link, &$requests) { - if ($type === self::TYPE_UPLOAD_DOCUMENTS) { - $requests[] = $this->createUploadDocumentRequest($link, $chunk); - } else { + // Upload documents require dedicated processing because of file upload specificities + if (self::TYPE_UPLOAD_DOCUMENTS === $type) { + $this->populateRequestsForUploadDocuments($link, $requests); + return; + } + + $this->eachBatch( + function (array $chunk) use ($type, $link, &$requests) { $requests[] = $link->createRequest( 'POST', ['operation' => $type], ['order' => $chunk] ); - } - }; + }, + $type + ); } /** - * Create custom request generation callback for uploadDocument + * Create requests for upload documents operation. We batch request by 20 + * to not send too many files at once. * * @param Hal\HalLink $link * @param array $requests * * @return \Psr\Http\Message\RequestInterface */ - private function createUploadDocumentRequest(Hal\HalLink $link, array $chunk) + private function populateRequestsForUploadDocuments(Hal\HalLink $link, \ArrayAccess $requests) { - $client = $link->getHalClient(); - - $files = []; - $payload = [ - 'order' => [], - ]; + $type = self::TYPE_UPLOAD_DOCUMENTS; - foreach ($chunk as $operation) { - $reference = $operation['reference']; - $channelName = $operation['channelName']; + foreach (array_chunk($this->getOperations($type), 20) as $batch) { + $files = []; + $payload = []; - $documents = []; + foreach ($batch as $operation) { + /** @var AbstractDocument $document */ + $document = $operation['document']; - foreach ($operation['documents'] as $document) { - /** @var UploadOrderDocument $document */ - $files[] = [ + $files[] = [ 'name' => 'files[]', - 'contents' => fopen($document->getPath(), 'rb'), + 'contents' => Psr7\Utils::tryFopen($document->getPath(), 'rb'), ]; - $documents[] = ['type' => $document->getPath()]; - } - $payload['order'][] = [ - 'reference' => $reference, - 'channelName' => $channelName, - 'documents' => $documents, - ]; - } + $payload[] = [ + 'reference' => $operation['reference'], + 'channelName' => $operation['channelName'], + 'documents' => [ + ['type' => $document->getType()] + ], + ]; + } - return $client->createRequest( - 'POST', - $link->getUri(['operation' => self::TYPE_UPLOAD_DOCUMENTS]), - [ - 'Content-Type' => 'multipart/form-data', - ], - [ - 'multipart' => [ + $requests[] = $link->createRequest( + 'POST', + ['operation' => $type], + [ $files, [ 'name' => 'body', - 'contents' => json_encode($payload), + 'contents' => json_encode([ + 'order' => $payload, + ]), ], ], - ] - ); + [ + 'Content-Type' => 'multipart/form-data', + ] + ); + } } /** diff --git a/src/Api/Order/UploadOrderDocument.php b/src/Api/Order/UploadOrderDocument.php deleted file mode 100644 index 61f5bc28..00000000 --- a/src/Api/Order/UploadOrderDocument.php +++ /dev/null @@ -1,54 +0,0 @@ -isAllowedType($type)) { - throw new Exception\InvalidArgumentException(sprintf( - 'Only %s types are accepted', - implode(', ', self::TYPES) - )); - } - - $this->path = $path; - $this->type = $type; - } - - public function getPath(): string - { - return $this->path; - } - - public function getType(): string - { - return $this->type; - } - - private function isAllowedType(string $type): bool - { - return in_array($type, self::TYPES); - } -} diff --git a/src/Api/Order/UploadOrderDocumentCollection.php b/src/Api/Order/UploadOrderDocumentCollection.php deleted file mode 100644 index 63eb477c..00000000 --- a/src/Api/Order/UploadOrderDocumentCollection.php +++ /dev/null @@ -1,19 +0,0 @@ -documents[] = $document; - } - - public function getDocuments(): array - { - return $this->documents; - } -} diff --git a/src/Hal/HalLink.php b/src/Hal/HalLink.php index 54529f2b..402d41a0 100644 --- a/src/Hal/HalLink.php +++ b/src/Hal/HalLink.php @@ -262,15 +262,24 @@ public function send($request, array $config = []) * * @return \Psr\Http\Message\RequestInterface */ - public function createRequest($method, array $variables = [], $body = null) + public function createRequest($method, array $variables = [], $body = null, $headers = []) { $uri = $this->getUri($variables); $method = strtoupper($method); - $headers = []; if ((null !== $body && '' !== $body) && in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'])) { - $headers['Content-Type'] = 'application/json'; - $body = Json::encode($body); + if (! isset($headers['Content-Type'])) { + $headers['Content-Type'] = 'application/json'; + } + + switch ($headers['Content-Type']) { + case 'application/json': + $body = Json::encode($body); + break; + case 'multipart/form-data': + $body = ['multipart' => $body]; + break; + } } return $this->client->createRequest($method, $uri, $headers, $body); From 3285d71489316d1d8caa3e1f4e3b959232891f8d Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 14 Nov 2023 18:00:01 +0100 Subject: [PATCH 03/10] Remove HalLink::getClient() --- src/Hal/HalLink.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Hal/HalLink.php b/src/Hal/HalLink.php index 402d41a0..0a67b42b 100644 --- a/src/Hal/HalLink.php +++ b/src/Hal/HalLink.php @@ -311,9 +311,4 @@ private function createExceptionCallback(callable $callback = null) call_user_func($callback, $exception); }; } - - public function getHalClient() - { - return $this->client; - } } From 5ac5b4f683d4d2d5c82c5569613fcc36e3726c9b Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 15 Nov 2023 10:07:55 +0100 Subject: [PATCH 04/10] Fix CI --- Dockerfile | 2 +- composer.json | 9 ++++++--- docker-compose.yml | 1 + docs/development/development.md | 5 +++++ src/Api/Order/Document/AbstractDocument.php | 3 ++- src/Api/Order/OrderOperation.php | 6 ++---- src/Hal/HalLink.php | 4 ++-- 7 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2a91ee08..3031b4f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM php:5.6-cli-alpine +FROM php:7.1-cli-alpine ARG MOUNTPOINT=/var/www ARG COMPOSER_BIN_DIR=/usr/local/bin diff --git a/composer.json b/composer.json index 1540d5fa..fe216b87 100644 --- a/composer.json +++ b/composer.json @@ -27,9 +27,12 @@ }, "scripts": { "test": [ - "@php vendor/bin/phpunit", - "@php vendor/bin/sfcs src --progress -vvv" - ] + "@tests-unit", + "@phpcs" + ], + "tests-unit": "vendor/bin/phpunit", + "phpcs": "vendor/bin/sfcs src --progress -vvv", + "phpcsfix": "vendor/bin/sfcs src --progress -vvv --autofix" }, "scripts-descriptions": { "test" : "Run PHPUnit tests suites and Coding standards validator" diff --git a/docker-compose.yml b/docker-compose.yml index 9a5001d7..d941fb89 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,7 @@ version: '3' services: sf-php-sdk-dev: + container_name: sfeed.php-sdk-dev image: php-sdk-dev build: context: . diff --git a/docs/development/development.md b/docs/development/development.md index 0d81efdc..d7f2c558 100644 --- a/docs/development/development.md +++ b/docs/development/development.md @@ -23,6 +23,11 @@ docker-compose build docker-compose run sf-php-sdk-dev composer install --dev ``` +3. Connect to container +```bash +docker-compose run sf-php-sdk-dev /bin/sh +``` + ## Code checks To help you test your code against our requirements, there is a composer test script configured : diff --git a/src/Api/Order/Document/AbstractDocument.php b/src/Api/Order/Document/AbstractDocument.php index 9a85641a..afdd2e25 100644 --- a/src/Api/Order/Document/AbstractDocument.php +++ b/src/Api/Order/Document/AbstractDocument.php @@ -17,7 +17,8 @@ abstract class AbstractDocument public function __construct( string $path, string $type - ) { + ) + { $this->path = $path; $this->type = $type; } diff --git a/src/Api/Order/OrderOperation.php b/src/Api/Order/OrderOperation.php index 791a5139..52b3eb99 100644 --- a/src/Api/Order/OrderOperation.php +++ b/src/Api/Order/OrderOperation.php @@ -285,7 +285,7 @@ private function populateRequestsForUploadDocuments(Hal\HalLink $link, \ArrayAcc 'reference' => $operation['reference'], 'channelName' => $operation['channelName'], 'documents' => [ - ['type' => $document->getType()] + ['type' => $document->getType()], ], ]; } @@ -297,9 +297,7 @@ private function populateRequestsForUploadDocuments(Hal\HalLink $link, \ArrayAcc $files, [ 'name' => 'body', - 'contents' => json_encode([ - 'order' => $payload, - ]), + 'contents' => json_encode(['order' => $payload]), ], ], [ diff --git a/src/Hal/HalLink.php b/src/Hal/HalLink.php index 0a67b42b..398100c8 100644 --- a/src/Hal/HalLink.php +++ b/src/Hal/HalLink.php @@ -264,8 +264,8 @@ public function send($request, array $config = []) */ public function createRequest($method, array $variables = [], $body = null, $headers = []) { - $uri = $this->getUri($variables); - $method = strtoupper($method); + $uri = $this->getUri($variables); + $method = strtoupper($method); if ((null !== $body && '' !== $body) && in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'])) { if (! isset($headers['Content-Type'])) { From 8b9865d6e3cc8051ebfeb8b9977c4e408e029b5f Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 15 Nov 2023 10:24:01 +0100 Subject: [PATCH 05/10] Add TU --- src/Api/Order/OrderOperation.php | 16 ++-- tests/unit/Api/Order/OrderOperationTest.php | 88 +++++++++++++-------- 2 files changed, 64 insertions(+), 40 deletions(-) diff --git a/src/Api/Order/OrderOperation.php b/src/Api/Order/OrderOperation.php index 52b3eb99..495fa883 100644 --- a/src/Api/Order/OrderOperation.php +++ b/src/Api/Order/OrderOperation.php @@ -13,14 +13,14 @@ class OrderOperation extends Operation\AbstractBulkOperation /** * Operation types */ - const TYPE_ACCEPT = 'accept'; - const TYPE_CANCEL = 'cancel'; - const TYPE_REFUSE = 'refuse'; - const TYPE_SHIP = 'ship'; - const TYPE_REFUND = 'refund'; - const TYPE_ACKNOWLEDGE = 'acknowledge'; - const TYPE_UNACKNOWLEDGE = 'unacknowledge'; - const TYPE_UPLOAD_DOCUMENTS = 'upload-documents'; + public const TYPE_ACCEPT = 'accept'; + public const TYPE_CANCEL = 'cancel'; + public const TYPE_REFUSE = 'refuse'; + public const TYPE_SHIP = 'ship'; + public const TYPE_REFUND = 'refund'; + public const TYPE_ACKNOWLEDGE = 'acknowledge'; + public const TYPE_UNACKNOWLEDGE = 'unacknowledge'; + public const TYPE_UPLOAD_DOCUMENTS = 'upload-documents'; /** * @var array diff --git a/tests/unit/Api/Order/OrderOperationTest.php b/tests/unit/Api/Order/OrderOperationTest.php index 7534ec80..fa46fac8 100644 --- a/tests/unit/Api/Order/OrderOperationTest.php +++ b/tests/unit/Api/Order/OrderOperationTest.php @@ -1,11 +1,10 @@ operationCount; $i++) { $orderOperation->addOperation( 'ref' . $i, 'amazon', - Sdk\Api\Order\OrderOperation::TYPE_ACCEPT + Api\Order\OrderOperation::TYPE_ACCEPT ); } } @@ -34,12 +33,12 @@ private function generateOperations(Sdk\Api\Order\OrderOperation $orderOperation */ public function testAddOperation() { - $orderOperation = new Sdk\Api\Order\OrderOperation(); + $orderOperation = new Api\Order\OrderOperation(); $this->generateOperations($orderOperation); $this->assertEquals( $this->operationCount, - $orderOperation->count(Sdk\Api\Order\OrderOperation::TYPE_ACCEPT) + $orderOperation->count(Api\Order\OrderOperation::TYPE_ACCEPT) ); } @@ -49,7 +48,7 @@ public function testAddOperation() public function testAcceptOperation() { $instance = $this - ->getMockBuilder(Sdk\Api\Order\OrderOperation::class) + ->getMockBuilder(Api\Order\OrderOperation::class) ->setMethods(['addOperation']) ->getMock(); @@ -59,12 +58,12 @@ public function testAcceptOperation() ->with( 'ref1', 'amazon', - Sdk\Api\Order\OrderOperation::TYPE_ACCEPT, + Api\Order\OrderOperation::TYPE_ACCEPT, ['reason' => 'noreason'] ); $this->assertInstanceOf( - Sdk\Api\Order\OrderOperation::class, + Api\Order\OrderOperation::class, $instance->accept( 'ref1', 'amazon', @@ -79,7 +78,7 @@ public function testAcceptOperation() public function testCancelOperation() { $instance = $this - ->getMockBuilder(Sdk\Api\Order\OrderOperation::class) + ->getMockBuilder(Api\Order\OrderOperation::class) ->setMethods(['addOperation']) ->getMock(); @@ -89,12 +88,12 @@ public function testCancelOperation() ->with( 'ref1', 'amazon', - Sdk\Api\Order\OrderOperation::TYPE_CANCEL, + Api\Order\OrderOperation::TYPE_CANCEL, ['reason' => 'noreason'] ); $this->assertInstanceOf( - Sdk\Api\Order\OrderOperation::class, + Api\Order\OrderOperation::class, $instance->cancel( 'ref1', 'amazon', @@ -109,7 +108,7 @@ public function testCancelOperation() public function testRefuseOperation() { $instance = $this - ->getMockBuilder(Sdk\Api\Order\OrderOperation::class) + ->getMockBuilder(Api\Order\OrderOperation::class) ->setMethods(['addOperation']) ->getMock(); @@ -119,11 +118,11 @@ public function testRefuseOperation() ->with( 'ref1', 'amazon', - Sdk\Api\Order\OrderOperation::TYPE_REFUSE + Api\Order\OrderOperation::TYPE_REFUSE ); $this->assertInstanceOf( - Sdk\Api\Order\OrderOperation::class, + Api\Order\OrderOperation::class, $instance->refuse( 'ref1', 'amazon' @@ -137,7 +136,7 @@ public function testRefuseOperation() public function testShipOperation() { $instance = $this - ->getMockBuilder(Sdk\Api\Order\OrderOperation::class) + ->getMockBuilder(Api\Order\OrderOperation::class) ->setMethods(['addOperation']) ->getMock(); @@ -147,7 +146,7 @@ public function testShipOperation() ->with( 'ref1', 'amazon', - Sdk\Api\Order\OrderOperation::TYPE_SHIP, + Api\Order\OrderOperation::TYPE_SHIP, [ 'carrier' => 'ups', 'trackingNumber' => '123654abc', @@ -156,7 +155,7 @@ public function testShipOperation() ); $this->assertInstanceOf( - Sdk\Api\Order\OrderOperation::class, + Api\Order\OrderOperation::class, $instance->ship( 'ref1', 'amazon', @@ -167,6 +166,31 @@ public function testShipOperation() ); } + public function testUploadDocumentOperation() + { + $document = new Api\Order\Document\Invoice('/tmp/ref1_amazon_invoice.pdf'); + + $instance = $this + ->getMockBuilder(Api\Order\OrderOperation::class) + ->setMethods(['addOperation']) + ->getMock(); + + $instance + ->expects($this->once()) + ->method('addOperation') + ->with( + 'ref1', + 'amazon', + Api\Order\OrderOperation::TYPE_UPLOAD_DOCUMENTS, + ['document' => $document] + ); + + $this->assertInstanceOf( + Api\Order\OrderOperation::class, + $instance->uploadDocument('ref1', 'amazon', $document) + ); + } + /** * @throws \Exception */ @@ -181,7 +205,7 @@ public function testAcknowledgeOperation() ]; $instance = $this - ->getMockBuilder(Sdk\Api\Order\OrderOperation::class) + ->getMockBuilder(Api\Order\OrderOperation::class) ->setMethods(['addOperation']) ->getMock(); @@ -191,7 +215,7 @@ public function testAcknowledgeOperation() ->with( 'ref1', 'amazon', - Sdk\Api\Order\OrderOperation::TYPE_ACKNOWLEDGE, + Api\Order\OrderOperation::TYPE_ACKNOWLEDGE, $this->callback( function ($param) { return $param['status'] === 'success' @@ -203,7 +227,7 @@ function ($param) { ); $this->assertInstanceOf( - Sdk\Api\Order\OrderOperation::class, + Api\Order\OrderOperation::class, $instance->acknowledge(...$data) ); } @@ -219,7 +243,7 @@ public function testUnacknowledgeOperation() 'Unacknowledged', ]; $instance = $this - ->getMockBuilder(Sdk\Api\Order\OrderOperation::class) + ->getMockBuilder(Api\Order\OrderOperation::class) ->setMethods(['addOperation']) ->getMock(); @@ -229,11 +253,11 @@ public function testUnacknowledgeOperation() ->with( 'ref2', 'amazon2', - Sdk\Api\Order\OrderOperation::TYPE_UNACKNOWLEDGE + Api\Order\OrderOperation::TYPE_UNACKNOWLEDGE ); $this->assertInstanceOf( - Sdk\Api\Order\OrderOperation::class, + Api\Order\OrderOperation::class, $instance->unacknowledge(...$data) ); } @@ -243,7 +267,7 @@ public function testUnacknowledgeOperation() */ public function testAddWrongOperation() { - $orderOperation = new Sdk\Api\Order\OrderOperation(); + $orderOperation = new Api\Order\OrderOperation(); $this->expectException(Sdk\Exception\InvalidArgumentException::class); @@ -264,9 +288,9 @@ public function testExecute() $this->createMock(RequestInterface::class) ); - /** @var Sdk\Api\Order\OrderOperation|\PHPUnit_Framework_MockObject_MockObject $instance */ + /** @var Api\Order\OrderOperation|\PHPUnit_Framework_MockObject_MockObject $instance */ $instance = $this - ->getMockBuilder(Sdk\Api\Order\OrderOperation::class) + ->getMockBuilder(Api\Order\OrderOperation::class) ->setMethods(['getPoolSize']) ->getMock(); @@ -291,7 +315,7 @@ function (Sdk\Hal\HalResource $resource) use (&$resources) { ); $this->assertInstanceOf( - Sdk\Api\Order\OrderOperationResult::class, + Api\Order\OrderOperationResult::class, $instance->execute($link) ); } @@ -302,7 +326,7 @@ function (Sdk\Hal\HalResource $resource) use (&$resources) { public function testRefundOperation() { $instance = $this - ->getMockBuilder(Sdk\Api\Order\OrderOperation::class) + ->getMockBuilder(Api\Order\OrderOperation::class) ->setMethods(['addOperation']) ->getMock(); @@ -312,7 +336,7 @@ public function testRefundOperation() ->with( 'ref1', 'amazon', - Sdk\Api\Order\OrderOperation::TYPE_REFUND, + Api\Order\OrderOperation::TYPE_REFUND, [ 'refund' => [ 'shipping' => true, @@ -325,7 +349,7 @@ public function testRefundOperation() ); $this->assertInstanceOf( - Sdk\Api\Order\OrderOperation::class, + Api\Order\OrderOperation::class, $instance->refund( 'ref1', 'amazon', From a757968c1733c1e74470e8a42372ded5a5aaf92c Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 15 Nov 2023 15:38:34 +0100 Subject: [PATCH 06/10] Fixes --- src/Api/Order/OrderOperation.php | 24 ++++++++++-------------- src/Hal/HalLink.php | 10 +++++----- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/Api/Order/OrderOperation.php b/src/Api/Order/OrderOperation.php index 495fa883..57e5858e 100644 --- a/src/Api/Order/OrderOperation.php +++ b/src/Api/Order/OrderOperation.php @@ -269,19 +269,19 @@ private function populateRequestsForUploadDocuments(Hal\HalLink $link, \ArrayAcc $type = self::TYPE_UPLOAD_DOCUMENTS; foreach (array_chunk($this->getOperations($type), 20) as $batch) { - $files = []; - $payload = []; + $body = []; + $orders = []; foreach ($batch as $operation) { /** @var AbstractDocument $document */ $document = $operation['document']; - $files[] = [ + $body[] = [ 'name' => 'files[]', 'contents' => Psr7\Utils::tryFopen($document->getPath(), 'rb'), ]; - $payload[] = [ + $orders[] = [ 'reference' => $operation['reference'], 'channelName' => $operation['channelName'], 'documents' => [ @@ -290,19 +290,15 @@ private function populateRequestsForUploadDocuments(Hal\HalLink $link, \ArrayAcc ]; } + $body[] = [ + 'name' => 'body', + 'contents' => json_encode(['order' => $orders]), + ]; + $requests[] = $link->createRequest( 'POST', ['operation' => $type], - [ - $files, - [ - 'name' => 'body', - 'contents' => json_encode(['order' => $payload]), - ], - ], - [ - 'Content-Type' => 'multipart/form-data', - ] + Psr7\Utils::streamFor(new Psr7\MultipartStream($body)) ); } } diff --git a/src/Hal/HalLink.php b/src/Hal/HalLink.php index 398100c8..f454d8b8 100644 --- a/src/Hal/HalLink.php +++ b/src/Hal/HalLink.php @@ -1,6 +1,7 @@ href = rtrim($instance->getUri($variables), '/') . - '/' . ltrim($path, '/'); + '/' . ltrim($path, '/'); return $instance; } @@ -267,7 +268,9 @@ public function createRequest($method, array $variables = [], $body = null, $hea $uri = $this->getUri($variables); $method = strtoupper($method); - if ((null !== $body && '' !== $body) && in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'])) { + $hasBody = null !== $body && '' !== $body && ! $body instanceof MultipartStream; + + if ($hasBody && in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'])) { if (! isset($headers['Content-Type'])) { $headers['Content-Type'] = 'application/json'; } @@ -276,9 +279,6 @@ public function createRequest($method, array $variables = [], $body = null, $hea case 'application/json': $body = Json::encode($body); break; - case 'multipart/form-data': - $body = ['multipart' => $body]; - break; } } From b2905986214a618c8400f8da203468a2edf89610 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 15 Nov 2023 16:28:32 +0100 Subject: [PATCH 07/10] Fix TU for more recent version of PHPUnit --- tests/unit/Hal/HalClientTest.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/unit/Hal/HalClientTest.php b/tests/unit/Hal/HalClientTest.php index b2e667d4..b5f0757a 100644 --- a/tests/unit/Hal/HalClientTest.php +++ b/tests/unit/Hal/HalClientTest.php @@ -1,10 +1,10 @@ expects($this->once()) ->method('getBody') - ->willReturn('{"foo":"bar", "foo2":"bar2"}'); + ->willReturn($this->createStream('{"foo":"bar", "foo2":"bar2"}')); $instance = new Hal\HalClient('http://fake.uri', $httpClient); $resource = $instance->createResource($response); @@ -155,7 +155,7 @@ public function testSendWithEmptyResponseSkipEncoding() $response = $this->createMock(Message\ResponseInterface::class); $response->method('getStatusCode')->willReturn(200); - $response->method('getBody')->willReturn(''); + $response->method('getBody')->willReturn($this->createStream('')); $client = $this->createMock(Http\Adapter\AdapterInterface::class); $client @@ -175,7 +175,7 @@ public function testItDecodeResponse() $response = $this->createMock(Message\ResponseInterface::class); $response->method('getStatusCode')->willReturn(200); - $response->method('getBody')->willReturn('{"status":"ok"}'); + $response->method('getBody')->willReturn($this->createStream('{"status":"ok"}')); $client = $this->createMock(Http\Adapter\AdapterInterface::class); $client @@ -196,4 +196,9 @@ public function testGetAdapter() $this->assertSame($httpClient, $instance->getAdapter()); } + + private function createStream(string $contents): Message\StreamInterface + { + return Psr7\Utils::streamFor($contents); + } } From 4a3ef547581be26468ca82a860da3d9e692a7a9a Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 15 Nov 2023 18:31:39 +0100 Subject: [PATCH 08/10] Remove useless streamFor --- src/Api/Order/OrderOperation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Api/Order/OrderOperation.php b/src/Api/Order/OrderOperation.php index 57e5858e..33b16911 100644 --- a/src/Api/Order/OrderOperation.php +++ b/src/Api/Order/OrderOperation.php @@ -298,7 +298,7 @@ private function populateRequestsForUploadDocuments(Hal\HalLink $link, \ArrayAcc $requests[] = $link->createRequest( 'POST', ['operation' => $type], - Psr7\Utils::streamFor(new Psr7\MultipartStream($body)) + new Psr7\MultipartStream($body) ); } } From a9b4fa8d53c4941685d58ea69fcaa0e2890aa0b6 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 23 Nov 2023 14:05:44 +0100 Subject: [PATCH 09/10] Set Guzzle dependencies inside GuzzleAdapter --- src/Api/Order/OrderOperation.php | 15 ++++++++++++--- src/Hal/HalLink.php | 3 +-- src/Http/Adapter/GuzzleHTTPAdapter.php | 5 +++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Api/Order/OrderOperation.php b/src/Api/Order/OrderOperation.php index 33b16911..57b1ad14 100644 --- a/src/Api/Order/OrderOperation.php +++ b/src/Api/Order/OrderOperation.php @@ -1,7 +1,7 @@ getPath(), 'rb'); + + if (false === $resource) { + throw new RuntimeException( + sprintf('Unable to read "%s"', $document->getPath()) + ); + } + $body[] = [ 'name' => 'files[]', - 'contents' => Psr7\Utils::tryFopen($document->getPath(), 'rb'), + 'contents' => $resource, ]; $orders[] = [ @@ -298,7 +306,8 @@ private function populateRequestsForUploadDocuments(Hal\HalLink $link, \ArrayAcc $requests[] = $link->createRequest( 'POST', ['operation' => $type], - new Psr7\MultipartStream($body) + $body, + ['Content-Type' => 'multipart/form-data'] ); } } diff --git a/src/Hal/HalLink.php b/src/Hal/HalLink.php index f454d8b8..794ab871 100644 --- a/src/Hal/HalLink.php +++ b/src/Hal/HalLink.php @@ -1,7 +1,6 @@ getUri($variables); $method = strtoupper($method); - $hasBody = null !== $body && '' !== $body && ! $body instanceof MultipartStream; + $hasBody = null !== $body && '' !== $body; if ($hasBody && in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'])) { if (! isset($headers['Content-Type'])) { diff --git a/src/Http/Adapter/GuzzleHTTPAdapter.php b/src/Http/Adapter/GuzzleHTTPAdapter.php index 3c7e789b..21201f1a 100644 --- a/src/Http/Adapter/GuzzleHTTPAdapter.php +++ b/src/Http/Adapter/GuzzleHTTPAdapter.php @@ -91,6 +91,11 @@ public function batchSend(array $requests, array $options = []) */ public function createRequest($method, $uri, array $headers = [], $body = null) { + if (isset($headers['Content-Type']) && 'multipart/form-data' === $headers['Content-Type']) { + unset($headers['Content-Type']); + $body = new GuzzleHttp\Psr7\MultipartStream($body); + } + return new GuzzleHttp\Psr7\Request($method, $uri, $headers, $body); } From 298d2a8f9ce75e252817e6b7bfeb847df3e4b580 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 23 Nov 2023 14:18:45 +0100 Subject: [PATCH 10/10] Use relevant channel example in upload-document documentation --- docs/manual/resources/order.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/resources/order.md b/docs/manual/resources/order.md index 1f1eee16..2a47b427 100644 --- a/docs/manual/resources/order.md +++ b/docs/manual/resources/order.md @@ -274,8 +274,8 @@ namespace ShoppingFeed\Sdk\Api\Order; $operation = new OrderOperation(); $operation - ->uploadDocument('ref1', 'amazon', new Document\Invoice('/tmp/amazon_ref1_invoice.pdf')) - ->uploadDocument('ref2', 'amazon', new Document\Invoice('/tmp/amazon_ref2_invoice.pdf')); + ->uploadDocument('ref1', 'leroymerlin', new Document\Invoice('/tmp/amazon_ref1_invoice.pdf')) + ->uploadDocument('ref2', 'leroymerlin', new Document\Invoice('/tmp/amazon_ref2_invoice.pdf')); $orderApi->execute($operation); ```