Suppose I have a Course Model like this :
class Course extends Model
{
public function users ()
{
return $this->belongsToMany('App\User', 'course_user', 'course_id', 'user_id');
}
public function lessons ()
{
return $this->hasMany('App\Lesson', 'course_id', 'course_id');
}
}
Course fields are :
course_id
title
Each Course can have multiple lessons.
Lesson Model is like :
class Lesson extends Model
{
public function course ()
{
return $this->belongsTo('App\Course', 'course_id', 'course_id');
}
public function users ()
{
return $this->belongsToMany('App\User', 'lesson_user', 'lesson_id', 'user_id');
}
}
And it's fields are:
lesson_id
title
course_id
As you see there is a OneToMany relation between Course and Lesson and a ManyToMany relation between User and Course.
User And Course Pivot table named ~course_user` have these fields :
course_id
user_id
In the other hand there is a ManyToMany relation between User and Lesson. pivot table for those named lesson_user and have these fields :
lesson_id
user_id
passed
passed field show status of a user in a lesson. if it was 0 ,means user has not passed it yet otherwise he passed it.
User Model is like :
class User extends Model
{
public function lessons()
{
return $this->belongsToMany('App\Lesson', 'lesson_user', 'user_id', 'lesson_id')
}
public function courses ()
{
return $this->belongsToMany('App\Course', 'course_user', 'user_id', 'course_id');
}
}
Now I want to get user courses and calculate percent of passed lessons in each Course via best way, for example nested where clauses.
I think this might be not the best way. But it is easy to understand and maintainable
$courses = $user->courses->map(function($cource){
$all_lessions = $cource->pivot->count();
$done_lessions = $cource->pivot->where(passed,'<>',0)->count();
$percent = $done_lessions * 100 / $all_lessions;
return $cource->push(['percent'=>$percent]);
});
Now you can access through
foreach ($courses as $cource){
$cource->percent;
$cource->title;
//...
}
With inspiration from #KmasterYC answer I wrote bellow codes and all things work:
$userCourses =
$currentUser->courses()
->take(3)
->get();
$userCourses->map(function ($course) use ($currentUser) {
$allLessonsCount = $course->lessons->count();
$courseLessonID = $course->lessons->lists('lesson_id')->toArray();
$userLessonsCount = $currentUser->lessons()
->where('passed', '=', true)
->whereIn('lesson_user.lesson_id', $courseLessonID)
->count();
$percent = round($userLessonsCount * 100 / $allLessonsCount);
$course['percent'] = $percent;
});
Related
Considering this table
Services Table
id
company_id
category_id
1
2
4
2
4
6
And this model
CategoryModel.php
public function companies():Attribute
{
return new Attribute(
get: fn () => CompanyModel::whereHas('services', function ($q) {
$q->where('category_id', $this->id);
}),
);
}
Considering that the services table holds both company_id and category_id columns what would be the best approach to query companies relationship that have services under the current category (The companies table does not have a category_id column), my current implemetation is not optimal as it does not allow me to perform any relationship constrains.
EDIT
Each company offers multiple services and each service belongs to a single category.
I also have a reviews table (related to each service) with a rating column
The above query worked efficiently until I needed to constrain/order categories based on the reviews table.
CategoryModel.php
public function scopeHasReviews($query)
{
$query->whereHas('companies', fn ($q) => $q->whereHas('reviews'));
}
This ofcourse will not work since there is no relationship.
Using Attribute in this context is dangerous, because it can lead to N+1 problems. This is a usual many-to-many relationship, so it needs to be implemented in models:
Category.php
public function companies(): BelongsToMany
{
return $this->belongsToMany(Company::class, 'services');
}
Company.php
public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class, 'services');
}
Further, since it is not clear exactly what problem must be solved - I will write an example with sorting by number of reviews:
Review.php
public function scopeWhereRawService(Builder $query, string $service): Builder
{
return $query->whereRaw('service_id = ' . $service);
}
Company.php
public function reviews(): HasManyThrough
{
return $this->hasManyThrough(Review::class, Service::class);
}
public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class, 'services');
}
public function categoriesOrderedByReviews(): BelongsToMany
{
return $this->categories()->withCount([
'reviews as reviews_count' => fn(Builder $q) => $q->whereRawService('services.id')
])->orderByDesc('reviews_count');
}
Category.php
public function reviews(): HasManyThrough
{
return $this->hasManyThrough(Review::class, Service::class);
}
public function companies(): BelongsToMany
{
return $this->belongsToMany(Company::class, 'services');
}
public function companiesOrderedByReviews(): BelongsToMany
{
return $this->companies()->withCount([
'reviews as reviews_count' => fn(Builder $q) => $q->whereRawService('services.id')
])->orderByDesc('reviews_count');
}
I am trying to write a query that selects columns from a model then selects some columns from a morph relationship table. But I have no idea to select columns, and relation tables have different columns. So some column has no slug, some have.
public function index()
{
$menus = Menu::whereActive(true)
->with([
'menuable' => function ($q) {
// This gives error if there is no relation Pages model
$q->whereActive(true)->select('pages.id', 'pages.slug');
// Below not working
// if($q->type === Page::class){
// $q->whereActive(true)->select('pages.id', 'pages.slug');
// } else if($q->type === Category::class){
// $q->whereActive(true)->select('categories.id',
'categories.slug');
// }
}
])
->get(['id', 'menuable_id', 'menuable_type', 'name']);
$response = [
'menus' => $menus,
];
return $this->sendResponse($response);
}
Models
class Menu extends Model
{
public function menuable()
{
return $this->morphTo();
}
}
class Page extends Model
{
public function menu()
{
return $this->morphOne(Menu::class, 'menuable');
}
}
class Category extends Model
{
public function menu()
{
return $this->morphOne(Menu::class, 'menuable');
}
}
How can I select specific columns from morph relation with checking morph type? I am using Laravel version 8.
The polymorphic relation is something the Eloquent aware of, and DBMS hasnot implemented this feature in it.
so there cannot be a sql query which join a table to another tables based on the morph column.
so you have to use distinct queries for every polymorphic join relation on your models:
//you can retrieve distinct menu based on their relation
Menu::whereActive(true)->hasPage()->with('pages');
//and having the ralations in the menu model:
public function posts
Menu::whereActive(true)->hasCategory();
//scope in menu class can be like:
public function scopePage($query){
return $query->where('menuable_type',Page::class);
}
public function scopeCategory($query){
return $query->where('menuable_type',Category::class);
}
//with these you can eager load the models
Menu::whereActive(true)->hasPage()->with('page');
Menu::whereActive(true)->hasCategory()->with('category');
public function page(){
return $this->belongsTo(Page::class);
}
public functioncategory(){
return $this->belongsTo(Category::class);
}
if you want a common interface to use one of these dynamically.
you can use:
$menu->menuable->id
$menu->menuable->slug
I am not sure which columns you want to response, but as i can guess from your question, I suppose you want id and slug from both models.
public function index(){
$pagedMenu = Menu::whereActive(true)->hasPage()->with('page');
$categoriedMenu = Menu::whereActive(true)->hasCategory()->with('category');
$menues = $pagedMenu->merge($categoriedMenu);
$response = [
'menus' => $menus,
];
return $this->sendResponse($response);
}
You can perfom separate filters based on the morph-class. This can be achieved with the whereHasMorph (https://laravel.com/docs/8.x/eloquent-relationships#querying-morph-to-relationships).
If you need to automatically resolve the relationship you can use with. This will preload morphables automatically into your resulting collection.
The following example uses an orWhere with 2 separate queries per morphable.
$menus = Menu::whereActive(true)
->whereHasMorph('menuable', [Page::class], function (Builder $query) {
// perform any query based on page-related entries
})
->orWhereHasMorph('menuable', [Category::class], function (Builder $query) {
// perform any query based on category-related entries
})
->with('menuable')
;
An alternative way is to pass both classes to the second argument. In that case you can check the type inside your closure.
$menus = Menu::whereActive(true)
->whereHasMorph('menuable', [Page::class, Category::class], function (Builder $query, $type) {
// perform any query independently of the morph-target
// $q->where...
if ($type === (new Page)->getMorphClass()) {
// perform any query based on category-related entries
}
if ($type === (new Category)->getMorphClass()) {
// perform any query based on category-related entries
}
})
->with('menuable')
If required you can also preload nested relationships. (https://laravel.com/docs/8.x/eloquent-relationships#nested-eager-loading-morphto-relationships)
$menus = Menu::whereActive(true)
->with(['menuable' => function (MorphTo $morphTo) {
$morphTo->morphWith([
Page::class => ['calendar'],
Category::class => ['tags'],
]);
}])
I have 5 tables.
Users
Categories
Products
Product_categories
Order Details
A user purchases an an item and in my order details table I store the quantities etc.
I wanted to return all items that are of the main category = 'Testing' via the user.
$user = Auth::user();
return $user->items();
I have the following relationship on my user model.
public function items()
{
return $this->hasMany('App\OrderDetail','user_id')->selectRaw('item_description,count(quantity) as count')->where('item_description','<>','Carriage')->groupBy('item_id')->get();
}
I know I've not associated the the categories table here but I'm wondering how I would pull all the users order details where item category is "testing". The item can be related to many categories hence the product_categories table.
I'm not after someone writing the answer I'd like to know where I start to look at linking these via the model?
Would I be right in saying I have to do a function within my model relation?
According to your requirements & structure, your table should be structured like this:
users
id
name
...
categories
id
name
...
products
id
name
cost
...
category_product
id
category_id
product_id
order_details
id
user_id
cost
...
product_order_detail
id
product_id
order_detail_id
Your models should be structured like this:
class User extends Model
{
public function orderDetails()
{
return $this->hasMany(OrderDetail::class);
}
}
class Product extends Model
{
public function categories()
{
return $this->belongsToMany(Category::class, 'category_product');
}
public function orderDetails()
{
return $this->belongsToMany(Order::class, 'product_order_detail');
}
}
class Category extends Model
{
public function product()
{
return $this->belongsToMany(Product::class, 'category_product');
}
}
class OrderDetail extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
public function products()
{
return $this->belongsToMany(Product::class, 'product_order_detail');
}
}
and to fetch all the items / products who belongs to the category named Testing and belongs to the user, who've ordered it:
$items = Product::whereHas('categories', function($q) {
$q->where('name', '=', 'Testing');
})->whereHas('orderDetails', function($q) use($user) {
$q->whereHas('user', function($q) use($user) {
$q->where('id', $user->id);
});
})->get();
Hope this helps!
I have the following model relationships. If a user logs in as an employee, I want them to be able to get a list of employees for a their company and the roles they have been assigned:
class User {
// A user can be of an employee user type
public function employee()
{
return $this->hasOne('App\Employee');
}
//
public function roles()
{
return $this->belongsToMany('App\Role');
}
}
class Employee {
// employee profile belong to a user
public function user()
{
return $this->belongsTo('App\User');
}
// employee belongs to a company
public function company()
{
return $this->belongsTo('App\Company');
}
}
class Company {
public function employees()
{
return $this->hasMany('App\Employee');
}
}
But the following query doesnt work. I get error Column not found: 1054 Unknown column companies.id in WHERE clause:
$employee = Auth::user()->employee;
$companyEmployees = Company::with(['employees.user.roles' => function ($query) use ($employee) {
$query->where('companies.id', '=', $employee->company_id)
->orderBy('users.created_at', 'desc');
}])->get();
The users and the employees table have a one to one relationship.
All employees have a base role type of employee in addition they may also have other roles such as manager, supervisor etc.
How do I write a query that gives me a company with all its employees and their roles?
I've tried to add a hasManyThrough relation to the Company model but that doesn't work either?
public function users()
{
return $this->hasManyThrough('App\User', 'App\Employee');
}
I think you're ring to get a list of coworkers for the current user and eager load the user and role?
$employee = Auth::user()->employee;
$companyEmployees = Company::with(['employees.user.roles')->find($employee->company_id);
Or perhaps:
$companyEmployees = Company::find($employee->company_id)->employees()->with('user.roles')->get();
$sorted = $companyEmployees->sortBy(function($employee){ return $employee->user->created_at; });
That might be a more direct route. Is your employee id in the user table or vice versa? The eloquent relationships are easy to set backwards.
Users::select('table_users.id')->with('roles')->join('table_employes', function($join) use ($employee) {
$join->on('table_employes.user_id','=','table_users.id')->where('table_employes.company_id', '=', $employee->company_id);
})->orderBy('tables_users.created_at')->get();
1. Create relationship for database table columns in migrtaion :
User Role
$table->foreign('user_id')->references('id')->on('users');
Users
$table->increments('id');
2. Create a model for each database table to define relationship
User.php (model)
public function userRoles()
{
return $this->hasOne('App\UserRoles', 'user_id', 'id');
}
Userroles.php (model)
public function user()
{
return $this->belongsTo('App\User', 'user_id', 'id');
}
3. Let controller handle database calls recommended to use REST api
Controller
use App\User;
use App\UserRoles;
class UserController extends Controller
{
public function index()
{
return User::with('userRoles')->orderBy('users.created_at', 'desc')->paginate(50);
}
}
I have the following tables:
Customer
id
Order
id
customer_id
Order_notes
order_id
note_id
Notes
id
If I want to get all order notes for a customer so I can do the following, how can I do it? Is there way to define a relationship in my model that goes through multiple pivot tables to join a customer to order notes?
#if($customer->order_notes->count() > 0)
#foreach($customer->order_notes as $note)
// output note
#endforeach
#endif
Create these relationships on your models.
class Customer extends Model
{
public function orders()
{
return $this->hasMany(Order::class);
}
public function order_notes()
{
// have not tried this yet
// but I believe this is what you wanted
return $this->hasManyThrough(Note::class, Order::class, 'customer_id', 'id');
}
}
class Order extends Model
{
public function notes()
{
return $this->belongsToMany(Note::class, 'order_notes', 'order_id', 'note_id');
}
}
class Note extends Model
{
}
You can get the relationships using this query:
$customer = Customer::with('orders.notes')->find(1);
What about 'belongsToMany' ?
E.g. something like
$customer->belongsToMany('OrderNote', 'orders', 'customer_id', 'id');
Of course, it'll not work directly, if you want to get order object also (but maybe you can use withPivot)
In the end I just did the following:
class Customer extends Model
{
public function order_notes()
{
return $this->hasManyThrough('App\Order_note', 'App\Order');
}
}
class Order_note extends Model
{
public function order()
{
return $this->belongsTo('App\Order');
}
public function note()
{
return $this->belongsTo('App\Note')->orderBy('notes.id','desc');
}
}
Then access the notes like so:
#foreach($customer->order_notes as $note)
echo $note->note->text;
#endforeach