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

Skip to content

Commit fc3ef8d

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

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

src/Symfony/Component/Filesystem/Filesystem.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,52 @@ 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+
* Resolve a path as a link.
388+
*
389+
* Using $canonicalize = false, this method will return the link next direct target.
390+
* Using $canonicalize = true, this method will fully resolve nested links and canonicalize the path.
391+
*
392+
* @param string $path A link path.
393+
* @param bool $canonicalize Whether or not to return a canonicalized path.
394+
*
395+
* @return string Return the resolved link.
396+
*
397+
* @throws IOException When the path does not exist or is not readable.
398+
*/
399+
public function readlink($path, $canonicalize = false)
400+
{
401+
if (!$this->exists($path)) {
402+
throw new IOException(sprintf('The path %s does not exist and cannot be read.', $path));
403+
}
404+
405+
// /link1 -> /link2 -> /file
406+
407+
// Windows: readlink(/link1) => /file
408+
// realpath(/link1) => /link2
409+
410+
// Unix: readlink(/link1) => /link2
411+
// realpath(/link1) => /file
412+
413+
if ($canonicalize) {
414+
if ('\\' === DIRECTORY_SEPARATOR) {
415+
return realpath(readlink($path));
416+
}
417+
418+
return realpath(realpath($path));
419+
}
420+
421+
if (!is_link($path)) {
422+
throw new IOException(sprintf('The path %s is not a link.', $path));
423+
}
424+
425+
if ('\\' === DIRECTORY_SEPARATOR) {
426+
return realpath($path);
427+
}
428+
429+
return readlink($path);
430+
}
431+
386432
/**
387433
* Given an existing path, convert it to a path relative to a given starting path.
388434
*

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

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

960+
public function testReadLink()
961+
{
962+
$this->markAsSkippedIfSymlinkIsMissing();
963+
964+
$file = $this->workspace.DIRECTORY_SEPARATOR.'file';
965+
$link1 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'link';
966+
$link2 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'subdir'.DIRECTORY_SEPARATOR.'link';
967+
968+
$linkDot = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'subdir'.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'link';
969+
$link3 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'subdir'.DIRECTORY_SEPARATOR.'link_dot';
970+
971+
touch($file);
972+
973+
$this->filesystem->symlink($file, $link1);
974+
$this->filesystem->symlink($link1, $link2);
975+
$this->filesystem->symlink($linkDot, $link3);
976+
977+
$this->assertTrue(is_link($link1));
978+
$this->assertEquals($file, $this->filesystem->readlink($link1));
979+
$this->assertTrue(is_link($link2));
980+
$this->assertEquals($link1, $this->filesystem->readlink($link2));
981+
$this->assertTrue(is_link($link3));
982+
$this->assertEquals($linkDot, $this->filesystem->readlink($link3));
983+
}
984+
985+
/**
986+
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
987+
*/
988+
public function testReadLinkNotLink()
989+
{
990+
$file = $this->workspace.DIRECTORY_SEPARATOR.'file';
991+
touch($file);
992+
$this->filesystem->readlink($file);
993+
}
994+
995+
/**
996+
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
997+
*/
998+
public function testReadLinkFails()
999+
{
1000+
$this->filesystem->readlink($this->workspace.DIRECTORY_SEPARATOR.'invalid');
1001+
}
1002+
1003+
public function testReadLinkCanonicalized()
1004+
{
1005+
$this->markAsSkippedIfSymlinkIsMissing();
1006+
1007+
$file = $this->workspace.DIRECTORY_SEPARATOR.'file';
1008+
$link1 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'link';
1009+
$link2 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'subdir'.DIRECTORY_SEPARATOR.'link';
1010+
1011+
$linkDot = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'subdir'.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'link';
1012+
$link3 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'subdir'.DIRECTORY_SEPARATOR.'link_dot';
1013+
1014+
touch($file);
1015+
1016+
$this->filesystem->symlink($file, $link1);
1017+
$this->filesystem->symlink($link1, $link2);
1018+
$this->filesystem->symlink($linkDot, $link3);
1019+
1020+
$this->assertTrue(is_link($link1));
1021+
$this->assertEquals($file, $this->filesystem->readlink($link1, true));
1022+
$this->assertTrue(is_link($link2));
1023+
$this->assertEquals($file, $this->filesystem->readlink($link2, true));
1024+
$this->assertTrue(is_link($link3));
1025+
$this->assertEquals($file, $this->filesystem->readlink($link3, true));
1026+
}
1027+
1028+
public function testReadLinkNotLinkCanonicalized()
1029+
{
1030+
$file = $this->workspace.DIRECTORY_SEPARATOR.'file';
1031+
touch($file);
1032+
$this->assertEquals($file, $this->filesystem->readlink($file, true));
1033+
}
1034+
1035+
/**
1036+
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
1037+
*/
1038+
public function testReadLinkFailsCanonicalized()
1039+
{
1040+
$this->filesystem->readlink($this->workspace.DIRECTORY_SEPARATOR.'invalid', true);
1041+
}
1042+
9601043
/**
9611044
* @dataProvider providePathsForMakePathRelative
9621045
*/

0 commit comments

Comments
 (0)
0