I am trying to only return specific profiles for users that have roles (role_id 5 & 6) that are active in both tables. It would also be nice if I can order by first_name ASC as well (user table).
user
+---------+---------+-------------+-----------+
| user_id | role_id | first_name | is_active |
+---------+---------+-------------+-----------+
| 1 | 5 | Dan | 1 |
| 2 | 6 | Bob | 0 |
+---------+---------+-------------+-----------+
profile
+------------+---------+------+-------------+-----------+
| profile_id | user_id | bio | avatar | is_active |
+------------+---------+------+-------------+-----------+
| 1 | 1 | text | example.jpg | 1 |
| 2 | 2 | text | noimage.gif | 1 |
+------------+---------+------+-------------+-----------+
My user model
namespace App\Model;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
protected $table = 'user';
protected $primaryKey = 'user_id';
protected $fillable = [
'role_id',
'first_name',
'is_active'
];
public function scopeActive(){
return $this->where('is_active', '=', 1);
}
public function role(){
return $this->belongsTo('App\Model\Role');
}
public function profile(){
return $this->hasOne('App\Model\Profile');
}
}
My profile model
namespace App\Model;
use Illuminate\Database\Eloquent\Model;
class Profile extends Model{
protected $table = 'profile';
protected $primaryKey = 'profile_id';
protected $fillable = [
'user_id',
'avatar',
'is_active'
];
public function scopeActive(){
return $this->where('is_active', '=', 1);
}
public function user(){
return $this->belongsTo('App\Model\User');
}
}
My UserController
namespace App\Controller\User;
use App\Model\User;
use App\Model\Profile;
use App\Controller\Controller;
final class UserController extends Controller{
public function listExpert($request, $response){
$user = User::active()->whereIn('role_id', array(5, 6))->orderBy('first_name', 'asc')->get();
$profile = $user->profile ?: new Profile;
$data['experts'] = $profile->active()->get();
$this->view->render($response, '/Frontend/experts.twig', $data);
return $response;
}
}
So I am getting all of my records just fine. I am getting all the profiles but not the ones that belong only to role_id's 5 & 6 in the user table. Also if I set is_active to 0 in the user table, they still show. But if I set is_active in the profile table they do not. I need them to not show whether the User or Profile table has those rows set to inactive. Because you can have a user but they may not want an active profile.
Okay I got it!
$data['experts'] = User::whereIn('role_id', array(5, 6))
->where('is_active', '1')
->whereHas('profile', function($q){
$q->where('is_active', '1');
})
->with('profile')
->orderBy('first_name', 'ASC')
->get();
In case you want to know how to return this in twig....
{% if experts|length > 0 %}
<ul>
{% for item in experts %}
<li>First Name: {{ item.first_name }}, Bio: {{ item.profile.bio }}, Avatar: {{ item.profile.avatar }}</li>
{% endfor %}
</ul>
{% else %}
<p>No records found.</p>
{% endif %}
Related
I have a small Laravel app that is basically a few tables as follows and I'm having a problem with Laravel Eloquent generating more SQL queries than it needs. I am basically trying to display all 'grades' for a student that is logged in but to keep the queries to a minimum.
Here is my controller
public function index(Request $request)
{
$student = $request->user();
return view('dashboard.index', [
'user' => $student,
'grades' => $student->grades->paginate(5)
]);
}
Here are my table structures
courses
select id, name from courses;
+----+--------------------+
| id | name |
+----+--------------------+
| 1 | English Literature |
| 2 | Stats |
| 3 | Biology |
+----+--------------------+
grades
select id, student_id, course_id, score, letter_grade from grades where student_id = 1;
+----+------------+-----------+--------+--------------+
| id | student_id | course_id | score | letter_grade |
+----+------------+-----------+--------+--------------+
| 1 | 1 | 1 | 90.00 | A |
| 2 | 1 | 2 | 50.00 | D |
| 3 | 1 | 3 | 100.00 | A |
+----+------------+-----------+--------+--------------+
course_student
select * from course_student;
+-----------+------------+
| course_id | student_id |
+-----------+------------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
+-----------+------------+
students
select id, email from students limit 1;
+----+---------------------------+
| id | email |
+----+---------------------------+
| 1 | bob#myapp.com |
+----+---------------------------+
My Laravel relations in the models look like the following:
Student.php
public function grades(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Grade::class);
}
public function courses(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Course::class);
}
Grade.php
class Grade extends Model
{
use HasFactory;
public function student(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Student::class);
}
public function course(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Course::class);
}
}
Course.php
class Course extends Model
{
use HasFactory;
public function department(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(Department::class, 'id', 'department_id');
}
public function students(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Student::class);
}
public function teachers(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Teacher::class);
}
}
DashboardController.php
public function index(Request $request)
{
$student = $request->user();
return view('dashboard.index', [
'user' => $student,
'grades' => $student->load('grades', 'courses')->grades->paginate(10)
]);
}
dashboard/index.blade.php view
<div class="py-2">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 sm:rounded-lg">
{{ $grades->links() }}
<table>
<tr>
<td>Course</td>
<td>Grade</td>
<td>Score</td>
<td>Date</td>
</tr>
#foreach ($grades as $key => $grade)
<tr>
<td>{{ $grade->course->name }}</td>
<td>{{ $grade->letter_grade }}</td>
<td>{{ $grade->score }}</td>
<td>{{ $grade->created_at }}</td>
</tr>
#endforeach
</table>
</div>
</div>
</div>
Using Laravel Debugbar I can see all of the queries it has generated - I don't have any errors but the SELECT * from courses could be optimised to use an IN() rather than an entire SELECT * from courses by primary key each time, how do I optimise my controller/model logic
select * from `students` where `id` = 1 and `students`.`deleted_at` is null limit 1
select * from `grades` where `grades`.`student_id` = 1 and `grades`.`student_id` is not null
select * from `courses` where `courses`.`id` = 1 limit 1
select * from `courses` where `courses`.`id` = 2 limit 1
select * from `courses` where `courses`.`id` = 3 limit 1
select * from `courses` where `courses`.`id` = 4 limit 1
select * from `courses` where `courses`.`id` = 5 limit 1
Expected output to use fewer queries to the courses table & to NOT lazy load queries
Your pagination is not hitting a queryBuilder but a Collection instead: Doing $student->grades already retrieves all the grades of the student, that's why the query has no limit in it.
You are using the courses relation of Grade::class in your blade yet you loaded the courses relation of Student::class in your controller.
To improve everything, you can do it like this
public function index(Request $request)
{
$student = $request->user();
return view('dashboard.index', [
'user' => $student,
'grades' => $student->grades()->with('courses')->paginate(10),
]);
}
Calling $student->grades() returns a QueryBuilder instead of calling $student->grades which returns a Collection
You have done excellent job setting up relationships in each Models.
For your question , you can achieve this using nested eager loading.
$student = Student::query()
->where('id', auth()->id())
->with('grades.courses')
->first();
$grades = $student->grades->paginate(5);
return view('dashboard.index', compact('student', 'grades'));
As documented here : https://laravel.com/docs/9.x/eloquent-relationships#nested-eager-loading
Now your laravel should run only 3 queries on table students, grades and courses.
And if you want only to get grades with pagination, try run this query.
$user = $request->user(); // or auth()->user()
$grades = Grade::query()
->where('student_id', $user->id)
->with('courses')
->paginate(5);
return view('dashboard.index', compact('user', 'grades'));
This still will run only 3 queries.
note: $request->user() is the same as auth()->user()
and in the query i use auth()->id() to get the logged in user Id
I'm trying to display the value of brand_id column from brands table. Here's so far what I've done:
Car model
use App\Brand;
class Car extends Model
{
public function brands(){
return $this->belongsToMany(Brand::class);
}
}
Brand model
use App\Car;
class Brand extends Model
{
protected $fillable = [
'brand_name'
];
public function cars(){
return $this->hasMany(Car::class);
}
}
ShowroomController
use App\Car;
class ShowroomController extends Controller
{
public function details($name){
$data = Car::where('car_name' , '=', $name)->first();
if ($data == null){
return redirect(route('index'));
}else{
return view('showroom')->with('detail', $data);
}
}
}
showroom view
#if (isset($detail))
{{ $detail }}
{{ $detail->brands->brand_name }} //this doesn't work
#endif
Database
Brands table:
+----+------------+
| id | brand_name |
+----+------------+
| 1 | Brand1 |
| 2 | Brand2 |
+----+------------+
Cars table:
+----+----------+----------+
| id | car_name | brand_id |
+----+----------+----------+
| 1 | Car | 1 |
+----+----------+----------+
I got lost on this point. Is this the right way to do the belongstomany and hasmany relationship? Thanks.
Change
return $this->belongsToMany(Brand::class);
to
return $this->belongsTo(Brand::class); on the Car model
Also rename name function to brand. because car have only single brand
After it you can do $detail->brand->brand_name
Hi I know it seems simple, thanks to #Imboom I got a hint to fix my problem. I made some changes on Car model:
return $this->belongsToMany(Brand::class); to return $this->belongsTo(Brand::class)
rename name function to brand
Lastly, I just added 'brand_id' to specify the column in cars table.
public function brand(){
return $this->belongsTo(Brand::class,'brand_id');
}
In ShowroomController, I changed my return statement detail to car. See the code below:
public function details($name){
$data = Car::where('car_name' , '=', $name)->first();
if ($data == null){
return redirect(route('index'));
}else{
return view('showroom')->with('car', $data);
}
}
Then in showroom view, $car->brand->brand_name .
#if (isset($car))
{{ $car->car_name }}
{{ $car->brand->brand_name }} // Output is Brand1
#endif
Thank you!
Here is my model User.php
class User extends Authenticatable
{
use Notifiable;
protected $fillable = [
'name', 'email', 'password', 'cell_phone', 'province_id', 'city_id', 'job'
];
protected $hidden = [
'password', 'remember_token',
];
public function city()
{
return $this->belongsTo('City');
}
}
And here is a part of my controller:
$user_info = User::find(Auth::user()->id);
dd($user_info->city);
And it throws this:
"Undefined class constant 'city'"
How can I fix the problem?
Tables structure:
// users
+----+--------+---------+----------
| id | name | city_id | ...
+----+--------+---------+----------
| 1 | Jack | 5 | ...
// city
+----+--------+
| id | name |
+----+--------+
| 1 | Tehran |
You need to pass full class name:
return $this->belongsTo('App\City');
Or:
return $this->belongsTo(City::class);
Also, you don't need to do that:
$user_info = User::find(Auth::user()->id);
Because Auth::user() already has user instance loaded, you can just get city instance with:
Auth::user()->city
Basically , you don't provide full path of your class name of city that's why you belongs To relation not working properly.
for example your code must be
return $this->belongs To('App/City');
Because your in app folder you have city class module and others.
Problem
I created a simple friendship relationship for my Laravel app which all worked ok until I noticed that when I queried the friendship of a user it would only search the current user on the UID1 field.
Since friendships are in essence a two-way relationship, Im trying to find a way in a laravel Model to retrieve ALL friendships relations by multiple columns.
Current Implementation
public function friends()
{
return $this->belongsToMany( App\Modules\Users\Models\User::class ,'friends', 'uid1');
}
Ideal Implementation
public function friends()
{
$a = $this->belongsToMany( App\Modules\Users\Models\User::class ,'users_friends', 'uid1');
$b = $this->belongsToMany( App\Modules\Users\Models\User::class ,'users_friends', 'uid2');
return combine($a,$b);
}
Table Structure
+----------------------+
| users table |
+----------------------+
+----| id: primary UserID |
| | fname: string |
| +----------------------+
|
|
| +----------------------+
| | friends table |
| +----------------------+
| | id: primary iD |
| | |
+----| uid1: user_id |
| | |
+----| uid2: user_id |
+----------------------+
The current implementation will only result in 1 of these records returning if the Current UserID = 1 as per the data in the friends table below.
+-------------------------------+
| friends table (data) |
+--------|---------|------------+
| id | uid1 | uid2 |
+--------|---------|------------+
| 1 | 1 | 7 |
| 2 | 7 | 1 |
| 3 | 9 | 1 |
+-------------------------------+
User Model
<?php
namespace App\Modules\Users\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $table = 'users';
protected $fillable = [
'username', 'email', 'password', .... .
];
public function friends()
{
return $this->belongsToMany( App\Modules\Users\Models\User::class ,'users_friends', 'uid1');
}
Environment
Server = Homestead/linux
PHP = 7
MySQL
Update
I have a FriendShip helper class I created which does something similar, however in this function I pass in the UserID explicitly
Friendship::where( [
[ 'uid1' ,'=', $uid],
])->orWhere( [
[ 'uid2', '=', $uid]
])->all();
You can add additional conditions when you're declaring relationship by simply chaining it.
<?php
//...
class User extends Model {
//...
public function friends() {
return $this->hasMany(/*...*/)->orWhere('uid2', $this->id);
}
//...
But keep in mind that eloquent is not grouping the first conditions of relation in parenthesis so you might end with SQL that will not work as expected in some cases (if using or, and should be fine)
For example the above might result in a SQL that looks like this
SELECT * FROM users_friends WHERE uid1 = ? AND uid1 IS NOT NULL OR uid2 = ?
Which is a correct SQL statement but without grouping you will not get the result that you're expecting.
Another way is to use accessor and two separate relationships
<?php
//...
public function friends1() {
return $this->belongsToMany(User::class, 'users_friends', 'uid1');
}
public function friends2() {
return $this->belongsToMany(User::class, 'users_friends', 'uid2');
}
public function getFriendsAttribute() {
return $this->friends1->merge($this->friends2);
}
//...
But this way you get two separate trips to DB.
I need to refactor project and I have problem. Below is old, working model, where 'active' column is in "people" table. I need to move 'active' column into "people_translations" table.
Do you have any Idea to modify scopeActive method?
Thanks a lot!
Old working model:
class BaseModel extends Eloquent
{
public function scopeActive($query)
{
return $query->where($this->table . '.active', '=', 1);
}
}
class People extends BaseModel
{
protected $table = 'peoples';
protected $translationModel = 'PeopleTranslation';
}
class PeopleTranslation extends Eloquent
{
public $timestamps = false;
protected $table = 'peoples_translations';
}
Old tables structure:
Table: peoples
id | type | date | active
-------------------------
7 | .... | ... | 1
Table: peoples_translations
id | people_id | language_id | name
-----------------------------------
1 | 7 | 1 | Ann
Old query:
$peoples = \People::active()->get();
New tables structure:
Table: peoples
id | type | date
----------------
7 | .... | ...
Table: peoples_translations
id | people_id | language_id | name | active
--------------------------------------------
1 | 7 | 1 | Ann | 1
Create a relation for translations inside People Model
public function translations()
{
return $this->hasMany('PeopleTranslation', 'people_id');
}
Create active scope in People model
public function scopeActive($query)
{
return $query->whereHas('translations', function($query) {
$query->where('active', 1);
});
}
It will make subquery for this table and as a result it will get where (count of translations with active = 1) > 0.
If you have one-to-one relation - look for hasOne relation method instead of hasMany.