【问题标题】:Laravel custom delete method via pivot tableLaravel 通过数据透视表自定义删除方法
【发布时间】:2018-09-06 03:59:31
【问题描述】:

我在ingredientsimages 之间有一个数据透视表。在Image 模型上,我有一个自定义删除方法,可以从 s3 存储中删除图像。问题是,如果我在数据透视表外键上使用onDelete('cascade')delete() 方法将不会被触发。 我尝试了一种解决方法,但没有成功。

我的Ingredient 模特:

class Ingredient extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['ingredient_category_id', 'name', 'units', 'price'];

    ///////////////////
    // Relationships //
    ///////////////////
    public function images() {
        return $this->belongsToMany(Image::class, 'ingredient_images')->withTimestamps();
    }

    /////////////
    // Methods //
    /////////////
    public function delete()
    {
        $this->images()->delete();
        $this->images()->detach();

        return parent::delete();
    }
}

我的Image 模特:

class Image extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['title', 'path'];

    ///////////////////
    // Relationships //
    ///////////////////
    public function ingredients() {
        return $this->belongsToMany(Ingredient::class, 'ingredient_images')->withTimestamps();
    }

    /////////////
    // Methods //
    /////////////
    public function delete()
    {
        $this->ingredients()->detach();

        Storage::disk('s3')->delete($this->path);

        return parent::delete();
    }
}

我的pivot 表(ingredient_images):

public function up()
{
    Schema::create('ingredient_images', function (Blueprint $table) {
        $table->increments('id');
        $table->unsignedInteger('ingredient_id');
        $table->foreign('ingredient_id')->references('id')->on('ingredients');
        $table->unsignedInteger('image_id');
        $table->foreign('image_id')->references('id')->on('images');
        $table->timestamps();
    });
}

我尝试在Ingredient 模型上使用自定义delete() 方法调用images() 删除方法,问题是delete() 来自Image 模型的方法没有被调用(这是假设从存储中删除图像并将其从数据透视表中分离)

当我尝试时:

Ingredient::findOrFail($ids)->images()->delete()
Ingredient::findOrFail($ids)->delete()

我明白了:

完整性约束违规:1451 无法删除或更新父行:外键约束失败

【问题讨论】:

    标签: php laravel laravel-5


    【解决方案1】:

    我认为问题出在数据透视表上。在数据透视表中有两个外键。当相关记录被删除时,您的表只会抛出错误,因为您没有在发生这种情况时给它一个“参考操作”来做某事。

    通过添加onDelete(),您可以提供这些参考操作。根据您的需求,您可以发出以下命令:cascadeset nullrestrictno actionset default。有关此here 的更多信息。

    在您的情况下,您想使用cascade,在此语法中基本上意味着:“如果外键被删除,则删除记录”。

    您的迁移将如下所示:

    public function up()
    {
        Schema::create('ingredient_images', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedInteger('ingredient_id');
            $table->foreign('ingredient_id')->references('id')->on('ingredients')->onDelete('cascade');
            $table->unsignedInteger('image_id');
            $table->foreign('image_id')->references('id')->on('images')->onDelete('cascade');
            $table->timestamps();
        });
    }
    

    评论后第一次更新。

    由于$ingredient->images()->delete() 将使用 Eloquent 构建器一次删除多条记录,因此永远不会调用 Image@delete

    简单的解决方案可能是:

    // Ingredient
    public function delete() {
        foreach ($this->images as $image) {
            $image->delete();
        }
    
        return parent::delete();
    }
    

    这当然会导致单独的查询以删除每个图像。取决于您是否想追求超高速,这不是您的选择。

    建议使用observer(保持模型干净)和event in combination with a queueable (optional) listener

    观察者:

    class IngredientObserver
    {
        public function deleting(Ingredient $ingredient) {
            // Loop here
            foreach ($ingredient->images as $image) {
                Storage::disk('s3')->delete($image->path); // Or this can also be done in a seperate observer for Image to ensure the image is always deleted on AWS when deleting an image, that would be my choice.
                $image->delete();
            }
    
            // Or use an event
    
            $paths = $ingredient->images()->lists('path');
    
            $ingredient->images()->delete();
    
            event(new RemoveAwsImages($paths));
        }
    }
    

    事件:

    class RemoveAwsImages
    {
        public $paths;
    
        public __construct($paths) {
            $this->paths = $paths;
        }
    }
    

    听众:

    use Illuminate\Contracts\Queue\ShouldQueue;
    
    class RemoveAwsImagesListener implements ShouldQueue // Remember ShouldQueue is optional
    {
        public function handle(RemoveAwsImages $event)
        {
            foreach ($event->paths as $paths) {
                Storage::disk('s3')->delete($path);
            }
        }
    }
    

    这样您不必在模型中添加删除方法,并且与onDelete('cascade') 结合使用时您不必分离它们。

    我没有测试过这段代码,所以可能会有一些小错误。

    【讨论】:

    • 是的,我很清楚 onDelete('cascade') 但遗憾的是这不会触发我在 Image 模型中覆盖的 delete() 方法
    • @Alphonse 不确定这是否属实,但看起来您是直接在Eloquent\Builder 上调用delete,这意味着永远不会触发delete 方法。我将用新的可能解决方案更新我的答案。
    • 我应该在哪里注册观察者? (抱歉,我以前从未使用过)在文档中他们做了类似的事情:您可以使用观察方法注册一个观察者实例: User::observe(new UserObserver);我应该这样做:成分::observer(new IngredientObserver)->findOrFail($ids)->delete()
    • 正如documentation 所说,您可以将观察者添加到您的AppServiceProvider,例如Ingredient::observer(IngredientObserver::class),无需findOrFaildelete
    • 谢谢,确实是这个问题:$ingredient->images()->delete() 会利用 Eloquent builder 一次性删除多条记录
    猜你喜欢
    • 2017-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-19
    • 1970-01-01
    相关资源
    最近更新 更多