Как синхронизировать несколько значений одного и того же атрибута в Laravel?

Я разрабатываю веб-сайт электронной коммерции (с функцией нескольких атрибутов продукта), используя Laravel 5.4. Все работает нормально. Но когда я пытаюсь синхронизировать несколько значений одного и того же атрибута в сводной таблице. Laravel игнорирует дубликаты пар. Например, у меня есть атрибут «Сеть», который имеет 3 значения: 2G, 3G, 4G. Мобильный телефон поддерживает сети 3G и 4G. Я хочу синхронизировать значение 3G и 4G в базе данных. Laravel игнорирует один из них.

Products Table: 

ID - Product Name
1  - Test Mobile



Attributes Table

ID - AttributeName
1 - Network



AttributeValues Table

ID - AttributeID - AttributeValue
1  - 1           - 2G
2  - 1           - 3G
3  - 1           - 4G



ProductAttributes Table

ID - AttributeID - ProductID - AttributeValue
1  - 1           - 1         - 3G
1  - 1           - 1         - 4G

Я хочу сохранить атрибуты продукта в таблице «ProductAttributes» примерно так. Но Laravel игнорирует один из них.

Я сохраняю данные так:

    $product = Product::create([
        'name' => 'Test Mobile'
    ]);

    $product->attributes()->sync([
        1 => ['AttributeValue' => '3G'], 
        1 => ['AttributeValue' => '4G']
    ]);

Любые предложения, идеи?


person Rizwan Khan    schedule 07.02.2017    source источник
comment
В вашем массиве есть повторяющиеся ключи.   -  person Khoon    schedule 07.02.2017
comment
В таблице ProductAttributes у вас должно быть поле AttributeValues.ID вместо AttributeID. Таким образом, ассоциация будет уникальной.   -  person Shadow    schedule 07.02.2017
comment
@Shadow Отличный момент. Не могли бы вы рассказать мне, как я могу получить доступ к таблице атрибутов через модель продукта?   -  person Rizwan Khan    schedule 07.02.2017


Ответы (5)


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

    $product->attributes()->sync([
        1 => ['AttributeValue' => '3G'], 
        1 => ['AttributeValue' => '4G']
    ]);

второй элемент в массиве перезапишет первый, поэтому в итоге в базе данных останется только запись «4G». На самом деле это не проблема laravel, это то, как реализованы ассоциативные массивы PHP - вы в принципе не можете иметь два элемента в массиве с одним и тем же индексом.

На самом деле есть два способа решить эту проблему

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

    $product->attributes()->sync([]);  // empty relation completely

    // add first item
    $product->attributes()->syncWithoutDetaching([
        1 => ['AttributeValue' => '3G'],
    ]);

    // add second item without detaching the first one
    $product->attributes()->syncWithoutDetaching([
        1 => ['AttributeValue' => '4G'], 
    ]);

это КРАЙНЕ неэффективно, потому что требуется один запрос, чтобы удалить все существующие данные из отношения, а затем добавить новые элементы один за другим. Вы также можете запустить syncWithoutDetaching внутри цикла, и общая неэффективность будет сильно зависеть от того, сколько элементов вам нужно синхронизировать.

2) первое решение было недостаточно хорошим, поэтому я продолжал копать и экспериментировать, пока не нашел способ, как это сделать. Вместо того, чтобы помещать ваши элементы в определенный индекс в массиве, вы можете отправить массив без указания конкретных индексов и поместить идентификатор в сам массив. Что-то вроде этого

    $product->attributes()->sync([
        ['AttributeID' => 1, 'AttributeValue' => '3G'], 
        ['AttributeID' => 1, 'AttributeValue' => '4G']
    ]);

Делая это таким образом, вы фактически можете отправить два элемента с одним и тем же AttributeID в метод sync(), не перезаписывая один другой.

person Becquerel    schedule 05.04.2019
comment
Но все еще были проблемы с laravel 5.7. Буду копать дальше и обновлять - person graphicmist; 12.10.2019
comment
Кажется, в laravel 8.5 syncWithoutDetaching() не работает, я использовал sync([]) и attach() - person mehdi jalilvand; 27.04.2021

Используйте метод синхронизации для хранения/обновления данных в контроллере с использованием отношения:

