*/
public function mkdir($dirs, $mode = 0777)
{
- foreach ($this->toIterator($dirs) as $dir) {
+ foreach ($this->toIterable($dirs) as $dir) {
if (is_dir($dir)) {
continue;
}
{
$maxPathLength = PHP_MAXPATHLEN - 2;
- foreach ($this->toIterator($files) as $file) {
+ foreach ($this->toIterable($files) as $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);
}
*/
public function touch($files, $time = null, $atime = null)
{
- foreach ($this->toIterator($files) as $file) {
+ foreach ($this->toIterable($files) as $file) {
$touch = $time ? @touch($file, $time, $atime) : @touch($file);
if (true !== $touch) {
throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file);
*/
public function chmod($files, $mode, $umask = 0000, $recursive = false)
{
- foreach ($this->toIterator($files) as $file) {
+ foreach ($this->toIterable($files) as $file) {
if (true !== @chmod($file, $mode & ~$umask)) {
throw new IOException(sprintf('Failed to chmod file "%s".', $file), 0, null, $file);
}
*/
public function chown($files, $user, $recursive = false)
{
- foreach ($this->toIterator($files) as $file) {
+ foreach ($this->toIterable($files) as $file) {
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chown(new \FilesystemIterator($file), $user, true);
}
*/
public function chgrp($files, $group, $recursive = false)
{
- foreach ($this->toIterator($files) as $file) {
+ foreach ($this->toIterable($files) as $file) {
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chgrp(new \FilesystemIterator($file), $group, true);
}
}
if (!$ok && true !== @symlink($originDir, $targetDir)) {
- $report = error_get_last();
- if (is_array($report)) {
- if ('\\' === DIRECTORY_SEPARATOR && false !== strpos($report['message'], 'error code(1314)')) {
- throw new IOException('Unable to create symlink due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', 0, null, $targetDir);
+ $this->linkException($originDir, $targetDir, 'symbolic');
+ }
+ }
+
+ /**
+ * Creates a hard link, or several hard links to a file.
+ *
+ * @param string $originFile The original file
+ * @param string|string[] $targetFiles The target file(s)
+ *
+ * @throws FileNotFoundException When original file is missing or not a file
+ * @throws IOException When link fails, including if link already exists
+ */
+ public function hardlink($originFile, $targetFiles)
+ {
+ if (!$this->exists($originFile)) {
+ throw new FileNotFoundException(null, 0, null, $originFile);
+ }
+
+ if (!is_file($originFile)) {
+ throw new FileNotFoundException(sprintf('Origin file "%s" is not a file', $originFile));
+ }
+
+ foreach ($this->toIterable($targetFiles) as $targetFile) {
+ if (is_file($targetFile)) {
+ if (fileinode($originFile) === fileinode($targetFile)) {
+ continue;
}
+ $this->remove($targetFile);
+ }
+
+ if (true !== @link($originFile, $targetFile)) {
+ $this->linkException($originFile, $targetFile, 'hard');
+ }
+ }
+ }
+
+ /**
+ * @param string $origin
+ * @param string $target
+ * @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
+ */
+ private function linkException($origin, $target, $linkType)
+ {
+ $report = error_get_last();
+ if (is_array($report)) {
+ if ('\\' === DIRECTORY_SEPARATOR && false !== strpos($report['message'], 'error code(1314)')) {
+ throw new IOException(sprintf('Unable to create %s link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target);
+ }
+ }
+ throw new IOException(sprintf('Failed to create %s link from "%s" to "%s".', $linkType, $origin, $target), 0, null, $target);
+ }
+
+ /**
+ * Resolves links in paths.
+ *
+ * With $canonicalize = false (default)
+ * - if $path does not exist or is not a link, returns null
+ * - if $path is a link, returns the next direct target of the link without considering the existence of the target
+ *
+ * With $canonicalize = true
+ * - if $path does not exist, returns null
+ * - if $path exists, returns its absolute fully resolved final version
+ *
+ * @param string $path A filesystem path
+ * @param bool $canonicalize Whether or not to return a canonicalized path
+ *
+ * @return string|null
+ */
+ public function readlink($path, $canonicalize = false)
+ {
+ if (!$canonicalize && !is_link($path)) {
+ return;
+ }
+
+ if ($canonicalize) {
+ if (!$this->exists($path)) {
+ return;
}
- throw new IOException(sprintf('Failed to create symbolic link from "%s" to "%s".', $originDir, $targetDir), 0, null, $targetDir);
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $path = readlink($path);
+ }
+
+ return realpath($path);
}
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ return realpath($path);
+ }
+
+ return readlink($path);
}
/**
*/
public function makePathRelative($endPath, $startPath)
{
+ if (!$this->isAbsolutePath($endPath) || !$this->isAbsolutePath($startPath)) {
+ @trigger_error(sprintf('Support for passing relative paths to %s() is deprecated since Symfony 3.4 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
+ }
+
// Normalize separators on Windows
if ('\\' === DIRECTORY_SEPARATOR) {
$endPath = str_replace('\\', '/', $endPath);
{
return strspn($file, '/\\', 0, 1)
|| (strlen($file) > 3 && ctype_alpha($file[0])
- && ':' === substr($file, 1, 1)
+ && ':' === $file[1]
&& strspn($file, '/\\', 2, 1)
)
|| null !== parse_url($file, PHP_URL_SCHEME)
/**
* Atomically dumps content into a file.
*
- * @param string $filename The file to be written to
- * @param string $content The data to write into the file
- * @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.
+ * @param string $filename The file to be written to
+ * @param string $content The data to write into the file
*
* @throws IOException if the file cannot be written to
*/
- public function dumpFile($filename, $content, $mode = 0666)
+ public function dumpFile($filename, $content)
{
$dir = dirname($filename);
throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
}
+ // Will create a temp file with 0600 access rights
+ // when the filesystem supports chmod.
$tmpFile = $this->tempnam($dir, basename($filename));
if (false === @file_put_contents($tmpFile, $content)) {
throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
}
- if (null !== $mode) {
- if (func_num_args() > 2) {
- @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);
- } elseif (file_exists($filename)) {
- @chmod($tmpFile, fileperms($filename));
- }
+ @chmod($tmpFile, file_exists($filename) ? fileperms($filename) : 0666 & ~umask());
$this->rename($tmpFile, $filename, true);
}
/**
- * @param mixed $files
+ * Appends content to an existing file.
*
- * @return \Traversable
+ * @param string $filename The file to which to append content
+ * @param string $content The content to append
+ *
+ * @throws IOException If the file is not writable
*/
- private function toIterator($files)
+ public function appendToFile($filename, $content)
{
- if (!$files instanceof \Traversable) {
- $files = new \ArrayObject(is_array($files) ? $files : array($files));
+ $dir = dirname($filename);
+
+ if (!is_dir($dir)) {
+ $this->mkdir($dir);
+ }
+
+ if (!is_writable($dir)) {
+ throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
+ }
+
+ if (false === @file_put_contents($filename, $content, FILE_APPEND)) {
+ throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
}
+ }
- return $files;
+ /**
+ * @param mixed $files
+ *
+ * @return array|\Traversable
+ */
+ private function toIterable($files)
+ {
+ return is_array($files) || $files instanceof \Traversable ? $files : array($files);
}
/**