8000 [Filesystem] Add a cross-platform readlink/realpath methods for neste… · symfony/symfony@c36507e · GitHub
[go: up one dir, main page]

Skip to content

Commit c36507e

Browse files
committed
[Filesystem] Add a cross-platform readlink/realpath methods for nested links
1 parent ad85c79 commit c36507e

File tree

3 files changed

+149
-0
lines changed

3 files changed

+149
-0
lines changed

src/Symfony/Component/Filesystem/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
3.2.0
5+
-----
6+
7+
* added `readlink()` as a platform independent method to read links
8+
49
3.0.0
510
-----
611

src/Symfony/Component/Filesystem/Filesystem.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,47 @@ private function linkException($origin, $target, $linkType)
383383
throw new IOException(sprintf('Failed to create %s link from "%s" to "%s".', $linkType, $origin, $target), 0, null, $target);
384384
}
385385

386+
/**
387+
* Resolves links in paths.
388+
*
389+
* With $canonicalize = false (default)
390+
* - if $path does not exist or is not a link, returns null
391+
* - if $path is a link, returns the next direct target of the link without considering the existence of the target
392+
*
393+
* With $canonicalize = true
394+
* - if $path does not exist, returns null
395+
* - if $path exists, returns its absolute fully resolved final version
396+
*
397+
* @param string $path A filesystem path
398+
* @param bool $canonicalize Whether or not to return a canonicalized path
399+
*
400+
* @return string|null
401+
*/
402+
public function readlink($path, $canonicalize = false)
403+
{
404+
if (!$canonicalize && !is_link($path)) {
405+
return;
406+
}
407+
408+
if ($canonicalize) {
409+
if (!$this->exists($path)) {
410+
return;
411+
}
412+
413+
if ('\\' === DIRECTORY_SEPARATOR) {
414+
$path = readlink($path);
415+
}
416+
417+
return realpath($path);
418+
}
419+
420+
if ('\\' === DIRECTORY_SEPARATOR) {
421+
return realpath($path);
422+
}
423+
424+
return readlink($path);
425+
}
426+
386427
/**
387428
* Given an existing path, convert it to a path relative to a given starting path.
388429
*

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

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,97 @@ public function testLinkWithSameTarget()
957957
$this->assertEquals(fileinode($file), fileinode($link));
958958
}
959959

960+
public function testReadRelativeLink()
961+
{
962+
$this->markAsSkippedIfSymlinkIsMissing();
963+
964+
if ('\\' === DIRECTORY_SEPARATOR) {
965+
$this->markTestSkipped('Relative symbolic links are not supported on Windows');
966+
}
967+
968+
$file = $this->workspace.'/file';
969+
$link1 = $this->workspace.'/dir/link';
970+
$link2 = $this->workspace.'/dir/link2';
971+
touch($file);
972+
973+
$this->filesystem->symlink('../file', $link1);
974+
$this->filesystem->symlink('link', $link2);
975+
976+
$this->assertEquals($this->normalize('../file'), $this->filesystem->readlink($link1));
977+
$this->assertEquals('link', $this->filesystem->readlink($link2));
978+
$this->assertEquals($file, $this->filesystem->readlink($link1, true));
979+
$this->assertEquals($ F438 file, $this->filesystem->readlink($link2, true));
980+
$this->assertEquals($file, $this->filesystem->readlink($file, true));
981+
}
982+
983+
public function testReadAbsoluteLink()
984+
{
985+
$this->markAsSkippedIfSymlinkIsMissing();
986+
987+
$file = $this->normalize($this->workspace.'/file');
988+
$link1 = $this->normalize($this->workspace.'/dir/link');
989+
$link2 = $this->normalize($this->workspace.'/dir/link2');
990+
touch($file);
991+
992+
$this->filesystem->symlink($file, $link1);
993+
$this->filesystem->symlink($link1, $link2);
994+
995+
$this->assertEquals($file, $this->filesystem->readlink($link1));
996+
$this->assertEquals($link1, $this->filesystem->readlink($link2));
997+
$this->assertEquals($file, $this->filesystem->readlink($link1, true));
998+
$this->assertEquals($file, $this->filesystem->readlink($link2, true));
999+
$this->assertEquals($file, $this->filesystem->readlink($file, true));
1000+
}
1001+
1002+
public function testReadBrokenLink()
1003+
{
1004+
$this->markAsSkippedIfSymlinkIsMissing();
1005+
1006+
if ('\\' === DIRECTORY_SEPARATOR) {
1007+
$this->markTestSkipped('Windows does not support creating "broken" symlinks');
1008+
}
1009+
1010+
$file = $this->workspace.'/file';
1011+
$link = $this->workspace.'/link';
1012+
1013+
$this->filesystem->symlink($file, $link);
1014+
1015+
$this->assertEquals($file, $this->filesystem->readlink($link));
1016+
$this->assertNull($this->filesystem->readlink($link, true));
1017+
1018+
touch($file);
1019+
$this->assertEquals($file, $this->filesystem->readlink($link, true));
1020+
}
1021+
1022+
public function testReadLinkDefaultPathDoesNotExist()
1023+
{
1024+
$this->assertNull($this->filesystem->readlink($this->normalize($this->workspace.'/invalid')));
1025+
}
1026+
1027+
public function testReadLinkDefaultPathNotLink()
1028+
{
1029+
$file = $this->normalize($this->workspace.'/file');
1030+
touch($file);
1031+
1032+
$this->assertNull($this->filesystem->readlink($file));
1033+
}
1034+
1035+
public function testReadLinkCanonicalizePath()
1036+
{
1037+
$this->markAsSkippedIfSymlinkIsMissing();
1038+
1039+
$file = $this->normalize($this->workspace.'/file');
1040+
mkdir($this->normalize($this->workspace.'/dir'));
1041+
touch($file);
1042+
1043+
$this->assertEquals($file, $this->filesystem->readlink($this->normalize($this->workspace.'/dir/../file'), true));
1044+
}
1045+
1046+
public function testReadLinkCanonicalizedPathDoesNotExist()
1047+
{
1048+
$this->assertNull($this->filesystem->readlink($this->normalize($this->workspace.'invalid'), true));
1049+
}
1050+
9601051
/**
9611052
* @dataProvider providePathsForMakePathRelative
9621053
*/
@@ -1321,4 +1412,16 @@ public function testCopyShouldKeepExecutionPermission()
13211412

13221413
$this->assertFilePermissions(767, $targetFilePath);
13231414
}
1415+
1416+
/**
1417+
* Normalize the given path (transform each blackslash into a real directory separator).
1418+
*
1419+
* @param string $path
1420+
*
1421+
* @return string
1422+
*/
1423+
private function normalize($path)
1424+
{
1425+
return str_replace('/', DIRECTORY_SEPARATOR, $path);
1426+
}
13241427
}

0 commit comments

Comments
 (0)
0