☰
PHP数组与字符串互相转换实例
2026/10/10 9:43:21 网站建设 项目流程

前言


数组和字符串的互转,是 PHP 日常代码里出现频率最高的一类操作。它其实分成两个层次:用分隔符拼接(implode()/explode(),适合「标签、ID 列表、URL 查询参数」这种简单场景),和结构化序列化(JSON、serialize(),适合有嵌套、有类型的数据)。层次搞错了,麻烦很快就会来——比如把含分隔符的值硬拼成一个字符串,回头再拆开时字段全错位。


几个反复出现的误解,本文都会点明:explode()的分隔符不能是空字符串;implode()的参数顺序有历史包袱,反着写在 PHP 8 已经不能用了;json_decode()默认返回的是对象而不是数组,得靠第二个参数切换;把explode()的结果当成「一定有元素」来用,会踩到边界值。


还有一个必须写在开头、而且最要紧:unserialize()绝对不能用在不可信数据上。这条不是风格建议,是安全红线,第三节会讲清楚原因和替代做法。


本文示例以 PHP 8.0 及以上为基准。另外,一些早已从 PHP 里移除的字符串/数组函数(split()、ereg_*、each())也会在坑点里一并给出当前的正确写法。


一、用分隔符互转:implode() 与 explode()


签名先摆正,这两个函数的参数顺序方向是相反的,记错就全军覆没:


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

implode()把数组元素用分隔符连接成一个字符串,数组键会被完全丢弃;explode()把字符串按分隔符切开,得到的是一个从0开始连续编号的列表。正因为键会丢,这两个函数不是互逆的——explode(',', implode(',', $arr))拿回来的永远是一个下标记为 0、1、2 的列表,而不是原来的键。


<?php // 适用于 PHP 8.0+
declare(strict_types=1);

$tags = ['php', 'mysql', 'linux'];

$str = implode(',', $tags); // "php,mysql,linux"
$back = explode(',', $str); // ['php', 'mysql', 'linux']

// 省略分隔符时需要显式写成单参数形式,等价于用空串连接
echo implode($tags), "\n"; // "phpmysqllinux"

// limit 的四种取值
var_dump(explode(',', 'a,b,c,d', 2)); // ['a', 'b,c,d'] 正数:最多 limit 个,最后一段是剩余全部
var_dump(explode(',', 'a,b,c,d', -1)); // ['a', 'b', 'c'] 负数:去掉末尾 1 个元素
var_dump(explode(',', 'a,b,c,d', 0)); // ['a,b,c,d'] 0 会被当成 1
var_dump(explode('|', 'a,b')); // ['a,b'] 不含分隔符:单元素数组

// 分隔符出现在开头或结尾,会产生空字符串元素
var_dump(explode(',', ',a,'));
// [0 => '', 1 => 'a', 2 => '']

