Как спроектировать базу данных?
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 шт):
Вариант #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();
});
Хранить все 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;
!! Название таблиц и столбцов могут отличаться от ваших. Сверьте с вашей БД
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)