Как спроектировать базу данных?

Route

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\YarnController;

Route::get('/yarn/{slug}', [YarnController::class, 'show'])->name('yarn.show');

YarnController

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Yarn;

class YarnController extends Controller
{
    public function show($slug)
    {
        return view('yarn.show', [
            'yarns' => Yarn::where('slug', $slug)->get()
        ]);
    }
}

Условием выборки является slug. Если в базе есть товары (пряжа) с таким slug, то мы показываем их пользователю.

Хочу реализовать:

У товара (пряжи) есть связанные модели. Country, Manufacturer, Fiber и т.д. Они имеют свой slug. Неплохо бы включить их в условие выборки.

/yarn/alize (Manufacturer)
/yarn/acrylic (Fiber)
/yarn/alize-lana-gold (Yarn)

Есть идея хранить все slug в одной таблице. При выборки товаров (пряжи) проверять, есть там такой slug или нет.

Просто пример. Наверное это так работать не будет.

Yarn::whereHas('table', function($query) use ($slug) {
    $query->where('slug', $slug);
})->get();

Трудность еще в том, что Country и Manufacturer имеет отношение один ко многим, а Fiber многие ко многим.

Не понятно, как при добавлении новой страны (например) записать все данные страны в таблицу Country, а слоган в таблицу Slug.

Любые идеи как реализовать?


Ответы (3 шт):

Автор решения: E_K

Вариант #1

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

URL

site.test/yarn?country[]=1&country[]=2&manufacture[]=5&manufacture[]=1&fibers[]=4

Простой пример. Можете заменить id на slug;

Route::get('yarn', static function (Request $request) {
    $builder = (new \App\Models\Product())->newModelQuery();

    if ($request->has('country')) {
        $builder->country($request->get('country'));
    }

    if ($request->has('manufacture')) {
        $builder->whereHas('manufacture', function ($q) use ($request) {
            $q->whereIn('id', $request->get('manufacture'));
        });
    }

    if ($request->has('fibers')) {
        $builder->whereHas('fibers', function ($q) use ($request) {
            $q->whereIn('id', $request->get('fibers'));
        });
    }

   $products = $builder->get();
});

whereHas можно заменить на Dynamic Scope.

class Product extends Model
{
    public function scopeCountry($builder, $ids)
    {
        return $builder->whereHas('country', function ($q) use ($ids) {
            $q->whereIn('id', $ids);
        });
    }
}

Вариант #2

Вы можете использовать не обязательные параметры роутера.

Если предположить, что потребуется выборка только по manufacture или fibers. Я бы в таком случае заменил название аргументов на $first и тд. А к slug перед сохранением добавлял префикс (ctr-ukraine, mfc-factory). Это позволит быстро уточнить к какому отношению относится параметр, составить правильный запрос и не привязываться к жёсткой структуре slug.

Route::get(
    '/yarn/{country?}/{manufacture?}/{fiber?}',
    static function (
        $country = null,
        $manufacture = null,
        $fiber = null
    ) {
    $builder = (new \App\Models\Product())->newModelQuery();

    if (! is_null($country)) {
        $builder->whereHas('country', function ($q) use ($country) {
            $q->where('id', $country);
        });
    }

    // . . .

   $products = $builder->get();
});
→ Ссылка
Автор решения: Inventor

Хранить все slug в одном месте - плохая идея. У вас получится нездоровая структура БД.

Используйте sql, это поможет вам делать с вашей базой данных всё что угодно, orm вас сильно ограничивает.

Если я правильно понял, то вы хотите получить все товары где slug равен введённой строке, а так же товары, где slug у связанных моделей равен введённой строке.

Я не знаю какая именно у вас база данных, но это решение будет работать для большинства:

WITH
    slug AS (
        SELECT 'you_slug' AS slug
    ),
    suitable_products AS ( -- сюда попадут все id товаров, у которых связанные модели подходят под введённую строку
        SELECT yarn_id FROM country WHERE slug=(SELECT slug FROM slug)
        UNION
        SELECT yarn_id FROM manufacturer WHERE slug=(SELECT slug FROM slug)
        UNION
        SELECT yarn_id
            FROM yarns_to_fibers ytf -- yarns_to_fibers - ваша таблица, где хранится many_to_many для продуктов и волокон
                INNER JOIN fiber f ON f.id = ytf.fiber_id AND f.slug=(SELECT slug FROM slug) 
    )


-- yarn - таблица, где хранятся ваши товары
SELECT y.*
FROM yarn y
WHERE y.id IN (
    suitable_products.yarn_id
) OR y.slug=(SELECT slug FROM slug)
LIMIT 20;

!! Название таблиц и столбцов могут отличаться от ваших. Сверьте с вашей БД

→ Ссылка
Автор решения: IndianCoding

Controller:

Ищем первый найденный объект любой из 4 моделей, отправляем его во вьюху, иначе возвращаем 404.

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Yarn;
use App\Models\Country;
use App\Models\Manufacture;
use App\Models\Fiber;

class YarnController extends Controller
{
    public function show($slug)
    {
        $models = [Yarn::class, Country::class, Manufacture::class, Fiber::class];

        foreach ($models as $model) {
            $item = $model::where('slug', slug)->first();
            if ($item) {
                if (method_exists($item, 'yarns')) {
                    $item->load(['yarns.childrenRelation', 'yarns.childrenRelation2']);
                } else {
                    $item->load(['childrenRelation', 'childrenRelation2']);
                }

                return view('yarn.show', compact('item'));
            }
        }

        abort(404);
    }
}

Это из расчёта, что у Yarn уникальный url. Вы почему-то делаете на ->first(), а get(), что навело на мысли, что url может быть не уникальным.

View:

Здесь обращаемся к релейшену yarns, который должен быть настроен внутри моделей Manufacture, Country, Fiber.

@if ($item->yarns)
    $item - экземпляр модели Fiber, Manufacture или Country.
    @foreach ($item->yarns)
        тут идут ярны у трёх моделей
    @endforeach
@else 
    $item - экземпляр модели Yarn
@endif

Скорее всего во вьюхе надо будет определять, что за модель содержится в $item, тогда у нас есть множество приёмов:

$item->getTable() === 'fibers'
$item->getMorphClass() === '\App\Models\Manufacture'
$item instanceof \App\Models\Country
is_a($this, \App\Models\Yarn::class)
→ Ссылка