Eloquent ORM order by deep relation - php

Models:
Category (id, name)
Item (id, name, category_id, ...)
OrderList (id, user_id, ...)
OrderListItem(id, item_id, order_list_id, user_sort_order, ...)
I want OrderListItems of each OrderList to be sorted:
By user_sort_order
Or by Category name
What I do:
$grouped = $request->input('grouped', false) === "true";
$user = User::findOrFail($id);
$order_lists = OrderList::where('kitchen_id', $user->id)
->with(['order_list_items' => function ($q) use ($grouped) {
$q->when($grouped, function ($q) {
return $q->with(['item' => function ($q) {
return $q->with(['category' => function ($q) {
return $q->orderBy('name', 'asc');
}]);
}]);
}, function ($q) {
return $q->orderBy('kitchen_sort_order', 'asc');
})->with('supplier')
->with(['item' => function ($q) {
return $q->with('category');
}]);
}])->get();
Ordering by category name isn't working. I had been searching for hours but found no answer. Is it possible to do something like this in Eloquent ORM? Btw, Django is able to do it in a really nice way.

You can use a HasManyThrough relationship as a BelongsToThrough and combine it with a modified withCount().Not the most elegant solution, but it works:
class OrderListItem extends Model {
public function category() {
return $this->hasManyThrough(Category::class, Item::class,
'id', 'id', 'item_id', 'category_id');
}
}
$order_lists = OrderList::where('kitchen_id', $user->id)
->with(['order_list_items' => function ($q) use ($grouped) {
$q->when($grouped, function ($q) {
return $q->withCount(['category as category_name' => function ($q) {
$q->select('categories.name');
}])->orderBy('category_name');
}, function ($q) {
return $q->orderBy('kitchen_sort_order', 'asc');
})->with('supplier')
->with(['item' => function ($q) {
return $q->with('category');
}]);
}])->get();

Related

Laravel use model property in relation method

I have relation method for a model which has a comparison on a property(created_at) of the model itself. Where I want to compare the created_at date of the 'Appointment' model with the created_at of the 'Treatment'. I only want a 'Treatment' if its 'created_at' date is later than that of the 'Appointment'
AppointmentController.php
public function index(): JsonResource
{
$users = User::with([
'appointment' => function ($query) {
$query->with(['appointment_events']);
},
])
->active()
->get();
return UserResource::collection($users);
}
Appointment.php
public function appointment_events(): HasMany
{
return $this->hasMany(AppointmentEvent::class)
->with([
'customer' => function ($query) {
$query->with([
'section' => function ($subquery) {
$subquery
->with([
'advice',
'treatment' => function ($subquery) {
$subquery->where('created_at', '>', blank); //How can I get the model's created_at date.
},
]);
},
]);
},
]);
}
Is there a way to do this?
I think you should move the query to a scope on Appointment model and keep the relationship definition simple.
Note: Below assumes table names and column names as per standard Laravel conventions. If you have different convention, please modify the table names and column names accordingly.
class Appointment extends Model
{
public function scopeWithFutureTreatments($query)
{
return $query->with([
'appointment_events' => function($query) {
$query->with([
'customers' => function($query) {
$query->with([
'section' => function($query) {
$query->with([
'advice',
'treatment' => function($query){
$query->join('sections', 'section.id', '=', 'treatments.section_id')
->join('customers', 'customers.id', '=', 'sections.customer_id')
->join('appointment_events', 'appointment_events.id', '=', 'customer.appointment_events_id')
->join('appointments', 'appointments.id', '=', 'appointment_events.appointment_id')
->whereColumn('treatments.created_at', '>', 'appointments.created_at')
->select('treatments.*')
}
]);
}
]);
}
]);
}
]);
}
public function appointment_events(): HasMany
{
return $this->hasMany(AppointmentEvent::class);
}
}
Then in AppointmentController the following should get you the desired results
public function index(): JsonResource
{
$users = User::with([
'appointment' => function ($query) {
$query->withFutureTreatments();
},
])
->active()
->get();
return UserResource::collection($users);
}
If I understand yours relations are
User ->HasManyAppointments ->HasManyAppointmentsEvents.
You can access quite directly to appointments events data from a user
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get all of the appointment_events for the user.
*/
public function appointment_events()
{
return $this->hasManyThrough(Appointment::class, AppointmentEvent::class);
}
}
From Laravel Doc: Has Many Through
So you can query
$collection = User::with(['appointments','appointment_events'])->where()->get();
which give you all appointments and related appointment_events on a particular user. and you can tap on that collection or filter or whatever business logic you want.

