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

Skip to content

Commit e79b1a5

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

File tree

3 files changed

+124
-6
lines changed

3 files changed

+124
-6
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: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,57 @@ 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+
* With $canonicalize = false
390+
* - if $path does not exist or is not a link, this method will return null
391+
* - if $path is link, this method will return the value of the link
392+
* (the next direct target of the link, without consideration of existence)
393+
*
394+
* With $canonicalize = true
395+
* - if $path does not exist, this method will return null
396+
* - if $path exists, this method will return the absolute, fully resolved final target of the link
397+
* (in the case of bar -> baz -> foo, this method would return `/absolute/path/to/foo`)
398+
*
399+
* @param string $path A link path.
400+
* @param bool $canonicalize Whether or not to return a canonicalized path.
401+
*
402+
* @return string|null
403+
*/
404+
public function readlink($path, $canonicalize = false)
405+
{
406+
if (!$this->exists($path)) {
407+
return;
408+
}
409+
410+
if (!$canonicalize && !is_link($path)) {
411+
return;
412+
}
413+
414+
// /link1 -> /link2 -> /file
415+
416+
// Windows: readlink(/link1) => /file
417+
// realpath(/link1) => /link2
418+
419+
// Unix: readlink(/link1) => /link2
420+
// realpath(/link1) => /file
421+
422+
if ($canonicalize) {
423+
if ('\\' === DIRECTORY_SEPARATOR) {
424+
return realpath(readlink($path));
425+
}
426+
427+
return realpath($path);
428+
}
429+
430+
if ('\\' === DIRECTORY_SEPARATOR) {
431+
return realpath($path);
432+
}
433+
434+
return readlink($path);
435+
}
436+
386437
/**
387438
* Given an existing path, convert it to a path relative to a given starting path.
388439
*

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

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

960-
/**
961-
* @dataProvider providePathsForMakePathRelative
962-
*/
963-
public function testMakePathRelative($endPath, $startPath, $expectedPath)
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->normalize($this->workspace.'/file');
969+
$link1 = $this->normalize($this->workspace.'/dir/link');
970+
$link2 = $this->normalize($this->workspace.'/dir/link2');
971+
touch($file);
972+
973+
$this->filesystem->symlink($this->normalize('../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($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 testReadLinkDefaultPathDoesNotExist()
9641003
{
965-
$path = $this->filesystem->makePathRelative($endPath, $startPath);
1004+
$this->assertNull($this->filesystem->readlink($this->normalize($this->workspace.'/invalid')));
1005+
}
1006+
1007+
public function testReadLinkDefaultPathNotLink()
1008+
{
1009+
$file = $this->normalize($this->workspace.'/file');
1010+
touch($file);
9661011

967-
$this->assertEquals($expectedPath, $path);
1012+
$this->assertNull($this->filesystem->readlink($file));
1013+
}
1014+
1015+
public function testReadLinkCanonicalizedPathDoesNotExist()
1016+
{
1017+
$this->assertNull($this->filesystem->readlink($this->normalize($this->workspace.'invalid'), true));
9681018
}
9691019

9701020
/**
@@ -1321,4 +1371,16 @@ public function testCopyShouldKeepExecutionPermission()
13211371

13221372
$this->assertFilePermissions(767, $targetFilePath);
13231373
}
1374+
1375+
/**
1376+
* Normalize the given path (transform each blackslash into a real directory separator).
1377+
*
1378+
* @param string $path
1379+
*
1380+
* @return string
1381+
*/
1382+
private function normalize($path)
1383+
{
1384+
return str_replace('/', DIRECTORY_SEPARATOR, $path);
1385+
}
13241386
}

0 commit comments

Comments
 (0)
0