| 1 |
<?php |
| 2 |
|
| 3 |
namespace Dudlewebs\WPMCS\s3\Aws\S3; |
| 4 |
|
| 5 |
use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\PromiseInterface; |
| 6 |
use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\PromisorInterface; |
| 7 |
use Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7; |
| 8 |
use Dudlewebs\WPMCS\s3\Psr\Http\Message\StreamInterface; |
| 9 |
/** |
| 10 |
* Uploads an object to S3, using a PutObject command or a multipart upload as |
| 11 |
* appropriate. |
| 12 |
*/ |
| 13 |
class ObjectUploader implements PromisorInterface |
| 14 |
{ |
| 15 |
const DEFAULT_MULTIPART_THRESHOLD = 16777216; |
| 16 |
private $client; |
| 17 |
private $bucket; |
| 18 |
private $key; |
| 19 |
private $body; |
| 20 |
private $acl; |
| 21 |
private $options; |
| 22 |
private static $defaults = ['before_upload' => null, 'concurrency' => 3, 'mup_threshold' => self::DEFAULT_MULTIPART_THRESHOLD, 'params' => [], 'part_size' => null]; |
| 23 |
/** |
| 24 |
* @param S3ClientInterface $client The S3 Client used to execute |
| 25 |
* the upload command(s). |
| 26 |
* @param string $bucket Bucket to upload the object, or |
| 27 |
* an S3 access point ARN. |
| 28 |
* @param string $key Key of the object. |
| 29 |
* @param mixed $body Object data to upload. Can be a |
| 30 |
* StreamInterface, PHP stream |
| 31 |
* resource, or a string of data to |
| 32 |
* upload. |
| 33 |
* @param string $acl ACL to apply to the copy |
| 34 |
* (default: private). |
| 35 |
* @param array $options Options used to configure the |
| 36 |
* copy process. Options passed in |
| 37 |
* through 'params' are added to |
| 38 |
* the sub command(s). |
| 39 |
*/ |
| 40 |
public function __construct(S3ClientInterface $client, $bucket, $key, $body, $acl = 'private', array $options = []) |
| 41 |
{ |
| 42 |
$this->client = $client; |
| 43 |
$this->bucket = $bucket; |
| 44 |
$this->key = $key; |
| 45 |
$this->body = Psr7\Utils::streamFor($body); |
| 46 |
$this->acl = $acl; |
| 47 |
$this->options = $options + self::$defaults; |
| 48 |
} |
| 49 |
/** |
| 50 |
* @return PromiseInterface |
| 51 |
*/ |
| 52 |
public function promise() |
| 53 |
{ |
| 54 |
/** @var int $mup_threshold */ |
| 55 |
$mup_threshold = $this->options['mup_threshold']; |
| 56 |
if ($this->requiresMultipart($this->body, $mup_threshold)) { |
| 57 |
// Perform a multipart upload. |
| 58 |
return (new MultipartUploader($this->client, $this->body, ['bucket' => $this->bucket, 'key' => $this->key, 'acl' => $this->acl] + $this->options))->promise(); |
| 59 |
} |
| 60 |
// Perform a regular PutObject operation. |
| 61 |
$command = $this->client->getCommand('PutObject', ['Bucket' => $this->bucket, 'Key' => $this->key, 'Body' => $this->body, 'ACL' => $this->acl] + $this->options['params']); |
| 62 |
if (\is_callable($this->options['before_upload'])) { |
| 63 |
$this->options['before_upload']($command); |
| 64 |
} |
| 65 |
return $this->client->executeAsync($command); |
| 66 |
} |
| 67 |
public function upload() |
| 68 |
{ |
| 69 |
return $this->promise()->wait(); |
| 70 |
} |
| 71 |
/** |
| 72 |
* Determines if the body should be uploaded using PutObject or the |
| 73 |
* Multipart Upload System. It also modifies the passed-in $body as needed |
| 74 |
* to support the upload. |
| 75 |
* |
| 76 |
* @param StreamInterface $body Stream representing the body. |
| 77 |
* @param integer $threshold Minimum bytes before using Multipart. |
| 78 |
* |
| 79 |
* @return bool |
| 80 |
*/ |
| 81 |
private function requiresMultipart(StreamInterface &$body, $threshold) |
| 82 |
{ |
| 83 |
// If body size known, compare to threshold to determine if Multipart. |
| 84 |
if ($body->getSize() !== null) { |
| 85 |
return $body->getSize() >= $threshold; |
| 86 |
} |
| 87 |
/** |
| 88 |
* Handle the situation where the body size is unknown. |
| 89 |
* Read up to 5MB into a buffer to determine how to upload the body. |
| 90 |
* @var StreamInterface $buffer |
| 91 |
*/ |
| 92 |
$buffer = Psr7\Utils::streamFor(); |
| 93 |
Psr7\Utils::copyToStream($body, $buffer, MultipartUploader::PART_MIN_SIZE); |
| 94 |
// If body < 5MB, use PutObject with the buffer. |
| 95 |
if ($buffer->getSize() < MultipartUploader::PART_MIN_SIZE) { |
| 96 |
$buffer->seek(0); |
| 97 |
$body = $buffer; |
| 98 |
return \false; |
| 99 |
} |
| 100 |
// If body >= 5 MB, then use multipart. [YES] |
| 101 |
if ($body->isSeekable() && $body->getMetadata('uri') !== 'php://input') { |
| 102 |
// If the body is seekable, just rewind the body. |
| 103 |
$body->seek(0); |
| 104 |
} else { |
| 105 |
// If the body is non-seekable, stitch the rewind the buffer and |
| 106 |
// the partially read body together into one stream. This avoids |
| 107 |
// unnecessary disc usage and does not require seeking on the |
| 108 |
// original stream. |
| 109 |
$buffer->seek(0); |
| 110 |
$body = new Psr7\AppendStream([$buffer, $body]); |
| 111 |
} |
| 112 |
return \true; |
| 113 |
} |
| 114 |
} |
| 115 |
|