Laravel nested relation filter

Have a query, how I can filter results by translation relation (by name column)
$item = Cart::select('product_id','quantity')
->with(['product.translation:product_id,name','product.manufacturer:id,name'])
->where($cartWhere)
->get();
my model
Cart.php
public function product($language = null)
{
return $this->hasOne('App\Models\Product','id','product_id');
}
Product.php
public function translations()
{
return $this->hasMany('App\Models\ProductTranslation','product_id','id');
}
Update v1.0
do like this, but query takes too long time
$item = Cart::select('product_id','quantity')
->with(['product.translation', 'product.manufacturer:id,name'])
->where($cartWhere)
->when($search,function ($q) use ($search) {
$q->whereHas('product.translation', function (Builder $query) use ($search) {
$query->where('name', 'like', '%'.$search.'%');
$query->select('name');
});
}
)
->get() ;
Inside the array within your with() method, you can pass a function as a value.
Cart::select('product_id','quantity')
->with([
'product', function($query) {
$query->where($filteringAndConditionsHere);
}
]);
https://laravel.com/docs/7.x/eloquent-relationships#eager-loading

Laravel Eloquent Model return data if relatioship has data

Good day, I'm trying to return data from Eloquent Model with relationships.
Is there a way to ( skip / not return) orders where relationship returns an empty array as result?
Order::with(['products' => function ($query) {
$query->whereHas('progress', function ($query) {
$query->where('progress_id', 30)->orWhereBetween('progress_id', [60, 90]);
});
$query->whereHas('product', function ($query) {
$query->where('vendor_id', 3);
})->with(['product' => function ($query) {
$query->select('id', 'identifier', 'reference', 'shipping_id');
}]);
$query->select('id', 'order_id', 'product_id', 'quantity');
}])
->whereHas('products')
->where('status_id', '=', 15)
->select('orders.id', 'orders.customer_id', 'orders.created_at')
->get();
So in this case I don't want to get that order because there are no products with it.
Also I don't understand why am I even getting results there are orders with status_id = 15 but non for vendor_id = 3.
How to do it? Thank you for reading.
Try this
Order::whereHas('products')->with(['products' => function ($query) {
$query->whereHas('progress', function ($query) {
$query->where('progress_id', 30)->orWhereBetween('progress_id', [60, 90]);
});
$query->whereHas('product', function ($query) {
$query->where('vendor_id', 3);
})->with(['product' => function ($query) {
$query->select('id', 'identifier', 'reference', 'shipping_id');
}]);
$query->select('id', 'order_id', 'product_id', 'quantity');
}])->where('status_id', '=', 15)
->select('orders.id', 'orders.customer_id', 'orders.created_at')
->get();
Lets try this code:
Order::has('product', '>=', 1)->get();
Try this:
Order::whereHas('products' => function ($query) {
$query->whereHas('progress', function ($query) {
$query->where('progress_id', 30)->orWhereBetween('progress_id', [60, 90]);
});
$query->whereHas('product', function ($query) {
$query->where('vendor_id', 3);
});
// $query->select('id', 'order_id', 'product_id', 'quantity'); // This line will cause error
}])->with(['products', 'products.product:id,identifier,reference,shipping_id,vendor_id'])
->where('status_id', 15)
->get();

Laravel Eager Loading Results Not Fiultering, Also Returning Null

