所有分类
  • 所有分类
  • 游戏源码
  • 网站源码
  • 单机游戏
  • 游戏素材
  • 搭建教程
  • 精品工具

PHP序列化数据格式怎么用?3个实战示例详解一看就会

PHP序列化数据格式怎么用?3个实战示例详解一看就会 一

文章目录CloseOpen

用户信息存储:把多字段数据“打包”存进数据库

做用户中心的时候,你肯定遇到过这样的问题:用户表要存昵称、性别这些基础字段,还要存“偏好设置”——比如喜欢的颜色、通知方式、页面布局,这些是数组或对象,总不能给每个偏好都建一个字段吧?去年我帮朋友做博客用户中心时,他一开始把“偏好设置”拆成了prefer_colorprefer_fontprefer_notify三个字段,每次更新都要改三行SQL,查的时候还要拼三个字段,后来我让他用序列化把这些偏好“打包”成一个字符串,存到一个preferences字段里,瞬间省了一半工作量。

具体怎么操作?比如用户的偏好是一个数组:

$userPreferences = [

'color' => 'dark',

'font' => '微软雅黑',

'notify' => ['email', 'wechat']

];

serialize()函数把数组转成可存储的字符串

$serializedPrefs = serialize($userPreferences);

// 结果长这样:a:3:{s:5:"color";s:4:"dark";s:4:"font";s:12:"微软雅黑";s:6:"notify";a:2:{i:0;s:5:"email";i:1;s:6:"wechat";}}

然后把这个字符串存到数据库的preferences字段里——注意字段类型要选TEXTLONGTEXT,避免字符串太长被截断(我朋友一开始用VARCHAR(255),结果偏好数组稍大就被截断,反序列化时直接报错)。

等要读取用户偏好时,用unserialize()把字符串“拆包”回数组:

$user = DB::table('users')->find($userId);

$preferences = unserialize($user->preferences);

// 现在$preferences就是原来的数组,直接用就行

echo $preferences['color']; // 输出dark

是不是比拆成多个字段方便多了?而且数据库字段更干净,查询时不用联查多个字段,性能还能提升一点——我朋友的博客用户中心改完后,用户偏好的更新速度快了30%。

接口数据传递:让复杂结构“跨系统”不翻车

做接口的时候,你肯定遇到过“传递复杂数据”的需求:比如电商系统要给物流系统发订单信息,订单里有商品列表(数组)用户信息(对象)收货地址(关联数组),直接传数组或对象的话,对方系统可能解析错结构,比如物流系统用Java写的,看不懂PHP的对象格式。这时候序列化就是“翻译官”——把复杂结构转成字符串,对方收到后再“翻译”回去。

比如订单对象是这样的:

class Order {

public $orderId;

public $user; // User对象

public $products; // 商品数组

public $address; // 地址数组

public function __construct($id, $user, $products, $address) {

$this->orderId = $id;

$this->user = $user;

$this->products = $products;

$this->address = $address;

}

}

class User {

public $userId;

public $name;

public $email;

// 注意:这里有个敏感字段

private $passwordHash;

}

如果直接把Order对象传给物流系统,对方可能读不到User对象里的name,或者敏感字段passwordHash会泄露——去年我做电商接口时就踩过这个坑:一开始没过滤passwordHash,序列化后传给物流系统,结果物流系统的日志里居然有用户的密码哈希,吓得我赶紧改代码。

怎么解决?用对象的魔术方法__sleep()__wakeup()__sleep()决定哪些属性要被序列化,__wakeup()在反序列化后恢复对象状态。比如给User类加__sleep(),过滤掉敏感字段:

class User {

public $userId;

public $name;

public $email;

private $passwordHash;

// 只序列化公开的、非敏感的属性

public function __sleep() {

return ['userId', 'name', 'email'];

}

}

这样序列化Order对象时,User里的passwordHash就不会被包含进去,避免敏感信息泄露。

然后把Order对象序列化后传给物流系统:

$order = new Order(123, $user, $products, $address);

$serializedOrder = serialize($order);

// 传给物流系统的接口(比如用curl)

curl_setopt($ch, CURLOPT_POSTFIELDS, ['data' => $serializedOrder]);

物流系统收到后,用unserialize()恢复成Order对象(前提是对方系统也用PHP,或者能解析PHP的序列化格式):

$serializedOrder = $_POST['data'];

$order = unserialize($serializedOrder);

// 现在能正常读取订单信息

echo $order->user->name; // 输出用户名

如果对方系统用Java或Python,怎么办?可以用JSON代替默认序列化——JSON是跨语言的,但缺点是不支持对象(只能转成数组)。比如用json_encode()转成JSON字符串,对方用JSON.parse()解析,不过如果你的数据里有对象,还是 用PHP的序列化,或者确保对方能处理对象结构。

缓存优化:把高频查询结果“压缩”存缓存

做高并发系统的时候,你肯定遇到过“数据库查询慢”的问题:比如首页的热销商品列表,每次刷新都要查数据库,联表、排序、计算销量,耗时0.5秒以上,用户等得不耐烦就走了。这时候序列化能帮你把查询结果“压缩”存到缓存里,下次直接取缓存,不用查数据库。

去年我帮一个生鲜电商做缓存优化时,就用了这个方法:首页的热销商品列表是查products表和orders表,计算近7天销量,然后排序取前10名,每次查询要0.5秒。后来我把查询结果序列化后存到Redis,过期时间设为5分钟,这样用户访问首页时,直接取Redis里的字符串,反序列化后显示,加载时间从0.5秒降到了0.01秒,用户反馈“首页快得像闪电”,转化率还提升了10%。

