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