我在ingredients
和images
之间有一个支点表。在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无法删除或更新父行:外键约束失败
我认为问题在于数据透视表。在数据透视表中有两个外键。你的表只会在删除相关记录时抛出错误,因为在发生这种情况时你没有给它一个“参考动作”来做某事。
通过添加onDelete()
,您可以给出这些参考动作。根据你的需要你可以给出如下命令:cascade
,set null
,restrict
,no action
和set 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')
结合使用,您不必将它们分离。
我没有测试过这段代码,所以可能会有一些小错误。