用 PHP explode 拆分字符串时,如何处理空值和分隔符缺失的情况

用 PHP explode 拆分字符串时,如何处理空值和分隔符缺失的情况

用 PHP explode 拆分字符串时,如何处理空值和分隔符缺失的情况

PHP 的 explode() 常用于把一段字符串按分隔符拆成数组,例如将逗号分隔的字段拆开、解析简单配置项,或处理一行文本中的固定标记。它的基本用法很直接,但实际数据经常不符合预期:变量可能是空字符串或 null,输入中可能没有分隔符,也可能以分隔符开头或结尾。

这些情况通常不会让 explode() 自动报错,却可能产生空数组元素、意料之外的类型,或让后续代码出现警告和逻辑错误。本文从函数行为出发,逐一说明如何判断输入、如何处理边界,以及哪些事情不能交给 explode() 解决。

1. explode() 的基本行为

explode() 按照指定的字符串分隔符拆分另一个字符串,返回一个数组。分隔符可以是一个字符,也可以是多个字符;匹配时区分大小写。

<?php

$line = 'red,green,blue';
$parts = explode(',', $line);

var_export($parts);
// array (
//   0 => 'red',
//   1 => 'green',
//   2 => 'blue',
// )

函数常见签名如下:

explode(string $separator, string $string, int $limit = PHP_INT_MAX): array

第一个参数是分隔符,第二个参数是待拆分的字符串,第三个参数是可选的拆分结果数量限制。这里的 limit 不是“最多拆分几次”的直观计数:正数限制结果数组的元素数量;负数会从结果末尾移除相应数量的元素;零会按 1 处理。下文会详细说明。

2. 分隔符不存在时会怎样

如果输入字符串中找不到分隔符,explode() 不会返回空数组,也不会返回 false。它会把整个输入作为数组的唯一一个元素返回。

<?php

$value = 'plain text';
$result = explode(',', $value);

var_export($result);
// array (
//   0 => 'plain text',
// )

echo count($result); // 1
echo $result[0];     // plain text

因此,“没有分隔符”与“没有数据”是两种不同情况。如果业务要求必须出现分隔符,就需要自行检查,而不能通过结果是否为空来判断。

<?php

function splitPair(string $value): array
{
    if (!str_contains($value, ':')) {
        throw new InvalidArgumentException('输入中缺少冒号');
    }

    return explode(':', $value, 2);
}

try {
    $pair = splitPair('name:alice');

    var_export($pair);
    // array (0 => 'name', 1 => 'alice')
} catch (InvalidArgumentException $e) {
    echo $e->getMessage();
}

str_contains() 从 PHP 8.0 起可用。如果项目需要兼容更旧版本,可以使用 strpos($value, ':') !== false。注意不能只写 if (strpos(...)):当匹配位置为字符串开头时,strpos() 返回整数 0,在布尔判断中会被当成假。

3. 空字符串会得到什么结果

如果输入字符串本身是空字符串,常见分隔符下的结果是只包含一个空字符串的数组,而不是空数组。

<?php

var_export(explode(',', ''));
// array (
//   0 => '',
// )

所以这两种值不能混为一谈:

  • []:没有数组元素。
  • ['']:有一个元素,但元素内容为空字符串。

这种差异会影响 count()、索引访问、遍历和校验。例如下面的代码会得到一个元素,并不代表输入中包含了有意义的字段:

<?php

$fields = explode(',', '');

echo count($fields); // 1

if ($fields[0] === '') {
    echo '字段为空';
}

如果业务上把空输入解释为“没有字段”,可以在拆分前明确处理:

<?php

function splitCsvLikeValue(string $value): array
{
    if ($value === '') {
        return [];
    }

    return explode(',', $value);
}

var_export(splitCsvLikeValue(''));
// array (

上面这个示例为了单独展示空输入分支,实际使用时应补上函数的结束部分和测试代码。一个完整、可运行的版本如下:

