Мягкое удаление/снятие и восстановление/присоединение связей с составными клавишами

У меня есть 2 модели, к которым присоединяется отношение, которое имеет составной ключ - это Product и Category. Мне нужно использовать мягкие удаления во всех таблицах, чтобы при необходимости могли восстанавливаться модели и отношения.

В моей модели продукта у меня есть:

function categories()
{
    return $this->belongsToMany('App\Category', 'product_categories')->whereNull('product_categories.deleted_at')->withTimestamps();
}

В моей модели категории у меня есть:

function products()
{
    return $this->belongsToMany('App\Product', 'product_categories')->whereNull('product_categories.deleted_at')->withTimestamps();
}

Я читал в другом месте о цепочке метода whereNull, поскольку запросы типа $category->products->contains($product->id) в противном случае возвращали мягкие удаленные отношения.

Мой вопрос - это лучший способ справиться с удалением и восстановлением этих мягких удаленных отношений? Например, для восстановления я попытался:

$product->categories()->restore($category_id);

Вышеописанная ошибка SQL говорит, что поле deleted_at было неоднозначным (поскольку оно присоединилось к таблице категорий к product_categories).

Обновить. Похоже, что основная проблема заключается в том, что класс BelongsToMany не поддерживает мягкие удаления - поэтому прикреплять, отсоединять и синхронизировать все выполнять жесткие удаления. Каким будет лучший подход к переопределению этого класса?

Ответы

Ответ 1

В принципе, будет только одно поле deleted_at и вместо использования $product->categories() использовать два обычных (общих) метода в моделях (Product и Category), например, вы можете создать признак, например это:

// SoftDeletePC.php
trait SoftDeletePC {
    // SoftDelete
    public function softDeleteProductCategory($productId, $categoryId)
    {
        \DB::table('product_categories')
        ->where('product_id', $productId)
        ->where('category_id', $categoryId)
        ->update(['deleted_at' => \DB::raw('NOW()')]);
    }

    // Restore
    public function restoreProductCategory($productId, $categoryId)
    {
        \DB::table('product_categories')
        ->where('product_id', $productId)
        ->where('category_id', $categoryId)
        ->update(['deleted_at' => null]);
    }
}

Затем используйте этот признак в обеих моделях с помощью use TraitProductCategory и вызовите метод из обеих моделей, например:

// App/Product.php
class product extends Model {
    use SoftDeletePC;
}

// App/Category.php
class Category extends Model {
    use SoftDeletePC;
}

Итак, вместо этого:

Product->find(1)->categories()->restore(2);

Вы можете использовать что-то вроде этого:

$product = Product->find(1);

$product->softDeleteProductCategory(1, 2); // Set timestamp to deleted_at

$product->restoreProductCategory(1, 2); // Set null to deleted_at

Надеюсь, это может сработать для вас.

Ответ 2

Я создал несколько пользовательских методов в моей модели продукта, чтобы выполнить то, что мне нужно, а не мое идеальное решение, но оно все же работает. Моя пользовательская синхронизация выглядит так:

class Product extends Model
{
    // update relationship to categories
    function categories_sync($category_ids)
    {
        // categories
        $existing_category_ids = $this->categories()->lists('category_id')->all();
        $trashed_category_ids = $this->categories('onlyTrashed')->lists('category_id')->all();

        if(is_array($category_ids)) {

            foreach($category_ids as $category_id) {
                if(in_array($category_id, $trashed_category_ids)) {
                    $this->categories()->updateExistingPivot($category_id, ['deleted_at' => null]);
                }
                elseif(!in_array($category_id, $existing_category_ids)) {
                    $this->categories()->attach($category_id);
                }
            }

            foreach($existing_category_ids as $category_id) {
                if(!in_array($category_id, $category_ids)) {
                    $this->categories()->updateExistingPivot($category_id, ['deleted_at' => date('YmdHis')]);
                }
            }
        }
        else {
            foreach($existing_category_ids as $category_id) {
                $this->categories()->updateExistingPivot($category_id, ['deleted_at' => date('YmdHis')]);
            }
        }
    }
}

Вышеизложенное зависит от метода расширенных категорий():

// relationship to categories
function categories($trashed=false)
{
    if($trashed=='withTrashed') {
        return $this->belongsToMany('App\Category', 'product_categories')->withTimestamps();
    }
    elseif($trashed=='onlyTrashed') {
        return $this->belongsToMany('App\Category', 'product_categories')->whereNotNull('product_categories.deleted_at')->withTimestamps();
    }
    else {
        return $this->belongsToMany('App\Category', 'product_categories')->whereNull('product_categories.deleted_at')->withTimestamps();
    }
}