*/
public function copy($originFile, $targetFile, $overwriteNewerFiles = false)
{
- if (stream_is_local($originFile) && !is_file($originFile)) {
+ $originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://');
+ if ($originIsLocal && !is_file($originFile)) {
throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile);
}
throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile);
}
- // Like `cp`, preserve executable permission bits
- @chmod($targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111));
+ if ($originIsLocal) {
+ // Like `cp`, preserve executable permission bits
+ @chmod($targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111));
- if (stream_is_local($originFile) && $bytesCopied !== ($bytesOrigin = filesize($originFile))) {
- throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile);
+ if ($bytesCopied !== $bytesOrigin = filesize($originFile)) {
+ throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile);
+ }
}
}
}
/**
* Creates a directory recursively.
*
- * @param string|array|\Traversable $dirs The directory path
- * @param int $mode The directory mode
+ * @param string|iterable $dirs The directory path
+ * @param int $mode The directory mode
*
* @throws IOException On any directory creation failure
*/
/**
* Checks the existence of files or directories.
*
- * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to check
+ * @param string|iterable $files A filename, an array of files, or a \Traversable instance to check
*
* @return bool true if the file exists, false otherwise
*/
public function exists($files)
{
+ $maxPathLength = PHP_MAXPATHLEN - 2;
+
foreach ($this->toIterator($files) as $file) {
- if ('\\' === DIRECTORY_SEPARATOR && strlen($file) > 258) {
- throw new IOException('Could not check if file exist because path length exceeds 258 characters.', 0, null, $file);
+ if (strlen($file) > $maxPathLength) {
+ throw new IOException(sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file);
}
if (!file_exists($file)) {
/**
* Sets access and modification time of file.
*
- * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to create
- * @param int $time The touch time as a Unix timestamp
- * @param int $atime The access time as a Unix timestamp
+ * @param string|iterable $files A filename, an array of files, or a \Traversable instance to create
+ * @param int $time The touch time as a Unix timestamp
+ * @param int $atime The access time as a Unix timestamp
*
* @throws IOException When touch fails
*/
/**
* Removes files or directories.
*
- * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to remove
+ * @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove
*
* @throws IOException When removal fails
*/
/**
* Change mode for an array of files or directories.
*
- * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change mode
- * @param int $mode The new mode (octal)
- * @param int $umask The mode mask (octal)
- * @param bool $recursive Whether change the mod recursively or not
+ * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change mode
+ * @param int $mode The new mode (octal)
+ * @param int $umask The mode mask (octal)
+ * @param bool $recursive Whether change the mod recursively or not
*
* @throws IOException When the change fail
*/
/**
* Change the owner of an array of files or directories.
*
- * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change owner
- * @param string $user The new owner user name
- * @param bool $recursive Whether change the owner recursively or not
+ * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change owner
+ * @param string $user The new owner user name
+ * @param bool $recursive Whether change the owner recursively or not
*
* @throws IOException When the change fail
*/
/**
* Change the group of an array of files or directories.
*
- * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change group
- * @param string $group The group name
- * @param bool $recursive Whether change the group recursively or not
+ * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group
+ * @param string $group The group name
+ * @param bool $recursive Whether change the group recursively or not
*
* @throws IOException When the change fail
*/
}
if (true !== @rename($origin, $target)) {
+ if (is_dir($origin)) {
+ // See https://bugs.php.net/bug.php?id=54097 & http://php.net/manual/en/function.rename.php#113943
+ $this->mirror($origin, $target, null, array('override' => $overwrite, 'delete' => $overwrite));
+ $this->remove($origin);
+
+ return;
+ }
throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target);
}
}
*/
private function isReadable($filename)
{
- if ('\\' === DIRECTORY_SEPARATOR && strlen($filename) > 258) {
- throw new IOException('Could not check if file is readable because path length exceeds 258 characters.', 0, null, $filename);
+ $maxPathLength = PHP_MAXPATHLEN - 2;
+
+ if (strlen($filename) > $maxPathLength) {
+ throw new IOException(sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename);
}
return is_readable($filename);
$startPath = str_replace('\\', '/', $startPath);
}
+ $stripDriveLetter = function ($path) {
+ if (strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) {
+ return substr($path, 2);
+ }
+
+ return $path;
+ };
+
+ $endPath = $stripDriveLetter($endPath);
+ $startPath = $stripDriveLetter($startPath);
+
// Split the paths into arrays
$startPathArr = explode('/', trim($startPath, '/'));
$endPathArr = explode('/', trim($endPath, '/'));
- if ('/' !== $startPath[0]) {
- array_shift($startPathArr);
- }
-
- if ('/' !== $endPath[0]) {
- array_shift($endPathArr);
- }
-
- $normalizePathArray = function ($pathSegments) {
+ $normalizePathArray = function ($pathSegments, $absolute) {
$result = array();
foreach ($pathSegments as $segment) {
- if ('..' === $segment) {
+ if ('..' === $segment && ($absolute || count($result))) {
array_pop($result);
- } else {
+ } elseif ('.' !== $segment) {
$result[] = $segment;
}
}
return $result;
};
- $startPathArr = $normalizePathArray($startPathArr);
- $endPathArr = $normalizePathArray($endPathArr);
+ $startPathArr = $normalizePathArray($startPathArr, static::isAbsolutePath($startPath));
+ $endPathArr = $normalizePathArray($endPathArr, static::isAbsolutePath($endPath));
// Find for which directory the common path stops
$index = 0;
}
// Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
- if (count($startPathArr) === 1 && $startPathArr[0] === '') {
+ if (1 === count($startPathArr) && '' === $startPathArr[0]) {
$depth = 0;
} else {
$depth = count($startPathArr) - $index;
}
- // When we need to traverse from the start, and we are starting from a root path, don't add '../'
- if ('/' === $startPath[0] && 0 === $index && 0 === $depth) {
- $traverser = '';
- } else {
- // Repeated "../" for each level need to reach the common path
- $traverser = str_repeat('../', $depth);
- }
+ // Repeated "../" for each level need to reach the common path
+ $traverser = str_repeat('../', $depth);
$endPathRemainder = implode('/', array_slice($endPathArr, $index));
/**
* Mirrors a directory to another.
*
+ * Copies files and directories from the origin directory into the target directory. By default:
+ *
+ * - existing files in the target directory will be overwritten, except if they are newer (see the `override` option)
+ * - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option)
+ *
* @param string $originDir The origin directory
* @param string $targetDir The target directory
- * @param \Traversable $iterator A Traversable instance
+ * @param \Traversable $iterator Iterator that filters which files and directories to copy
* @param array $options An array of boolean options
* Valid options are:
- * - $options['override'] Whether to override an existing file on copy or not (see copy())
- * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink())
+ * - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false)
+ * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false)
* - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
*
* @throws IOException When file type is unknown
{
$targetDir = rtrim($targetDir, '/\\');
$originDir = rtrim($originDir, '/\\');
+ $originDirLen = strlen($originDir);
// Iterate in destination folder to remove obsolete entries
if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) {
$flags = \FilesystemIterator::SKIP_DOTS;
$deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST);
}
+ $targetDirLen = strlen($targetDir);
foreach ($deleteIterator as $file) {
- $origin = str_replace($targetDir, $originDir, $file->getPathname());
+ $origin = $originDir.substr($file->getPathname(), $targetDirLen);
if (!$this->exists($origin)) {
$this->remove($file);
}
}
foreach ($iterator as $file) {
- $target = str_replace($originDir, $targetDir, $file->getPathname());
+ $target = $targetDir.substr($file->getPathname(), $originDirLen);
if ($copyOnWindows) {
if (is_file($file)) {
{
return strspn($file, '/\\', 0, 1)
|| (strlen($file) > 3 && ctype_alpha($file[0])
- && substr($file, 1, 1) === ':'
+ && ':' === substr($file, 1, 1)
&& strspn($file, '/\\', 2, 1)
)
|| null !== parse_url($file, PHP_URL_SCHEME)
* @param null|int $mode The file mode (octal). If null, file permissions are not modified
* Deprecated since version 2.3.12, to be removed in 3.0.
*
- * @throws IOException If the file cannot be written to.
+ * @throws IOException if the file cannot be written to
*/
public function dumpFile($filename, $content, $mode = 0666)
{
if (null !== $mode) {
if (func_num_args() > 2) {
- @trigger_error('Support for modifying file permissions is deprecated since version 2.3.12 and will be removed in 3.0.', E_USER_DEPRECATED);
+ @trigger_error('Support for modifying file permissions is deprecated since Symfony 2.3.12 and will be removed in 3.0.', E_USER_DEPRECATED);
}
$this->chmod($tmpFile, $mode);