10000 [Filesystem] Add a cross-platform readlink method · symfony/symfony@911beda · GitHub
[go: up one dir, main page]

Skip to content

Commit 911beda

Browse files
committed
[Filesystem] Add a cross-platform readlink method
1 parent bb2727a commit 911beda

File tree

2 files changed

+140
-0
lines changed

2 files changed

+140
-0
lines changed

src/Symfony/Component/Filesystem/Filesystem.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,72 @@ public function symlink($originDir, $targetDir, $copyOnWindows = false)
337337
}
338338
}
339339

340+
/**
341+
* Return the next direct target of a link.
342+
*
343+
* Tto recursively follow links until a final target
344+
* is reached, use @see Filesystem::realpath($path).
345+
*
346+
* @param string $path A link path.
347+
*
348+
* @return string Return the resolved link.
349+
*
350+
* @throws IOException When the link does not exist or is not readable.
351+
*/
352+
public function readlink($path)
353+
{
354+
if (!$this->exists($path)) {
355+
throw new IOException(sprintf('The link %s does not exist and cannot be read.', $path));
356+
}
357+
358+
if (!is_link($path)) {
359+
throw new IOException(sprintf('The path %s is not a link.', $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 ('\\' === DIRECTORY_SEPARATOR) {
371+
return realpath($path);
372+
}
373+
374+
return readlink($path);
375+
}
376+
377+
/**
378+
* Return the final target of a link by recursively following links.
379+
*
380+
* To find only the direct next target of a link,
381+
* use @see Filesystem::readlink($path).
382+
*
383+
* @param string $path A link path.
384+
*
385+
* @return string Return the final target of the link.
386+
*
387+
* @throws IOException When the link does not exist or is not readable.
388+
*/
389+
public function realpath($path)
390+
{
391+
if (!$this->exists($path)) {
392+
throw new IOException(sprintf('The link %s does not exist and cannot be read.', $path));
393+
}
394+
395+
if (!is_link($path)) {
396+
throw new IOException(sprintf('The path %s is not a link.', $path));
397+
}
398+
399+
if ('\\' === DIRECTORY_SEPARATOR) {
400+
return readlink($path);
401+
}
402+
403+
return realpath($path);
404+
}
405+
340406
/**
341407
* Given an existing path, convert it to a path relative to a given starting path.
342408
*

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,80 @@ 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+
touch($file);
811+
812+
$this->filesystem->symlink($file, $link1);
813+
$this->filesystem->symlink($link1, $link2);
814+
815+
$this->assertTrue(is_link($link1));
816+
$this->assertEquals($file, $this->filesystem->readlink($link1));
817+
$this->assertTrue(is_link($link2));
818+
$this->assertEquals($link1, $this->filesystem->readlink($link2));
819+
}
820+
821+
/**
822+
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
823+
*/
824+
public function testReadLinkNotLink()
825+
{
826+
$file = $this->workspace.DIRECTORY_SEPARATOR.'file';
827+
touch($file);
828+
$this->filesystem->readlink($file);
829+
}
830+
831+
/**
832+
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
833+
*/
834+
public function testReadLinkFails()
835+
{
836+
$this->filesystem->readlink($this->workspace.DIRECTORY_SEPARATOR.'invalid');
837+
}
838+
839+
public function testRealPath()
840+
{
841+
$this->markAsSkippedIfSymlinkIsMissing();
842+
843+
$file = $this->workspace.DIRECTORY_SEPARATOR.'file';
844+
$link1 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'link';
845+
$link2 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'subdir'.DIRECTORY_SEPARATOR.'link';
846+
847+
touch($file);
848+
849+
$this->filesystem->symlink($file, $link1);
850+
$this->filesystem->symlink($link1, $link2);
851+
852+
$this->assertTrue(is_link($link1));
853+
$this->assertEquals($file, $this->filesystem->realpath($link1));
854+
$this->assertTrue(is_link($link2));
855+
$this->assertEquals($file, $this->filesystem->realpath($link2));
856+
}
857+
858+
/**
859+
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
860+
*/
861+
public function testRealPathNotLink()
862+
{
863+
$file = $this->workspace.DIRECTORY_SEPARATOR.'file';
864+
touch($file);
865+
$this->filesystem->realpath($file);
866+
}
867+
868+
/**
869+
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
870+
*/
871+
public function testRealPathFails()
872+
{
873+
$this->filesystem->realpath($this->workspace.DIRECTORY_SEPARATOR.'invalid');
874+
}
875+
802876
/**
803877
* @dataProvider providePathsForMakePathRelative
804878
*/

0 commit comments

Comments
 (0)
0