I am writing an Eloquent query. The condition is as follows:
There are four tables:
Table 1: medicare_advantage_table
Table 2: medicare_advantage_plan_table
Table 3: medicare_advantage_features_table
Table 4: medicare_advantage_company_table
Table 1 has ManyToMany relation with Table 3 via medicare_advantage_features_pivot table
Table 1 has ManyToOne relation with Table 2
Table 1 has ManyToOne relation with Table 4
Now I want to query all the records from medicare_advantage_table provided medicare_advantage_plan_id, medicare_advantage_company_id and medicare_advantage_features_id as arrays. I have written this eloquent query. It is fetching all the records from medicare_advantage_table without constraining and selecting from other three tables like this:
Result Set Image
What am I doing wrong here? Any help would be appreciated
final public function getMedicarePolicies(Request $request): JsonResponse
{
$data = MedicareAdvantage::with([
'features' => static function (BelongsToMany $query) use ($request) {
$query->select('id', 'feature_name')->whereIn('id', $request->input('features') ?? []);
},
'company' => static function (BelongsTo $query) use ($request) {
$query->select('id', 'company_logo', 'company_name')->whereIn('id', $request->input('companies') ?? []);
},
'medicareAdvantagePlan' => static function (BelongsTo $query) use ($request) {
$query->select('id', 'medicare_advantage_plan_type')->whereIn('id', $request->input('plans') ?? []);
}
])->select('id', 'name', 'monthly_premium', 'primary_doctor_co_pay', 'specialist_co_pay', 'company_id')->get();
return response()->json($data, 200);
}
Actually, I managed to solve this anyways. Here's the solution:
$data = MedicareAdvantage::with('features', 'medicareAdvantagePlan', 'company')
->whereHas('features', static function (Builder $query) use ($request) {
$query->select('id', 'feature_name')->where(static function (Builder $query) use ($request) {
if ($request->input('features') !== null) {
$query->whereIn('id', $request->input('features'));
}
});
})
->whereHas('medicareAdvantagePlan', static function (Builder $query) use ($request) {
$query->select('id', 'medicare_advantage_plan_type')->where(static function (Builder $query) use ($request) {
if ($request->input('plans') !== null) {
$query->whereIn('id', $request->input('plans'));
}
});
})
->whereHas('company', static function (Builder $query) use ($request) {
$query->select('id', 'company_name', 'company_logo')->where(static function (Builder $query) use ($request) {
if ($request->input('companies') !== null) {
$query->whereIn('id', $request->input('companies'));
}
});
})
->select('id', 'name', 'monthly_premium', 'primary_doctor_co_pay', 'specialist_co_pay')->get();

Laravel belongsToMany with OR condition

How do I create brackets around my orWhere:
public function categories()
{
return $this->belongsToMany('App\Category', 'user_category')->orWhere('default', 1);
}
So it is converted to this:
where (`user_category`.`user_id` = ? or `default` = 1)
Currently the brackets here are missing and mess up the whole query. I tried for example:
public function categories()
{
$join = $this->belongsToMany('App\Category', 'user_category')
->orWhere('default', 1);
return $this->where(function ($query) use ($join) {
return $join;
});
}
But here I am loosing my model and getting Call to undefined method Illuminate\Database\Query\Builder::...
You can use advanced where clause like:
Model::where(function ($query) {
$query->where('a', '=', 1)
->orWhere('b', '=', 1);
})->where(function ($query) {
$query->where('c', '=', 1)
->orWhere('d', '=', 1);
});
Or nested clause like:
Model::where(function($query)
{
$query->where('a', 'like', 'keyword');
$query->or_where('b', 'like', 'keyword');
})
->where('c', '=', '1');
Are you trying to get all the categories related to an user and the categories where the default field is 1?
If that is the case:
public function categories()
{
return $this->belongsToMany('App\Category');
}
public function relatedCategories()
{
return App\Category::all()->where('default',1)->merge($this->categories);
}
The relatedCategories() method should return a collection with the desired categories. Take in care that the non-related categories but with default=1 will not have the pivot object, because these categories doesn't exist in your pivot table.

Categories