|
@@ -12,6 +12,9 @@
|
|
|
|
|
|
namespace Composer\Util;
|
|
|
|
|
|
+use RecursiveDirectoryIterator;
|
|
|
+use RecursiveIteratorIterator;
|
|
|
+
|
|
|
/**
|
|
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
|
|
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
|
@@ -38,12 +41,25 @@ class Filesystem
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Recursively remove a directory
|
|
|
+ *
|
|
|
+ * Uses the process component if proc_open is enabled on the PHP
|
|
|
+ * installation.
|
|
|
+ *
|
|
|
+ * @param string $directory
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
public function removeDirectory($directory)
|
|
|
{
|
|
|
if (!is_dir($directory)) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+ if (!function_exists('proc_open')) {
|
|
|
+ return $this->removeDirectoryPhp($directory);
|
|
|
+ }
|
|
|
+
|
|
|
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
|
|
$cmd = sprintf('rmdir /S /Q %s', escapeshellarg(realpath($directory)));
|
|
|
} else {
|
|
@@ -58,6 +74,32 @@ class Filesystem
|
|
|
return $result && !is_dir($directory);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Recursively delete directory using PHP iterators.
|
|
|
+ *
|
|
|
+ * Uses a CHILD_FIRST RecursiveIteratorIterator to sort files
|
|
|
+ * before directories, creating a single non-recursive loop
|
|
|
+ * to delete files/directories in the correct order.
|
|
|
+ *
|
|
|
+ * @param string $directory
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ public function removeDirectoryPhp($directory)
|
|
|
+ {
|
|
|
+ $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
|
|
|
+ $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
|
|
|
+
|
|
|
+ foreach ($ri as $file) {
|
|
|
+ if ($file->isDir()) {
|
|
|
+ rmdir($file->getPathname());
|
|
|
+ } else {
|
|
|
+ unlink($file->getPathname());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return rmdir($directory);
|
|
|
+ }
|
|
|
+
|
|
|
public function ensureDirectoryExists($directory)
|
|
|
{
|
|
|
if (!is_dir($directory)) {
|
|
@@ -74,12 +116,46 @@ class Filesystem
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Copy then delete is a non-atomic version of {@link rename}.
|
|
|
+ *
|
|
|
+ * Some systems can't rename and also dont have proc_open,
|
|
|
+ * which requires this solution.
|
|
|
+ *
|
|
|
+ * @param string $source
|
|
|
+ * @param string $target
|
|
|
+ */
|
|
|
+ public function copyThenRemove($source, $target)
|
|
|
+ {
|
|
|
+ $it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS);
|
|
|
+ $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST);
|
|
|
+
|
|
|
+ if ( !file_exists($target)) {
|
|
|
+ mkdir($target, 0777, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($ri as $file) {
|
|
|
+ $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathName();
|
|
|
+ if ($file->isDir()) {
|
|
|
+ mkdir($targetPath);
|
|
|
+ } else {
|
|
|
+ copy($file->getPathname(), $targetPath);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->removeDirectoryPhp($source);
|
|
|
+ }
|
|
|
+
|
|
|
public function rename($source, $target)
|
|
|
{
|
|
|
if (true === @rename($source, $target)) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ if (!function_exists('proc_open')) {
|
|
|
+ return $this->copyThenRemove($source, $target);
|
|
|
+ }
|
|
|
+
|
|
|
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
|
|
// Try to copy & delete - this is a workaround for random "Access denied" errors.
|
|
|
$command = sprintf('xcopy %s %s /E /I /Q', escapeshellarg($source), escapeshellarg($target));
|