1<?php 2 3namespace League\Flysystem; 4 5use InvalidArgumentException; 6use League\Flysystem\Plugin\PluggableTrait; 7use League\Flysystem\Plugin\PluginNotFoundException; 8 9/** 10 * Class MountManager. 11 * 12 * Proxies methods to Filesystem (@see __call): 13 * 14 * @method AdapterInterface getAdapter($prefix) 15 * @method Config getConfig($prefix) 16 * @method array listFiles($directory = '', $recursive = false) 17 * @method array listPaths($directory = '', $recursive = false) 18 * @method array getWithMetadata($path, array $metadata) 19 * @method Filesystem flushCache() 20 * @method void assertPresent($path) 21 * @method void assertAbsent($path) 22 * @method Filesystem addPlugin(PluginInterface $plugin) 23 * 24 * @deprecated This functionality will be removed in 2.0 25 */ 26class MountManager implements FilesystemInterface 27{ 28 use PluggableTrait; 29 30 /** 31 * @var FilesystemInterface[] 32 */ 33 protected $filesystems = []; 34 35 /** 36 * Constructor. 37 * 38 * @param FilesystemInterface[] $filesystems [:prefix => Filesystem,] 39 * 40 * @throws InvalidArgumentException 41 */ 42 public function __construct(array $filesystems = []) 43 { 44 $this->mountFilesystems($filesystems); 45 } 46 47 /** 48 * Mount filesystems. 49 * 50 * @param FilesystemInterface[] $filesystems [:prefix => Filesystem,] 51 * 52 * @throws InvalidArgumentException 53 * 54 * @return $this 55 */ 56 public function mountFilesystems(array $filesystems) 57 { 58 foreach ($filesystems as $prefix => $filesystem) { 59 $this->mountFilesystem($prefix, $filesystem); 60 } 61 62 return $this; 63 } 64 65 /** 66 * Mount filesystems. 67 * 68 * @param string $prefix 69 * @param FilesystemInterface $filesystem 70 * 71 * @throws InvalidArgumentException 72 * 73 * @return $this 74 */ 75 public function mountFilesystem($prefix, FilesystemInterface $filesystem) 76 { 77 if ( ! is_string($prefix)) { 78 throw new InvalidArgumentException(__METHOD__ . ' expects argument #1 to be a string.'); 79 } 80 81 $this->filesystems[$prefix] = $filesystem; 82 83 return $this; 84 } 85 86 /** 87 * Get the filesystem with the corresponding prefix. 88 * 89 * @param string $prefix 90 * 91 * @throws FilesystemNotFoundException 92 * 93 * @return FilesystemInterface 94 */ 95 public function getFilesystem($prefix) 96 { 97 if ( ! isset($this->filesystems[$prefix])) { 98 throw new FilesystemNotFoundException('No filesystem mounted with prefix ' . $prefix); 99 } 100 101 return $this->filesystems[$prefix]; 102 } 103 104 /** 105 * Retrieve the prefix from an arguments array. 106 * 107 * @param array $arguments 108 * 109 * @throws InvalidArgumentException 110 * 111 * @return array [:prefix, :arguments] 112 */ 113 public function filterPrefix(array $arguments) 114 { 115 if (empty($arguments)) { 116 throw new InvalidArgumentException('At least one argument needed'); 117 } 118 119 $path = array_shift($arguments); 120 121 if ( ! is_string($path)) { 122 throw new InvalidArgumentException('First argument should be a string'); 123 } 124 125 list($prefix, $path) = $this->getPrefixAndPath($path); 126 array_unshift($arguments, $path); 127 128 return [$prefix, $arguments]; 129 } 130 131 /** 132 * @param string $directory 133 * @param bool $recursive 134 * 135 * @throws InvalidArgumentException 136 * @throws FilesystemNotFoundException 137 * 138 * @return array 139 */ 140 public function listContents($directory = '', $recursive = false) 141 { 142 list($prefix, $directory) = $this->getPrefixAndPath($directory); 143 $filesystem = $this->getFilesystem($prefix); 144 $result = $filesystem->listContents($directory, $recursive); 145 146 foreach ($result as &$file) { 147 $file['filesystem'] = $prefix; 148 } 149 150 return $result; 151 } 152 153 /** 154 * Call forwarder. 155 * 156 * @param string $method 157 * @param array $arguments 158 * 159 * @throws InvalidArgumentException 160 * @throws FilesystemNotFoundException 161 * 162 * @return mixed 163 */ 164 public function __call($method, $arguments) 165 { 166 list($prefix, $arguments) = $this->filterPrefix($arguments); 167 168 return $this->invokePluginOnFilesystem($method, $arguments, $prefix); 169 } 170 171 /** 172 * @param string $from 173 * @param string $to 174 * @param array $config 175 * 176 * @throws InvalidArgumentException 177 * @throws FilesystemNotFoundException 178 * @throws FileExistsException 179 * 180 * @return bool 181 */ 182 public function copy($from, $to, array $config = []) 183 { 184 list($prefixFrom, $from) = $this->getPrefixAndPath($from); 185 186 $buffer = $this->getFilesystem($prefixFrom)->readStream($from); 187 188 if ($buffer === false) { 189 return false; 190 } 191 192 list($prefixTo, $to) = $this->getPrefixAndPath($to); 193 194 $result = $this->getFilesystem($prefixTo)->writeStream($to, $buffer, $config); 195 196 if (is_resource($buffer)) { 197 fclose($buffer); 198 } 199 200 return $result; 201 } 202 203 /** 204 * List with plugin adapter. 205 * 206 * @param array $keys 207 * @param string $directory 208 * @param bool $recursive 209 * 210 * @throws InvalidArgumentException 211 * @throws FilesystemNotFoundException 212 * 213 * @return array 214 */ 215 public function listWith(array $keys = [], $directory = '', $recursive = false) 216 { 217 list($prefix, $directory) = $this->getPrefixAndPath($directory); 218 $arguments = [$keys, $directory, $recursive]; 219 220 return $this->invokePluginOnFilesystem('listWith', $arguments, $prefix); 221 } 222 223 /** 224 * Move a file. 225 * 226 * @param string $from 227 * @param string $to 228 * @param array $config 229 * 230 * @throws InvalidArgumentException 231 * @throws FilesystemNotFoundException 232 * 233 * @return bool 234 */ 235 public function move($from, $to, array $config = []) 236 { 237 list($prefixFrom, $pathFrom) = $this->getPrefixAndPath($from); 238 list($prefixTo, $pathTo) = $this->getPrefixAndPath($to); 239 240 if ($prefixFrom === $prefixTo) { 241 $filesystem = $this->getFilesystem($prefixFrom); 242 $renamed = $filesystem->rename($pathFrom, $pathTo); 243 244 if ($renamed && isset($config['visibility'])) { 245 return $filesystem->setVisibility($pathTo, $config['visibility']); 246 } 247 248 return $renamed; 249 } 250 251 $copied = $this->copy($from, $to, $config); 252 253 if ($copied) { 254 return $this->delete($from); 255 } 256 257 return false; 258 } 259 260 /** 261 * Invoke a plugin on a filesystem mounted on a given prefix. 262 * 263 * @param string $method 264 * @param array $arguments 265 * @param string $prefix 266 * 267 * @throws FilesystemNotFoundException 268 * 269 * @return mixed 270 */ 271 public function invokePluginOnFilesystem($method, $arguments, $prefix) 272 { 273 $filesystem = $this->getFilesystem($prefix); 274 275 try { 276 return $this->invokePlugin($method, $arguments, $filesystem); 277 } catch (PluginNotFoundException $e) { 278 // Let it pass, it's ok, don't panic. 279 } 280 281 $callback = [$filesystem, $method]; 282 283 return call_user_func_array($callback, $arguments); 284 } 285 286 /** 287 * @param string $path 288 * 289 * @throws InvalidArgumentException 290 * 291 * @return string[] [:prefix, :path] 292 */ 293 protected function getPrefixAndPath($path) 294 { 295 if (strpos($path, '://') < 1) { 296 throw new InvalidArgumentException('No prefix detected in path: ' . $path); 297 } 298 299 return explode('://', $path, 2); 300 } 301 302 /** 303 * Check whether a file exists. 304 * 305 * @param string $path 306 * 307 * @return bool 308 */ 309 public function has($path) 310 { 311 list($prefix, $path) = $this->getPrefixAndPath($path); 312 313 return $this->getFilesystem($prefix)->has($path); 314 } 315 316 /** 317 * Read a file. 318 * 319 * @param string $path The path to the file. 320 * 321 * @throws FileNotFoundException 322 * 323 * @return string|false The file contents or false on failure. 324 */ 325 public function read($path) 326 { 327 list($prefix, $path) = $this->getPrefixAndPath($path); 328 329 return $this->getFilesystem($prefix)->read($path); 330 } 331 332 /** 333 * Retrieves a read-stream for a path. 334 * 335 * @param string $path The path to the file. 336 * 337 * @throws FileNotFoundException 338 * 339 * @return resource|false The path resource or false on failure. 340 */ 341 public function readStream($path) 342 { 343 list($prefix, $path) = $this->getPrefixAndPath($path); 344 345 return $this->getFilesystem($prefix)->readStream($path); 346 } 347 348 /** 349 * Get a file's metadata. 350 * 351 * @param string $path The path to the file. 352 * 353 * @throws FileNotFoundException 354 * 355 * @return array|false The file metadata or false on failure. 356 */ 357 public function getMetadata($path) 358 { 359 list($prefix, $path) = $this->getPrefixAndPath($path); 360 361 return $this->getFilesystem($prefix)->getMetadata($path); 362 } 363 364 /** 365 * Get a file's size. 366 * 367 * @param string $path The path to the file. 368 * 369 * @throws FileNotFoundException 370 * 371 * @return int|false The file size or false on failure. 372 */ 373 public function getSize($path) 374 { 375 list($prefix, $path) = $this->getPrefixAndPath($path); 376 377 return $this->getFilesystem($prefix)->getSize($path); 378 } 379 380 /** 381 * Get a file's mime-type. 382 * 383 * @param string $path The path to the file. 384 * 385 * @throws FileNotFoundException 386 * 387 * @return string|false The file mime-type or false on failure. 388 */ 389 public function getMimetype($path) 390 { 391 list($prefix, $path) = $this->getPrefixAndPath($path); 392 393 return $this->getFilesystem($prefix)->getMimetype($path); 394 } 395 396 /** 397 * Get a file's timestamp. 398 * 399 * @param string $path The path to the file. 400 * 401 * @throws FileNotFoundException 402 * 403 * @return string|false The timestamp or false on failure. 404 */ 405 public function getTimestamp($path) 406 { 407 list($prefix, $path) = $this->getPrefixAndPath($path); 408 409 return $this->getFilesystem($prefix)->getTimestamp($path); 410 } 411 412 /** 413 * Get a file's visibility. 414 * 415 * @param string $path The path to the file. 416 * 417 * @throws FileNotFoundException 418 * 419 * @return string|false The visibility (public|private) or false on failure. 420 */ 421 public function getVisibility($path) 422 { 423 list($prefix, $path) = $this->getPrefixAndPath($path); 424 425 return $this->getFilesystem($prefix)->getVisibility($path); 426 } 427 428 /** 429 * Write a new file. 430 * 431 * @param string $path The path of the new file. 432 * @param string $contents The file contents. 433 * @param array $config An optional configuration array. 434 * 435 * @throws FileExistsException 436 * 437 * @return bool True on success, false on failure. 438 */ 439 public function write($path, $contents, array $config = []) 440 { 441 list($prefix, $path) = $this->getPrefixAndPath($path); 442 443 return $this->getFilesystem($prefix)->write($path, $contents, $config); 444 } 445 446 /** 447 * Write a new file using a stream. 448 * 449 * @param string $path The path of the new file. 450 * @param resource $resource The file handle. 451 * @param array $config An optional configuration array. 452 * 453 * @throws InvalidArgumentException If $resource is not a file handle. 454 * @throws FileExistsException 455 * 456 * @return bool True on success, false on failure. 457 */ 458 public function writeStream($path, $resource, array $config = []) 459 { 460 list($prefix, $path) = $this->getPrefixAndPath($path); 461 462 return $this->getFilesystem($prefix)->writeStream($path, $resource, $config); 463 } 464 465 /** 466 * Update an existing file. 467 * 468 * @param string $path The path of the existing file. 469 * @param string $contents The file contents. 470 * @param array $config An optional configuration array. 471 * 472 * @throws FileNotFoundException 473 * 474 * @return bool True on success, false on failure. 475 */ 476 public function update($path, $contents, array $config = []) 477 { 478 list($prefix, $path) = $this->getPrefixAndPath($path); 479 480 return $this->getFilesystem($prefix)->update($path, $contents, $config); 481 } 482 483 /** 484 * Update an existing file using a stream. 485 * 486 * @param string $path The path of the existing file. 487 * @param resource $resource The file handle. 488 * @param array $config An optional configuration array. 489 * 490 * @throws InvalidArgumentException If $resource is not a file handle. 491 * @throws FileNotFoundException 492 * 493 * @return bool True on success, false on failure. 494 */ 495 public function updateStream($path, $resource, array $config = []) 496 { 497 list($prefix, $path) = $this->getPrefixAndPath($path); 498 499 return $this->getFilesystem($prefix)->updateStream($path, $resource, $config); 500 } 501 502 /** 503 * Rename a file. 504 * 505 * @param string $path Path to the existing file. 506 * @param string $newpath The new path of the file. 507 * 508 * @throws FileExistsException Thrown if $newpath exists. 509 * @throws FileNotFoundException Thrown if $path does not exist. 510 * 511 * @return bool True on success, false on failure. 512 */ 513 public function rename($path, $newpath) 514 { 515 list($prefix, $path) = $this->getPrefixAndPath($path); 516 517 return $this->getFilesystem($prefix)->rename($path, $newpath); 518 } 519 520 /** 521 * Delete a file. 522 * 523 * @param string $path 524 * 525 * @throws FileNotFoundException 526 * 527 * @return bool True on success, false on failure. 528 */ 529 public function delete($path) 530 { 531 list($prefix, $path) = $this->getPrefixAndPath($path); 532 533 return $this->getFilesystem($prefix)->delete($path); 534 } 535 536 /** 537 * Delete a directory. 538 * 539 * @param string $dirname 540 * 541 * @throws RootViolationException Thrown if $dirname is empty. 542 * 543 * @return bool True on success, false on failure. 544 */ 545 public function deleteDir($dirname) 546 { 547 list($prefix, $dirname) = $this->getPrefixAndPath($dirname); 548 549 return $this->getFilesystem($prefix)->deleteDir($dirname); 550 } 551 552 /** 553 * Create a directory. 554 * 555 * @param string $dirname The name of the new directory. 556 * @param array $config An optional configuration array. 557 * 558 * @return bool True on success, false on failure. 559 */ 560 public function createDir($dirname, array $config = []) 561 { 562 list($prefix, $dirname) = $this->getPrefixAndPath($dirname); 563 564 return $this->getFilesystem($prefix)->createDir($dirname); 565 } 566 567 /** 568 * Set the visibility for a file. 569 * 570 * @param string $path The path to the file. 571 * @param string $visibility One of 'public' or 'private'. 572 * 573 * @throws FileNotFoundException 574 * 575 * @return bool True on success, false on failure. 576 */ 577 public function setVisibility($path, $visibility) 578 { 579 list($prefix, $path) = $this->getPrefixAndPath($path); 580 581 return $this->getFilesystem($prefix)->setVisibility($path, $visibility); 582 } 583 584 /** 585 * Create a file or update if exists. 586 * 587 * @param string $path The path to the file. 588 * @param string $contents The file contents. 589 * @param array $config An optional configuration array. 590 * 591 * @return bool True on success, false on failure. 592 */ 593 public function put($path, $contents, array $config = []) 594 { 595 list($prefix, $path) = $this->getPrefixAndPath($path); 596 597 return $this->getFilesystem($prefix)->put($path, $contents, $config); 598 } 599 600 /** 601 * Create a file or update if exists. 602 * 603 * @param string $path The path to the file. 604 * @param resource $resource The file handle. 605 * @param array $config An optional configuration array. 606 * 607 * @throws InvalidArgumentException Thrown if $resource is not a resource. 608 * 609 * @return bool True on success, false on failure. 610 */ 611 public function putStream($path, $resource, array $config = []) 612 { 613 list($prefix, $path) = $this->getPrefixAndPath($path); 614 615 return $this->getFilesystem($prefix)->putStream($path, $resource, $config); 616 } 617 618 /** 619 * Read and delete a file. 620 * 621 * @param string $path The path to the file. 622 * 623 * @throws FileNotFoundException 624 * 625 * @return string|false The file contents, or false on failure. 626 */ 627 public function readAndDelete($path) 628 { 629 list($prefix, $path) = $this->getPrefixAndPath($path); 630 631 return $this->getFilesystem($prefix)->readAndDelete($path); 632 } 633 634 /** 635 * Get a file/directory handler. 636 * 637 * @deprecated 638 * 639 * @param string $path The path to the file. 640 * @param Handler $handler An optional existing handler to populate. 641 * 642 * @return Handler Either a file or directory handler. 643 */ 644 public function get($path, Handler $handler = null) 645 { 646 list($prefix, $path) = $this->getPrefixAndPath($path); 647 648 return $this->getFilesystem($prefix)->get($path); 649 } 650} 651