Laravel通过数据透视表自定义删除方法

问题描述 投票:1回答:1

我在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个回答
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')结合使用,您不必将它们分离。

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

© www.soinside.com 2019 - 2024. All rights reserved.