PHP中file_put_contents后执行unlink导致写入内容全为NULL问题求解
PHP发票状态处理脚本写入文件异常问题分析
问题背景
我尝试从JSON文件中检测发票状态,若状态为已确认付款,则更新状态后将JSON写入新路径,再删除原有路径的JSON文件,代码如下:
<?php // get posted variables or die; if (isset($_POST['num'])) { $invoice = strip_tags($_POST['num']); $filename = $invoice.'.json'; } else { die; } if (isset($_POST['status'])) { $status = strip_tags($_POST['status']); } else { die; } // get existing invoice $content = file_get_contents('data/'.$invoice.'.json'); $data = json_decode($content, true); // read json into variables $email = $data['email']; $id = $data['id']; $addr = $data['tac_address']; $os = $data['os']; $exp = $data['experience']; $hosting = $data['type']; if (isset($data['telegram']) && $data['telegram'] != '') { $telegram = $data['telegram']; } else { $telegram = ''; } if (isset($data['linkedin']) && $data['linkedin'] != '') { $linkedin = $data['linkedin']; } else { $linkedin = ''; } if (isset($data['pay_status']) && $data['pay_status'] != '' && $data['pay_status'] == $status) { $status = $data['pay_status']; } $payment_addr = $data['bitcoin']; $payment_value = $data['value']; $payment = substr($payment_value, 0, -4); // turn variables into json array $arr = array( 'id' => $invoice, 'email' => $email, 'tac_address' => $addr, 'os' => $os, 'experience' => $exp, 'type' => $hosting, 'telegram' => $telegram, 'linkedin' => $linkedin, 'bitcoin' => $payment_addr, 'value' => $payment_value, 'pay_status' => $status ); $json = json_encode($arr); // check status if paid save output to new location and delete old file if ($status == 'Confirmed Payment') { file_put_contents('paid_data/'.$filename, $json); unlink('data/'.$filename); }
我遇到的问题是:执行file_put_contents('paid_data/'.$filename, $json)后生成的文件中存在大量NULL变量,若移除unlink代码则写入结果正常,重新加回unlink后变量又全部为NULL。请问如何验证file_put_contents执行完成后再执行unlink操作?另外该问题的成因是什么?PHP本应为线性执行逻辑,file_put_contents应该执行完成后才会运行下一行代码,为什么会出现unlink先于文件写入完成执行的情况?
问题成因
file_put_contents默认是同步阻塞执行的,写完才会返回结果,不会出现unlink先执行的情况,问题主要出自以下两个场景:
- 并发请求冲突:两个相同发票ID的请求同时触发,第一个请求刚读完原目录的JSON文件还没执行写入逻辑,第二个请求先跑完了完整流程删掉了原文件,第一个请求后续解析字段时原文件已经失效,
json_decode返回null,最终写入的变量全为空值。 - 无错误校验逻辑:
paid_data目录权限不足导致写入失败,你看到的无unlink时的正常文件实际是之前遗留的旧文件,加了unlink后旧文件被删,写入失败的新文件自然为空。
修复方案
1. 加文件操作校验,确认写入成功后再删除原文件
改写最后一段逻辑,所有操作都做合法性校验:
if ($status == 'Confirmed Payment') { // 先校验json编码是否成功 if ($json === false) { // 可自行加日志记录错误 die('json编码失败'); } $targetPath = 'paid_data/'.$filename; $writeBytes = file_put_contents($targetPath, $json); // 多重校验确保写入成功:返回字节数匹配、文件存在、内容一致 if ($writeBytes === false || $writeBytes !== strlen($json) || !file_exists($targetPath) || file_get_contents($targetPath) !== $json ) { die('文件写入失败'); } // 确认写入完成再删除原文件 unlink('data/'.$filename); }
2. 加文件锁避免并发冲突
读取原文件时加独占锁,防止多个请求同时操作同一个发票文件:
// 替换原来的file_get_contents读取逻辑 $sourcePath = 'data/'.$invoice.'.json'; $fp = fopen($sourcePath, 'r'); if (!$fp || !flock($fp, LOCK_EX)) { die('文件锁获取失败'); } $content = fread($fp, filesize($sourcePath)); flock($fp, LOCK_UN); fclose($fp); // 加json解码校验 $data = json_decode($content, true); if ($data === null) { die('无效的JSON文件'); }
3. 所有字段读取加默认值
避免原文件字段缺失导致变量未定义:
$email = $data['email'] ?? ''; $id = $data['id'] ?? ''; $addr = $data['tac_address'] ?? ''; // 其余所有字段都按此逻辑改写即可
内容的提问来源于stack exchange,提问作者Bruce
相关产品推荐
相关产品推荐

