Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
9 changes: 6 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ version: '3'

services:
sf-php-sdk-dev:
container_name: sfeed.php-sdk-dev
image: php-sdk-dev
build:
context: .
Expand Down
5 changes: 5 additions & 0 deletions docs/development/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 :
Expand Down
20 changes: 20 additions & 0 deletions docs/manual/resources/order.md
Original file line number Diff line number Diff line change
Expand Up @@ -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', 'leroymerlin', new Document\Invoice('/tmp/amazon_ref1_invoice.pdf'))
->uploadDocument('ref2', 'leroymerlin', new Document\Invoice('/tmp/amazon_ref2_invoice.pdf'));

$orderApi->execute($operation);
```
35 changes: 35 additions & 0 deletions src/Api/Order/Document/AbstractDocument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace ShoppingFeed\Sdk\Api\Order\Document;

abstract class AbstractDocument
{
/**
* @var string
*/
private $path;

/**
* @var string
*/
private $type;

public function __construct(
string $path,
string $type
)
{
$this->path = $path;
$this->type = $type;
}

public function getPath(): string
{
return $this->path;
}

public function getType(): string
{
return $this->type;
}
}
11 changes: 11 additions & 0 deletions src/Api/Order/Document/Invoice.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace ShoppingFeed\Sdk\Api\Order\Document;

class Invoice extends AbstractDocument
{
public function __construct(string $path)
{
parent::__construct($path, 'invoice');
}
}
117 changes: 98 additions & 19 deletions src/Api/Order/OrderOperation.php
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
<?php
namespace ShoppingFeed\Sdk\Api\Order;

use RuntimeException;
use ShoppingFeed\Sdk\Api;
use ShoppingFeed\Sdk\Api\Order\Document\AbstractDocument;
use ShoppingFeed\Sdk\Exception;
use ShoppingFeed\Sdk\Hal;
use ShoppingFeed\Sdk\Operation;
use ShoppingFeed\Sdk\Exception;

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';
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
Expand All @@ -30,6 +33,7 @@ class OrderOperation extends Operation\AbstractBulkOperation
self::TYPE_REFUND,
self::TYPE_ACKNOWLEDGE,
self::TYPE_UNACKNOWLEDGE,
self::TYPE_UPLOAD_DOCUMENTS,
];

/**
Expand Down Expand Up @@ -179,6 +183,27 @@ public function unacknowledge($reference, $channelName)
return $this;
}

/**
* @param string $reference The channel's order reference
* @param string $channelName The channel's name
* @param Document\AbstractDocument $document
*
* @return OrderOperation
*
* @throws Exception\InvalidArgumentException
*/
public function uploadDocument($reference, $channelName, Document\AbstractDocument $document): self
{
$this->addOperation(
$reference,
$channelName,
self::TYPE_UPLOAD_DOCUMENTS,
['document' => $document]
);

return $this;
}

/**
* Execute all declared operations
*
Expand All @@ -192,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(
Expand All @@ -213,24 +235,81 @@ function (Hal\HalResource $batch) use ($resources) {
);
}

private function populateRequests($type, Hal\HalLink $link, \ArrayAccess $requests): void
{
// 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 request generation callback
* Create requests for upload documents operation. We batch request by 20
* to not send too many files at once.
*
* @param string $type
* @param Hal\HalLink $link
* @param array $requests
*
* @return \Closure
* @return \Psr\Http\Message\RequestInterface
*/
private function createRequestGenerator($type, Hal\HalLink $link, \ArrayAccess $requests)
private function populateRequestsForUploadDocuments(Hal\HalLink $link, \ArrayAccess $requests)
{
return function (array $chunk) use ($type, $link, &$requests) {
$type = self::TYPE_UPLOAD_DOCUMENTS;

foreach (array_chunk($this->getOperations($type), 20) as $batch) {
$body = [];
$orders = [];

foreach ($batch as $operation) {
/** @var AbstractDocument $document */
$document = $operation['document'];

$resource = fopen($document->getPath(), 'rb');

if (false === $resource) {
throw new RuntimeException(
sprintf('Unable to read "%s"', $document->getPath())
);
}

$body[] = [
'name' => 'files[]',
'contents' => $resource,
];

$orders[] = [
'reference' => $operation['reference'],
'channelName' => $operation['channelName'],
'documents' => [
['type' => $document->getType()],
],
];
}

$body[] = [
'name' => 'body',
'contents' => json_encode(['order' => $orders]),
];

$requests[] = $link->createRequest(
'POST',
['operation' => $type],
['order' => $chunk]
$body,
['Content-Type' => 'multipart/form-data']
);
};
}
}

/**
Expand Down
24 changes: 16 additions & 8 deletions src/Hal/HalLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public function withAddedHref($path, $variables = [])
{
$instance = clone $this;
$instance->href = rtrim($instance->getUri($variables), '/') .
'/' . ltrim($path, '/');
'/' . ltrim($path, '/');

return $instance;
}
Expand Down Expand Up @@ -262,15 +262,23 @@ 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 = [];
$uri = $this->getUri($variables);
$method = strtoupper($method);

if ((null !== $body && '' !== $body) && in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'])) {
$headers['Content-Type'] = 'application/json';
$body = Json::encode($body);
$hasBody = null !== $body && '' !== $body;

if ($hasBody && in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'])) {
if (! isset($headers['Content-Type'])) {
$headers['Content-Type'] = 'application/json';
}

switch ($headers['Content-Type']) {
case 'application/json':
$body = Json::encode($body);
break;
}
}

return $this->client->createRequest($method, $uri, $headers, $body);
Expand Down
5 changes: 5 additions & 0 deletions src/Http/Adapter/GuzzleHTTPAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Loading