<?php

function splitCsvLikeValue(string $value): array
{
    if ($value === '') {
        return [];
    }

    return explode(',', $value);
}

var_export(splitCsvLikeValue(''));
// array ( )

var_export(splitCsvLikeValue('a,b'));
// array (0 => 'a', 1 => 'b')

这里的规则是业务选择,不是 PHP 对空字符串的默认处理。若空字符串代表一个合法的空字段,就不应擅自把它变成空数组。设计接口或数据格式时,最好明确区分“字段不存在”“字段存在但为空”和“整段输入为空”。

4. 边界分隔符会产生空元素

分隔符出现在开头、结尾或连续出现时,explode() 会保留对应的空片段,不会自动删除。

<?php

var_export(explode(',', ',a,b,'));
// array (
//   0 => '',
//   1 => 'a',
//   2 => 'b',
//   3 => '',
// )

var_export(explode(',', 'a,,b'));
// array (
//   0 => 'a',
//   1 => '',
//   2 => 'b',
// )

这些空项可能是错误,也可能是有效数据。例如逗号分隔的字段格式可能允许空字段;在其他场景中,连续分隔符可能说明用户漏填了字段。是否移除空项应由格式规则决定。

如果明确要求去掉首尾空白,并且忽略空字段,可以组合 trim() 与过滤操作:

<?php

$input = ' apple, ,banana,, pear ';

$parts = explode(',', $input);
$parts = array_map('trim', $parts);
$parts = array_values(array_filter(
    $parts,
    static fn (string $part): bool => $part !== ''
));

var_export($parts);
// array (
//   0 => 'apple',
//   1 => 'banana',
//   2 => 'pear',
// )

这里使用严格比较 $part !== '',是为了只删除空字符串。不要随手把过滤回调写成 array_filter($parts),因为不带回调的 array_filter() 会移除所有被 PHP 判定为假的值,除了空字符串,还会移除字符串 '0'、整数 0、false 和 null 等。若 '0' 是合法字段,这种写法会造成数据丢失。

另外,trim() 只处理字符串首尾的一组默认空白字符,不会删除字段中间的空格,也不负责理解引号、转义符或完整的 CSV 语法。对于含引号和转义规则的 CSV 文件,应使用 PHP 的 fgetcsv() 等专用工具,而不是简单地用逗号拆分。

5. 处理 null 和其他非字符串值

explode() 的输入参数要求是字符串。来自表单、数据库、JSON 解码结果或其他函数的变量,不一定始终是字符串。把 null、数组、对象等值直接传进去,不应当依赖 PHP 自动转换。

在 PHP 8.1 及更高版本中,弱类型调用中把 null 传给非空参数的内置函数会产生弃用通知;项目若启用了严格类型,或传入了不支持转换的值,也可能遇到 TypeError。不同 PHP 版本和调用方式的行为可能不同。可靠做法是在调用前明确处理输入类型和空值,而不是依赖隐式转换。

<?php

function splitNullable(?string $value): array
{
    if ($value === null || $value === '') {
        return [];
    }

    return explode(',', $value);
}

var_export(splitNullable(null));
// array ( )

var_export(splitNullable(''));
// array ( )

var_export(splitNullable('a,b'));
// array (0 => 'a', 1 => 'b')

上面的函数通过可空类型 ?string 明确表达了“可以接收字符串或 null”。如果来源值类型不确定,应该先检查类型,再决定拒绝、转换或使用默认值:

<?php

function splitUnknownValue(mixed $value): array
{
    if ($value === null) {
        return [];
    }

    if (!is_string($value)) {
        throw new InvalidArgumentException('输入必须是字符串或 null');
    }

    if ($value === '') {
        return [];
    }

    return explode(',', $value);
}

var_export(splitUnknownValue('a,b'));
// array (0 => 'a', 1 => 'b')

