[Filesystem] Add a cross-platform readlink/realpath methods for nested links
This commit is contained in:
parent
ad85c79d3d
commit
c36507e64f
@ -1,6 +1,11 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
3.2.0
|
||||
-----
|
||||
|
||||
* added `readlink()` as a platform independent method to read links
|
||||
|
||||
3.0.0
|
||||
-----
|
||||
|
||||
|
@ -383,6 +383,47 @@ class Filesystem
|
||||
throw new IOException(sprintf('Failed to create %s link from "%s" to "%s".', $linkType, $origin, $target), 0, null, $target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves links in paths.
|
||||
*
|
||||
* With $canonicalize = false (default)
|
||||
* - if $path does not exist or is not a link, returns null
|
||||
* - if $path is a link, returns the next direct target of the link without considering the existence of the target
|
||||
*
|
||||
* With $canonicalize = true
|
||||
* - if $path does not exist, returns null
|
||||
* - if $path exists, returns its absolute fully resolved final version
|
||||
*
|
||||
* @param string $path A filesystem path
|
||||
* @param bool $canonicalize Whether or not to return a canonicalized path
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function readlink($path, $canonicalize = false)
|
||||
{
|
||||
if (!$canonicalize && !is_link($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($canonicalize) {
|
||||
if (!$this->exists($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
||||
$path = readlink($path);
|
||||
}
|
||||
|
||||
return realpath($path);
|
||||
}
|
||||
|
||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
||||
return realpath($path);
|
||||
}
|
||||
|
||||
return readlink($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an existing path, convert it to a path relative to a given starting path.
|
||||
*
|
||||
|
@ -957,6 +957,97 @@ class FilesystemTest extends FilesystemTestCase
|
||||
$this->assertEquals(fileinode($file), fileinode($link));
|
||||
}
|
||||
|
||||
public function testReadRelativeLink()
|
||||
{
|
||||
$this->markAsSkippedIfSymlinkIsMissing();
|
||||
|
||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
||||
$this->markTestSkipped('Relative symbolic links are not supported on Windows');
|
||||
}
|
||||
|
||||
$file = $this->workspace.'/file';
|
||||
$link1 = $this->workspace.'/dir/link';
|
||||
$link2 = $this->workspace.'/dir/link2';
|
||||
touch($file);
|
||||
|
||||
$this->filesystem->symlink('../file', $link1);
|
||||
$this->filesystem->symlink('link', $link2);
|
||||
|
||||
$this->assertEquals($this->normalize('../file'), $this->filesystem->readlink($link1));
|
||||
$this->assertEquals('link', $this->filesystem->readlink($link2));
|
||||
$this->assertEquals($file, $this->filesystem->readlink($link1, true));
|
||||
$this->assertEquals($file, $this->filesystem->readlink($link2, true));
|
||||
$this->assertEquals($file, $this->filesystem->readlink($file, true));
|
||||
}
|
||||
|
||||
public function testReadAbsoluteLink()
|
||||
{
|
||||
$this->markAsSkippedIfSymlinkIsMissing();
|
||||
|
||||
$file = $this->normalize($this->workspace.'/file');
|
||||
$link1 = $this->normalize($this->workspace.'/dir/link');
|
||||
$link2 = $this->normalize($this->workspace.'/dir/link2');
|
||||
touch($file);
|
||||
|
||||
$this->filesystem->symlink($file, $link1);
|
||||
$this->filesystem->symlink($link1, $link2);
|
||||
|
||||
$this->assertEquals($file, $this->filesystem->readlink($link1));
|
||||
$this->assertEquals($link1, $this->filesystem->readlink($link2));
|
||||
$this->assertEquals($file, $this->filesystem->readlink($link1, true));
|
||||
$this->assertEquals($file, $this->filesystem->readlink($link2, true));
|
||||
$this->assertEquals($file, $this->filesystem->readlink($file, true));
|
||||
}
|
||||
|
||||
public function testReadBrokenLink()
|
||||
{
|
||||
$this->markAsSkippedIfSymlinkIsMissing();
|
||||
|
||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
||||
$this->markTestSkipped('Windows does not support creating "broken" symlinks');
|
||||
}
|
||||
|
||||
$file = $this->workspace.'/file';
|
||||
$link = $this->workspace.'/link';
|
||||
|
||||
$this->filesystem->symlink($file, $link);
|
||||
|
||||
$this->assertEquals($file, $this->filesystem->readlink($link));
|
||||
$this->assertNull($this->filesystem->readlink($link, true));
|
||||
|
||||
touch($file);
|
||||
$this->assertEquals($file, $this->filesystem->readlink($link, true));
|
||||
}
|
||||
|
||||
public function testReadLinkDefaultPathDoesNotExist()
|
||||
{
|
||||
$this->assertNull($this->filesystem->readlink($this->normalize($this->workspace.'/invalid')));
|
||||
}
|
||||
|
||||
public function testReadLinkDefaultPathNotLink()
|
||||
{
|
||||
$file = $this->normalize($this->workspace.'/file');
|
||||
touch($file);
|
||||
|
||||
$this->assertNull($this->filesystem->readlink($file));
|
||||
}
|
||||
|
||||
public function testReadLinkCanonicalizePath()
|
||||
{
|
||||
$this->markAsSkippedIfSymlinkIsMissing();
|
||||
|
||||
$file = $this->normalize($this->workspace.'/file');
|
||||
mkdir($this->normalize($this->workspace.'/dir'));
|
||||
touch($file);
|
||||
|
||||
$this->assertEquals($file, $this->filesystem->readlink($this->normalize($this->workspace.'/dir/../file'), true));
|
||||
}
|
||||
|
||||
public function testReadLinkCanonicalizedPathDoesNotExist()
|
||||
{
|
||||
$this->assertNull($this->filesystem->readlink($this->normalize($this->workspace.'invalid'), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providePathsForMakePathRelative
|
||||
*/
|
||||
@ -1321,4 +1412,16 @@ class FilesystemTest extends FilesystemTestCase
|
||||
|
||||
$this->assertFilePermissions(767, $targetFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the given path (transform each blackslash into a real directory separator).
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function normalize($path)
|
||||
{
|
||||
return str_replace('/', DIRECTORY_SEPARATOR, $path);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user