PHP7 realpath函数一个长期存在的bug
PHP7 realpath函数一个长期存在的bug
曾建凯 发表于1年前
PHP7 realpath函数一个长期存在的bug
  • 发表于 1年前
  • 阅读 2030
  • 收藏 12
  • 点赞 2
  • 评论 27

腾讯云 技术升级10大核心产品年终让利>>>   

摘要: 本文最后结论:PHP的realpath函数不支持phar文件。经过网友指出,是我脑袋发热,自以为是的 let_it_work函数,以为生效,其实里面存在非常致命的逻辑错误,我记住教训了。

本文最后结论:PHP的realpath函数不支持phar文件。经过网友指出,是我脑袋发热,自以为是的 let_it_work函数,以为生效,其实里面存在非常致命的逻辑错误,我记住教训了

为了留下的记录,文章内容一字不改,只在 let_it_work 函数那里注释了一下。

这事情教育我,还是得要单元测试,这种想当然错误太明显了。

实在是忍不住要吐槽一下,从7.0.0到7.0.4的时候,我一直在看这个bug,而且也发去php issues了,已经说修复了,但是显然并没有修复。后来忙,就没管这个问题了,可是到今天,都7.0.14了,7.1.0都发布了,还是没修复啊!

phar包

假定我有一个phar包,包内结构如下:

$paths = [
	'phar://phar_test.phar/hello',
	'phar://phar_test.phar/hello/a.php',
];

上述的这个路径,是存在的。

第一次测试

执行测试:

foreach ($paths as $path) {
	var_dump(file_exists($path)); // return true
	var_dump(realpath($path)); // return false
}

输出结果如下:

如果用file_exists检查,那么他是返回true的,文件是存在的。

用realpath检查,他返回了false,就是返回真实路径的时候,他无法返回相对应的真实路径。

所以,一般到这里,会让我们得出一个想当然的结论:realpath显然不支持phar包。

第二次测试

问题并没有结束,显然realpath是支持phar的,这次我们加一个函数:

function let_it_work(string $path)
{
	$realPath = realpath($path);
	if ($realPath !== false) {
		$path = $realPath;
	}
    // 这里我想当然了
    // 应该改为 return false
	return $path;
}

这个函数其实没啥特别,但是他却能让我们得到想要的结果。

执行下列测试程序:

foreach ($paths as $path) {
	var_dump(file_exists($path)); // return true
	var_dump(realpath($path)); // return false
	var_dump(let_it_work($path)); // 输出 "phar://phar_test.phar/hello" 和 "phar://phar_test.phar/hello/a.php"
}

我们会得到如下的结果:

显然realpath函数还是生效了。不然是不会得到真实路径的。

第三次测试

那么是不是给realpath包一层函数,就能得到我们想要的结果呢?那我再加一个函数:

function realpath2(string $path)
{
	return realpath($path);
}

执行以下测试:

foreach ($paths as $path) {
	var_dump(file_exists($path)); // return true
	var_dump(realpath($path)); // return false
	var_dump(let_it_work($path)); // 输出 "phar://phar_test.phar/hello" 和 "phar://phar_test.phar/hello/a.php"
	var_dump(realpath2($path)); // 和realpath返回结果一样
}

得到如下的结果:

显然包一层函数是不能解决问题的。

第四次测试

这个问题,并不止于realpath函数,所有获取realpath的函数,都存在这个问题。比如目录迭代器 DirectoryIterator,我们再写一个函数:

function entry(DirectoryIterator $dir)
{
	foreach ($dir as $item) {
		$path = $item->getPathname();
		var_dump(file_exists($path)); // true
		var_dump($item->getRealPath()); // false
		var_dump(let_it_work($path)); // 这里返回的结果,是正确的
	}
}

entry(new DirectoryIterator('phar://phar_test.phar'));

这个测试很简单,就是传入一个目录迭代器,然后遍历目录下的内容,然后调用 getRealPath 方法以进行测试。

执行结果如下:

不出所料,getRealPath返回还是返回无效的结果。

结论