如果确实想把整数等标量转换成字符串,可以显式转换,并考虑业务含义:

<?php

$value = 123;
$parts = explode(',', (string) $value);

var_export($parts);
// array (0 => '123')

不过,显式强制转换不是通用的输入验证方案。对数组、对象或未经验证的请求数据,随意转换可能掩盖上游数据错误。更重要的是,(string) $value 不会验证内容是否符合预期格式。

6. 分隔符为空是另一类问题

空字符串作为分隔符,不能用来表示“逐字符拆分”。在 PHP 8.0 及更高版本中,空分隔符会抛出 ValueError;在 PHP 8.0 之前,行为是发出警告并返回 false。因此,必须确保分隔符非空,尤其是当分隔符来自配置或用户输入时。

<?php

$separator = ',';

if ($separator === '') {
    throw new InvalidArgumentException('分隔符不能为空');
}

$parts = explode($separator, 'a,b');
var_export($parts);

参数顺序也很容易弄错:第一个参数是分隔符,第二个参数才是待拆分的字符串。若交换位置,函数可能不会报错,却会按照错误的内容进行拆分。

<?php

$text = 'a,b';

// 正确:先传分隔符,再传输入字符串
$correct = explode(',', $text);

// 错误示范:把输入字符串误当成分隔符
$wrong = explode($text, ',');

var_export($correct);
// array (0 => 'a', 1 => 'b')

var_export($wrong);
// array (0 => ',')

第二种调用在 PHP 看来仍然是合法的:它试图在字符串 ',' 中查找分隔符 'a,b',找不到时返回包含原字符串的数组。所以参数颠倒不一定能靠异常发现。

7. 使用 limit 控制拆分范围

第三个参数 $limit 的行为需要特别留意。正数表示返回结果最多包含这么多个元素;若仍有未拆分部分,剩余内容会合并到最后一个元素中。

<?php

$path = 'users/42/profile';
$result = explode('/', $path, 2);

var_export($result);
// array (
//   0 => 'users',
//   1 => '42/profile',
// )

这在只想拆分第一个分隔符时很有用,例如解析“键:值”格式:

<?php

$line = 'title: PHP: practical guide';
$pair = explode(':', $line, 2);

var_export($pair);
// array (
//   0 => 'title',
//   1 => ' PHP: practical guide',
// )

注意,explode() 不会自动去除冒号后的空格;若格式规定要去除,应显式处理:

<?php

[$key, $value] = explode(':', 'title: PHP: practical guide', 2);

$key = trim($key);
$value = trim($value);

var_export([$key, $value]);
// array (
//   0 => 'title',
//   1 => 'PHP: practical guide',
// )

负数 limit 表示先按通常规则拆分,再从结果末尾移除相应数量的元素;如果移除后没有剩余元素,结果可能为空数组。零则按 1 处理,不表示返回零个元素。

<?php

$value = 'a,b,c';

var_export(explode(',', $value, 2));
// array (0 => 'a', 1 => 'b,c')

var_export(explode(',', $value, -1));
// array (0 => 'a', 1 => 'b')

var_export(explode(',', $value, 0));
// array (0 => 'a,b,c')

负数限制适用于明确知道要丢弃末尾若干部分的情况,不宜用来代替格式校验。若代码依赖固定字段数量,应在拆分后检查数量和每个字段的合法性。

8. 编写稳健的拆分函数

如果项目中多处都要执行同一种拆分,可以把规则集中到一个函数里:约定空值处理方式、拒绝空分隔符,并在必要时检查字段数量。这样调用方不必各自猜测边界行为。

<?php

function splitRequiredPair(?string $value, string $separator = ':'): array
{
    if ($separator === '') {
        throw new InvalidArgumentException('分隔符不能为空');
    }

    if ($value === null || $value === '') {
        throw new InvalidArgumentException('输入不能为空');
    }

    $parts = explode($separator, $value, 2);

    if (count($parts) !== 2) {
        throw new InvalidArgumentException('输入中缺少分隔符');
    }

    $left = trim($parts[0]);
    $right = trim($parts[1]);

    if ($left === '' || $right === '') {
        throw new InvalidArgumentException('分隔符两侧都必须有内容');
    }

    return [$left, $right];
}

