如何使用Laravel Eloquent删除多条记录

33

从我所看到的,这个应该很简单。

我希望能够从数据库中删除多条记录。我拥有所有我想要删除的记录的id。我使用逗号分隔的id列表调用resource.destroy路由(id是postgres类型uuid),如下所示:

Request URL:http://foo.app/products/62100dd6-7ecf-4870-aa79-4b132e60c904,c4b369f1-d1ef-4aa2-b4df-b9bc300a4ff5
Request Method:DELETE

在另一端,我的控制器操作看起来像这样:

public function destroy($id)
{
    try {
        $ids = explode(",", $id);
        $org->products()->find($ids)->delete();
    }
    catch(...) {
    }
}

这给了我以下错误:

BadMethodCallException in Macroable.php line 81:
Method delete does not exist.

in Macroable.php line 81
at Collection->__call('delete', array()) in ProductsController.php line 251
at Collection->delete() in ProductsController.php line 251
at ProductsController->destroy('62100dd6-7ecf-4870-aa79-4b132e60c904,c4b369f1-d1ef-4aa2-b4df-b9bc300a4ff5')

我已经确认find()返回了一个集合,其中包含与指定id匹配的产品

我漏掉了什么吗?

附注: 1. 模型Product与其他模型有几个belongsTo关系。 2. 如果我传递一个单一的idproduct.destroy代码可以正常工作。

编辑 我想知道这两者之间的区别:

$org->products()->find($ids)->delete()

$org->products()->whereIn('id', $ids)->get()->delete()

是什么?从我看到的情况,findget 都返回了 Collections


有没有关于这里的文档有什么不清楚的地方?特别是 destroy() 方法? - maiorano84
我正在使用这个线程作为参考。我看到了你提到的文档。我有点紧张,因为恶意用户可能会删除属于其他“orgs”(“orgs”有许多“products”)的产品,所以我不太敢调用Model::destroy来删除产品ID。我更愿意先查找记录(基于用户所属的“organization”),然后再删除它们。我可以使用一个for循环(n个查询)。我也可以使用带有“in”子句的“delete”查询。只是想知道是否有更方便/优雅的方法。 - Code Poet
@maytham-ɯɐɥıλɐɯ 我还没有解决它。请看我上面的评论,了解我心中的解决方案。 - Code Poet
2
问题是,就像下面有人提到的那样,你正在调用delete()来删除一个集合,而不是实际的对象本身。使用你的示例,你可以这样做:$product->delete(); });``` - edc598
4个回答

40
问题是你正在对一个集合调用delete()方法,但集合并没有这个方法。
你有几个选择。 模型事件 如果你有deleting/deleted模型事件的事件监听器,你需要确保删除以一种每个模型都被加载并被删除的方式进行。
在这种情况下,你可以使用带有ID列表的模型上的destroy方法。它将为每个ID加载一个新的模型,然后对其调用delete()方法。正如你在评论中提到的,它不会限制仅针对组织中的那些产品进行删除,因此在将列表传递给destroy()方法之前,你需要过滤出这些ID。
public function destroy($id)
{
    try {
        $ids = explode(",", $id);
        // intersect the product ids for the org with those passed in
        $orgIds = array_intersect($org->products()->lists('id'), $ids);
        // now this will only destroy ids associated with the org
        \App\Product::destroy($orgIds);
    }
    catch(...) {
    }
}

如果您不特别喜欢这种方法,您需要迭代组织产品的集合并单独调用delete()。您可以使用标准的foreach循环,也可以使用集合上的each方法:

public function destroy($id)
{
    try {
        $ids = explode(",", $id);
        $org->products()->find($ids)->each(function ($product, $key) {
            $product->delete();
        });
    }
    catch(...) {
    }
}

没有模型事件

如果您没有需要侦听的任何模型事件,那么情况会变得更加简单。在这种情况下,您只需在查询构建器上调用 delete() 方法,它将直接删除记录而不加载任何模型对象。因此,您可以获得更干净、性能更好的代码:

public function destroy($id)
{
    try {
        $ids = explode(",", $id);
        // call delete on the query builder (no get())
        $org->products()->whereIn('id', $ids)->delete();
    }
    catch(...) {
    }
}

此外,如果只有一个ID,请使用WHERE。 - Conan
查询构建器:whereIn条件的文档。 - tinystone

6
如果你创建一个关于你的产品的模型,它将有助于这些类型的操作。
例如:
模型Products.php
<?php

namespace App\Http\Models;

use Illuminate\Database\Eloquent\Model;

class Products extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'products';
    protected $primaryKey = 'id';

    protected $fillable = ['name', 'price', 'description'];


}

控制器 Products.php

你可以使用destroy方法并将一个或多个主键作为参数传递给它。

<?php

    namespace App\Http\Controllers;

    use App\Http\Models\Products;

    class Products 
    {
        
        public function destroy($id)
        {
            try {
                
                $ids = explode(",", $id);
                //$ids is a Array with the primary keys
                Products::destroy($ids);
            }
            catch(...) {
            }
        }

    }

您可以使用此选项来删除具有自定义参数的查询结果。
$deletedRows = Products::where('name', 'phones')->delete();

你可以查看Laravel文档 https://laravel.com/docs/8.x/eloquent#soft-deleting,其中包含有关软删除的相关信息。

尝试使用 whereIn() 删除多个值时,我收到以下错误:不允许将嵌套数组传递给whereIn方法。 - Pathros

0

当您使用find方法时,它只会查找单个ID。您应该使用whereIn来匹配多个ID

public function destroy($id)
{
    try {
        $ids = explode(",", $id);
        $org->products()->whereIn('id', $ids)->get()->delete(); 
    }
    catch(...) {
    }
}

通过这种方式,您将找到所有具有给定ID的产品并将它们全部删除。


实际上,find()支持一个ID数组。虽然没有记录在文档中,但你可以查看源代码 https://github.com/laravel/framework/blob/5.1/src/Illuminate/Database/Eloquent/Builder.php#L79 - Code Poet
是的,你说得对。它做的跟whereIn一样。 - Andrés Smerkin
find() 方法有时不支持多个 ID,您可以使用 findMany($id's)。还有一件事,findMany() 和 find() 方法都没有 delete() 方法。 - Rubanraj Ravichandran
你不能同时使用get()和delete()。 - Rubanraj Ravichandran

-2

我也遇到了这个问题。假设$orgs是一个记录集合,现在你可以使用类似下面的循环轻松删除这些记录-

foreach($orgs as $org) 
{
    $org->delete();
}

3
这个想法不好,因为每次都会向数据库发送一个新的查询,建议改进。 - Marco

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