ObjectUploader.php 5.0 KB

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