Laravel创建或更新不使用两个查询

13

我希望使用一个表单来同时创建和更新。这两种操作都通过这个方法保存:

public function store() {
    $data = Input::all();
    $data['company_id'] = Auth::user()->company_id;
    $validator = Validator::make($data, Feature::$rules);

    if($validator->fails()) {
        return Redirect::back()->withErrors($validator)->withInput();
    }

    Feature::firstOrNew(['id' => Input::get('id')])->update($data);

    return Redirect::route('features.index');
}

如何改写这一行:

Feature::firstOrNew(['id' => Input::get('id')])->update($data);

那么它就不需要先从数据库获取对象吗? 没有必要这样做; 我什么都不会做。 如果 Input::get('id') 被设置,它应该发出一个 INSERT,否则应该发出一个 UPDATE


值得思考的事情:我认为这种“困难”直接关系到许多数据库缺乏“upsert”概念。它在不同的数据库之间的不一致性足以使大多数ORM跳过该功能。 - fideloper
@fideloper 不一定要使用 ON DUPLICATE KEY UPDATE 风格的查询来完成;我们可以在 PHP 端进行分支。虽然在数据库端执行会更可靠,以防止在您开始编辑和单击保存之间有人删除记录。 - mpen
当然,我并不是说那不是一种可行的方法! - fideloper
6个回答

33

这是我使用的:

Model::updateOrCreate(
   ['primary_key' => 8],
   ['field' => 'value', 'another_field' => 'another value']
);

第二个参数是一个包含你想要保存的模型数据的数组。


1
它仍然会进行2个查询: if (! is_null($instance = $this->where($attributes)->first())) { return $instance; } return $this->model->newInstance($attributes); - hpaknia

10
如果您的模型中所有字段都没有保护,我认为您可以这样做:
$feature = new Feature($data);
$feature->exists = Input::has('id');
$feature->save();

如果您有一些需要保护的字段,那么您可以先取消保护:

$feature = new Feature();
$feature->unguard();
$feature->fill($data);
$feature->exists = Input::has('id');
$feature->reguard();
$feature->save();

如果您不对模型进行其他操作,则实际上不需要调用reguard()


6

我曾遇到过类似的问题,并在Laravel上创建了一份被接受的拉取请求,您可以使用它。尝试下面的代码。您基本上需要的方法是findOrNew

public function store($id=0)
{
    $user = User::findOrNew($id);
    $data = Input::all();

    if (!$user->validate($data)) {
        return Redirect::back()->withInput()->withErrors($user->errors);
    }

    $user->fill($data);
    $user->save();
    return Redirect::to('/')->with('message','Record successfully saved!');
}

我的模型使用自我验证,但是你可以像现在拥有的那样创建自己的验证。


你忽略了问题的全部前提。findOrNew是一个查询,save是另一个。它们不需要两个。不过我确实喜欢将$id作为参数传递。实际上,findOrNew对于合并我的创建/编辑方法也可能很有用..嗯。 - mpen
是的,您可以通过使用 $id 作为参考,将创建/编辑方法合并为单个帖子,并使用 findOrNew 进行操作。如果 $id0null,或者模型不存在,则进行 INSERT 操作,否则进行 UPDATE 操作。这使得您的表单和代码可重用。 - yajra
1
你没有理解。findOrNew 会发出 SELECT 命令。我是说我们可以跳过这个 SELECT。因为我不需要里面的任何数据,所有数据都来自表单。 - mpen

5
根据主键id查找或新建数据。
$data = Input::all();
$user = User::findOrNew($id);  // if exist then update else insert
$user->name= $data['user_name'];
$user->save();

基于非主键的单个字段,优先考虑首次或最新记录

$user = User::firstOrNew(['field' => $data['value'] ]);
$user->name= $data['user_name'];
$user->save();

根据非主键多个字段选择First或New

$user = User::firstOrNew([
                     'field1'=>$data['value1'],
                     'field2'=>$data['value2']
                     ]);
$user->name= $data['user_name'];
$user->save();

findOrNew + save 是两个查询。firstOrNew 不会执行更新操作。 - mpen

2

如果你正在遵循Laravel的资源路由方法,你应该使用一个名为update的单独方法来更新你的模型,这样分离可以由框架完成。这样做仍然可以重用你的表单。

如果你真的想避免使用新方法来更新模型,你可以按以下方式重写它:

public function store() {
    $model = Input::has('id')
        ? ModelClass::findOrFail(Input::get('id'))
        : new ModelClass;

    $inputData = Input::all();
    $validator = Validator::make($inputData, ModelClass::$rules);

    if ($validator->fails()) {
        return Redirect::back()
            ->withErrors($validator)
            ->withInput();
    }

    $model->fill($inputData);
    $model->save();

    return Redirect::route('modelclass.index');
}

这仍然通过 findOrFail 发出第二个查询,但我认为出错比对空执行更新更好。 - mpen
1
为了避免第二个 SQL 查询,我认为你需要使用一个带有 INSERT INTO ... ON DUPLICATE KEY UPDATE 语句的原始查询,但这样你将绕过 Eloquent ORM。 - peaceman
1
“ON DUPLICATE KEY UPDATE”是MySQL特有的,对于任何想知道的人来说都是如此。这意味着在代码中使用它会将您绑定到特定的数据库类型。对于许多人来说,这可能并不重要,但在使用之前考虑一下是值得的。 - fideloper

2
你可以尝试这个:
$data = Input::except('_token');
$newOrUpdate = Input::has('id') ? true : false;
$isSaved = with(new Feature)->newInstance($data, $newOrUpdate)->save();

如果$data包含id/primary key,它将被更新,否则将执行插入操作。换句话说,要进行更新,需要在$data/attributes中传递id/primary key和其他属性,并且在newInstance方法的第二个参数中必须是true
如果将false/default传递给newInstance方法,那么它将执行插入操作,但$data不能包含id/primary key。你已经明白了,这三行代码应该可以工作。 $isSaved将是一个布尔值,true/false

你可以使用一行代码来编写:

with(new Feature)->newInstance($data, array_key_exists('id', $data))->save();

如果$data包含id/主键,那么将会执行更新操作,否则会执行插入操作。

1
我唯一不喜欢的是它实例化了两次Feature对象。newInstance应该是静态的,但它不是。也许我会创建一个帮助方法。 - mpen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接