我在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()
删除方法,问题是{{1 }}模型不会被调用(应该从存储中删除图像以及将其与数据透视表分离)
当我尝试:
delete()
我得到:
违反完整性约束:1451无法删除或更新父行:外键约束失败
答案 0 :(得分:1)
我认为问题出在数据透视表上。在数据透视表中有两个外键。在删除相关记录时,您的表什么也不会做,只会引发错误,因为在发生这种情况时,您没有给它提供“引用操作”的作用。
通过添加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')
结合使用。
我尚未测试此代码,因此可能存在一些小错误。