1<?php 2 3namespace Illuminate\Filesystem; 4 5use ErrorException; 6use FilesystemIterator; 7use Illuminate\Contracts\Filesystem\FileNotFoundException; 8use Illuminate\Support\LazyCollection; 9use Illuminate\Support\Traits\Macroable; 10use RuntimeException; 11use SplFileObject; 12use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; 13use Symfony\Component\Finder\Finder; 14use Symfony\Component\Mime\MimeTypes; 15 16class Filesystem 17{ 18 use Macroable; 19 20 /** 21 * Determine if a file or directory exists. 22 * 23 * @param string $path 24 * @return bool 25 */ 26 public function exists($path) 27 { 28 return file_exists($path); 29 } 30 31 /** 32 * Determine if a file or directory is missing. 33 * 34 * @param string $path 35 * @return bool 36 */ 37 public function missing($path) 38 { 39 return ! $this->exists($path); 40 } 41 42 /** 43 * Get the contents of a file. 44 * 45 * @param string $path 46 * @param bool $lock 47 * @return string 48 * 49 * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException 50 */ 51 public function get($path, $lock = false) 52 { 53 if ($this->isFile($path)) { 54 return $lock ? $this->sharedGet($path) : file_get_contents($path); 55 } 56 57 throw new FileNotFoundException("File does not exist at path {$path}."); 58 } 59 60 /** 61 * Get contents of a file with shared access. 62 * 63 * @param string $path 64 * @return string 65 */ 66 public function sharedGet($path) 67 { 68 $contents = ''; 69 70 $handle = fopen($path, 'rb'); 71 72 if ($handle) { 73 try { 74 if (flock($handle, LOCK_SH)) { 75 clearstatcache(true, $path); 76 77 $contents = fread($handle, $this->size($path) ?: 1); 78 79 flock($handle, LOCK_UN); 80 } 81 } finally { 82 fclose($handle); 83 } 84 } 85 86 return $contents; 87 } 88 89 /** 90 * Get the returned value of a file. 91 * 92 * @param string $path 93 * @param array $data 94 * @return mixed 95 * 96 * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException 97 */ 98 public function getRequire($path, array $data = []) 99 { 100 if ($this->isFile($path)) { 101 $__path = $path; 102 $__data = $data; 103 104 return (static function () use ($__path, $__data) { 105 extract($__data, EXTR_SKIP); 106 107 return require $__path; 108 })(); 109 } 110 111 throw new FileNotFoundException("File does not exist at path {$path}."); 112 } 113 114 /** 115 * Require the given file once. 116 * 117 * @param string $path 118 * @param array $data 119 * @return mixed 120 */ 121 public function requireOnce($path, array $data = []) 122 { 123 if ($this->isFile($path)) { 124 $__path = $path; 125 $__data = $data; 126 127 return (static function () use ($__path, $__data) { 128 extract($__data, EXTR_SKIP); 129 130 return require_once $__path; 131 })(); 132 } 133 134 throw new FileNotFoundException("File does not exist at path {$path}."); 135 } 136 137 /** 138 * Get the contents of a file one line at a time. 139 * 140 * @param string $path 141 * @return \Illuminate\Support\LazyCollection 142 * 143 * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException 144 */ 145 public function lines($path) 146 { 147 if (! $this->isFile($path)) { 148 throw new FileNotFoundException( 149 "File does not exist at path {$path}." 150 ); 151 } 152 153 return LazyCollection::make(function () use ($path) { 154 $file = new SplFileObject($path); 155 156 $file->setFlags(SplFileObject::DROP_NEW_LINE); 157 158 while (! $file->eof()) { 159 yield $file->fgets(); 160 } 161 }); 162 } 163 164 /** 165 * Get the MD5 hash of the file at the given path. 166 * 167 * @param string $path 168 * @return string 169 */ 170 public function hash($path) 171 { 172 return md5_file($path); 173 } 174 175 /** 176 * Write the contents of a file. 177 * 178 * @param string $path 179 * @param string $contents 180 * @param bool $lock 181 * @return int|bool 182 */ 183 public function put($path, $contents, $lock = false) 184 { 185 return file_put_contents($path, $contents, $lock ? LOCK_EX : 0); 186 } 187 188 /** 189 * Write the contents of a file, replacing it atomically if it already exists. 190 * 191 * @param string $path 192 * @param string $content 193 * @return void 194 */ 195 public function replace($path, $content) 196 { 197 // If the path already exists and is a symlink, get the real path... 198 clearstatcache(true, $path); 199 200 $path = realpath($path) ?: $path; 201 202 $tempPath = tempnam(dirname($path), basename($path)); 203 204 // Fix permissions of tempPath because `tempnam()` creates it with permissions set to 0600... 205 chmod($tempPath, 0777 - umask()); 206 207 file_put_contents($tempPath, $content); 208 209 rename($tempPath, $path); 210 } 211 212 /** 213 * Prepend to a file. 214 * 215 * @param string $path 216 * @param string $data 217 * @return int 218 */ 219 public function prepend($path, $data) 220 { 221 if ($this->exists($path)) { 222 return $this->put($path, $data.$this->get($path)); 223 } 224 225 return $this->put($path, $data); 226 } 227 228 /** 229 * Append to a file. 230 * 231 * @param string $path 232 * @param string $data 233 * @return int 234 */ 235 public function append($path, $data) 236 { 237 return file_put_contents($path, $data, FILE_APPEND); 238 } 239 240 /** 241 * Get or set UNIX mode of a file or directory. 242 * 243 * @param string $path 244 * @param int|null $mode 245 * @return mixed 246 */ 247 public function chmod($path, $mode = null) 248 { 249 if ($mode) { 250 return chmod($path, $mode); 251 } 252 253 return substr(sprintf('%o', fileperms($path)), -4); 254 } 255 256 /** 257 * Delete the file at a given path. 258 * 259 * @param string|array $paths 260 * @return bool 261 */ 262 public function delete($paths) 263 { 264 $paths = is_array($paths) ? $paths : func_get_args(); 265 266 $success = true; 267 268 foreach ($paths as $path) { 269 try { 270 if (! @unlink($path)) { 271 $success = false; 272 } 273 } catch (ErrorException $e) { 274 $success = false; 275 } 276 } 277 278 return $success; 279 } 280 281 /** 282 * Move a file to a new location. 283 * 284 * @param string $path 285 * @param string $target 286 * @return bool 287 */ 288 public function move($path, $target) 289 { 290 return rename($path, $target); 291 } 292 293 /** 294 * Copy a file to a new location. 295 * 296 * @param string $path 297 * @param string $target 298 * @return bool 299 */ 300 public function copy($path, $target) 301 { 302 return copy($path, $target); 303 } 304 305 /** 306 * Create a symlink to the target file or directory. On Windows, a hard link is created if the target is a file. 307 * 308 * @param string $target 309 * @param string $link 310 * @return void 311 */ 312 public function link($target, $link) 313 { 314 if (! windows_os()) { 315 return symlink($target, $link); 316 } 317 318 $mode = $this->isDirectory($target) ? 'J' : 'H'; 319 320 exec("mklink /{$mode} ".escapeshellarg($link).' '.escapeshellarg($target)); 321 } 322 323 /** 324 * Create a relative symlink to the target file or directory. 325 * 326 * @param string $target 327 * @param string $link 328 * @return void 329 */ 330 public function relativeLink($target, $link) 331 { 332 if (! class_exists(SymfonyFilesystem::class)) { 333 throw new RuntimeException( 334 'To enable support for relative links, please install the symfony/filesystem package.' 335 ); 336 } 337 338 $relativeTarget = (new SymfonyFilesystem)->makePathRelative($target, dirname($link)); 339 340 $this->link($relativeTarget, $link); 341 } 342 343 /** 344 * Extract the file name from a file path. 345 * 346 * @param string $path 347 * @return string 348 */ 349 public function name($path) 350 { 351 return pathinfo($path, PATHINFO_FILENAME); 352 } 353 354 /** 355 * Extract the trailing name component from a file path. 356 * 357 * @param string $path 358 * @return string 359 */ 360 public function basename($path) 361 { 362 return pathinfo($path, PATHINFO_BASENAME); 363 } 364 365 /** 366 * Extract the parent directory from a file path. 367 * 368 * @param string $path 369 * @return string 370 */ 371 public function dirname($path) 372 { 373 return pathinfo($path, PATHINFO_DIRNAME); 374 } 375 376 /** 377 * Extract the file extension from a file path. 378 * 379 * @param string $path 380 * @return string 381 */ 382 public function extension($path) 383 { 384 return pathinfo($path, PATHINFO_EXTENSION); 385 } 386 387 /** 388 * Guess the file extension from the mime-type of a given file. 389 * 390 * @param string $path 391 * @return string|null 392 */ 393 public function guessExtension($path) 394 { 395 if (! class_exists(MimeTypes::class)) { 396 throw new RuntimeException( 397 'To enable support for guessing extensions, please install the symfony/mime package.' 398 ); 399 } 400 401 return (new MimeTypes)->getExtensions($this->mimeType($path))[0] ?? null; 402 } 403 404 /** 405 * Get the file type of a given file. 406 * 407 * @param string $path 408 * @return string 409 */ 410 public function type($path) 411 { 412 return filetype($path); 413 } 414 415 /** 416 * Get the mime-type of a given file. 417 * 418 * @param string $path 419 * @return string|false 420 */ 421 public function mimeType($path) 422 { 423 return finfo_file(finfo_open(FILEINFO_MIME_TYPE), $path); 424 } 425 426 /** 427 * Get the file size of a given file. 428 * 429 * @param string $path 430 * @return int 431 */ 432 public function size($path) 433 { 434 return filesize($path); 435 } 436 437 /** 438 * Get the file's last modification time. 439 * 440 * @param string $path 441 * @return int 442 */ 443 public function lastModified($path) 444 { 445 return filemtime($path); 446 } 447 448 /** 449 * Determine if the given path is a directory. 450 * 451 * @param string $directory 452 * @return bool 453 */ 454 public function isDirectory($directory) 455 { 456 return is_dir($directory); 457 } 458 459 /** 460 * Determine if the given path is readable. 461 * 462 * @param string $path 463 * @return bool 464 */ 465 public function isReadable($path) 466 { 467 return is_readable($path); 468 } 469 470 /** 471 * Determine if the given path is writable. 472 * 473 * @param string $path 474 * @return bool 475 */ 476 public function isWritable($path) 477 { 478 return is_writable($path); 479 } 480 481 /** 482 * Determine if the given path is a file. 483 * 484 * @param string $file 485 * @return bool 486 */ 487 public function isFile($file) 488 { 489 return is_file($file); 490 } 491 492 /** 493 * Find path names matching a given pattern. 494 * 495 * @param string $pattern 496 * @param int $flags 497 * @return array 498 */ 499 public function glob($pattern, $flags = 0) 500 { 501 return glob($pattern, $flags); 502 } 503 504 /** 505 * Get an array of all files in a directory. 506 * 507 * @param string $directory 508 * @param bool $hidden 509 * @return \Symfony\Component\Finder\SplFileInfo[] 510 */ 511 public function files($directory, $hidden = false) 512 { 513 return iterator_to_array( 514 Finder::create()->files()->ignoreDotFiles(! $hidden)->in($directory)->depth(0)->sortByName(), 515 false 516 ); 517 } 518 519 /** 520 * Get all of the files from the given directory (recursive). 521 * 522 * @param string $directory 523 * @param bool $hidden 524 * @return \Symfony\Component\Finder\SplFileInfo[] 525 */ 526 public function allFiles($directory, $hidden = false) 527 { 528 return iterator_to_array( 529 Finder::create()->files()->ignoreDotFiles(! $hidden)->in($directory)->sortByName(), 530 false 531 ); 532 } 533 534 /** 535 * Get all of the directories within a given directory. 536 * 537 * @param string $directory 538 * @return array 539 */ 540 public function directories($directory) 541 { 542 $directories = []; 543 544 foreach (Finder::create()->in($directory)->directories()->depth(0)->sortByName() as $dir) { 545 $directories[] = $dir->getPathname(); 546 } 547 548 return $directories; 549 } 550 551 /** 552 * Ensure a directory exists. 553 * 554 * @param string $path 555 * @param int $mode 556 * @param bool $recursive 557 * @return void 558 */ 559 public function ensureDirectoryExists($path, $mode = 0755, $recursive = true) 560 { 561 if (! $this->isDirectory($path)) { 562 $this->makeDirectory($path, $mode, $recursive); 563 } 564 } 565 566 /** 567 * Create a directory. 568 * 569 * @param string $path 570 * @param int $mode 571 * @param bool $recursive 572 * @param bool $force 573 * @return bool 574 */ 575 public function makeDirectory($path, $mode = 0755, $recursive = false, $force = false) 576 { 577 if ($force) { 578 return @mkdir($path, $mode, $recursive); 579 } 580 581 return mkdir($path, $mode, $recursive); 582 } 583 584 /** 585 * Move a directory. 586 * 587 * @param string $from 588 * @param string $to 589 * @param bool $overwrite 590 * @return bool 591 */ 592 public function moveDirectory($from, $to, $overwrite = false) 593 { 594 if ($overwrite && $this->isDirectory($to) && ! $this->deleteDirectory($to)) { 595 return false; 596 } 597 598 return @rename($from, $to) === true; 599 } 600 601 /** 602 * Copy a directory from one location to another. 603 * 604 * @param string $directory 605 * @param string $destination 606 * @param int|null $options 607 * @return bool 608 */ 609 public function copyDirectory($directory, $destination, $options = null) 610 { 611 if (! $this->isDirectory($directory)) { 612 return false; 613 } 614 615 $options = $options ?: FilesystemIterator::SKIP_DOTS; 616 617 // If the destination directory does not actually exist, we will go ahead and 618 // create it recursively, which just gets the destination prepared to copy 619 // the files over. Once we make the directory we'll proceed the copying. 620 $this->ensureDirectoryExists($destination, 0777); 621 622 $items = new FilesystemIterator($directory, $options); 623 624 foreach ($items as $item) { 625 // As we spin through items, we will check to see if the current file is actually 626 // a directory or a file. When it is actually a directory we will need to call 627 // back into this function recursively to keep copying these nested folders. 628 $target = $destination.'/'.$item->getBasename(); 629 630 if ($item->isDir()) { 631 $path = $item->getPathname(); 632 633 if (! $this->copyDirectory($path, $target, $options)) { 634 return false; 635 } 636 } 637 638 // If the current items is just a regular file, we will just copy this to the new 639 // location and keep looping. If for some reason the copy fails we'll bail out 640 // and return false, so the developer is aware that the copy process failed. 641 else { 642 if (! $this->copy($item->getPathname(), $target)) { 643 return false; 644 } 645 } 646 } 647 648 return true; 649 } 650 651 /** 652 * Recursively delete a directory. 653 * 654 * The directory itself may be optionally preserved. 655 * 656 * @param string $directory 657 * @param bool $preserve 658 * @return bool 659 */ 660 public function deleteDirectory($directory, $preserve = false) 661 { 662 if (! $this->isDirectory($directory)) { 663 return false; 664 } 665 666 $items = new FilesystemIterator($directory); 667 668 foreach ($items as $item) { 669 // If the item is a directory, we can just recurse into the function and 670 // delete that sub-directory otherwise we'll just delete the file and 671 // keep iterating through each file until the directory is cleaned. 672 if ($item->isDir() && ! $item->isLink()) { 673 $this->deleteDirectory($item->getPathname()); 674 } 675 676 // If the item is just a file, we can go ahead and delete it since we're 677 // just looping through and waxing all of the files in this directory 678 // and calling directories recursively, so we delete the real path. 679 else { 680 $this->delete($item->getPathname()); 681 } 682 } 683 684 if (! $preserve) { 685 @rmdir($directory); 686 } 687 688 return true; 689 } 690 691 /** 692 * Remove all of the directories within a given directory. 693 * 694 * @param string $directory 695 * @return bool 696 */ 697 public function deleteDirectories($directory) 698 { 699 $allDirectories = $this->directories($directory); 700 701 if (! empty($allDirectories)) { 702 foreach ($allDirectories as $directoryName) { 703 $this->deleteDirectory($directoryName); 704 } 705 706 return true; 707 } 708 709 return false; 710 } 711 712 /** 713 * Empty the specified directory of all files and folders. 714 * 715 * @param string $directory 716 * @return bool 717 */ 718 public function cleanDirectory($directory) 719 { 720 return $this->deleteDirectory($directory, true); 721 } 722} 723