orm的save引发的bug分析

说明: 今天下午接到运营反馈的一个bug,经过排查发现是同事的方法存在一个orm用法的bug,记录分享下来,防止后续继续踩坑

bug分析:出现问题的表实体所对应的数据库表是一个属性附表,附表使用用主表的主键做主键,然后附表去掉主键自增的特性,而表实体里面未设置$incrementing = fasle,由此导致调用save后主键变成0的问题

源码分析

$memberAttrModel = new MemberAttr();
$memberAttrModel->uid = 1234;// 说明:uid为MemberAttr主键,且未设置自增属性,因此取自增主键的默认值true
$memberAttrModel->save();
public function save(array $options = array())
{
    $query = $this->newQueryWithoutScopes();

    if ($this->fireModelEvent('saving') === false)
    {
        return false;
    }


    if ($this->exists)
    {
        $saved = $this->performUpdate($query, $options);
    }

    // new 一个模型调用save走insert
    else
    {
        $saved = $this->performInsert($query, $options);
    }

    if ($saved) $this->finishSave($options);

    return $saved;
}
# 这里看performInsert的代码
protected function performInsert(Builder $query, array $options = [])
{
    if ($this->fireModelEvent('creating') === false) return false;

    if ($this->timestamps && array_get($options, 'timestamps', true))
    {
        $this->updateTimestamps();
    }


    $attributes = $this->attributes;

    # $this->incrementing默认值为true,执行if中的代码段,问题就出在insertAndSetId中,接着看下边代码
    if ($this->incrementing)
    {
        $this->insertAndSetId($query, $attributes);
    }

    else
    {
        $query->insert($attributes);
    }

    $this->exists = true;

    $this->fireModelEvent('created', false);

    return true;
}


# insertAndSetId
protected function insertAndSetId(Builder $query, $attributes)
{
    # 在表未设置自增的情况下,$query->insertGetId会返回0,然后重新设置属性,oh my god,出问题了
    # insertGetId最终执行的是pdo扩展的insertGetId方法,而pdo扩展的insertGetId方法的源码中最终执行的是mysql中的mysql_insert_id
    # mysql_insert_id函数返回的是储存在有AUTO_INCREMENT约束的字段的值,如果表中的字段不使用AUTO_INCREMENT约束或者使用自己生成的唯一值插入,那么该函数不会返回你所存储的值,而是返回NULL或0。因此,在没有使用AUTO_INCREMENT约束的表中,或者ID是自己生成的唯一ID,lastInsertId函数返回的都是0。
    $id = $query->insertGetId($attributes, $keyName = $this->getKeyName());

    $this->setAttribute($keyName, $id);
}

发表评论