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

时间:2018-09-05 18:30:35

标签: php laravel laravel-5

我在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()删除方法,问题是{{1 }}模型不会被调用(应该从存储中删除图像以及将其与数据透视表分离)

当我尝试:

delete()

我得到:

  

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

1 个答案:

答案 0 :(得分: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')结合使用。

我尚未测试此代码,因此可能存在一些小错误。