Как наиболее эффективно «повернуть» отношения в laravel? - PullRequest
0 голосов
/ 17 февраля 2020

Я создаю сайт электронной коммерции в Laravel. У меня есть таблица attribute , заполненная информацией об атрибутах, такой как «Цвет» и «Размер». Таблица ** attribute_values ​​содержит все значения атрибутов, такие как «красный», «синий» и «XXL».

Поскольку я хочу повторно использовать атрибуты, я не создаю новый набор атрибутов для продукта, но я связываю все значения атрибутов с продуктами с помощью сводной таблицы attribute_value_product .

Структура отношения модели:

Attribute -> hasMany -> AttributeValue
Product -> belongsToMany -> AttributeValue

Таблицы следующие:

products
--------------------------------
| id | name            | price |
--------------------------------
| 1  | Example product | 200   |
--------------------------------

attributes
--------------
| id | name  |
--------------
| 1  | Color |
--------------

attribute_values
-----------------------------
| id | attribute_id | value |
-----------------------------
| 1  | 1            | Red   |
-----------------------------
| 2  | 1            | Blue  |
-----------------------------

attribute_value_product
----------------------------------------
| id | attribute_value_id | product_id |
----------------------------------------
| 1  | 1                  | 1          |
----------------------------------------
| 2  | 2                  | 1          |
----------------------------------------

Код контроллера (немного упрощенный) выглядит следующим образом:

public function show($id) {

  $product = Product::where('id', $id)
    ->with('attributeValues.attribute')
    ->first();

  return view('product')->withProduct($product);

}

результат JSON при возврате переменной $ product из контроллера выглядит так:

{
    id: 1,
    name: "Example product",
    price: 200,
    attribute_values: [
        {
            id: 1,
            attribute_id: 1,
            value: "Red",
            attribute: {
                id: 1,
                name: "Color",
            }
        },
        {
            id: 2,
            attribute_id: 1,
            value: "Blue",
            attribute: {
                id: 1,
                name: "Color",
            }
        }
    ]
}

Как видите, это немного «противно». Когда я получаю множество атрибутов для множества продуктов, атрибут get загружается для каждого значения. Когда мне нужно только один раз на самом деле. Кроме того, способ структурирования данных, на мой взгляд, «неправильный».

Предпочтительный вывод, который я хотел бы видеть:

{
    id: 1,
    name: "Example product",
    price: 200,
    attributes: [
        {
            id: 1,
            name: "Color",
            values: [
                {
                    id: 1,
                    attribute_id: 1,
                    value: "Blue",
                },
                {
                    id: 2,
                    attribute_id: 1,
                    value: "Red",
                }
            ]
        },
    ]
}

Таким образом, значения будут загружаться для каждого атрибута продукта , Есть ли (Laravel) способ структурировать отношения таким образом, чтобы я мог получать данные в последнем формате, сохраняя при этом схему базы данных, которая у меня есть?

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

Ответы [ 2 ]

1 голос
/ 17 февраля 2020

Возможно, вы хотите найти Eloquent Resources. Это в основном способ форматирования ваших моделей при преобразовании их в массив.

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class Product extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'price' => $this->price,
            'attributes' => $this->attribute_values->mapToGroups(fn($item, $key) => [
                [
                    'id' => $item->pivot->id,     // color id
                    'name' => $item->pivot->name, // color name
                    'values' => [
                        'id' => $item->id,
                        'attribute_id' => $item->attribute_id,
                        'value' => $item->value,
                    ]
                ]
            ])
            ->flatten(1),
        ];
    }
}
use App\Http\Resources\Product as ProductResource;
...
$product = Product::with('attributeValues')->first();

return new ProductResource($product);

Поскольку у меня нет вашей точной структуры, я могу только догадываться. Просто поиграйте с методами группировки коллекций, пока данные не примут нужный вам формат.

1 голос
/ 17 февраля 2020

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


Метод 1: Используйте HasManyThrough Отношения на Product::class

Laravel $this->hasManyThrough() отношения были предназначены, чтобы позволить вам получить доступ к вложенным отношениям, которые соответствуют вашему варианту использования.

class Product extends Model
{
    /**
     * Get all of the attributes for the product.
     */
    public function attributes()
    {
        return $this->hasManyThrough('App\Attribute', 'App\AttributeValue');
    }
}

И использовать Product::with('attributes.attributeValues')->first()

Использование точечной нотации, приведенной выше, предотвращает проблемы n + 1, также загружая вложенную связь attributeValues. см. Документы для активной загрузки


Метод 2: Использование Eloquent Resource

По сути, это ведущий модели, который дает вам возможность управлять предоставленными данными, возвращаемыми через конечную точку API.

Eloquent Resources может быть создан с помощью команды консоли

php artisan make:resource ProductResource

Который сгенерирует файл ProductResource.php в каталоге app\Http\Resources.

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class ProductResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        // This will use the Default Eloquent Model toArray methods
        $resource = $this->resource->toArray(); 

        // Now you can append in any custom fields that you need.

        if($this->whenLoaded('attributeValues'){
            // whenLoaded is used here to dynamically append this attribute only when the relationship has been eager loaded
            $resource['attributes'] = $this->resource->attributeValues->groupBy('attribute.name');
        }

        return $resource;
    }
}

Использование $this->whenLoaded выше выполняет две вещи:

  1. Пределы N Ошибки +1 (только для $product->attributeValues, а не для вложенных отношений $product->attributeValues->attributes)
  2. Позволяет многократно использовать ProductResource::class в ситуациях, когда Attribute и AttributeValue не загружены / не нужны.

Примечание. Эту строку $resource['attributes'] = $this->resource->attributeValues->groupBy('attribute.name'); вполне можно заменить вызовами Eloquent Resource Collection с дополнительными вложенными ресурсами и / или ResourceColletions, если вы решите полностью принять Модель Presenter pattern.

Наконец-то под вашим контролем r вы бы сделали что-то вроде следующего:

// Use of dot notation here will eager load the attributes as well avoiding potential n+1 problems

$product = Product::with('attributeValues.attribute')->first()

return new App\Http\Resources\Product($product);
...