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

Skip to content

Commit 259b4dd

Browse files
committed
[Filesystem] Add a cross-platform readlink/realpath methods for nested links
1 parent 3a165e5 commit 259b4dd

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
@@ -340,6 +340,52 @@ public function symlink($originDir, $targetDir, $copyOnWindows = false)
340340
}
341341
}
342342

343+
/**
344+
* Resolve a path as a link.
345+
*
346+
* Using $canonicalize = false, this method will return the link next direct target.
347+
* Using $canonicalize = true, this method will fully resolve nested links and canonicalize the path.
348+
*
349+
* @param string $path A link path.
350+
* @param bool $canonicalize Whether or not to return a canonicalized path.
351+
*
352+
* @return string Return the resolved link.
353+
*
354+
* @throws IOException When the path does not exist or is not readable.
355+
*/
356+
public function readlink($path, $canonicalize = false)
357+
{
358+
if (!$this->exists($path)) {
359+
throw new IOException(sprintf('The path %s does not exist and cannot be read.', $path));
360+
}
361+
362+
// /link1 -> /link2 -> /file
363+
364+
// Windows: readlink(/link1) => /file
365+
// realpath(/link1) => /link2
366+
367+
// Unix: readlink(/link1) => /link2
368+
// realpath(/link1) => /file
369+
370+
if ($canonicalize) {
371+
if ('\\' === DIRECTORY_SEPARATOR) {
372+
return realpath(readlink($path));
373+
}
374+
375+
return realpath(realpath($path));
376+
}
377+
378+
if (!is_link($path)) {
379+
throw new IOException(sprintf('The path %s is not a link.', $path));
380+
}
381+
382+
if ('\\' === DIRECTORY_SEPARATOR) {
383+
return realpath($path);
384+
}
385+
386+
return readlink($path);
387+
}
388+
343389
/**
344390
* Given an existing path, convert it to a path relative to a given starting path.
345391
*

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

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,89 @@ public function testSymlinkCreatesTargetDirectoryIfItDoesNotExist()
799799
$this->assertEquals($file, readlink($link2));
800800
}
801801

802+
public function testReadLink()
803+
{
804+
$this->markAsSkippedIfSymlinkIsMissing();
805+
806+
$file = $this->workspace.DIRECTORY_SEPARATOR.'file';
807+
$link1 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'link';
808+
$link2 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'subdir'.DIRECTORY_SEPARATOR.'link';
809+
810+
$linkDot = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'subdir'.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'link';
811+
$link3 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'subdir'.DIRECTORY_SEPARATOR.'link_dot';
812+
813+
touch($file);
814+
815+
$this->filesystem->symlink($file, $link1);
816+
$this->filesystem->symlink($link1, $link2);
817+
$this->filesystem->symlink($linkDot, $link3);
818+
819+
$this->assertTrue(is_link($link1));
820+
$this->assertEquals($file, $this->filesystem->readlink($link1));
821+
$this->assertTrue(is_link($link2));
822+
$this->assertEquals($link1, $this->filesystem->readlink($link2));
823+
$this->assertTrue(is_link($link3));
824+
$this->assertEquals($linkDot, $this->filesystem->readlink($link3));
825+
}
826+
827+
/**
828+
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
829+
*/
830+
public function testReadLinkNotLink()
831+
{
832+
$file = $this->workspace.DIRECTORY_SEPARATOR.'file';
833+
touch($file);
834+
$this->filesystem->readlink($file);
835+
}
836+
837+
/**
838+
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
839+
*/
840+
public function testReadLinkFails()
841+
{
842+
$this->filesystem->readlink($this->workspace.DIRECTORY_SEPARATOR.'invalid');
843+
}
844+
845+
public function testReadLinkCanonicalized()
846+
{
847+
$this->markAsSkippedIfSymlinkIsMissing();
848+
849+
$file = $this->workspace.DIRECTORY_SEPARATOR.'file';
850+
$link1 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'link';
851+
$link2 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'subdir'.DIRECTORY_SEPARATOR.'link';
852+
853+
$linkDot = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'subdir'.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'link';
854+
$link3 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'subdir'.DIRECTORY_SEPARATOR.'link_dot';
855+
856+
touch($file);
857+
858+
$this->filesystem->symlink($file, $link1);
859+
$this->filesystem->symlink($link1, $link2);
860+
$this->filesystem->symlink($linkDot, $link3);
861+
862+
$this->assertTrue(is_link($link1));
863+
$this->assertEquals($file, $this->filesystem->readlink($link1, true));
864+
$this->assertTrue(is_link($link2));
865+
$this->assertEquals($file, $this->filesystem->readlink($link2, true));
866+
$this->assertTrue(is_link($link3));
867+
$this->assertEquals($file, $this->filesystem->readlink($link3, true));
868+
}
869+
870+
public function testReadLinkNotLinkCanonicalized()
871+
{
872+
$file = $this->workspace.DIRECTORY_SEPARATOR.'file';
873+
touch($file);
874+
$this->assertEquals($file, $this->filesystem->readlink($file, true));
875+
}
876+
877+
/**
878+
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
879+
*/
880+
public function testReadLinkFailsCanonicalized()
881+
{
882+
$this->filesystem->readlink($this->workspace.DIRECTORY_SEPARATOR.'invalid', true);
883+
}
884+
802885
/**
803886
* @dataProvider providePathsForMakePathRelative
804887
*/

0 commit comments

Comments
 (0)
0