ObjectCopier.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. <?php
  2. namespace Aws\S3;
  3. use Aws\Arn\ArnParser;
  4. use Aws\Arn\S3\AccessPointArn;
  5. use Aws\Exception\MultipartUploadException;
  6. use Aws\Result;
  7. use Aws\S3\Exception\S3Exception;
  8. use GuzzleHttp\Promise\Coroutine;
  9. use GuzzleHttp\Promise\PromiseInterface;
  10. use GuzzleHttp\Promise\PromisorInterface;
  11. use InvalidArgumentException;
  12. /**
  13. * Copies objects from one S3 location to another, utilizing a multipart copy
  14. * when appropriate.
  15. */
  16. class ObjectCopier implements PromisorInterface
  17. {
  18. const DEFAULT_MULTIPART_THRESHOLD = MultipartUploader::PART_MAX_SIZE;
  19. private $client;
  20. private $source;
  21. private $destination;
  22. private $acl;
  23. private $options;
  24. private static $defaults = [
  25. 'before_lookup' => null,
  26. 'before_upload' => null,
  27. 'concurrency' => 5,
  28. 'mup_threshold' => self::DEFAULT_MULTIPART_THRESHOLD,
  29. 'params' => [],
  30. 'part_size' => null,
  31. 'version_id' => null,
  32. ];
  33. /**
  34. * @param S3ClientInterface $client The S3 Client used to execute
  35. * the copy command(s).
  36. * @param array $source The object to copy, specified as
  37. * an array with a 'Bucket' and
  38. * 'Key' keys. Provide a
  39. * 'VersionID' key to copy a
  40. * specified version of an object.
  41. * @param array $destination The bucket and key to which to
  42. * copy the $source, specified as
  43. * an array with a 'Bucket' and
  44. * 'Key' keys.
  45. * @param string $acl ACL to apply to the copy
  46. * (default: private).
  47. * @param array $options Options used to configure the
  48. * copy process. Options passed in
  49. * through 'params' are added to
  50. * the sub commands.
  51. *
  52. * @throws InvalidArgumentException
  53. */
  54. public function __construct(
  55. S3ClientInterface $client,
  56. array $source,
  57. array $destination,
  58. $acl = 'private',
  59. array $options = []
  60. ) {
  61. $this->validateLocation($source);
  62. $this->validateLocation($destination);
  63. $this->client = $client;
  64. $this->source = $source;
  65. $this->destination = $destination;
  66. $this->acl = $acl;
  67. $this->options = $options + self::$defaults;
  68. }
  69. /**
  70. * Perform the configured copy asynchronously. Returns a promise that is
  71. * fulfilled with the result of the CompleteMultipartUpload or CopyObject
  72. * operation or rejected with an exception.
  73. *
  74. * @return Coroutine
  75. */
  76. public function promise(): PromiseInterface
  77. {
  78. return Coroutine::of(function () {
  79. $headObjectCommand = $this->client->getCommand(
  80. 'HeadObject',
  81. $this->options['params'] + $this->source
  82. );
  83. if (is_callable($this->options['before_lookup'])) {
  84. $this->options['before_lookup']($headObjectCommand);
  85. }
  86. $objectStats = (yield $this->client->executeAsync(
  87. $headObjectCommand
  88. ));
  89. if ($objectStats['ContentLength'] > $this->options['mup_threshold']) {
  90. $mup = new MultipartCopy(
  91. $this->client,
  92. $this->getSourcePath(),
  93. ['source_metadata' => $objectStats, 'acl' => $this->acl]
  94. + $this->destination
  95. + $this->options
  96. );
  97. yield $mup->promise();
  98. } else {
  99. $defaults = [
  100. 'ACL' => $this->acl,
  101. 'MetadataDirective' => 'COPY',
  102. 'CopySource' => $this->getSourcePath(),
  103. ];
  104. $params = array_diff_key($this->options, self::$defaults)
  105. + $this->destination + $defaults + $this->options['params'];
  106. yield $this->client->executeAsync(
  107. $this->client->getCommand('CopyObject', $params)
  108. );
  109. }
  110. });
  111. }
  112. /**
  113. * Perform the configured copy synchronously. Returns the result of the
  114. * CompleteMultipartUpload or CopyObject operation.
  115. *
  116. * @return Result
  117. *
  118. * @throws S3Exception
  119. * @throws MultipartUploadException
  120. */
  121. public function copy()
  122. {
  123. return $this->promise()->wait();
  124. }
  125. private function validateLocation(array $location)
  126. {
  127. if (empty($location['Bucket']) || empty($location['Key'])) {
  128. throw new \InvalidArgumentException('Locations provided to an'
  129. . ' Aws\S3\ObjectCopier must have a non-empty Bucket and Key');
  130. }
  131. }
  132. private function getSourcePath()
  133. {
  134. $path = "/{$this->source['Bucket']}/";
  135. if (ArnParser::isArn($this->source['Bucket'])) {
  136. try {
  137. new AccessPointArn($this->source['Bucket']);
  138. $path = "{$this->source['Bucket']}/object/";
  139. } catch (\Exception $e) {
  140. throw new \InvalidArgumentException(
  141. 'Provided ARN was a not a valid S3 access point ARN ('
  142. . $e->getMessage() . ')',
  143. 0,
  144. $e
  145. );
  146. }
  147. }
  148. $sourcePath = $path . rawurlencode($this->source['Key']);
  149. if (isset($this->source['VersionId'])) {
  150. $sourcePath .= "?versionId={$this->source['VersionId']}";
  151. }
  152. return $sourcePath;
  153. }
  154. }