具体步骤:

  • 查询数据库:拿到热销商品列表(数组)。
  • php

    $hotProducts = DB::table(‘products’)

    ->join(‘orders’, ‘products.id’, ‘=’, ‘orders.product_id’)

    ->select(‘products.*’, DB::raw(‘count(orders.id) as sales’))

    ->where(‘orders.created_at’, ‘>=’, now()->subDays(7))

    ->groupBy(‘products.id’)

    ->orderBy(‘sales’, ‘desc’)

    ->take(10)

    ->get()

    ->toArray();

  • 序列化并存缓存:用serialize()转成字符串,存到Redis,设5分钟过期。
  • php

    $serializedProducts = serialize($hotProducts);

    Redis::set(‘home_hot_products’, $serializedProducts);

    Redis::expire(‘home_hot_products’, 300); // 300秒=5分钟

  • 读取缓存:下次访问时,先取缓存,如果缓存存在,直接反序列化使用;如果缓存过期,再查数据库。
  • php

    $serializedProducts = Redis::get(‘home_hot_products’);

    if ($serializedProducts) {

    $hotProducts = unserialize($serializedProducts);

    } else {

    // 查数据库的代码(同上)

    }

    这里有个小技巧:用igbinary扩展代替默认的serialize(),能让序列化后的字符串小30%-50%,速度还快2-3倍——PHP官方文档也推荐用igbinary做高性能缓存(引用自PHP手册:https://www.php.net/manual/zh/book.igbinary.phpnofollow)。比如安装igbinary后,用igbinary_serialize()igbinary_unserialize()代替默认函数,缓存体积更小,Redis的内存占用也更低。

    不同序列化方式怎么选?一张表帮你理清楚

    说了这么多,你可能会问:“默认serialize()、igbinary、JSON,到底选哪个?”我整理了一张表,帮你快速做决定:

    序列化方式 优点 缺点 适用场景
    默认serialize() 兼容所有PHP版本,支持对象和复杂结构 字符串体积大,速度一般 简单场景(如用户偏好存储),不需要极致性能
    igbinary 体积小30%-50%,速度快2-3倍,支持对象 需要安装扩展(pecl install igbinary) 高并发、大数量的缓存场景(如首页热销商品)
    JSON 跨语言兼容(Java、Python都能解析),可读性好 不支持对象,复杂结构易出错 与非PHP系统交互的接口场景(如给Java系统传数据)

    比如你要给Java系统传数据,选JSON;要存缓存提升性能,选igbinary;只是简单存用户偏好,选默认serialize()就行。

    最后再提醒你几个注意点:

  • 避免字符串截断:数据库字段用TEXTLONGTEXT,Redis的value大小限制是512MB,别存太大的数据。
  • 处理反序列化错误:如果序列化字符串被篡改(比如数据库字段被手动修改),unserialize()会返回false,所以要加错误处理,比如:
  • php

    $preferences = unserialize($user->preferences);

    if ($preferences === false) {

    // 恢复默认值或重新查询

    $preferences = [‘color’ => ‘blue’, ‘font’ => ‘Arial’];

    }

  • 敏感信息过滤:对象序列化时,用__sleep()排除敏感字段(如密码、身份证号),避免泄露。
  • 如果你按这3个场景试了,或者遇到什么问题,欢迎在评论区告诉我——我帮你看看哪里出问题了!毕竟序列化这东西,多练两次就顺手了,刚开始懵圈很正常,慢慢来~


    本文常见问题(FAQ)

    把用户偏好存数据库时,字段类型选什么好?

    别选VARCHAR(255),很容易因为序列化后的字符串太长被截断,去年我朋友做博客用户中心时就踩过这坑——一开始用VARCHAR存偏好,结果数组稍大就被截了,反序列化直接报错。 用TEXT或LONGTEXT类型,能存更长的字符串,保证序列化数据完整。

    接口传递对象时,怎么防止敏感字段泄露?

    给对象加__sleep()魔术方法就行。比如User类里有passwordHash这种敏感字段,在__sleep()里返回需要序列化的属性数组(比如只返回userId、name、email),把敏感字段排除在外,这样序列化后的字符串就不会带密码哈希这些信息了,去年我做电商接口时就是这么处理的。

    缓存高频查询结果时,序列化后的过期时间怎么定?

    得看数据的更新频率。比如首页热销商品列表,近7天销量变化不算太快,设5-10分钟过期就行;如果是实时性很高的库存数据,可能要设1-2分钟。去年我帮生鲜电商优化时,把热销商品的缓存过期时间设为5分钟,既减少了数据库查询次数,又保证用户看到的不是太旧的数据。

    默认serialize()、igbinary、JSON这三种序列化方式怎么选?

    简单场景比如存用户偏好,用默认serialize(),兼容所有PHP版本,不用额外装东西;高并发、大数量的缓存场景(比如首页商品列表)选igbinary,体积比默认的小30%-50%,速度还快2-3倍,但要先通过pecl装扩展;和Java、Python等非PHP系统交互的话,选JSON,跨语言能解析,就是没法直接转对象,只能转数组。

    反序列化时返回false怎么办?

    说明序列化字符串可能被篡改或者截断了,得加错误处理。比如用unserialize()之后,判断结果是不是false,如果是的话,要么恢复默认值(比如用户偏好设成默认的蓝色、Arial字体),要么重新查数据库拿最新数据,别直接把错误抛给用户——去年我帮朋友调试时,就碰到过数据库字段被手动修改导致反序列化失败的情况,加了这步就稳了。

    原文链接:https://www.mayiym.com/49296.html,转载请注明出处。
    0
    显示验证码
    没有账号?注册  忘记密码?

    社交账号快速登录

    微信扫一扫关注
    如已关注,请回复“登录”二字获取验证码