$product-> attribute()->sync($request->input('product_ids', []));
person Aneeqa Chowdhury    schedule 09.08.2018

Глядя на ответ Беккереля от 5 апреля, ответ №2, и читая исходный код метода sync() в /laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php (это, кажется, Laravel 2.4), я не вижу (или не могу определить) код, который будет поддерживать эту функциональность «массива массива». Собственно, вот исходный код всего метода:

public function sync($ids, $detaching = true)
{
    $changes = [
        'attached' => [], 'detached' => [], 'updated' => [],
    ];

    if ($ids instanceof Collection) {
        $ids = $ids->modelKeys();
    }

    // First we need to attach any of the associated models that are not currently
    // in this joining table. We'll spin through the given IDs, checking to see
    // if they exist in the array of current ones, and if not we will insert.
    $current = $this->newPivotQuery()->pluck($this->otherKey);

    $records = $this->formatSyncList($ids);

    $detach = array_diff($current, array_keys($records));

    // Next, we will take the differences of the currents and given IDs and detach
    // all of the entities that exist in the "current" array but are not in the
    // the array of the IDs given to the method which will complete the sync.
    if ($detaching && count($detach) > 0) {
        $this->detach($detach);

        $changes['detached'] = (array) array_map(function ($v) {
            return is_numeric($v) ? (int) $v : (string) $v;
        }, $detach);
    }

    // Now we are finally ready to attach the new records. Note that we'll disable
    // touching until after the entire operation is complete so we don't fire a
    // ton of touch operations until we are totally done syncing the records.
    $changes = array_merge(
        $changes, $this->attachNew($records, $current, false)
    );

    if (count($changes['attached']) || count($changes['updated'])) {
        $this->touchIfTouching();
    }

    return $changes;
}

Итак, Laravel полон внедрения зависимостей и другой магии (очевидно, похожей на понятие «карты» в Perl?), но я не вижу здесь ничего, что могло бы делать то, что говорит Беккерель. И, вообще говоря, документация Laravel действительно не сообщает, что она делает с повторяющимися значениями в отношениях «многие ко многим» и знает ли она о них вообще.

Я также заметил, что реализация метода, как показано выше, на самом деле очень похожа на «альтернатива № 1», которую он называет «крайне неэффективной». Кажется, что он классифицирует ключи по трем блокам... никогда не допускает повторения, по моему чтению... а затем выполняет операции вставки, обновления и удаления по мере необходимости. (Как я вижу, нигде нет "транзакций" SQL, что меня тоже очень удивляет... они там как-то "волшебным образом"?)

Я просто не могу определить, делает ли Laravel более одного экземпляра значения в (набор связанных записей в) внешней таблице, что-нибудь разумное, например возвращает их в виде массива.

Я дал этот очень многословный ответ в надежде получить дополнительные комментарии. Спасибо.

person Mike Robinson    schedule 14.11.2019
comment
P.S. Беккерель, я полагаю, что вы правы. Конечно. Но, черт возьми, если я вижу в коде, почему ты прав! :=) И так называемая документация Laravel, которая представляет собой скорее просто разговор, на самом деле не дает мне ответов. Исходный код тоже. - person Mike Robinson; 14.11.2019
comment
К вашему сведению ... может быть, это отчасти потому, что я вынужден использовать довольно старую версию Laravel, что я наконец и успешно сделал, так это использовал DB::transaction() для включения короткой процедуры, которая сначала выполняла detach() для удаления всех существующих дочерних записей, а затем зацикливалась массив для attach() замен. Коротко и мило, и это сработало просто отлично. (Транзакции гарантируют, что либо все, либо ничего. Либо все операторы SQL вступают в силу одновременно, либо ни один из них.) - person Mike Robinson; 15.11.2019

Теперь,

$product->attributes()->sync([]);

$product->attributes()->sync([
    ['AttributeID' => 1, 'AttributeValue' => '3G'], 
    ['AttributeID' => 1, 'AttributeValue' => '4G']
]);
person Harat    schedule 04.05.2021

sync() в Laravel автоматически считывает дубликаты. Вы можете заставить его с

$product->attribute()->syncWithoutDetaching([
    1 => ['AttributeValue' => '3G'], 
    1 => ['AttributeValue' => '4G']
]);

Удачи друг!

person EddyTheDove    schedule 07.02.2017