Сложное eloquent relation в laravel
Всем привет. Возник вопрос по eloquent relation в проекте на laravel 10. Допустим есть 3 таблицы:
- orders - таблица с данными заказов
- id - id заказа
- shop_id - id магазина, к которому относится заказ
- orders_products - таблица с данными товаров в заказах
- order_id - id заказа
- product_id - id товара
- remainings - таблица с остатками товаров на дату в разрезе магазинов
- date - дата
- shop_id - id магазина
- product_id - id товара
- remaining - остаток
Вопрос: какое отношение нужно написать в классе OrdersProduct, чтобы получить остаток по product_id, shop_id (через таблицу orders) и на самую позднюю дату? Пробовал через hasOneTrough:
public function remainings(): HasOneThrough
{
return $this->hasOneThrough(Remaining::class, Order::class, 'id', 'shop_id', 'order_id', 'shop_id')->whereProductId($this->product_id)->latest('date');
}
но тогда на каждый вызов этого метода происходит отдельный подзапрос к БД, хотелось бы по "правильному" загрузить все данные через отношение. Или laravel пока такое не умеет?
Ответы (1 шт):
Я думаю есть более оптимальные решения, нужно протестировать это на коде, поскольку по памяти без теста я мог допустить ошибки.
Чтобы получить остаток по product_id
и shop_id
(через таблицу orders
) на самую позднюю дату, вам нужно определить отношение в модели OrdersProduct
. Однако из-за того, что вам нужно получить shop_id
из связанной модели Order
, стандартные отношения Eloquent не позволяют напрямую использовать свойства других моделей при определении отношений.
Тем не менее, вы можете использовать метод hasOne
с замыканием для добавления дополнительных условий в отношение. Вот как вы можете это сделать:
class OrdersProduct extends Model
{
// Определяем отношение к модели Order
public function order()
{
return $this->belongsTo(Order::class, 'order_id');
}
// Определяем отношение к модели Remaining с дополнительными условиями
public function remaining()
{
return $this->hasOne(Remaining::class, 'product_id', 'product_id')
->where('shop_id', $this->order->shop_id)
->orderBy('date', 'desc');
}
}
Однако при таком определении возникнет проблема: вы не можете использовать $this->order->shop_id
внутри метода remaining()
, потому что при определении отношения экземпляр модели еще не загружен.
Чтобы обойти это ограничение, вы можете использовать замыкание в методе hasOne
, которое принимает объект запроса и модель, и внутри него добавить необходимые условия. Вот скорректированный вариант:
public function remaining()
{
return $this->hasOne(Remaining::class, 'product_id', 'product_id')
->join('orders', 'orders.shop_id', '=', 'remainings.shop_id')
->whereColumn('orders.id', 'orders_products.order_id')
->orderBy('remainings.date', 'desc')
->select('remainings.*');
}
В этом случае мы используем join
для соединения таблиц и whereColumn
для сравнения столбцов.
Однако такой подход может быть сложным и не совсем соответствовать идеологии Eloquent.
Рекомендуемый подход
Лучше определить метод, который будет выполнять нужный запрос и возвращать модель Remaining
. Это позволит использовать необходимые свойства и условия без ограничений отношений. Вот как это можно сделать:
public function getRemainingAttribute()
{
return Remaining::where('product_id', $this->product_id)
->where('shop_id', $this->order->shop_id)
->orderBy('date', 'desc')
->first();
}
Теперь вы можете обращаться к остатку через свойство remaining
:
$ordersProduct = OrdersProduct::with('order')->find($id);
$remaining = $ordersProduct->remaining;
Пояснение
- Определение аксессора
getRemainingAttribute()
: Это позволяет динамически получать остаток с учетом условий. - Использование метода
with('order')
: При загрузкеOrdersProduct
важно подгрузить связанную модельOrder
, чтобыorder
был доступен внутри метода. - Преимущества данного подхода:
- Гибкость: Вы можете легко настроить дополнительные условия.
- Простота: Избегаете сложных определений отношений с замыканиями и
join
. - Производительность: Запрос выполняется только при обращении к свойству
remaining
.
Заключение
Из-за сложности отношения и необходимости использовать свойства связанной модели, лучше определить метод или аксессор в модели OrdersProduct
, который будет возвращать нужный вам остаток. Это позволит вам точно контролировать условия запроса и обеспечить корректную работу вашего приложения.