Пользовательский метод удаления Laravel через сводную таблицу - PullRequest
0 голосов
/ 05 сентября 2018

У меня есть сводная таблица между 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 Не удается удалить или обновить родительскую строку: ограничение внешнего ключа не выполняется

1 Ответ

0 голосов
/ 05 сентября 2018

Я думаю, что проблема в сводной таблице. В сводной таблице есть два внешних ключа. Ваша таблица ничего не сделает, кроме как выдаст ошибку при удалении связанной записи, поскольку вы не дали ей «ссылочное действие», чтобы что-то сделать, когда это произойдет.

Добавляя onDelete(), вы можете указать эти ссылочные действия. В зависимости от того, что вы хотите, вы можете отдавать такие заказы, как: cascade, set null, restrict, no action и set default. Подробнее об этом здесь .

В вашем случае вы хотите использовать 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 Builder для удаления нескольких записей одновременно, Image@delete никогда не будет вызываться.

Простое решение может быть:

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

    return parent::delete();
}

Это, конечно, приведет к отдельным запросам на удаление каждого изображения. В зависимости от того, хотите ли вы использовать сверхмегаперспективную скорость, это не будет вашим выбором.

Рекомендуется использовать наблюдатель (для поддержания чистоты ваших моделей) и событие в сочетании с ожидающим (необязательным) слушателем .

Наблюдатель:

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));
    }
}

Event:

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') вам не нужно их отсоединять.

Я не проверял этот код, поэтому могут быть небольшие ошибки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...