几个必须记住的边界行为:



  • 分隔符不能是空字符串。PHP 8.0 起,explode('', 'abc')会抛出ValueError(提示explode(): Argument #1 ($separator) must not be empty);在 PHP 8.0 之前,它发一条警告并返回false。想把字符串拆成单个字符,用str_split(),多字节字符用mb_str_split()。

  • implode()的参数顺序是「分隔符在前」。因为历史原因,它曾允许反过来写implode($pieces, $glue),这让implode()和explode()看起来参数顺序一致。这个写法在PHP 7.4 被废弃,PHP 8.0 已移除,现在反着传会直接报错。

  • 元素不是标量时会出问题。嵌套数组元素会触发Array to string conversion警告,并在结果里变成字面量Array;没有实现__toString()的对象则直接抛出Error。

  • 值里含分隔符时不安全。如果业务值本身可能包含逗号(例如用户填的备注),implode()出来的字符串再也无法可靠地拆回去。这种情况要么先str_replace()转义,要么改用下面第二节的结构化格式。


如果确实需要处理「值里含分隔符」的表格数据,标准答案是 CSV 那一套:写出用fputcsv(),读入用fgetcsv()或str_getcsv(),它们会自己处理引号与转义。规则稍微复杂的话,用preg_split()也往往比手写explode()稳妥。


二、结构化互转:json_encode() 与 json_decode()


JSON 是目前最通用的方案,优点是跨语言、可读、有明确的标准,数据嵌套多深都能表达。


json_encode(mixed $value, int $flags = 0, int $depth = 512): string|false
json_decode(string $json, ?bool $associative = null, int $depth = 512, int $flags = 0): mixed

先说编码方向的这两个关键点:


第一,数组编码成 JSON 数组还是 JSON 对象,取决于键。如果键恰好是0到count($array)-1的连续序列(也就是array_is_list()返回true的那种),编码结果是 JSON 数组[...];否则一律编码成 JSON 对象{...}。


第二,编码失败时返回false而不是抛异常,所以必须检查返回值。若不希望每次都手工判断,可以传JSON_THROW_ON_ERROR让它抛JsonException。


解码方向同样有两个关键点:


第一,默认返回对象。json_decode()的第二个参数$associative不传时,JSON 对象会被解码成stdClass实例,要用$obj->name访问;传true才得到关联数组,用$arr['name']访问。这个参数是本文出现频率最高的坑。


第二,JSON 只有一种数字类型。超出 PHP 整数范围的数字解码后是浮点数,精度可能已经丢失;需要精确保存大整数(例如订单号、雪花 ID)时,应当以字符串形式存进 JSON。


<?php // 适用于 PHP 8.1+(JSON_THROW_ON_ERROR 需要 PHP 7.3+)
declare(strict_types=1);

$config = [
'name' => 'demo',
'tags' => ['php', 'web'],
'meta' => ['a' => 1, 'b' => 2],
];

$json = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
// {"name":"demo","tags":["php","web"],"meta":{"a":1,"b":2}}

$obj = json_decode($json); // stdClass
$arr = json_decode($json, true); // 关联数组

// 列表与「非列表」的编码差异
echo json_encode([1, 2, 3]), "\n"; // [1,2,3]
echo json_encode([1 => 1, 2 => 2]), "\n"; // {"1":1,"2":2} 键不从 0 连续 → JSON 对象
echo json_encode(['a' => 1]), "\n"; // {"a":1}

// 想让「列表」强制编码成对象,可以传 JSON_FORCE_OBJECT
echo json_encode([1, 2], JSON_FORCE_OBJECT), "\n"; // {"0":1,"1":2}

// 错误处理:不抛异常时,靠 json_last_error() / json_last_error_msg() 判断
$bad = json_decode('{不是合法 JSON}', true);
if ($bad === null && json_last_error() !== JSON_ERROR_NONE) {
echo '解析失败:' . json_last_error_msg() . "\n";
}

// 或者让它抛异常,省掉手工判断
try {
json_decode('{不是合法 JSON}', true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
echo '抛出异常:' . $e->getMessage() . "\n";
}

JSON_THROW_ON_ERROR是PHP 7.3.0 引入的,json_encode()和json_decode()都支持。抛出的是JsonException(继承自Exception),其中getMessage()对应json_last_error_msg(),getCode()对应json_last_error()。


注意上面「不抛异常」的那段代码里为什么要写成$bad === null && json_last_error() !== JSON_ERROR_NONE:JSON 里的null是合法值,解码成功也可能得到null,单看返回值无法区分成功与失败,必须配合错误状态判断。


三、PHP 专用序列化:serialize() 与它的安全红线


serialize()把一个 PHP 值转成 PHP 自己的序列化格式字符串,unserialize()再还原回来。它比 JSON 强的地方在于:能完整保留类型、嵌套结构、对象(及其类名与属性)、甚至引用关系。


<?php // 适用于 PHP 7.0+
$data = ['name' => 'Tom', 'age' => 20];

$s = serialize($data);
echo $s, "\n";
// a:2:{s:4:"name";s:3:"Tom";s:3:"age";i:20;}

var_dump(unserialize($s));
// 还原成 ['name' => 'Tom', 'age' => 20],整数仍然是整数

格式本身就说明了它的定位:a:2:{...}表示 2 个元素的数组,s:4:"name"表示长度 4 的字符串,i:20表示整数。它是为 PHP 自己设计的,不适合当对外数据交换格式:其他语言解析不了,跨大版本时对象结构一变也可能还原失败。


然后是最重要的部分——安全红线:



不要把不可信的数据传给unserialize()。



PHP 官方手册在这页专门挂了 Warning,意思很明确:无论allowed_classes选项取什么值,都不要把不可信输入交给unserialize()。原因是反序列化过程会自动实例化对象、并触发类的自动加载(autoload),攻击者可以借此让程序加载并执行非预期的代码(这类问题通常叫 PHP 对象注入,PHP Object Injection)。


防御侧的正确做法,按优先级排列:



  1. 改用json_decode()。如果数据只是用来传输,不需要保留 PHP 对象,JSON 就够了,而且 JSON 解析不会实例化任意类。

  2. 只做单向序列化。把serialize()的结果存进自己的缓存、会话或数据库,同时保证读出来的那份数据不会被用户改写(例如放在用户无法写入的存储里)。

  3. 必须反序列化外部数据时,限制允许的类。unserialize()的第二个参数接受['allowed_classes' => ...](PHP 7.0 引入):



  • 传false:不允许任何类,遇到对象会还原成__PHP_Incomplete_Class而不是真的实例化;

  • 传类名数组:白名单,只有列出的类会被实例化;

  • 传true:允许全部(等同于不传该选项,也就是手册不推荐的做法)。


<?php // 适用于 PHP 8.1+;allowed_classes 需要 PHP 7.0+
declare(strict_types=1);

// 只允许还原为标量和数组,任何对象都变成 __PHP_Incomplete_Class
$options = ['allowed_classes' => false];

$raw = 'a:1:{i:0;i:42;}';
var_dump(unserialize($raw, $options));
// [0 => 42]

// 如果传入了非法类型(PHP 7.1 起,既不是数组也不是布尔值)
$result = @unserialize($raw, ['allowed_classes' => 'DateTime']);
var_dump($result); // bool(false),同时有一条 E_WARNING

最后一行是版本差异,值得记住:PHP 7.1.0 起,allowed_classes的值若不是数组也不是布尔值,unserialize()会返回false并发出E_WARNING(PHP 7.0 不报这个警告)。写这份代码时别把「字符串形式的类名」当成白名单传进去。


四、顺带:数组与对象的转换


数组和对象之间的转换不属于「数组与字符串」,但在同一个业务里经常连用,这里一并交代清楚。


(object)强转把数组变成stdClass,数组的键直接成为属性名;get_object_vars(object $object): array做反向操作,返回对象的属性组成的关联数组。


<?php // 适用于 PHP 7.0+
$arr = ['name' => 'Tom', 'age' => 20];

$obj = (object) $arr;
echo $obj->name, "\n"; // Tom

$back = get_object_vars($obj);
print_r($back);
// [name] => Tom
// [age] => 20

// 属性名是数字时,要用花括号语法访问
$n = (object) [0 => '零'];
echo $n->{'0'}, "\n"; // 零

两个要点:这两步都只处理一层,嵌套的数组不会自动变成嵌套对象;get_object_vars()返回的属性范围取决于调用位置——在类外部调用只返回公开属性,在类的方法内部调用则会包含private、protected属性。做数据交换时,json_decode($json, true)得到的关联数组通常比「stdClass+get_object_vars()」更好用,因为它天生就是递归的。


常见坑点



  1. ❌explode('', $str)想按字符拆开字符串。


✅ PHP 8.0 起抛ValueError(PHP 7 是警告加false);按字符拆用str_split(),多字节用mb_str_split()。



  1. ❌ 把implode()反着写:implode($array, ',')。


✅ 正确顺序是分隔符在前:implode(',', $array);反序写法在 PHP 7.4 废弃、PHP 8.0 移除,现在直接报错。



  1. ❌ 用explode()拆完直接取$parts[0],没考虑字符串里没有分隔符或输入为空的情况。


✅ 不含分隔符时返回单元素数组,分隔符在首尾会产生空字符串元素;先判断数组是否为空再取下标。



  1. ❌ 直接json_decode($json)然后写$data['key'],结果报「Cannot use object of type stdClass as array」。


✅ 默认返回stdClass,要数组就传第二个参数true:json_decode($json, true)。



  1. ❌ 用if (json_decode($json) === null)判断解析失败。


✅ JSON 里的null是合法值,解码成功也可能得到null;配合json_last_error()判断,或使用JSON_THROW_ON_ERROR(PHP 7.3+)接住JsonException。



  1. ❌ 把用户可写的数据(Cookie、表单、第三方回调内容)直接交给unserialize()。


✅ 改用json_decode();确实要反序列化时,至少传['allowed_classes' => false]或类名白名单,并牢记手册的提醒:无论如何都不要把不可信数据传给unserialize()。



  1. ❌ 继续使用split()、ereg_*()、each()这套老函数来做字符串与数组转换。


✅split()、spliti()、ereg_*()已在PHP 7.0 移除,改用explode()/preg_split()/preg_match()(注意preg_*的模式必须带定界符,例如'/^abc/');each()已在PHP 8.0 移除,遍历数组统一用foreach。



  1. ❌ 以为(object) $arr能把多维数组整个变成对象树。


✅ 它只转一层,嵌套仍是数组;需要递归转换就自己递归处理,或直接用 JSON 表达嵌套结构。


总结




方式函数适合的场景主要限制



分隔符拼接implode()标签、ID 列表、简单参数丢键;值含分隔符时无法可靠还原

分隔符拆分explode()拆简单列表分隔符须非空;limit为 0 视作 1

JSONjson_encode()/json_decode()跨语言、可读的数据交换默认解成对象;只有一种数字类型

PHP 序列化serialize()/unserialize()PHP 内部缓存、会话、保留对象与类型不可用于不可信数据;不可跨语言

对象互转(object)/get_object_vars()结构化访问属性只转一层;属性可见性随调用位置变化



选择哪种方式,先问自己三个问题:数据里有没有分隔符之类的特殊字符?需要对端是不是别的语言?有没有不可信的输入?前两个问题回答「有」或「是」,就该用 JSON;第三个问题回答「是」,就绝不能碰unserialize(),最多只做serialize()单向写出。把这三问放在心上前置判断,绝大多数数组与字符串互转的坑都可以提前避开。




需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询