Включение удаленных отношений, когда прямая связь недоступна (поля прямой связи отображаются как нулевые) - PullRequest
1 голос
/ 10 октября 2019

Фон

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

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

Например,

Category: "Tires"
Attributes: ["Manufacturer", "Dimensions", "FuelEconomyClassification", "WetGripClassification"]

Product: "Goodyear EfficientGrip"
Attributes: [
  "Manufacturer" => "Goodyear",
  "Dimensions" => "225/50/17",
  "FuelEconomyClassification" => "D",
  "WetGripClassification" => "B"
]

Я также сделал очень минималистичное изображение отношений, чтобы описатькак это все получается: Table relations

Реализация Laravel

Версия: 6.1

Я сопоставил всеочень простые таблицы с отношениями hasMany / ownTo

Category
attributes() => hasMany(CategoryAttribute)
products() => hasMany(Product)

Product
attributes() => hasMany(ProductAttribute)
category() => belongsTo(Category)

CategoryAttribute
category() => belongsTo(Category)

ProductAttribute
categoryAttribute() => belongsTo(CategoryAttribute)
product() => belongsTo(Product)

Я также использую Eloquent API Resources , чтобы сделать данные API пригодными для обработки и форматирования.

Проблема

Нормальным отношением, где я хочу получить атрибуты Products, является Product->ProductAttribute[]. Это в большинстве случаев работает отлично. Однако проблема возникает, когда в какой-то момент возникает необходимость добавить новый атрибут CategoryAttribute в категорию.

Моя цель - иметь возможность видеть все атрибуты на странице сведений о продуктах, даже если значение неназначенный (ProductAttribute).

В настоящее время, если бы я должен был добавить новый CategoryAttribute, а затем попытался просмотреть Product, вновь добавленный CategoryAttribute не отобразился бы, как ожидается, поскольку между ними нет отношения ProductAttribute.

Итак, я бы хотел как-то объединить CategoryAttribute и ProductAttribute с одним, чтобы иметь возможность видеть все атрибуты и те, у которых нет ProductAttribute в середине и отображать его значения в ноль (как слеваjoin), либо напрямую через отношение, либо через ресурс API, не вызывая выполнения множества SQL-запросов.

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

1 Ответ

0 голосов
/ 12 октября 2019

Уже немного поздно, но, возможно, это может быть полезно, я надеюсь.

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

Для начала, скажем, у нас есть 3 необходимые таблицы: «категории», «атрибуты», «продукты». С помощью этих 3 таблиц мы можем построить некоторую нормальную структуру для нашей системы. При этом у нас может быть продукт, который может иметь несколько атрибутов, который относится к какой-то категории. Это хорошо. В этом случае, если администратор изменит какой-либо атрибут (имя атрибута или что-то подобное), это повлияет на все продукты, которые имеют этот атрибут. Но что если администратор захочет изменить какое-либо свойство атрибута только для конкретного продукта? Мы застрянем на этом. Вот четвертая таблица: «attribute_templates». Здесь, в этой новой таблице, мы будем хранить все атрибуты вместо таблицы атрибутов, как раньше. Но в таблице «атрибуты» мы будем хранить атрибуты с назначением их конкретного продукта. В этом случае администратор может изменить некоторое значение свойства атрибута для конкретного продукта, и это не повлияет на другие продукты. Также любой атрибут может иметь различный тип, и он должен принадлежать к какой-то определенной категории. Например: продукт телефона (это категория) (Galaxy S9) может иметь некоторые атрибуты, такие как «bluetooth», который будет иметь логический тип (иметь / не иметь), или «заднюю камеру», который будет иметь тип smallInteger (7MP), 10MP, 12MP и т. Д.). Таким образом, каждый атрибут будет иметь свой тип (в таблице «attribute_templates») и значение (в таблице «attribute»). В случае, когда администратор хочет добавить / удалить некоторые новые атрибуты, система должна добавить / удалить эти атрибуты только в / из таблицы «attribute_templates». А в запросах мы будем использовать только таблицу атрибутов (ничего общего с таблицей шаблонов). Этот шаблон хорош также, когда вы хотите добавить новую функциональность в БД. Например, в одном из моих проектов у меня были квартиры вместо продуктов. У них были атрибуты, а также удобства. Для этого я также создал таблицы «amenity_templates» и «удобствами» и соединил их с таблицами «квартиры» и «категории» (как я это сделал для таблицы «attribute_templates» и «атрибуты»).

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

enter image description here

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

  1. создать категории
  2. создать начальные атрибуты в 'attribute_templates' (в этомтаблица case 'attribute' еще пуста)
  3. создать продукт. если продукт должен иметь, например, 3 атрибута, то он скопирует эти три записи из «attribute_templates» и импортирует их в таблицу «атрибутов», и назначит их все этому продукту с соответствующим значением атрибута.

Ниже я напишу коды миграций (с правильной последовательностью) для каждой модели.

2019_05_19_100000_create_categories_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateCategoriesTable extends Migration
{
    public function up(){
        Schema::create('categories', function (Blueprint $table) {
            // PRIMARY
            $table->tinyIncrements('id');
            // ADDITIONAL
            $table->string('name', 250);
            // TIME
            $table->timestamps();
        });
    }
    public function down(){
        Schema::dropIfExists('categories');
    }
}

