PHP文本文件行验证函数异常:为何返回false而非预期true?
问题描述
我有一个来自POST请求的文件,需要验证其是否符合以下约束:
- 所有行必须仅由ASCII可打印字符组成。
- 必须至少存在一条XYZ记录(以
@XYZ开头的行)。 - XYZ记录最多不能超过999999条
为此我编写了一个通用函数,通过分块读取文件并将每行传递给回调函数进行验证:
/* * Iterates over each line of the file, passing them to the callback function for validation. * When the callback function returns false, or when there is an error, * the validation process ends. * * @param string $filename The name of the file to validate. * @param callable $callback The callback function to use for validating each line. * @param string $line_delimiter The line-ending delimiter (default is "\n"). * @param integer $buffer_size The maximum number of bytes to read from the file at a time (default is 8192). * * @return Returns true when $callback returned true for each line, false if not, and NULL on error. * * @warning When $buffer_size is not large enough to contain a whole line, $callback will validate chunks of lines. */ function validate_file_lines($filename, $callback, $line_delimiter = "\n", $buffer_size = 8192) { $handle = fopen($filename, 'rb'); $is_valid = (false === $handle ? null : true); $remainder = ''; while ( $is_valid && !feof($handle) ) { $buffer = fread($handle, $buffer_size); if ( false === $buffer ) { $is_valid = null; } else { $lines_array = explode($line_delimiter, $buffer); $lines_array_key_last = count($lines_array) - 1; $lines_array[0] = $remainder . $lines_array[0]; if ( $lines_array_key_last !== 0 ) { $remainder = $lines_array[$lines_array_key_last]; unset($lines_array[$lines_array_key_last]); } foreach ( $lines_array as $line ) { $is_valid = $callback($line); if ( ! $is_valid ) break; } } } @fclose($handle); return $is_valid; }
现在,我使用该函数验证一个示例文件:
HEAD good @XYZ 1 @XYZ 1 %END HEAD better @XYZ 2 2 %END
验证代码:
$xyz_count = 0; $xyz_min = 1; $xyz_max = 999999; $is_valid_line = function($line) use(&$xyz_count, $xyz_max) { $is_valid = true; if ( ctype_print($line) ) { if ( substr($line, 0, 6) === '@XYZ ' ) { ++$xyz_count; $is_valid = $xyz_count <= $xyz_max; } } else if ( '' !== @$line[0] ) { $is_valid = false; } return $is_valid; }; var_dump( validate_file_lines('file.txt', $is_valid_line) && $xyz_count >= $xyz_min );
当前输出为:
bool(false)
而我预期的输出是:
bool(true)
请问我哪里出错了?
额外问题
SPL是否提供用于遍历文件行的类?
错误分析与解决方案
核心错误点
1. XYZ记录判断的长度错误
@XYZ (@+X+Y+Z+空格)实际是5个字符,但你使用substr($line, 0, 6)取前6个字符进行匹配,导致所有XYZ记录都无法被识别,$xyz_count始终为0,最终$xyz_count >= $xyz_min的判断不成立,返回false。
2. 未处理文件末尾的剩余内容
当文件最后一行没有以指定的行分隔符结尾时,$remainder会保留这行内容,但原函数在循环结束后没有将其传递给回调函数验证,导致最后一行既不被校验,也无法计入XYZ计数。
修复后的代码
修复通用文件验证函数
添加循环结束后处理剩余内容的逻辑:
function validate_file_lines($filename, $callback, $line_delimiter = "\n", $buffer_size = 8192) { $handle = fopen($filename, 'rb'); $is_valid = (false === $handle ? null : true); $remainder = ''; while ( $is_valid && !feof($handle) ) { $buffer = fread($handle, $buffer_size); if ( false === $buffer ) { $is_valid = null; break; } else { $lines_array = explode($line_delimiter, $buffer); $lines_array_key_last = count($lines_array) - 1; $lines_array[0] = $remainder . $lines_array[0]; if ( $lines_array_key_last !== 0 ) { $remainder = $lines_array[$lines_array_key_last]; unset($lines_array[$lines_array_key_last]); } else { // 单元素为不完整行,暂存到remainder $remainder = $lines_array[0]; continue; } foreach ( $lines_array as $line ) { $is_valid = $callback($line); if ( ! $is_valid ) break; } } } // 处理最后剩余的未完成行 if ( $is_valid !== null && $remainder !== '' ) { $is_valid = $callback($remainder); } @fclose($handle); return $is_valid; }
修复回调函数的XYZ匹配逻辑
将substr的长度参数改为5:
$xyz_count = 0; $xyz_min = 1; $xyz_max = 999999; $is_valid_line = function($line) use(&$xyz_count, $xyz_max) { $is_valid = true; if ( ctype_print($line) ) { // 修正:匹配前5个字符判断XYZ记录 if ( substr($line, 0, 5) === '@XYZ ' ) { ++$xyz_count; $is_valid = $xyz_count <= $xyz_max; } } else if ( '' !== @$line[0] ) { $is_valid = false; } return $is_valid; }; var_dump( validate_file_lines('file.txt', $is_valid_line) && $xyz_count >= $xyz_min );
修复后,示例文件中的3条XYZ记录会被正确计数,最终输出bool(true)。
额外问题解答
SPL提供了SplFileObject类,它内置了行迭代功能,可自动处理不同操作系统的换行符,无需手动实现分块读取和行拼接。示例用法:
$file = new SplFileObject('file.txt'); $xyz_count = 0; $is_valid = true; foreach ($file as $line) { // 去除换行符(按需选择) $line = rtrim($line, "\n\r"); if (!ctype_print($line)) { $is_valid = false; break; } if (substr($line, 0, 5) === '@XYZ ') { $xyz_count++; if ($xyz_count > 999999) { $is_valid = false; break; } } } var_dump($is_valid && $xyz_count >= 1);
该类还支持设置读取模式、跳过空行、按行号定位等功能,比手动分块读取更简洁可靠。
内容的提问来源于stack exchange,提问作者Fravadona
相关产品推荐
相关产品推荐

