Skip to content

Commit 2e21cf5

Browse files
committed
[Filesystem] Add appendToFile()
1 parent 0ab6628 commit 2e21cf5

File tree

2 files changed

+113
-18
lines changed

2 files changed

+113
-18
lines changed

src/Symfony/Component/Filesystem/Filesystem.php

+38-18
Original file line numberDiff line numberDiff line change
@@ -624,28 +624,24 @@ public function tempnam($dir, $prefix)
624624
* @param string $filename The file to be written to
625625
* @param string $content The data to write into the file
626626
*
627-
* @throws IOException If the file cannot be written to.
627+
* @throws IOException If the file cannot be written to
628628
*/
629629
public function dumpFile($filename, $content)
630630
{
631-
$dir = dirname($filename);
632-
633-
if (!is_dir($dir)) {
634-
$this->mkdir($dir);
635-
} elseif (!is_writable($dir)) {
636-
throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
637-
}
638-
639-
// Will create a temp file with 0600 access rights
640-
// when the filesystem supports chmod.
641-
$tmpFile = $this->tempnam($dir, basename($filename));
642-
643-
if (false === @file_put_contents($tmpFile, $content)) {
644-
throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
645-
}
631+
return $this->writeFile($filename, $content);
632+
}
646633

647-
@chmod($tmpFile, 0666 & ~umask());
648-
$this->rename($tmpFile, $filename, true);
634+
/**
635+
* Atomically appends content to an existing file.
636+
*
637+
* @param string $filename The file for which to append content
638+
* @param string|array|resource $content The content to append
639+
*
640+
* @throws IOException If the file is not writable
641+
*/
642+
public function appendToFile($filename, $content)
643+
{
644+
return $this->writeFile($filename, $content, true);
649645
}
650646

651647
/**
@@ -675,4 +671,28 @@ private function getSchemeAndHierarchy($filename)
675671

676672
return 2 === count($components) ? array($components[0], $components[1]) : array(null, $components[0]);
677673
}
674+
675+
/**
676+
* Writes to a file.
677+
*
678+
* @param string $filename The file to write into
679+
* @param string|array|resource $content The content to write
680+
* @param bool $append Whether to append content to the file if it already exists
681+
*
682+
* @throws IOException If the file is not writable
683+
*/
684+
private function writeFile($filename, $content, $append = false)
685+
{
686+
$dir = dirname($filename);
687+
688+
if (!is_dir($dir)) {
689+
$this->mkdir($dir);
690+
} elseif (!is_writable($dir)) {
691+
throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
692+
}
693+
694+
if (false === @file_put_contents($filename, $content, $append ? FILE_APPEND | LOCK_EX : LOCK_EX)) {
695+
throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
696+
}
697+
}
678698
}

src/Symfony/Component/Filesystem/Tests/FilesystemTest.php

+75
Original file line numberDiff line numberDiff line change
@@ -1406,6 +1406,81 @@ public function testDumpFileWithZlibScheme()
14061406
$this->assertSame('bar', file_get_contents($filename));
14071407
}
14081408

1409+
public function testAppendToFile()
1410+
{
1411+
$filename = $this->workspace.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.txt';
1412+
1413+
// skip mode check on Windows
1414+
if ('\\' !== DIRECTORY_SEPARATOR) {
1415+
$oldMask = umask(0002);
1416+
}
1417+
1418+
$this->filesystem->dumpFile($filename, 'foo');
1419+
1420+
$this->filesystem->appendToFile($filename, 'bar');
1421+
1422+
$this->assertFileExists($filename);
1423+
$this->assertSame('foobar', file_get_contents($filename));
1424+
1425+
// skip mode check on Windows
1426+
if ('\\' !== DIRECTORY_SEPARATOR) {
1427+
$this->assertFilePermissions(664, $filename, 'The written file should keep the same permissions as before.');
1428+
umask($oldMask);
1429+
}
1430+
}
1431+
1432+
public function testAppendToFileWithScheme()
1433+
{
1434+
if (defined('HHVM_VERSION')) {
1435+
$this->markTestSkipped('HHVM does not handle the file:// scheme correctly');
1436+
}
1437+
1438+
$scheme = 'file://';
1439+
$filename = $scheme.$this->workspace.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'baz.txt';
1440+
$this->filesystem->dumpFile($filename, 'foo');
1441+
1442+
$this->filesystem->appendToFile($filename, 'bar');
1443+
1444+
$this->assertFileExists($filename);
1445+
$this->assertSame('foobar', file_get_contents($filename));
1446+
}
1447+
1448+
public function testAppendToFileWithZlibScheme()
1449+
{
1450+
$scheme = 'compress.zlib://';
1451+
$filename = $this->workspace.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'baz.txt';
1452+
$this->filesystem->dumpFile($filename, 'foo');
1453+
1454+
// Zlib stat uses file:// wrapper so remove it
1455+
$this->assertSame('foo', file_get_contents(str_replace($scheme, '', $filename)));
1456+
1457+
$this->filesystem->appendToFile($filename, 'bar');
1458+
1459+
$this->assertFileExists($filename);
1460+
$this->assertSame('foobar', file_get_contents($filename));
1461+
}
1462+
1463+
public function testAppendToFileCreateTheFileIfNotExists()
1464+
{
1465+
$filename = $this->workspace.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.txt';
1466+
1467+
// skip mode check on Windows
1468+
if ('\\' !== DIRECTORY_SEPARATOR) {
1469+
$oldMask = umask(0002);
1470+
}
1471+
1472+
$this->filesystem->appendToFile($filename, 'bar');
1473+
1474+
// skip mode check on Windows
1475+
if ('\\' !== DIRECTORY_SEPARATOR) {
1476+
$this->assertFilePermissions(664, $filename);
1477+
umask($oldMask);
1478+
}
1479+
1480+
$this->assertFileExists($filename);
1481+
$this->assertSame('bar', file_get_contents($filename));
1482+
}
1483+
14091484
public function testCopyShouldKeepExecutionPermission()
14101485
{
14111486
$this->markAsSkippedIfChmodIsMissing();

0 commit comments

Comments
 (0)