2019_05_19_200000_create_attribute_templates.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateAttributeTemplatesTable extends Migration
{
    public function up()
    {
        Schema::create('attribute_templates', function (Blueprint $table) {
            // PRIMARY
            $table->bigIncrements('id');
            // FOREIGN
            $table->unsignedTinyInteger('category_id')->nullable();
            $table->foreign('category_id')->references('id')->on('categories')->onUpdate('cascade');
            // ADDITIONAL
            $table->string('name', 250);
            $table->string('value_type', 20)->nullable();
            // TIME
            $table->timestamps();
        });
    }
    public function down(){
        Schema::dropIfExists('attribute_templates');
    }
}

2019_05_19_300000_create_products_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateProductsTable extends Migration
{
    public function up(){
        Schema::create('products', function (Blueprint $table) {
            // PRIMARY
            $table->bigIncrements('id');
            // FOREIGN
            $table->unsignedTinyInteger('category_id')->nullable();
            $table->foreign('category_id')->references('id')->on('categories')->onUpdate('cascade');
            // ADDITIONAL
            $table->string('title', 250);
            $table->string('image', 250)->nullable();
            // TIME
            $table->timestamps();
        });
    }
    public function down(){
        Schema::dropIfExists('products');
    }
}

2019_05_19_400000_create_attributes_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateProductAttributesTable extends Migration
{
    public function up(){
        Schema::create('product_attributes', function (Blueprint $table) {
            // PRIMARY
            $table->bigIncrements('id');
            // FOREIGN
            $table->unsignedBigInteger('product_id');
            $table->foreign('product_id')->references('id')->on('products')->onUpdate('cascade')->onDelete('cascade');
            $table->unsignedBigInteger('template_id');
            $table->foreign('template_id')->references('id')->on('attribute_templates')->onUpdate('cascade');
            // ADDITIONAL
            $table->text('value')->nullable();
            // TIME
            $table->timestamps();
        });
    }
    public function down(){
        Schema::dropIfExists('product_attributes');
    }
}
Ниже я напишу коды Моделей (с правильной последовательностью).

Category.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    protected $table = 'categories';

    protected $fillable = [
        // PRIMARY
        'id', // tinyIncrements
        // ADDITIONAL
        'name', // string 250
    ];

    // RELATIONS
    public function products(){
        return $this->hasMany(Product::class, 'category_id', 'id');
    }
    public function productAttributeTemplates() {
        $this->hasMany(ProductAttributeTemplate::class, 'category_id');
    }
}

AttributeTemplate.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class AttributeTemplate extends Model
{
    protected $table = 'attribute_templates';

    protected $fillable = [
        // PRIMARY
        'id', // bigIncrements
        // FOREIGN
        'category_id', // unsignedTinyInteger nullable
        // ADDITIONAL
        'name', // string 250
        'value_type', // string 20 nullable
    ];

    // RELATIONS
    public function attribute(){
        return $this->hasOne(ProductAttribute::class, 'template_id');
    }
    public function category(){
        return $this->belongsTo(Category::class, 'category_id');
    }
}

Product.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    protected $table = 'products';

    protected $fillable = [
        // PRIMARY
        'id', // bigIncrements
        // FOREIGN
        'category_id', // unsignedTinyInteger
        // ADDITIONAL
        'title', // string 250
        'image', // string 250 nullable
    ];

    // RELATIONS
    public function category(){
        return $this->belongsTo(Category::class, 'category_id');
    }
    public function attributes(){
        return $this->hasMany(ProductAttribute::class, 'product_id');
    }
}

Attribute.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Attribute extends Model
{
    protected $table = 'product_attributes';

    protected $fillable = [
        // PRIMARY
        'id', // bigIncrements
        // FOREIGN
        'product_id', // unsignedBigInteger
        'template_id', // unsignedBigInteger
        // ADDITIONAL
        'value', // text
    ];

    // RELATIONS
    public function product(){
        return $this->belongsTo(Product::class, 'product_id');
    }
    public function template(){
        return $this->belongsTo(AttributeTemplate::class, 'template_id');
    }
}

Теперь, скажем, мы хотимдобавить некоторый телефонный продукт (OnePlus 7T), который будет следоватьАтрибуты ing: OS (OxygenOS 10), bluetooth (true), наушники (Type-C).

  1. Если продукт «телефон» не существует, нам нужно создать его в таблице «категории».
  2. Создать новый продукт «OnePlus 7T» в таблице «продукты» и присвоить его таблице «категории». .
  3. Скопируйте атрибуты «OS», «Bluetooth», «наушники» из таблицы «attribute_templates», вставьте их в таблицу «атрибутов» и назначьте их в таблицу «категории» и таблицу «продукты» (для соответствующейкатегории «телефон» и «OnePlus 7T»). А также, если вам нужно, вы можете написать «значение» атрибутов для каждого шаблона. Для нас это будет: «OxygenOS 10» для ОС, «true» для Bluetooth, «Type-C» для наушников.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...