You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel firstOrCreate()结合事件监听器时重复存储手机号问题

问题

我有一个名为phones的表,包含id和number字段,要求仅存储唯一手机号。之前用$phone = Phone::firstOrCreate(['number' => $phone['number']]);可以正常实现去重。现在需要存储无空格的手机号,但展示时带空格提升可读性,于是在Phone模型中注册了saving和retrieved事件监听器:

protected $dispatchesEvents = [
    'saving' => \App\Events\Phone\PhoneSavingEvent::class,
    'retrieved' => \App\Events\Phone\PhoneRetrievedEvent::class,
];

监听器实现如下:

// PhoneSavingEvent 处理逻辑
public function handle(PhoneSavingEvent $event)
{
    $event->phone->number = preg_replace('/\s+/', '', $event->phone->number);
}
// PhoneRetrievedEvent 处理逻辑
public function handle(PhoneRetrievedEvent $event)
{
    $event->phone->number = wordwrap($event->phone->number, 3, ' ', true);
}

监听器能正常处理手机号的存储和展示,但firstOrCreate()现在会存储重复手机号。

补充说明:如果不用$dispatchesEvents,改用模型启动方法:

protected static function booted()
{
    static::saving(function ($phone) {
        $phone->number = preg_replace('/\s+/', '', $phone->number);
    });
}

存储无空格手机号(如“111222333”)时firstOrCreate()可正常去重,但存储带空格的手机号(如“111 222 333”)时,会转为无空格形式后重复存储,而该手机号实际已存在。请问该如何处理手机号来解决这个问题?

解决方案

方法1:先格式化查询条件再调用firstOrCreate

firstOrCreate的逻辑是先按传入条件查记录,找不到再创建。问题核心是查询时用的是原始带空格的手机号,而数据库存的是无空格版本,导致查不到就重复创建。

直接先格式化手机号,再执行方法:

$formattedNumber = preg_replace('/\s+/', '', $phone['number']);
$phone = Phone::firstOrCreate(['number' => $formattedNumber]);

方法2:用模型访问器+修改器(推荐)

Laravel的访问器(读取时格式化)和修改器(存储时格式化)是专门处理字段格式的方案,比事件更直观,还能避免firstOrCreate的查询问题:

  1. 定义修改器,存储时自动去空格:
public function setNumberAttribute($value)
{
    $this->attributes['number'] = preg_replace('/\s+/', '', $value);
}
  1. 定义访问器,读取时自动加空格:
public function getNumberAttribute($value)
{
    return wordwrap($value, 3, ' ', true);
}

不管传入带空格还是无空格的手机号,修改器都会先处理成无空格再存储;读取时访问器自动格式化展示。而且调用firstOrCreate时,Laravel会自动用修改器处理传入的number字段,再执行查询和创建,不会出现重复。

方法3:重写模型的firstOrCreate方法(可选)

如果不想每次调用都手动格式化,可以在Phone模型里重写方法,自动处理手机号:

public static function firstOrCreate(array $attributes, array $values = [])
{
    if (isset($attributes['number'])) {
        $attributes['number'] = preg_replace('/\s+/', '', $attributes['number']);
    }
    return parent::firstOrCreate($attributes, $values);
}

之后直接调用Phone::firstOrCreate(['number' => $phone['number']]);即可,内部会自动格式化手机号后执行逻辑。

内容的提问来源于stack exchange,提问作者andrysikova.petra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 09:15:32