ObjectCopier.php 5.6 KB

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