浅谈PHP中打开文件(fopen)的一些坑

准备自己实现一个基于文件的简单缓存类,用于一些小外包项目。原本只是打算按照特定的JSON格式进行存储,然后用很方便的两个函数 file_get_contentfile_put_content 进行读写
后来想给缓存类加个自增/减的方法,用来做简单的统计之类。如果考虑到瞬间并发情况,为了防止两个请求互相干扰,就必须得上文件锁

首先是设置缓存,这个没啥难度:

  • 首先一个$h = fopen($path, 'w+b')打开文件
  • 然后flock($h, LOCK_EX)锁定它,防止两次并发请求同时读写
  • 接着fwrite($h, 'xxx...')写入内容
  • 继续flock($h, LOCK_UN)解锁文件
  • 最后fclose($h)关闭文件指针

然后是读取缓存,按照上面流程如法炮制。只是把第3步的 fwrite 换成 fread,把读出来的内容解析一下返回。看起来好像没什么问题。

然而我无论怎么试,缓存总是能正常写入,却无法读取

这我就懵逼了。经过一阵调试,发现 fread 总是返回空,这是为毛!?
再一阵调试过后,我发现在 fopen 之后,文件就已经变成空了。这就很奇怪了
我打开官方文档,仔细地再次阅读了w+模式的说明

读写方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。

沃日感情你所谓的将文件大小截为零就是会清空文件内容的意思啊?这表达得也太隐晦了吧?
看来w+是不能用了。我试着把fopen的模式切换到 r+x+a+ 以及 c+,都无法达到先读取文件内容,然后覆盖写入的效果

中间折腾过程略……

后来发现一个函数 ftruncate ,文档中描述为将文件截断到给定的长度。我用 r+b 打开、锁定文件,读取内容后用该函数将其清空;递增/减数值后写入,解锁。看起来一切都那么顺利

然而我最终保存的文件,在正常内容前有一串奇怪的空字符,导致再次读取时格式不对
怀疑是清空内容后没有将指针复位。找到一个 rewind 函数,执行后再进行写入操作。最终一切正常,可喜可贺、可喜可贺!