@@ -572,6 +572,44 @@ public static function replaceFileLineContainsString(string $file, string $find,
572572 return file_put_contents ($ file , implode ('' , $ lines ));
573573 }
574574
575+ /**
576+ * Move file or directory, handling cross-device scenarios
577+ * Uses rename() if possible, falls back to copy+delete for cross-device moves
578+ *
579+ * @param string $source Source path
580+ * @param string $dest Destination path
581+ */
582+ public static function moveFileOrDir (string $ source , string $ dest ): void
583+ {
584+ $ source = self ::convertPath ($ source );
585+ $ dest = self ::convertPath ($ dest );
586+
587+ // Check if source and dest are on the same device to avoid cross-device rename errors
588+ $ source_stat = @stat ($ source );
589+ $ dest_parent = dirname ($ dest );
590+ $ dest_stat = @stat ($ dest_parent );
591+
592+ // Only use rename if on same device
593+ if ($ source_stat !== false && $ dest_stat !== false && $ source_stat ['dev ' ] === $ dest_stat ['dev ' ]) {
594+ if (@rename ($ source , $ dest )) {
595+ return ;
596+ }
597+ }
598+
599+ // Fall back to copy + delete for cross-device moves or if rename failed
600+ if (is_dir ($ source )) {
601+ self ::copyDir ($ source , $ dest );
602+ self ::removeDir ($ source );
603+ } else {
604+ if (!copy ($ source , $ dest )) {
605+ throw new FileSystemException ("Failed to copy file from {$ source } to {$ dest }" );
606+ }
607+ if (!unlink ($ source )) {
608+ throw new FileSystemException ("Failed to remove source file: {$ source }" );
609+ }
610+ }
611+ }
612+
575613 private static function extractArchive (string $ filename , string $ target ): void
576614 {
577615 // Create base dir
@@ -648,44 +686,6 @@ private static function extractWithType(string $source_type, string $filename, s
648686 };
649687 }
650688
651- /**
652- * Move file or directory, handling cross-device scenarios
653- * Uses rename() if possible, falls back to copy+delete for cross-device moves
654- *
655- * @param string $source Source path
656- * @param string $dest Destination path
657- */
658- private static function moveFileOrDir (string $ source , string $ dest ): void
659- {
660- $ source = self ::convertPath ($ source );
661- $ dest = self ::convertPath ($ dest );
662-
663- // Check if source and dest are on the same device to avoid cross-device rename errors
664- $ source_stat = @stat ($ source );
665- $ dest_parent = dirname ($ dest );
666- $ dest_stat = @stat ($ dest_parent );
667-
668- // Only use rename if on same device
669- if ($ source_stat !== false && $ dest_stat !== false && $ source_stat ['dev ' ] === $ dest_stat ['dev ' ]) {
670- if (@rename ($ source , $ dest )) {
671- return ;
672- }
673- }
674-
675- // Fall back to copy + delete for cross-device moves or if rename failed
676- if (is_dir ($ source )) {
677- self ::copyDir ($ source , $ dest );
678- self ::removeDir ($ source );
679- } else {
680- if (!copy ($ source , $ dest )) {
681- throw new FileSystemException ("Failed to copy file from {$ source } to {$ dest }" );
682- }
683- if (!unlink ($ source )) {
684- throw new FileSystemException ("Failed to remove source file: {$ source }" );
685- }
686- }
687- }
688-
689689 /**
690690 * Unzip file with stripping top-level directory
691691 */
0 commit comments