@PRodion

Как связать значения характеристики с товаром?

Структура базы данных.
products
--- id
--- name

attributes
--- id
--- name

attribute_values
--- id
--- attribute_id
--- name

attribute_product
--- id
--- attribute_id
--- product_id


использую данные во View

foreach ($products as $product) {
    $product->name

    foreach ($product->attributes as $attribute) {
        $attribute->name

        foreach ($attribute->values as $value) {
            $value->name
        }
    }
}


получаем на выходе
{
    "name": "Product",
    "attributes": [
        {
            "name": "Attribute 1",
            "values": [
                "name": "Value 1",
                "name": "Value 2",
            ]
        }
    ]
}

Products
  • Продукту может принадлежать много Атрибутов.

Attributes
  • Атрибуту может принадлежать много Продуктов.
  • Атрибуту может принадлежать много Значений.

Attribute Values
  • Значению может принадлежать один Атрибут.

Проблема текущего функционала в том, что у Продукта нет связи со Значениями. Когда я получаю продукт, я могу получить все присвоенные ему Атрибуты, но у меня нет возможности присвоить ему только определенные Значения данного Атрибута.
Пример: Атрибут "Цвет" имеет значения "Красный", "Синий". Один Продукт может быть "Красный", а второй "Синий". У меня выведется и "Синий" и "Красный" для обоих Продуктов.

Я могу связь продукт со значениями. Через значение имеет доступ к атрибуту. Это будет работать, но даст на выходе данные не в том формате, к которому я стремлюсь.
spoiler
foreach ($products as $product) {
    $product->name

    foreach ($product->attributeValues as $attributeValue) {
        $attributeValue->attribute->name
        $attributeValue->name
    }
}

и

{
    "name": "Product",
    "values": [
        {
            "attribute": "Attribute 1",
            "value": Value 1",
        },
        {
            "attribute": "Attribute 1",
            "value": Value 2",
        }
    ]
}

Я же питаюсь получить:
spoiler
{
    "name": "Product",
    "attributes": [
        {
            "name": "Attribute 1",
            "values": [
                "name": "Value 1",
                "name": "Value 2",
            ]
        }
    ]
}
  • Вопрос задан
  • 379 просмотров
Пригласить эксперта
Ответы на вопрос 2
@PiloTeZ
...
Предлагаю следующую структуру
products
--- id
--- name

product_attributes
--- id
--- name

product_attribute_values
--- id
--- attribute_id
--- product_id
--- value


Дока для реализации получения данных через таблицу-связь:
https://laravel.com/docs/9.x/eloquent-relationship...

Я стремлюсь к тому, что получить на выходе результат в таком виде

Эту группировку нужно делать на стороне кода, а не в БД
Ответ написан
iMedved2009
@iMedved2009
Не люблю людей
Окей берем вариант Дмитрий Л
products
--- id
--- name

product_attributes
--- id
--- name

product_attribute_values
--- id
--- attribute_id
--- product_id
--- value

Окей. Предположим

class Product

class Product extends Model
{
    use HasFactory;

    protected $fillable = ['name'];

    public function attributes(): BelongsToMany
    {
        return $this->belongsToMany(Attribute::class, 'product_attribute_value')->withPivot(['value']);
    }
}


class Attribute

class Attribute extends Model
{
    use HasFactory;

    protected $fillable = ['name'];

    public function getValuesAttribute($value){
        return collect(explode(',', $value));
    }
}


данные

$a1 = \App\Models\Attribute::create([
            'name' => 'Attr1',
        ]);
        $a2 = \App\Models\Attribute::create([
            'name' => 'Attr2',
        ]);
        $a3 = \App\Models\Attribute::create([
            'name' => 'Multy Attr3',
        ]);


        $p1 = \App\Models\Product::create([
            'name' => 'Product1',
        ]);
        $p1->attributes()->attach($a1->id, [
            'value' => '2012',
        ]);
        $p1->attributes()->attach($a2->id, [
            'value' => 'Джинса',
        ]);
        $p1->attributes()->attach($a3->id, [
            'value' => 'Синий',
        ]);
        $p1->attributes()->attach($a3->id, [
            'value' => 'Черный',
        ]);
        $p1->attributes()->attach($a3->id, [
            'value' => 'Зеленый',
        ]);



        $p2 = \App\Models\Product::create([
            'name' => 'Product2',
        ]);
        $p2->attributes()->attach($a1->id, [
            'value' => '2013',
        ]);
        $p2->attributes()->attach($a2->id, [
            'value' => 'Лен',
        ]);
        $p2->attributes()->attach($a3->id, [
            'value' => 'Красный',
        ]);
        $p2->attributes()->attach($a3->id, [
            'value' => 'Серобурый',
        ]);

        $p3 = \App\Models\Product::create([
            'name' => 'Product3',
        ]);
        $p3->attributes()->attach($a1->id, [
            'value' => '2015',
        ]);
        $p3->attributes()->attach($a2->id, [
            'value' => 'Лен',
        ]);
        $p3->attributes()->attach($a3->id, [
            'value' => 'Синий',
        ]);
        $p3->attributes()->attach($a3->id, [
            'value' => 'Желтый',
        ]);



Естественно если мы дернем $products = Product::with('attributes')->get(); то у нас будет картинка которая автору не нравится.

Но нет препятствий патриотам, добавляем в класс Product метод
public function attributes_with_grouped_values(): HasMany{
        $instance = $this->newRelatedInstance(Attribute::class);

        $query = $instance->newQuery()->join('product_attribute_value', 'product_attribute_value.attribute_id', '=', 'attributes.id')
            ->groupBy('attributes.id', 'attributes.name', 'product_attribute_value.product_id')
            ->select('attributes.id', 'attributes.name', 'product_id', DB::raw('string_agg(product_attribute_value.value, \',\') as values'));
        // в случае с мускул string_agg(product_attribute_value.value, \',\') заменить на group_concact
        return $this->newHasMany(
            $query, $this, 'product_attribute_value.product_id', 'id'
        );
    }


добавляем в класс Attribute метод
public function getValuesAttribute($value){
        return collect(explode(',', $value));
    }


Проверяем

$products = Product::with('attributes_with_grouped_values')->get();
        foreach ($products as $product){
            echo $product->name.PHP_EOL;
            foreach ($product->attributes_with_grouped_values as $attribute){
                dump(json_encode([$attribute->name => $attribute->values->all()]));
            }
        }


Product1
^ "{"Attr1":["2012"]}"
^ "{"Attr2":["\u0414\u0436\u0438\u043d\u0441\u0430"]}"
^ "{"Multy Attr3":["\u0421\u0438\u043d\u0438\u0439","\u0417\u0435\u043b\u0435\u043d\u044b\u0439","\u0427\u0435\u0440\u043d\u044b\u0439"]}"
Product2
^ "{"Attr1":["2013"]}"
^ "{"Attr2":["\u041b\u0435\u043d"]}"
^ "{"Multy Attr3":["\u041a\u0440\u0430\u0441\u043d\u044b\u0439","\u0421\u0435\u0440\u043e\u0431\u0443\u0440\u044b\u0439"]}"
Product3
^ "{"Attr1":["2015"]}"
^ "{"Attr2":["\u041b\u0435\u043d"]}"
^ "{"Multy Attr3":["\u0416\u0435\u043b\u0442\u044b\u0439","\u0421\u0438\u043d\u0438\u0439"]}"


Ровно как хотели.
Является это хорошим решением? Не факт. Возможно в вашем случае стоит завести readonly виртуальную модель, возможно стоит смотреть в сторону view models и есть еще куча вариантов решения проблемы
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы