MountManager.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. <?php
  2. declare(strict_types=1);
  3. namespace League\Flysystem;
  4. use function sprintf;
  5. class MountManager implements FilesystemOperator
  6. {
  7. /**
  8. * @var array<string, FilesystemOperator>
  9. */
  10. private $filesystems = [];
  11. /**
  12. * MountManager constructor.
  13. *
  14. * @param array<string,FilesystemOperator> $filesystems
  15. */
  16. public function __construct(array $filesystems = [])
  17. {
  18. $this->mountFilesystems($filesystems);
  19. }
  20. public function fileExists(string $location): bool
  21. {
  22. /** @var FilesystemOperator $filesystem */
  23. [$filesystem, $path] = $this->determineFilesystemAndPath($location);
  24. try {
  25. return $filesystem->fileExists($path);
  26. } catch (UnableToCheckFileExistence $exception) {
  27. throw UnableToCheckFileExistence::forLocation($location, $exception);
  28. }
  29. }
  30. public function read(string $location): string
  31. {
  32. /** @var FilesystemOperator $filesystem */
  33. [$filesystem, $path] = $this->determineFilesystemAndPath($location);
  34. try {
  35. return $filesystem->read($path);
  36. } catch (UnableToReadFile $exception) {
  37. throw UnableToReadFile::fromLocation($location, $exception->reason(), $exception);
  38. }
  39. }
  40. public function readStream(string $location)
  41. {
  42. /** @var FilesystemOperator $filesystem */
  43. [$filesystem, $path] = $this->determineFilesystemAndPath($location);
  44. try {
  45. return $filesystem->readStream($path);
  46. } catch (UnableToReadFile $exception) {
  47. throw UnableToReadFile::fromLocation($location, $exception->reason(), $exception);
  48. }
  49. }
  50. public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing
  51. {
  52. /** @var FilesystemOperator $filesystem */
  53. [$filesystem, $path, $mountIdentifier] = $this->determineFilesystemAndPath($location);
  54. return
  55. $filesystem
  56. ->listContents($path, $deep)
  57. ->map(
  58. function (StorageAttributes $attributes) use ($mountIdentifier) {
  59. return $attributes->withPath(sprintf('%s://%s', $mountIdentifier, $attributes->path()));
  60. }
  61. );
  62. }
  63. public function lastModified(string $location): int
  64. {
  65. /** @var FilesystemOperator $filesystem */
  66. [$filesystem, $path] = $this->determineFilesystemAndPath($location);
  67. try {
  68. return $filesystem->lastModified($path);
  69. } catch (UnableToRetrieveMetadata $exception) {
  70. throw UnableToRetrieveMetadata::lastModified($location, $exception->reason(), $exception);
  71. }
  72. }
  73. public function fileSize(string $location): int
  74. {
  75. /** @var FilesystemOperator $filesystem */
  76. [$filesystem, $path] = $this->determineFilesystemAndPath($location);
  77. try {
  78. return $filesystem->fileSize($path);
  79. } catch (UnableToRetrieveMetadata $exception) {
  80. throw UnableToRetrieveMetadata::fileSize($location, $exception->reason(), $exception);
  81. }
  82. }
  83. public function mimeType(string $location): string
  84. {
  85. /** @var FilesystemOperator $filesystem */
  86. [$filesystem, $path] = $this->determineFilesystemAndPath($location);
  87. try {
  88. return $filesystem->mimeType($path);
  89. } catch (UnableToRetrieveMetadata $exception) {
  90. throw UnableToRetrieveMetadata::mimeType($location, $exception->reason(), $exception);
  91. }
  92. }
  93. public function visibility(string $location): string
  94. {
  95. /** @var FilesystemOperator $filesystem */
  96. [$filesystem, $path] = $this->determineFilesystemAndPath($location);
  97. try {
  98. return $filesystem->visibility($path);
  99. } catch (UnableToRetrieveMetadata $exception) {
  100. throw UnableToRetrieveMetadata::visibility($location, $exception->reason(), $exception);
  101. }
  102. }
  103. public function write(string $location, string $contents, array $config = []): void
  104. {
  105. /** @var FilesystemOperator $filesystem */
  106. [$filesystem, $path] = $this->determineFilesystemAndPath($location);
  107. try {
  108. $filesystem->write($path, $contents, $config);
  109. } catch (UnableToWriteFile $exception) {
  110. throw UnableToWriteFile::atLocation($location, $exception->reason(), $exception);
  111. }
  112. }
  113. public function writeStream(string $location, $contents, array $config = []): void
  114. {
  115. /** @var FilesystemOperator $filesystem */
  116. [$filesystem, $path] = $this->determineFilesystemAndPath($location);
  117. $filesystem->writeStream($path, $contents, $config);
  118. }
  119. public function setVisibility(string $path, string $visibility): void
  120. {
  121. /** @var FilesystemOperator $filesystem */
  122. [$filesystem, $path] = $this->determineFilesystemAndPath($path);
  123. $filesystem->setVisibility($path, $visibility);
  124. }
  125. public function delete(string $location): void
  126. {
  127. /** @var FilesystemOperator $filesystem */
  128. [$filesystem, $path] = $this->determineFilesystemAndPath($location);
  129. try {
  130. $filesystem->delete($path);
  131. } catch (UnableToDeleteFile $exception) {
  132. throw UnableToDeleteFile::atLocation($location, '', $exception);
  133. }
  134. }
  135. public function deleteDirectory(string $location): void
  136. {
  137. /** @var FilesystemOperator $filesystem */
  138. [$filesystem, $path] = $this->determineFilesystemAndPath($location);
  139. try {
  140. $filesystem->deleteDirectory($path);
  141. } catch (UnableToDeleteDirectory $exception) {
  142. throw UnableToDeleteDirectory::atLocation($location, '', $exception);
  143. }
  144. }
  145. public function createDirectory(string $location, array $config = []): void
  146. {
  147. /** @var FilesystemOperator $filesystem */
  148. [$filesystem, $path] = $this->determineFilesystemAndPath($location);
  149. try {
  150. $filesystem->createDirectory($path, $config);
  151. } catch (UnableToCreateDirectory $exception) {
  152. throw UnableToCreateDirectory::dueToFailure($location, $exception);
  153. }
  154. }
  155. public function move(string $source, string $destination, array $config = []): void
  156. {
  157. /** @var FilesystemOperator $sourceFilesystem */
  158. /* @var FilesystemOperator $destinationFilesystem */
  159. [$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($source);
  160. [$destinationFilesystem, $destinationPath] = $this->determineFilesystemAndPath($destination);
  161. $sourceFilesystem === $destinationFilesystem ? $this->moveInTheSameFilesystem(
  162. $sourceFilesystem,
  163. $sourcePath,
  164. $destinationPath,
  165. $source,
  166. $destination
  167. ) : $this->moveAcrossFilesystems($source, $destination);
  168. }
  169. public function copy(string $source, string $destination, array $config = []): void
  170. {
  171. /** @var FilesystemOperator $sourceFilesystem */
  172. /* @var FilesystemOperator $destinationFilesystem */
  173. [$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($source);
  174. [$destinationFilesystem, $destinationPath] = $this->determineFilesystemAndPath($destination);
  175. $sourceFilesystem === $destinationFilesystem ? $this->copyInSameFilesystem(
  176. $sourceFilesystem,
  177. $sourcePath,
  178. $destinationPath,
  179. $source,
  180. $destination
  181. ) : $this->copyAcrossFilesystem(
  182. $config['visibility'] ?? null,
  183. $sourceFilesystem,
  184. $sourcePath,
  185. $destinationFilesystem,
  186. $destinationPath,
  187. $source,
  188. $destination
  189. );
  190. }
  191. private function mountFilesystems(array $filesystems): void
  192. {
  193. foreach ($filesystems as $key => $filesystem) {
  194. $this->guardAgainstInvalidMount($key, $filesystem);
  195. /* @var string $key */
  196. /* @var FilesystemOperator $filesystem */
  197. $this->mountFilesystem($key, $filesystem);
  198. }
  199. }
  200. /**
  201. * @param mixed $key
  202. * @param mixed $filesystem
  203. */
  204. private function guardAgainstInvalidMount($key, $filesystem): void
  205. {
  206. if ( ! is_string($key)) {
  207. throw UnableToMountFilesystem::becauseTheKeyIsNotValid($key);
  208. }
  209. if ( ! $filesystem instanceof FilesystemOperator) {
  210. throw UnableToMountFilesystem::becauseTheFilesystemWasNotValid($filesystem);
  211. }
  212. }
  213. private function mountFilesystem(string $key, FilesystemOperator $filesystem): void
  214. {
  215. $this->filesystems[$key] = $filesystem;
  216. }
  217. /**
  218. * @param string $path
  219. *
  220. * @return array{0:FilesystemOperator, 1:string}
  221. */
  222. private function determineFilesystemAndPath(string $path): array
  223. {
  224. if (strpos($path, '://') < 1) {
  225. throw UnableToResolveFilesystemMount::becauseTheSeparatorIsMissing($path);
  226. }
  227. /** @var string $mountIdentifier */
  228. /** @var string $mountPath */
  229. [$mountIdentifier, $mountPath] = explode('://', $path, 2);
  230. if ( ! array_key_exists($mountIdentifier, $this->filesystems)) {
  231. throw UnableToResolveFilesystemMount::becauseTheMountWasNotRegistered($mountIdentifier);
  232. }
  233. return [$this->filesystems[$mountIdentifier], $mountPath, $mountIdentifier];
  234. }
  235. private function copyInSameFilesystem(
  236. FilesystemOperator $sourceFilesystem,
  237. string $sourcePath,
  238. string $destinationPath,
  239. string $source,
  240. string $destination
  241. ): void {
  242. try {
  243. $sourceFilesystem->copy($sourcePath, $destinationPath);
  244. } catch (UnableToCopyFile $exception) {
  245. throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
  246. }
  247. }
  248. private function copyAcrossFilesystem(
  249. ?string $visibility,
  250. FilesystemOperator $sourceFilesystem,
  251. string $sourcePath,
  252. FilesystemOperator $destinationFilesystem,
  253. string $destinationPath,
  254. string $source,
  255. string $destination
  256. ): void {
  257. try {
  258. $visibility = $visibility ?? $sourceFilesystem->visibility($sourcePath);
  259. $stream = $sourceFilesystem->readStream($sourcePath);
  260. $destinationFilesystem->writeStream($destinationPath, $stream, compact('visibility'));
  261. } catch (UnableToRetrieveMetadata | UnableToReadFile | UnableToWriteFile $exception) {
  262. throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
  263. }
  264. }
  265. private function moveInTheSameFilesystem(
  266. FilesystemOperator $sourceFilesystem,
  267. string $sourcePath,
  268. string $destinationPath,
  269. string $source,
  270. string $destination
  271. ): void {
  272. try {
  273. $sourceFilesystem->move($sourcePath, $destinationPath);
  274. } catch (UnableToMoveFile $exception) {
  275. throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
  276. }
  277. }
  278. private function moveAcrossFilesystems(string $source, string $destination): void
  279. {
  280. try {
  281. $this->copy($source, $destination);
  282. $this->delete($source);
  283. } catch (UnableToCopyFile | UnableToDeleteFile $exception) {
  284. throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
  285. }
  286. }
  287. }