try {
    [$name, $value] = splitRequiredPair(' name : Alice ');
    var_export([$name, $value]);
    // array (0 => 'name', 1 => 'Alice')
} catch (InvalidArgumentException $e) {
    echo $e->getMessage();
}

该函数使用 limit = 2,所以值中额外出现冒号时,额外内容会留在第二部分中,而不会悄悄丢失。是否允许这种情况,仍要由具体格式决定。如果要求输入中恰好只能出现一个分隔符,需要增加专门校验,不能把 limit 误认为“只接受一个分隔符”。

9. explode() 不是通用解析器,也不是安全过滤器

explode() 只做简单的字符串切分,不会验证拆分结果是否符合业务规则。它不会自动处理 CSV 引号、反斜线转义、引号中的分隔符、字符编码错误或嵌套结构。数据格式越复杂,越应使用对应的解析器,而不是不断叠加字符串操作。

例如,以下内容如果作为 CSV 文本处理,用逗号直接拆分就不够可靠:

<?php

$csvLine = 'Alice,"London, UK",42';
$parts = explode(',', $csvLine);

var_export($parts);
// 会把引号中的逗号也当作分隔符,不能正确得到三个 CSV 字段

explode() 也不提供安全保证。它不会自动防止 SQL 注入、跨站脚本(XSS)、命令注入或路径穿越。拆分后的值仍需根据使用场景进行验证、参数化或编码:访问数据库时使用预处理语句;输出到 HTML 时按上下文进行转义;传入命令或文件路径时采用相应的安全接口和约束。

同样,简单地按逗号拆分用户输入,并不能证明每个字段都安全,也不能替代长度限制、字符集规则、枚举校验或权限检查。输入校验应针对“字段可以是什么”,输出编码应针对“数据将被放到哪里”,二者不能互相替代。

10. 常见错误检查清单

  • 把没有分隔符当成空数组:没有匹配项时,explode() 返回包含原字符串的单元素数组。
  • 把空字符串当成零个元素:通常会得到 [''];若业务需要空数组,必须自行定义空输入规则。
  • 忽略开头、结尾和连续分隔符:这些位置会形成空字符串元素,不会自动消失。
  • 分隔符可能为空却直接调用:PHP 8.0 及更高版本会抛出 ValueError。
  • 依赖 null 的隐式转换:应使用可空类型、类型检查或明确的默认值处理。
  • 不带回调地过滤数组:可能误删合法的 '0' 等假值。
  • 把 limit 理解成最大拆分次数:正数限制的是结果元素数量,最后一个元素可以包含剩余文本。
  • 把字符串拆分当成格式验证:拆分成功不代表字段完整、合法或安全。
  • 用逗号直接解析 CSV:含引号或转义规则的数据应使用 CSV 专用解析方法。

总结

处理 explode() 的空值和缺少分隔符情况,关键不是记住某一种固定结果,而是先明确输入契约:空字符串和 null 是拒绝、忽略还是表示空字段;分隔符缺失是允许保留原文,还是必须报错;首尾空项和连续分隔符是否有效。

实践中可以遵循几条简单原则:调用前确认分隔符非空;对外部输入明确检查类型和空值;不要把分隔符缺失误判为空数组;根据业务决定是否保留空项;使用 limit 时牢记正数、负数和零的不同含义;复杂格式交给专用解析器;拆分后仍要进行业务校验和与使用场景相匹配的安全处理。

只要这些规则写清楚,explode() 仍是处理简单字符串格式的实用工具,也更不容易在边界数据到来时产生难以发现的问题。

© 版权声明

相关文章