其实这个问题看上去,并不是一个很严重的问题,而且也有解决方案了,所以也没什么可抱怨的。

但是冷静分析一下let_it_work的函数,问题的关键在于:

if ($realPath !== false) {
	$path = $realPath;
}

$path变量是函数的参数传入的,但是到这里,我把他和false做了一次比较,然后又把他写入了另一个变量的结果,这样就改变了结果。这里其实是一个很严重的问题,就是变量的内存地址问题。也就是说,通过一些操作,就让一个变量的内存地址发生了变化,取回了正确的值。怎么想都觉得非常诡异,莫名其妙。

这个问题从7.0.0发布的时候我就发现了,因为这个问题,这一年来我一直在认真考虑转Java还是C#的问题。

当然,也许我可以通过一些内存跟踪的手段去明确这个问题的根本,但我实在懒得折腾了。

其实PHP7还有一些让我不太满意的问题,比如ArrayObject的问题,比如:ArrayObject->item += 1,是无法触发offsetSet和offsetGet接口的。这个可能不算bug,也许到php7,关闭了这个特性。

无论如何,我对PHP的态度是,我只是这个语言的使用者,如果你让我折腾C,我不如去写Go、Java、C#等等,多了去的选择。所以如果这个语言本身不可靠,那我真的应该考虑换一个语言了。

共有 人打赏支持
粉丝 321
博文 56
码字总数 88252
评论 (27)
红薯
已经微信将此博客转发鸟哥 :)
红薯
@Laruence :)
eechen
显然,你这个bug仅限于操作phar包路径的场景.
leo108
第二次测试的let_it_work逻辑有问题,楼主没有发现么
leo108
另外结论中说到的内存地址问题,只是一个普通的变量赋值而已,看不出问题在哪
leo108
let_it_work的问题在于,输入===输出,即let_it_work('abc')=='abc',和文件是否存在已经无关。初步结论就是realpath不支持phar
海诺者
realpath returns the canonicalized absolut.在我看来phar://是一个协议,不是一个相对路径。你的用法是错误的
justintung
file_exists都是和stream wrapper 相关的,会在/path,默认认为是file:///path ,realpath和这个无关;
感觉发现新大陆一样,写这么多
digmeup
你的愤怒都是出自你第二个想当然的测试,第二个测试怎么就证明了realpath支持phar协议呢?
Fenying
第二个测试你return的是let_it_work的参数啊,跟realpath的返回值有关系????????????????????????
suconghou
too naive . let_it_work 什么都没做,还是返回输入, 一个变量赋值还说的那么诡异,扯什么内存地址问题.
悟庭
楼主赶紧删帖吧
OSC首席键客
我看你这个函数let_it_work就是原原本本的返回了传进来的参数$path啊,里面的realpath肯定是返回的false,那个if里面的语句就没执行。
Fenying

引用来自“红薯”的评论

已经微信将此博客转发鸟哥 :)

这是PHP被黑得最惨的一次→_→
OSC首席键客

引用来自“Fenying”的评论

第二个测试你return的是let_it_work的参数啊,跟realpath的返回值有关系????????????????????????
一脸懵逼吧?我也是,没看懂。我还返回看了他那个let_it_work函数。
宋宁126
if ($realPath !== false) {   $path = $realPath; }

$path变量是函数的参数传入的,但是到这里,我把他和false做了一次比较,然后又把他写入了另一个变量的结果,这样就改变了结果。这里其实是一个很严重的问题,就是变量的内存地址问题。也就是说,通过一些操作,就让一个变量的内存地址发生了变化,取回了正确的值。怎么想都觉得非常诡异,莫名其妙。

得不到你说的结论。
函数参数默认按值传递,你给它赋值有什么用
宋宁126

引用来自“leo108”的评论

第二次测试的let_it_work逻辑有问题,楼主没有发现么

赞同
wangdmeng
简直牛逼坏了
BlinkCG
看的尴尬癌都犯了。赶紧换语言吧。
不是simaguo
leo108 说的对,第二个逻辑有问题
×
曾建凯
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: